early-access version 4047

This commit is contained in:
pineappleEA 2024-01-07 00:50:43 +01:00
parent c3fa970dd5
commit dae302f84a
142 changed files with 16262 additions and 208 deletions

View file

@ -1,7 +1,7 @@
yuzu emulator early access
=============
This is the source code for early-access 4046.
This is the source code for early-access 4047.
## Legal Notice

View file

@ -185,6 +185,7 @@ add_subdirectory(common)
add_subdirectory(core)
add_subdirectory(audio_core)
add_subdirectory(video_core)
add_subdirectory(hid_core)
add_subdirectory(network)
add_subdirectory(input_common)
add_subdirectory(frontend_common)

View file

@ -45,15 +45,15 @@
#include "core/frontend/applets/profile_select.h"
#include "core/frontend/applets/software_keyboard.h"
#include "core/frontend/applets/web_browser.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
#include "core/hid/hid_types.h"
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/am/applet_oe.h"
#include "core/hle/service/am/applets/applets.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
#include "frontend_common/config.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "hid_core/hid_types.h"
#include "jni/android_common/android_common.h"
#include "jni/id_cache.h"
#include "jni/native.h"

View file

@ -183,22 +183,6 @@ add_library(core STATIC
frontend/framebuffer_layout.cpp
frontend/framebuffer_layout.h
frontend/graphics_context.h
hid/emulated_console.cpp
hid/emulated_console.h
hid/emulated_controller.cpp
hid/emulated_controller.h
hid/emulated_devices.cpp
hid/emulated_devices.h
hid/hid_core.cpp
hid/hid_core.h
hid/hid_types.h
hid/input_converter.cpp
hid/input_converter.h
hid/input_interpreter.cpp
hid/input_interpreter.h
hid/irs_types.h
hid/motion_input.cpp
hid/motion_input.h
hle/api_version.h
hle/ipc.h
hle/kernel/board/nintendo/nx/k_memory_layout.cpp
@ -531,94 +515,16 @@ add_library(core STATIC
hle/service/hid/hid.h
hle/service/hid/hid_debug_server.cpp
hle/service/hid/hid_debug_server.h
hle/service/hid/hid_firmware_settings.cpp
hle/service/hid/hid_firmware_settings.h
hle/service/hid/hid_server.cpp
hle/service/hid/hid_server.h
hle/service/hid/hid_system_server.cpp
hle/service/hid/hid_system_server.h
hle/service/hid/hid_util.h
hle/service/hid/hidbus.cpp
hle/service/hid/hidbus.h
hle/service/hid/irs.cpp
hle/service/hid/irs.h
hle/service/hid/irs_ring_lifo.h
hle/service/hid/resource_manager.cpp
hle/service/hid/resource_manager.h
hle/service/hid/ring_lifo.h
hle/service/hid/xcd.cpp
hle/service/hid/xcd.h
hle/service/hid/errors.h
hle/service/hid/controllers/npad/npad_data.cpp
hle/service/hid/controllers/npad/npad_data.h
hle/service/hid/controllers/npad/npad_resource.cpp
hle/service/hid/controllers/npad/npad_resource.h
hle/service/hid/controllers/types/debug_pad_types.h
hle/service/hid/controllers/types/keyboard_types.h
hle/service/hid/controllers/types/mouse_types.h
hle/service/hid/controllers/types/npad_types.h
hle/service/hid/controllers/types/shared_memory_format.h
hle/service/hid/controllers/types/touch_types.h
hle/service/hid/controllers/applet_resource.cpp
hle/service/hid/controllers/applet_resource.h
hle/service/hid/controllers/capture_button.cpp
hle/service/hid/controllers/capture_button.h
hle/service/hid/controllers/console_six_axis.cpp
hle/service/hid/controllers/console_six_axis.h
hle/service/hid/controllers/controller_base.cpp
hle/service/hid/controllers/controller_base.h
hle/service/hid/controllers/debug_mouse.cpp
hle/service/hid/controllers/debug_mouse.h
hle/service/hid/controllers/debug_pad.cpp
hle/service/hid/controllers/debug_pad.h
hle/service/hid/controllers/digitizer.cpp
hle/service/hid/controllers/digitizer.h
hle/service/hid/controllers/gesture.cpp
hle/service/hid/controllers/gesture.h
hle/service/hid/controllers/home_button.cpp
hle/service/hid/controllers/home_button.h
hle/service/hid/controllers/keyboard.cpp
hle/service/hid/controllers/keyboard.h
hle/service/hid/controllers/mouse.cpp
hle/service/hid/controllers/mouse.h
hle/service/hid/controllers/npad.cpp
hle/service/hid/controllers/npad.h
hle/service/hid/controllers/palma.cpp
hle/service/hid/controllers/palma.h
hle/service/hid/controllers/seven_six_axis.cpp
hle/service/hid/controllers/seven_six_axis.h
hle/service/hid/controllers/shared_memory_holder.cpp
hle/service/hid/controllers/shared_memory_holder.h
hle/service/hid/controllers/six_axis.cpp
hle/service/hid/controllers/six_axis.h
hle/service/hid/controllers/sleep_button.cpp
hle/service/hid/controllers/sleep_button.h
hle/service/hid/controllers/touchscreen.cpp
hle/service/hid/controllers/touchscreen.h
hle/service/hid/controllers/unique_pad.cpp
hle/service/hid/controllers/unique_pad.h
hle/service/hid/hidbus/hidbus_base.cpp
hle/service/hid/hidbus/hidbus_base.h
hle/service/hid/hidbus/ringcon.cpp
hle/service/hid/hidbus/ringcon.h
hle/service/hid/hidbus/starlink.cpp
hle/service/hid/hidbus/starlink.h
hle/service/hid/hidbus/stubbed.cpp
hle/service/hid/hidbus/stubbed.h
hle/service/hid/irsensor/clustering_processor.cpp
hle/service/hid/irsensor/clustering_processor.h
hle/service/hid/irsensor/image_transfer_processor.cpp
hle/service/hid/irsensor/image_transfer_processor.h
hle/service/hid/irsensor/ir_led_processor.cpp
hle/service/hid/irsensor/ir_led_processor.h
hle/service/hid/irsensor/moment_processor.cpp
hle/service/hid/irsensor/moment_processor.h
hle/service/hid/irsensor/pointing_processor.cpp
hle/service/hid/irsensor/pointing_processor.h
hle/service/hid/irsensor/processor_base.cpp
hle/service/hid/irsensor/processor_base.h
hle/service/hid/irsensor/tera_plugin_processor.cpp
hle/service/hid/irsensor/tera_plugin_processor.h
hle/service/lbl/lbl.cpp
hle/service/lbl/lbl.h
hle/service/ldn/lan_discovery.cpp
@ -959,7 +865,7 @@ endif()
create_target_directory_groups(core)
target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb)
target_link_libraries(core PUBLIC common PRIVATE audio_core hid_core network video_core nx_tzdb)
target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls RenderDoc::API)
if (MINGW)
target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY})

View file

@ -28,7 +28,6 @@
#include "core/file_sys/savedata_factory.h"
#include "core/file_sys/vfs_concat.h"
#include "core/file_sys/vfs_real.h"
#include "core/hid/hid_core.h"
#include "core/hle/kernel/k_memory_manager.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_resource_limit.h"
@ -52,6 +51,7 @@
#include "core/telemetry_session.h"
#include "core/tools/freezer.h"
#include "core/tools/renderdoc.h"
#include "hid_core/hid_core.h"
#include "network/network.h"
#include "video_core/host1x/host1x.h"
#include "video_core/renderer_base.h"

View file

@ -6,9 +6,9 @@
#include "common/settings.h"
#include "common/settings_enums.h"
#include "core/frontend/applets/controller.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
#include "core/hid/hid_types.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "hid_core/hid_types.h"
namespace Core::Frontend {

View file

@ -13,7 +13,6 @@
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/savedata_factory.h"
#include "core/hid/hid_types.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_transfer_memory.h"
#include "core/hle/result.h"
@ -37,7 +36,6 @@
#include "core/hle/service/caps/caps_su.h"
#include "core/hle/service/caps/caps_types.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/ns/ns.h"
#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
@ -48,6 +46,8 @@
#include "core/hle/service/vi/vi.h"
#include "core/hle/service/vi/vi_results.h"
#include "core/memory.h"
#include "hid_core/hid_types.h"
#include "hid_core/resources/npad/npad.h"
namespace Service::AM {

View file

@ -5,13 +5,13 @@
#include "common/logging/log.h"
#include "core/core.h"
#include "core/frontend/applets/cabinet.h"
#include "core/hid/hid_core.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/applet_cabinet.h"
#include "core/hle/service/mii/mii_manager.h"
#include "core/hle/service/nfc/common/device.h"
#include "hid_core/hid_core.h"
namespace Service::AM::Applets {

View file

@ -9,13 +9,13 @@
#include "common/string_util.h"
#include "core/core.h"
#include "core/frontend/applets/controller.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
#include "core/hid/hid_types.h"
#include "core/hle/result.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/applet_controller.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "hid_core/hid_types.h"
#include "hid_core/resources/npad/npad.h"
namespace Service::AM::Applets {

View file

@ -5,14 +5,14 @@
#include "core/hle/kernel/kernel.h"
#include "core/hle/service/hid/hid.h"
#include "core/hle/service/hid/hid_debug_server.h"
#include "core/hle/service/hid/hid_firmware_settings.h"
#include "core/hle/service/hid/hid_server.h"
#include "core/hle/service/hid/hid_system_server.h"
#include "core/hle/service/hid/hidbus.h"
#include "core/hle/service/hid/irs.h"
#include "core/hle/service/hid/resource_manager.h"
#include "core/hle/service/hid/xcd.h"
#include "core/hle/service/server_manager.h"
#include "hid_core/resource_manager.h"
#include "hid_core/resources/hid_firmware_settings.h"
namespace Service::HID {

View file

@ -2,8 +2,8 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include "core/hle/service/hid/hid_debug_server.h"
#include "core/hle/service/hid/resource_manager.h"
#include "core/hle/service/ipc_helpers.h"
#include "hid_core/resource_manager.h"
namespace Service::HID {

View file

@ -5,30 +5,29 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "core/hid/hid_core.h"
#include "core/hle/kernel/k_shared_memory.h"
#include "core/hle/kernel/k_transfer_memory.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/service/hid/errors.h"
#include "core/hle/service/hid/hid_firmware_settings.h"
#include "core/hle/service/hid/hid_server.h"
#include "core/hle/service/hid/hid_util.h"
#include "core/hle/service/hid/resource_manager.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/memory.h"
#include "hid_core/hid_result.h"
#include "hid_core/hid_util.h"
#include "hid_core/resource_manager.h"
#include "hid_core/resources/hid_firmware_settings.h"
#include "core/hle/service/hid/controllers/console_six_axis.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/hle/service/hid/controllers/debug_pad.h"
#include "core/hle/service/hid/controllers/gesture.h"
#include "core/hle/service/hid/controllers/keyboard.h"
#include "core/hle/service/hid/controllers/mouse.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "core/hle/service/hid/controllers/palma.h"
#include "core/hle/service/hid/controllers/seven_six_axis.h"
#include "core/hle/service/hid/controllers/six_axis.h"
#include "core/hle/service/hid/controllers/touchscreen.h"
#include "core/hle/service/hid/controllers/types/npad_types.h"
#include "hid_core/resources/controller_base.h"
#include "hid_core/resources/debug_pad/debug_pad.h"
#include "hid_core/resources/keyboard/keyboard.h"
#include "hid_core/resources/mouse/mouse.h"
#include "hid_core/resources/npad/npad.h"
#include "hid_core/resources/npad/npad_types.h"
#include "hid_core/resources/palma/palma.h"
#include "hid_core/resources/six_axis/console_six_axis.h"
#include "hid_core/resources/six_axis/seven_six_axis.h"
#include "hid_core/resources/six_axis/six_axis.h"
#include "hid_core/resources/touch_screen/gesture.h"
#include "hid_core/resources/touch_screen/touch_screen.h"
namespace Service::HID {

View file

@ -1,15 +1,14 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "core/hid/hid_core.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "core/hle/service/hid/controllers/palma.h"
#include "core/hle/service/hid/controllers/touchscreen.h"
#include "core/hle/service/hid/controllers/types/npad_types.h"
#include "core/hle/service/hid/errors.h"
#include "core/hle/service/hid/hid_system_server.h"
#include "core/hle/service/hid/resource_manager.h"
#include "core/hle/service/ipc_helpers.h"
#include "hid_core/hid_result.h"
#include "hid_core/resource_manager.h"
#include "hid_core/resources/npad/npad.h"
#include "hid_core/resources/npad/npad_types.h"
#include "hid_core/resources/palma/palma.h"
#include "hid_core/resources/touch_screen/touch_screen.h"
namespace Service::HID {
@ -270,7 +269,7 @@ void IHidSystemServer::GetLastActiveNpad(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.PushEnum(system.HIDCore().GetLastActiveController());
rb.Push(0); // Dont forget to fix this
}
void IHidSystemServer::ApplyNpadSystemCommonPolicyFull(HLERequestContext& ctx) {

View file

@ -5,18 +5,18 @@
#include "common/settings.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hid/hid_types.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/k_shared_memory.h"
#include "core/hle/kernel/k_transfer_memory.h"
#include "core/hle/service/hid/hidbus.h"
#include "core/hle/service/hid/hidbus/ringcon.h"
#include "core/hle/service/hid/hidbus/starlink.h"
#include "core/hle/service/hid/hidbus/stubbed.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/service.h"
#include "core/memory.h"
#include "hid_core/hid_types.h"
#include "hid_core/hidbus/ringcon.h"
#include "hid_core/hidbus/starlink.h"
#include "hid_core/hidbus/stubbed.h"
namespace Service::HID {
// (15ms, 66Hz)

View file

@ -5,9 +5,9 @@
#include <functional>
#include "core/hle/service/hid/hidbus/hidbus_base.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/service.h"
#include "hid_core/hidbus/hidbus_base.h"
namespace Core::Timing {
struct EventType;

View file

@ -6,22 +6,22 @@
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
#include "core/hle/kernel/k_shared_memory.h"
#include "core/hle/kernel/k_transfer_memory.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/service/hid/errors.h"
#include "core/hle/service/hid/hid_util.h"
#include "core/hle/service/hid/irs.h"
#include "core/hle/service/hid/irsensor/clustering_processor.h"
#include "core/hle/service/hid/irsensor/image_transfer_processor.h"
#include "core/hle/service/hid/irsensor/ir_led_processor.h"
#include "core/hle/service/hid/irsensor/moment_processor.h"
#include "core/hle/service/hid/irsensor/pointing_processor.h"
#include "core/hle/service/hid/irsensor/tera_plugin_processor.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/memory.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "hid_core/hid_result.h"
#include "hid_core/hid_util.h"
#include "hid_core/irsensor/clustering_processor.h"
#include "hid_core/irsensor/image_transfer_processor.h"
#include "hid_core/irsensor/ir_led_processor.h"
#include "hid_core/irsensor/moment_processor.h"
#include "hid_core/irsensor/pointing_processor.h"
#include "hid_core/irsensor/tera_plugin_processor.h"
namespace Service::IRS {

View file

@ -4,10 +4,10 @@
#pragma once
#include "core/core.h"
#include "core/hid/hid_types.h"
#include "core/hid/irs_types.h"
#include "core/hle/service/hid/irsensor/processor_base.h"
#include "core/hle/service/service.h"
#include "hid_core/hid_types.h"
#include "hid_core/irsensor/irs_types.h"
#include "hid_core/irsensor/processor_base.h"
namespace Core::HID {
class EmulatedController;

View file

@ -22,9 +22,6 @@
#include "common/string_util.h"
#include "common/tiny_mt.h"
#include "core/core.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
#include "core/hid/hid_types.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/mii/mii_manager.h"
@ -33,6 +30,9 @@
#include "core/hle/service/nfc/mifare_result.h"
#include "core/hle/service/nfc/nfc_result.h"
#include "core/hle/service/time/time_manager.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "hid_core/hid_types.h"
namespace Service::NFC {
NfcDevice::NfcDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,

View file

@ -5,15 +5,15 @@
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hid/hid_types.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/hid/hid_util.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/nfc/common/device.h"
#include "core/hle/service/nfc/common/device_manager.h"
#include "core/hle/service/nfc/nfc_result.h"
#include "core/hle/service/time/clock_types.h"
#include "core/hle/service/time/time_manager.h"
#include "hid_core/hid_types.h"
#include "hid_core/hid_util.h"
namespace Service::NFC {

View file

@ -8,13 +8,13 @@
#include <optional>
#include <span>
#include "core/hid/hid_types.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/nfc/mifare_types.h"
#include "core/hle/service/nfc/nfc_types.h"
#include "core/hle/service/nfp/nfp_types.h"
#include "core/hle/service/service.h"
#include "core/hle/service/time/clock_types.h"
#include "hid_core/hid_types.h"
namespace Service::NFC {
class NfcDevice;

View file

@ -3,7 +3,6 @@
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hid/hid_types.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/nfc/common/device.h"
@ -15,6 +14,7 @@
#include "core/hle/service/nfc/nfc_types.h"
#include "core/hle/service/nfp/nfp_result.h"
#include "core/hle/service/time/clock_types.h"
#include "hid_core/hid_types.h"
namespace Service::NFC {

View file

@ -3,7 +3,6 @@
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hid/hid_types.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/nfc/common/device.h"
@ -12,6 +11,7 @@
#include "core/hle/service/nfp/nfp_interface.h"
#include "core/hle/service/nfp/nfp_result.h"
#include "core/hle/service/nfp/nfp_types.h"
#include "hid_core/hid_types.h"
namespace Service::NFP {

View file

@ -9,12 +9,12 @@
#include "core/core_timing.h"
#include "core/hle/kernel/k_page_table.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "core/hle/service/hid/hid_server.h"
#include "core/hle/service/hid/resource_manager.h"
#include "core/hle/service/sm/sm.h"
#include "core/memory.h"
#include "core/memory/cheat_engine.h"
#include "hid_core/resource_manager.h"
#include "hid_core/resources/npad/npad.h"
namespace Core::Memory {
namespace {

View file

@ -11,7 +11,7 @@
#include "config.h"
#include "core/core.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "hid_core/resources/npad/npad.h"
#include "network/network.h"
#include <boost/algorithm/string/replace.hpp>

126
src/hid_core/CMakeLists.txt Executable file
View file

@ -0,0 +1,126 @@
# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
add_library(hid_core STATIC
frontend/emulated_console.cpp
frontend/emulated_console.h
frontend/emulated_controller.cpp
frontend/emulated_controller.h
frontend/emulated_devices.cpp
frontend/emulated_devices.h
frontend/input_converter.cpp
frontend/input_converter.h
frontend/input_interpreter.cpp
frontend/input_interpreter.h
frontend/motion_input.cpp
frontend/motion_input.h
hidbus/hidbus_base.cpp
hidbus/hidbus_base.h
hidbus/ringcon.cpp
hidbus/ringcon.h
hidbus/starlink.cpp
hidbus/starlink.h
hidbus/stubbed.cpp
hidbus/stubbed.h
irsensor/clustering_processor.cpp
irsensor/clustering_processor.h
irsensor/image_transfer_processor.cpp
irsensor/image_transfer_processor.h
irsensor/ir_led_processor.cpp
irsensor/ir_led_processor.h
irsensor/moment_processor.cpp
irsensor/moment_processor.h
irsensor/pointing_processor.cpp
irsensor/pointing_processor.h
irsensor/processor_base.cpp
irsensor/processor_base.h
irsensor/tera_plugin_processor.cpp
irsensor/tera_plugin_processor.h
resources/debug_pad/debug_pad.cpp
resources/debug_pad/debug_pad.h
resources/debug_pad/debug_pad_types.h
resources/digitizer/digitizer.cpp
resources/digitizer/digitizer.h
resources/keyboard/keyboard.cpp
resources/keyboard/keyboard.h
resources/keyboard/keyboard_types.h
resources/mouse/debug_mouse.cpp
resources/mouse/debug_mouse.h
resources/mouse/mouse.cpp
resources/mouse/mouse.h
resources/mouse/mouse_types.h
resources/npad/npad.cpp
resources/npad/npad.h
resources/npad/npad_data.cpp
resources/npad/npad_data.h
resources/npad/npad_resource.cpp
resources/npad/npad_resource.h
resources/npad/npad_types.h
resources/palma/palma.cpp
resources/palma/palma.h
resources/six_axis/console_six_axis.cpp
resources/six_axis/console_six_axis.h
resources/six_axis/seven_six_axis.cpp
resources/six_axis/seven_six_axis.h
resources/six_axis/six_axis.cpp
resources/six_axis/six_axis.h
resources/system_buttons/capture_button.cpp
resources/system_buttons/capture_button.h
resources/system_buttons/home_button.cpp
resources/system_buttons/home_button.h
resources/system_buttons/sleep_button.cpp
resources/system_buttons/sleep_button.h
resources/touch_screen/gesture.cpp
resources/touch_screen/gesture.h
resources/touch_screen/gesture_types.h
resources/touch_screen/touch_screen.cpp
resources/touch_screen/touch_screen.h
resources/touch_screen/touch_types.h
resources/unique_pad/unique_pad.cpp
resources/unique_pad/unique_pad.h
resources/applet_resource.cpp
resources/applet_resource.h
resources/controller_base.cpp
resources/controller_base.h
resources/hid_firmware_settings.cpp
resources/hid_firmware_settings.h
resources/irs_ring_lifo.h
resources/ring_lifo.h
resources/shared_memory_format.h
resources/shared_memory_holder.cpp
resources/shared_memory_holder.h
hid_core.cpp
hid_core.h
hid_result.h
hid_types.h
hid_util.h
precompiled_headers.h
resource_manager.cpp
resource_manager.h
)
if (MSVC)
target_compile_options(hid_core PRIVATE
/we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data
/we4244 # 'conversion': conversion from 'type1' to 'type2', possible loss of data
/we4245 # 'conversion': conversion from 'type1' to 'type2', signed/unsigned mismatch
/we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data
/we4800 # Implicit conversion from 'type' to bool. Possible information loss
)
else()
target_compile_options(hid_core PRIVATE
-Werror=conversion
-Wno-sign-conversion
-Wno-cast-function-type
$<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation>
)
endif()
create_target_directory_groups(hid_core)
target_link_libraries(hid_core PUBLIC core)
if (YUZU_USE_PRECOMPILED_HEADERS)
target_precompile_headers(hid_core PRIVATE precompiled_headers.h)
endif()

View file

@ -0,0 +1,324 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/settings.h"
#include "hid_core/frontend/emulated_console.h"
#include "hid_core/frontend/input_converter.h"
namespace Core::HID {
EmulatedConsole::EmulatedConsole() = default;
EmulatedConsole::~EmulatedConsole() = default;
void EmulatedConsole::ReloadFromSettings() {
// Using first motion device from player 1. No need to assign any unique config at the moment
const auto& player = Settings::values.players.GetValue()[0];
motion_params[0] = Common::ParamPackage(player.motions[0]);
ReloadInput();
}
void EmulatedConsole::SetTouchParams() {
std::size_t index = 0;
// We can't use mouse as touch if native mouse is enabled
if (!Settings::values.mouse_enabled) {
touch_params[index++] =
Common::ParamPackage{"engine:mouse,axis_x:0,axis_y:1,button:0,port:2"};
}
touch_params[index++] =
Common::ParamPackage{"engine:cemuhookudp,axis_x:17,axis_y:18,button:65536"};
touch_params[index++] =
Common::ParamPackage{"engine:cemuhookudp,axis_x:19,axis_y:20,button:131072"};
for (int i = 0; i < static_cast<int>(MaxActiveTouchInputs); i++) {
Common::ParamPackage touchscreen_param{};
touchscreen_param.Set("engine", "touch");
touchscreen_param.Set("axis_x", i * 2);
touchscreen_param.Set("axis_y", (i * 2) + 1);
touchscreen_param.Set("button", i);
touch_params[index++] = std::move(touchscreen_param);
}
if (Settings::values.touch_from_button_maps.empty()) {
LOG_WARNING(Input, "touch_from_button_maps is unset by frontend config");
return;
}
const auto button_index =
static_cast<u64>(Settings::values.touch_from_button_map_index.GetValue());
const auto& touch_buttons = Settings::values.touch_from_button_maps[button_index].buttons;
// Map the rest of the fingers from touch from button configuration
for (const auto& config_entry : touch_buttons) {
if (index >= MaxTouchDevices) {
continue;
}
Common::ParamPackage params{config_entry};
Common::ParamPackage touch_button_params;
const int x = params.Get("x", 0);
const int y = params.Get("y", 0);
params.Erase("x");
params.Erase("y");
touch_button_params.Set("engine", "touch_from_button");
touch_button_params.Set("button", params.Serialize());
touch_button_params.Set("x", x);
touch_button_params.Set("y", y);
touch_params[index] = std::move(touch_button_params);
index++;
}
}
void EmulatedConsole::ReloadInput() {
// If you load any device here add the equivalent to the UnloadInput() function
SetTouchParams();
motion_params[1] = Common::ParamPackage{"engine:virtual_gamepad,port:8,motion:0"};
for (std::size_t index = 0; index < motion_devices.size(); ++index) {
motion_devices[index] = Common::Input::CreateInputDevice(motion_params[index]);
if (!motion_devices[index]) {
continue;
}
motion_devices[index]->SetCallback({
.on_change =
[this](const Common::Input::CallbackStatus& callback) { SetMotion(callback); },
});
}
// Restore motion state
auto& emulated_motion = console.motion_values.emulated;
auto& motion = console.motion_state;
emulated_motion.ResetRotations();
emulated_motion.ResetQuaternion();
motion.accel = emulated_motion.GetAcceleration();
motion.gyro = emulated_motion.GetGyroscope();
motion.rotation = emulated_motion.GetRotations();
motion.orientation = emulated_motion.GetOrientation();
motion.is_at_rest = !emulated_motion.IsMoving(motion_sensitivity);
// Unique index for identifying touch device source
std::size_t index = 0;
for (auto& touch_device : touch_devices) {
touch_device = Common::Input::CreateInputDevice(touch_params[index]);
if (!touch_device) {
continue;
}
touch_device->SetCallback({
.on_change =
[this, index](const Common::Input::CallbackStatus& callback) {
SetTouch(callback, index);
},
});
index++;
}
}
void EmulatedConsole::UnloadInput() {
for (auto& motion : motion_devices) {
motion.reset();
}
for (auto& touch : touch_devices) {
touch.reset();
}
}
void EmulatedConsole::EnableConfiguration() {
is_configuring = true;
SaveCurrentConfig();
}
void EmulatedConsole::DisableConfiguration() {
is_configuring = false;
}
bool EmulatedConsole::IsConfiguring() const {
return is_configuring;
}
void EmulatedConsole::SaveCurrentConfig() {
if (!is_configuring) {
return;
}
}
void EmulatedConsole::RestoreConfig() {
if (!is_configuring) {
return;
}
ReloadFromSettings();
}
Common::ParamPackage EmulatedConsole::GetMotionParam() const {
return motion_params[0];
}
void EmulatedConsole::SetMotionParam(Common::ParamPackage param) {
motion_params[0] = std::move(param);
ReloadInput();
}
void EmulatedConsole::SetMotion(const Common::Input::CallbackStatus& callback) {
std::unique_lock lock{mutex};
auto& raw_status = console.motion_values.raw_status;
auto& emulated = console.motion_values.emulated;
raw_status = TransformToMotion(callback);
emulated.SetAcceleration(Common::Vec3f{
raw_status.accel.x.value,
raw_status.accel.y.value,
raw_status.accel.z.value,
});
emulated.SetGyroscope(Common::Vec3f{
raw_status.gyro.x.value,
raw_status.gyro.y.value,
raw_status.gyro.z.value,
});
emulated.UpdateRotation(raw_status.delta_timestamp);
emulated.UpdateOrientation(raw_status.delta_timestamp);
if (is_configuring) {
lock.unlock();
TriggerOnChange(ConsoleTriggerType::Motion);
return;
}
auto& motion = console.motion_state;
motion.accel = emulated.GetAcceleration();
motion.gyro = emulated.GetGyroscope();
motion.rotation = emulated.GetRotations();
motion.orientation = emulated.GetOrientation();
motion.quaternion = emulated.GetQuaternion();
motion.gyro_bias = emulated.GetGyroBias();
motion.is_at_rest = !emulated.IsMoving(motion_sensitivity);
// Find what is this value
motion.verticalization_error = 0.0f;
lock.unlock();
TriggerOnChange(ConsoleTriggerType::Motion);
}
void EmulatedConsole::SetTouch(const Common::Input::CallbackStatus& callback, std::size_t index) {
if (index >= MaxTouchDevices) {
return;
}
std::unique_lock lock{mutex};
const auto touch_input = TransformToTouch(callback);
auto touch_index = GetIndexFromFingerId(index);
bool is_new_input = false;
if (!touch_index.has_value() && touch_input.pressed.value) {
touch_index = GetNextFreeIndex();
is_new_input = true;
}
// No free entries or invalid state. Ignore input
if (!touch_index.has_value()) {
return;
}
auto& touch_value = console.touch_values[touch_index.value()];
if (is_new_input) {
touch_value.pressed.value = true;
touch_value.id = static_cast<int>(index);
}
touch_value.x = touch_input.x;
touch_value.y = touch_input.y;
if (!touch_input.pressed.value) {
touch_value.pressed.value = false;
}
if (is_configuring) {
lock.unlock();
TriggerOnChange(ConsoleTriggerType::Touch);
return;
}
// Touch outside allowed range. Ignore input
if (touch_index.value() >= MaxActiveTouchInputs) {
return;
}
console.touch_state[touch_index.value()] = {
.position = {touch_value.x.value, touch_value.y.value},
.id = static_cast<u32>(touch_index.value()),
.pressed = touch_input.pressed.value,
};
lock.unlock();
TriggerOnChange(ConsoleTriggerType::Touch);
}
ConsoleMotionValues EmulatedConsole::GetMotionValues() const {
std::scoped_lock lock{mutex};
return console.motion_values;
}
TouchValues EmulatedConsole::GetTouchValues() const {
std::scoped_lock lock{mutex};
return console.touch_values;
}
ConsoleMotion EmulatedConsole::GetMotion() const {
std::scoped_lock lock{mutex};
return console.motion_state;
}
TouchFingerState EmulatedConsole::GetTouch() const {
std::scoped_lock lock{mutex};
return console.touch_state;
}
std::optional<std::size_t> EmulatedConsole::GetIndexFromFingerId(std::size_t finger_id) const {
for (std::size_t index = 0; index < MaxTouchDevices; ++index) {
const auto& finger = console.touch_values[index];
if (!finger.pressed.value) {
continue;
}
if (finger.id == static_cast<int>(finger_id)) {
return index;
}
}
return std::nullopt;
}
std::optional<std::size_t> EmulatedConsole::GetNextFreeIndex() const {
for (std::size_t index = 0; index < MaxTouchDevices; ++index) {
if (!console.touch_values[index].pressed.value) {
return index;
}
}
return std::nullopt;
}
void EmulatedConsole::TriggerOnChange(ConsoleTriggerType type) {
std::scoped_lock lock{callback_mutex};
for (const auto& poller_pair : callback_list) {
const ConsoleUpdateCallback& poller = poller_pair.second;
if (poller.on_change) {
poller.on_change(type);
}
}
}
int EmulatedConsole::SetCallback(ConsoleUpdateCallback update_callback) {
std::scoped_lock lock{callback_mutex};
callback_list.insert_or_assign(last_callback_key, std::move(update_callback));
return last_callback_key++;
}
void EmulatedConsole::DeleteCallback(int key) {
std::scoped_lock lock{callback_mutex};
const auto& iterator = callback_list.find(key);
if (iterator == callback_list.end()) {
LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
return;
}
callback_list.erase(iterator);
}
} // namespace Core::HID

View file

@ -0,0 +1,192 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <unordered_map>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/input.h"
#include "common/param_package.h"
#include "common/point.h"
#include "common/quaternion.h"
#include "common/vector_math.h"
#include "hid_core/frontend/motion_input.h"
#include "hid_core/hid_types.h"
namespace Core::HID {
static constexpr std::size_t MaxTouchDevices = 32;
static constexpr std::size_t MaxActiveTouchInputs = 16;
struct ConsoleMotionInfo {
Common::Input::MotionStatus raw_status{};
MotionInput emulated{};
};
using ConsoleMotionDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, 2>;
using TouchDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, MaxTouchDevices>;
using ConsoleMotionParams = std::array<Common::ParamPackage, 2>;
using TouchParams = std::array<Common::ParamPackage, MaxTouchDevices>;
using ConsoleMotionValues = ConsoleMotionInfo;
using TouchValues = std::array<Common::Input::TouchStatus, MaxTouchDevices>;
// Contains all motion related data that is used on the services
struct ConsoleMotion {
Common::Vec3f accel{};
Common::Vec3f gyro{};
Common::Vec3f rotation{};
std::array<Common::Vec3f, 3> orientation{};
Common::Quaternion<f32> quaternion{};
Common::Vec3f gyro_bias{};
f32 verticalization_error{};
bool is_at_rest{};
};
using TouchFingerState = std::array<TouchFinger, MaxActiveTouchInputs>;
struct ConsoleStatus {
// Data from input_common
ConsoleMotionValues motion_values{};
TouchValues touch_values{};
// Data for HID services
ConsoleMotion motion_state{};
TouchFingerState touch_state{};
};
enum class ConsoleTriggerType {
Motion,
Touch,
All,
};
struct ConsoleUpdateCallback {
std::function<void(ConsoleTriggerType)> on_change;
};
class EmulatedConsole {
public:
/**
* Contains all input data within the emulated switch console tablet such as touch and motion
*/
explicit EmulatedConsole();
~EmulatedConsole();
YUZU_NON_COPYABLE(EmulatedConsole);
YUZU_NON_MOVEABLE(EmulatedConsole);
/// Removes all callbacks created from input devices
void UnloadInput();
/**
* Sets the emulated console into configuring mode
* This prevents the modification of the HID state of the emulated console by input commands
*/
void EnableConfiguration();
/// Returns the emulated console into normal mode, allowing the modification of the HID state
void DisableConfiguration();
/// Returns true if the emulated console is in configuring mode
bool IsConfiguring() const;
/// Reload all input devices
void ReloadInput();
/// Overrides current mapped devices with the stored configuration and reloads all input devices
void ReloadFromSettings();
/// Saves the current mapped configuration
void SaveCurrentConfig();
/// Reverts any mapped changes made that weren't saved
void RestoreConfig();
// Returns the current mapped motion device
Common::ParamPackage GetMotionParam() const;
/**
* Updates the current mapped motion device
* @param param ParamPackage with controller data to be mapped
*/
void SetMotionParam(Common::ParamPackage param);
/// Returns the latest status of motion input from the console with parameters
ConsoleMotionValues GetMotionValues() const;
/// Returns the latest status of touch input from the console with parameters
TouchValues GetTouchValues() const;
/// Returns the latest status of motion input from the console
ConsoleMotion GetMotion() const;
/// Returns the latest status of touch input from the console
TouchFingerState GetTouch() const;
/**
* Adds a callback to the list of events
* @param update_callback A ConsoleUpdateCallback that will be triggered
* @return an unique key corresponding to the callback index in the list
*/
int SetCallback(ConsoleUpdateCallback update_callback);
/**
* Removes a callback from the list stopping any future events to this object
* @param key Key corresponding to the callback index in the list
*/
void DeleteCallback(int key);
private:
/// Creates and stores the touch params
void SetTouchParams();
/**
* Updates the motion status of the console
* @param callback A CallbackStatus containing gyro and accelerometer data
*/
void SetMotion(const Common::Input::CallbackStatus& callback);
/**
* Updates the touch status of the console
* @param callback A CallbackStatus containing the touch position
* @param index Finger ID to be updated
*/
void SetTouch(const Common::Input::CallbackStatus& callback, std::size_t index);
std::optional<std::size_t> GetIndexFromFingerId(std::size_t finger_id) const;
std::optional<std::size_t> GetNextFreeIndex() const;
/**
* Triggers a callback that something has changed on the console status
* @param type Input type of the event to trigger
*/
void TriggerOnChange(ConsoleTriggerType type);
bool is_configuring{false};
f32 motion_sensitivity{0.01f};
ConsoleMotionParams motion_params;
TouchParams touch_params;
ConsoleMotionDevices motion_devices;
TouchDevices touch_devices;
mutable std::mutex mutex;
mutable std::mutex callback_mutex;
std::unordered_map<int, ConsoleUpdateCallback> callback_list;
int last_callback_key = 0;
// Stores the current status of all console input
ConsoleStatus console;
};
} // namespace Core::HID

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,619 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <functional>
#include <memory>
#include <mutex>
#include <unordered_map>
#include <vector>
#include "common/common_types.h"
#include "common/input.h"
#include "common/param_package.h"
#include "common/settings.h"
#include "common/vector_math.h"
#include "hid_core/frontend/motion_input.h"
#include "hid_core/hid_types.h"
#include "hid_core/irsensor/irs_types.h"
namespace Core::HID {
const std::size_t max_emulated_controllers = 2;
const std::size_t output_devices_size = 4;
struct ControllerMotionInfo {
Common::Input::MotionStatus raw_status{};
MotionInput emulated{};
};
using ButtonDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeButton::NumButtons>;
using StickDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeAnalog::NumAnalogs>;
using ControllerMotionDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeMotion::NumMotions>;
using TriggerDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeTrigger::NumTriggers>;
using ColorDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
using BatteryDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
using CameraDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
using RingAnalogDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
using NfcDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
using OutputDevices = std::array<std::unique_ptr<Common::Input::OutputDevice>, output_devices_size>;
using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>;
using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>;
using ControllerMotionParams = std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions>;
using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>;
using ColorParams = std::array<Common::ParamPackage, max_emulated_controllers>;
using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>;
using CameraParams = std::array<Common::ParamPackage, max_emulated_controllers>;
using RingAnalogParams = std::array<Common::ParamPackage, max_emulated_controllers>;
using NfcParams = std::array<Common::ParamPackage, max_emulated_controllers>;
using OutputParams = std::array<Common::ParamPackage, output_devices_size>;
using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>;
using SticksValues = std::array<Common::Input::StickStatus, Settings::NativeAnalog::NumAnalogs>;
using TriggerValues =
std::array<Common::Input::TriggerStatus, Settings::NativeTrigger::NumTriggers>;
using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::NativeMotion::NumMotions>;
using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>;
using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>;
using CameraValues = Common::Input::CameraStatus;
using RingAnalogValue = Common::Input::AnalogStatus;
using NfcValues = Common::Input::NfcStatus;
using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>;
struct AnalogSticks {
AnalogStickState left{};
AnalogStickState right{};
};
struct ControllerColors {
NpadControllerColor fullkey{};
NpadControllerColor left{};
NpadControllerColor right{};
};
struct BatteryLevelState {
NpadPowerInfo dual{};
NpadPowerInfo left{};
NpadPowerInfo right{};
};
struct CameraState {
Core::IrSensor::ImageTransferProcessorFormat format{};
std::vector<u8> data{};
std::size_t sample{};
};
struct RingSensorForce {
f32 force;
};
using NfcState = Common::Input::NfcStatus;
struct ControllerMotion {
Common::Vec3f accel{};
Common::Vec3f gyro{};
Common::Vec3f rotation{};
Common::Vec3f euler{};
std::array<Common::Vec3f, 3> orientation{};
bool is_at_rest{};
};
enum EmulatedDeviceIndex : u8 {
LeftIndex,
RightIndex,
DualIndex,
AllDevices,
};
using MotionState = std::array<ControllerMotion, 2>;
struct ControllerStatus {
// Data from input_common
ButtonValues button_values{};
SticksValues stick_values{};
ControllerMotionValues motion_values{};
TriggerValues trigger_values{};
ColorValues color_values{};
BatteryValues battery_values{};
VibrationValues vibration_values{};
CameraValues camera_values{};
RingAnalogValue ring_analog_value{};
NfcValues nfc_values{};
// Data for HID services
HomeButtonState home_button_state{};
CaptureButtonState capture_button_state{};
NpadButtonState npad_button_state{};
DebugPadButton debug_pad_button_state{};
AnalogSticks analog_stick_state{};
MotionState motion_state{};
NpadGcTriggerState gc_trigger_state{};
ControllerColors colors_state{};
BatteryLevelState battery_state{};
CameraState camera_state{};
RingSensorForce ring_analog_state{};
NfcState nfc_state{};
Common::Input::PollingMode left_polling_mode{};
Common::Input::PollingMode right_polling_mode{};
};
enum class ControllerTriggerType {
Button,
Stick,
Trigger,
Motion,
Color,
Battery,
Vibration,
IrSensor,
RingController,
Nfc,
Connected,
Disconnected,
Type,
All,
};
struct ControllerUpdateCallback {
std::function<void(ControllerTriggerType)> on_change;
bool is_npad_service;
};
class EmulatedController {
public:
/**
* Contains all input data (buttons, joysticks, vibration, and motion) within this controller.
* @param npad_id_type npad id type for this specific controller
*/
explicit EmulatedController(NpadIdType npad_id_type_);
~EmulatedController();
YUZU_NON_COPYABLE(EmulatedController);
YUZU_NON_MOVEABLE(EmulatedController);
/// Converts the controller type from settings to npad type
static NpadStyleIndex MapSettingsTypeToNPad(Settings::ControllerType type);
/// Converts npad type to the equivalent of controller type from settings
static Settings::ControllerType MapNPadToSettingsType(NpadStyleIndex type);
/// Gets the NpadIdType for this controller
NpadIdType GetNpadIdType() const;
/// Sets the NpadStyleIndex for this controller
void SetNpadStyleIndex(NpadStyleIndex npad_type_);
/**
* Gets the NpadStyleIndex for this controller
* @param get_temporary_value If true tmp_npad_type will be returned
* @return NpadStyleIndex set on the controller
*/
NpadStyleIndex GetNpadStyleIndex(bool get_temporary_value = false) const;
/**
* Sets the supported controller types. Disconnects the controller if current type is not
* supported
* @param supported_styles bitflag with supported types
*/
void SetSupportedNpadStyleTag(NpadStyleTag supported_styles);
/**
* Sets the connected status to true
* @param use_temporary_value If true tmp_npad_type will be used
*/
void Connect(bool use_temporary_value = false);
/// Sets the connected status to false
void Disconnect();
/**
* Is the emulated connected
* @param get_temporary_value If true tmp_is_connected will be returned
* @return true if the controller has the connected status
*/
bool IsConnected(bool get_temporary_value = false) const;
/// Removes all callbacks created from input devices
void UnloadInput();
/**
* Sets the emulated controller into configuring mode
* This prevents the modification of the HID state of the emulated controller by input commands
*/
void EnableConfiguration();
/// Returns the emulated controller into normal mode, allowing the modification of the HID state
void DisableConfiguration();
/// Enables Home and Screenshot buttons
void EnableSystemButtons();
/// Disables Home and Screenshot buttons
void DisableSystemButtons();
/// Sets Home and Screenshot buttons to false
void ResetSystemButtons();
/// Returns true if the emulated controller is in configuring mode
bool IsConfiguring() const;
/// Reload all input devices
void ReloadInput();
/// Overrides current mapped devices with the stored configuration and reloads all input devices
void ReloadFromSettings();
/// Updates current colors with the ones stored in the configuration
void ReloadColorsFromSettings();
/// Saves the current mapped configuration
void SaveCurrentConfig();
/// Reverts any mapped changes made that weren't saved
void RestoreConfig();
/// Returns a vector of mapped devices from the mapped button and stick parameters
std::vector<Common::ParamPackage> GetMappedDevices() const;
// Returns the current mapped button device
Common::ParamPackage GetButtonParam(std::size_t index) const;
// Returns the current mapped stick device
Common::ParamPackage GetStickParam(std::size_t index) const;
// Returns the current mapped motion device
Common::ParamPackage GetMotionParam(std::size_t index) const;
/**
* Updates the current mapped button device
* @param param ParamPackage with controller data to be mapped
*/
void SetButtonParam(std::size_t index, Common::ParamPackage param);
/**
* Updates the current mapped stick device
* @param param ParamPackage with controller data to be mapped
*/
void SetStickParam(std::size_t index, Common::ParamPackage param);
/**
* Updates the current mapped motion device
* @param param ParamPackage with controller data to be mapped
*/
void SetMotionParam(std::size_t index, Common::ParamPackage param);
/// Auto calibrates the current motion devices
void StartMotionCalibration();
/// Returns the latest button status from the controller with parameters
ButtonValues GetButtonsValues() const;
/// Returns the latest analog stick status from the controller with parameters
SticksValues GetSticksValues() const;
/// Returns the latest trigger status from the controller with parameters
TriggerValues GetTriggersValues() const;
/// Returns the latest motion status from the controller with parameters
ControllerMotionValues GetMotionValues() const;
/// Returns the latest color status from the controller with parameters
ColorValues GetColorsValues() const;
/// Returns the latest battery status from the controller with parameters
BatteryValues GetBatteryValues() const;
/// Returns the latest camera status from the controller with parameters
CameraValues GetCameraValues() const;
/// Returns the latest status of analog input from the ring sensor with parameters
RingAnalogValue GetRingSensorValues() const;
/// Returns the latest status of button input for the hid::HomeButton service
HomeButtonState GetHomeButtons() const;
/// Returns the latest status of button input for the hid::CaptureButton service
CaptureButtonState GetCaptureButtons() const;
/// Returns the latest status of button input for the hid::Npad service
NpadButtonState GetNpadButtons() const;
/// Returns the latest status of button input for the debug pad service
DebugPadButton GetDebugPadButtons() const;
/// Returns the latest status of stick input from the mouse
AnalogSticks GetSticks() const;
/// Returns the latest status of trigger input from the mouse
NpadGcTriggerState GetTriggers() const;
/// Returns the latest status of motion input from the mouse
MotionState GetMotions() const;
/// Returns the latest color value from the controller
ControllerColors GetColors() const;
/// Returns the latest battery status from the controller
BatteryLevelState GetBattery() const;
/// Returns the latest camera status from the controller
const CameraState& GetCamera() const;
/// Returns the latest ringcon force sensor value
RingSensorForce GetRingSensorForce() const;
/// Returns the latest ntag status from the controller
const NfcState& GetNfc() const;
/**
* Sends a specific vibration to the output device
* @return true if vibration had no errors
*/
bool SetVibration(std::size_t device_index, VibrationValue vibration);
/**
* Sends a small vibration to the output device
* @return true if SetVibration was successful
*/
bool IsVibrationEnabled(std::size_t device_index);
/**
* Sets the desired data to be polled from a controller
* @param device_index index of the controller to set the polling mode
* @param polling_mode type of input desired buttons, gyro, nfc, ir, etc.
* @return driver result from this command
*/
Common::Input::DriverResult SetPollingMode(EmulatedDeviceIndex device_index,
Common::Input::PollingMode polling_mode);
/**
* Get the current polling mode from a controller
* @param device_index index of the controller to set the polling mode
* @return current polling mode
*/
Common::Input::PollingMode GetPollingMode(EmulatedDeviceIndex device_index) const;
/**
* Sets the desired camera format to be polled from a controller
* @param camera_format size of each frame
* @return true if SetCameraFormat was successful
*/
bool SetCameraFormat(Core::IrSensor::ImageTransferProcessorFormat camera_format);
// Returns the current mapped ring device
Common::ParamPackage GetRingParam() const;
/**
* Updates the current mapped ring device
* @param param ParamPackage with ring sensor data to be mapped
*/
void SetRingParam(Common::ParamPackage param);
/// Returns true if the device has nfc support
bool HasNfc() const;
/// Sets the joycon in nfc mode and increments the handle count
bool AddNfcHandle();
/// Decrements the handle count if zero sets the joycon in active mode
bool RemoveNfcHandle();
/// Start searching for nfc tags
bool StartNfcPolling();
/// Stop searching for nfc tags
bool StopNfcPolling();
/// Returns true if the nfc tag was readable
bool ReadAmiiboData(std::vector<u8>& data);
/// Returns true if the nfc tag was written
bool WriteNfc(const std::vector<u8>& data);
/// Returns true if the nfc tag was readable
bool ReadMifareData(const Common::Input::MifareRequest& request,
Common::Input::MifareRequest& out_data);
/// Returns true if the nfc tag was written
bool WriteMifareData(const Common::Input::MifareRequest& request);
/// Returns the led pattern corresponding to this emulated controller
LedPattern GetLedPattern() const;
/// Asks the output device to change the player led pattern
void SetLedPattern();
/// Changes sensitivity of the motion sensor
void SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode mode);
/**
* Adds a callback to the list of events
* @param update_callback A ConsoleUpdateCallback that will be triggered
* @return an unique key corresponding to the callback index in the list
*/
int SetCallback(ControllerUpdateCallback update_callback);
/**
* Removes a callback from the list stopping any future events to this object
* @param key Key corresponding to the callback index in the list
*/
void DeleteCallback(int key);
/// Swaps the state of the turbo buttons and updates motion input
void StatusUpdate();
private:
/// creates input devices from params
void LoadDevices();
/// Set the params for TAS devices
void LoadTASParams();
/// Set the params for virtual pad devices
void LoadVirtualGamepadParams();
/**
* @param use_temporary_value If true tmp_npad_type will be used
* @return true if the controller style is fullkey
*/
bool IsControllerFullkey(bool use_temporary_value = false) const;
/**
* Checks the current controller type against the supported_style_tag
* @param use_temporary_value If true tmp_npad_type will be used
* @return true if the controller is supported
*/
bool IsControllerSupported(bool use_temporary_value = false) const;
/**
* Updates the button status of the controller
* @param callback A CallbackStatus containing the button status
* @param index Button ID of the to be updated
*/
void SetButton(const Common::Input::CallbackStatus& callback, std::size_t index,
Common::UUID uuid);
/**
* Updates the analog stick status of the controller
* @param callback A CallbackStatus containing the analog stick status
* @param index stick ID of the to be updated
*/
void SetStick(const Common::Input::CallbackStatus& callback, std::size_t index,
Common::UUID uuid);
/**
* Updates the trigger status of the controller
* @param callback A CallbackStatus containing the trigger status
* @param index trigger ID of the to be updated
*/
void SetTrigger(const Common::Input::CallbackStatus& callback, std::size_t index,
Common::UUID uuid);
/**
* Updates the motion status of the controller
* @param callback A CallbackStatus containing gyro and accelerometer data
* @param index motion ID of the to be updated
*/
void SetMotion(const Common::Input::CallbackStatus& callback, std::size_t index);
/**
* Updates the color status of the controller
* @param callback A CallbackStatus containing the color status
* @param index color ID of the to be updated
*/
void SetColors(const Common::Input::CallbackStatus& callback, std::size_t index);
/**
* Updates the battery status of the controller
* @param callback A CallbackStatus containing the battery status
* @param index battery ID of the to be updated
*/
void SetBattery(const Common::Input::CallbackStatus& callback, std::size_t index);
/**
* Updates the camera status of the controller
* @param callback A CallbackStatus containing the camera status
*/
void SetCamera(const Common::Input::CallbackStatus& callback);
/**
* Updates the ring analog sensor status of the ring controller
* @param callback A CallbackStatus containing the force status
*/
void SetRingAnalog(const Common::Input::CallbackStatus& callback);
/**
* Updates the nfc status of the controller
* @param callback A CallbackStatus containing the nfc status
*/
void SetNfc(const Common::Input::CallbackStatus& callback);
/**
* Converts a color format from bgra to rgba
* @param color in bgra format
* @return NpadColor in rgba format
*/
NpadColor GetNpadColor(u32 color);
/**
* Triggers a callback that something has changed on the controller status
* @param type Input type of the event to trigger
* @param is_service_update indicates if this event should only be sent to HID services
*/
void TriggerOnChange(ControllerTriggerType type, bool is_service_update);
NpadButton GetTurboButtonMask() const;
const NpadIdType npad_id_type;
NpadStyleIndex npad_type{NpadStyleIndex::None};
NpadStyleIndex original_npad_type{NpadStyleIndex::None};
NpadStyleTag supported_style_tag{NpadStyleSet::All};
bool is_connected{false};
bool is_configuring{false};
bool is_initalized{false};
bool system_buttons_enabled{true};
f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard};
u32 turbo_button_state{0};
std::size_t nfc_handles{0};
// Temporary values to avoid doing changes while the controller is in configuring mode
NpadStyleIndex tmp_npad_type{NpadStyleIndex::None};
bool tmp_is_connected{false};
ButtonParams button_params;
StickParams stick_params;
ControllerMotionParams motion_params;
TriggerParams trigger_params;
BatteryParams battery_params;
ColorParams color_params;
CameraParams camera_params;
RingAnalogParams ring_params;
NfcParams nfc_params;
OutputParams output_params;
ButtonDevices button_devices;
StickDevices stick_devices;
ControllerMotionDevices motion_devices;
TriggerDevices trigger_devices;
BatteryDevices battery_devices;
ColorDevices color_devices;
CameraDevices camera_devices;
RingAnalogDevices ring_analog_devices;
NfcDevices nfc_devices;
OutputDevices output_devices;
// TAS related variables
ButtonParams tas_button_params;
StickParams tas_stick_params;
ButtonDevices tas_button_devices;
StickDevices tas_stick_devices;
// Virtual gamepad related variables
ButtonParams virtual_button_params;
StickParams virtual_stick_params;
ControllerMotionParams virtual_motion_params;
ButtonDevices virtual_button_devices;
StickDevices virtual_stick_devices;
ControllerMotionDevices virtual_motion_devices;
mutable std::mutex mutex;
mutable std::mutex callback_mutex;
mutable std::mutex npad_mutex;
mutable std::mutex connect_mutex;
std::unordered_map<int, ControllerUpdateCallback> callback_list;
int last_callback_key = 0;
// Stores the current status of all controller input
ControllerStatus controller;
};
} // namespace Core::HID

View file

@ -0,0 +1,483 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <fmt/format.h>
#include "hid_core/frontend/emulated_devices.h"
#include "hid_core/frontend/input_converter.h"
namespace Core::HID {
EmulatedDevices::EmulatedDevices() = default;
EmulatedDevices::~EmulatedDevices() = default;
void EmulatedDevices::ReloadFromSettings() {
ReloadInput();
}
void EmulatedDevices::ReloadInput() {
// If you load any device here add the equivalent to the UnloadInput() function
// Native Mouse is mapped on port 1, pad 0
const Common::ParamPackage mouse_params{"engine:mouse,port:1,pad:0"};
// Keyboard keys is mapped on port 1, pad 0 for normal keys, pad 1 for moddifier keys
const Common::ParamPackage keyboard_params{"engine:keyboard,port:1"};
std::size_t key_index = 0;
for (auto& mouse_device : mouse_button_devices) {
Common::ParamPackage mouse_button_params = mouse_params;
mouse_button_params.Set("button", static_cast<int>(key_index));
mouse_device = Common::Input::CreateInputDevice(mouse_button_params);
key_index++;
}
Common::ParamPackage mouse_position_params = mouse_params;
mouse_position_params.Set("axis_x", 0);
mouse_position_params.Set("axis_y", 1);
mouse_position_params.Set("deadzone", 0.0f);
mouse_position_params.Set("range", 1.0f);
mouse_position_params.Set("threshold", 0.0f);
mouse_stick_device = Common::Input::CreateInputDevice(mouse_position_params);
// First two axis are reserved for mouse position
key_index = 2;
for (auto& mouse_device : mouse_wheel_devices) {
Common::ParamPackage mouse_wheel_params = mouse_params;
mouse_wheel_params.Set("axis", static_cast<int>(key_index));
mouse_device = Common::Input::CreateInputDevice(mouse_wheel_params);
key_index++;
}
key_index = 0;
for (auto& keyboard_device : keyboard_devices) {
Common::ParamPackage keyboard_key_params = keyboard_params;
keyboard_key_params.Set("button", static_cast<int>(key_index));
keyboard_key_params.Set("pad", 0);
keyboard_device = Common::Input::CreateInputDevice(keyboard_key_params);
key_index++;
}
key_index = 0;
for (auto& keyboard_device : keyboard_modifier_devices) {
Common::ParamPackage keyboard_moddifier_params = keyboard_params;
keyboard_moddifier_params.Set("button", static_cast<int>(key_index));
keyboard_moddifier_params.Set("pad", 1);
keyboard_device = Common::Input::CreateInputDevice(keyboard_moddifier_params);
key_index++;
}
for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) {
if (!mouse_button_devices[index]) {
continue;
}
mouse_button_devices[index]->SetCallback({
.on_change =
[this, index](const Common::Input::CallbackStatus& callback) {
SetMouseButton(callback, index);
},
});
}
for (std::size_t index = 0; index < mouse_wheel_devices.size(); ++index) {
if (!mouse_wheel_devices[index]) {
continue;
}
mouse_wheel_devices[index]->SetCallback({
.on_change =
[this, index](const Common::Input::CallbackStatus& callback) {
SetMouseWheel(callback, index);
},
});
}
if (mouse_stick_device) {
mouse_stick_device->SetCallback({
.on_change =
[this](const Common::Input::CallbackStatus& callback) {
SetMousePosition(callback);
},
});
}
for (std::size_t index = 0; index < keyboard_devices.size(); ++index) {
if (!keyboard_devices[index]) {
continue;
}
keyboard_devices[index]->SetCallback({
.on_change =
[this, index](const Common::Input::CallbackStatus& callback) {
SetKeyboardButton(callback, index);
},
});
}
for (std::size_t index = 0; index < keyboard_modifier_devices.size(); ++index) {
if (!keyboard_modifier_devices[index]) {
continue;
}
keyboard_modifier_devices[index]->SetCallback({
.on_change =
[this, index](const Common::Input::CallbackStatus& callback) {
SetKeyboardModifier(callback, index);
},
});
}
}
void EmulatedDevices::UnloadInput() {
for (auto& button : mouse_button_devices) {
button.reset();
}
for (auto& analog : mouse_wheel_devices) {
analog.reset();
}
mouse_stick_device.reset();
for (auto& button : keyboard_devices) {
button.reset();
}
for (auto& button : keyboard_modifier_devices) {
button.reset();
}
}
void EmulatedDevices::EnableConfiguration() {
is_configuring = true;
SaveCurrentConfig();
}
void EmulatedDevices::DisableConfiguration() {
is_configuring = false;
}
bool EmulatedDevices::IsConfiguring() const {
return is_configuring;
}
void EmulatedDevices::SaveCurrentConfig() {
if (!is_configuring) {
return;
}
}
void EmulatedDevices::RestoreConfig() {
if (!is_configuring) {
return;
}
ReloadFromSettings();
}
void EmulatedDevices::SetKeyboardButton(const Common::Input::CallbackStatus& callback,
std::size_t index) {
if (index >= device_status.keyboard_values.size()) {
return;
}
std::unique_lock lock{mutex};
bool value_changed = false;
const auto new_status = TransformToButton(callback);
auto& current_status = device_status.keyboard_values[index];
current_status.toggle = new_status.toggle;
// Update button status with current status
if (!current_status.toggle) {
current_status.locked = false;
if (current_status.value != new_status.value) {
current_status.value = new_status.value;
value_changed = true;
}
} else {
// Toggle button and lock status
if (new_status.value && !current_status.locked) {
current_status.locked = true;
current_status.value = !current_status.value;
value_changed = true;
}
// Unlock button, ready for next press
if (!new_status.value && current_status.locked) {
current_status.locked = false;
}
}
if (!value_changed) {
return;
}
if (is_configuring) {
lock.unlock();
TriggerOnChange(DeviceTriggerType::Keyboard);
return;
}
// Index should be converted from NativeKeyboard to KeyboardKeyIndex
UpdateKey(index, current_status.value);
lock.unlock();
TriggerOnChange(DeviceTriggerType::Keyboard);
}
void EmulatedDevices::UpdateKey(std::size_t key_index, bool status) {
constexpr std::size_t KEYS_PER_BYTE = 8;
auto& entry = device_status.keyboard_state.key[key_index / KEYS_PER_BYTE];
const u8 mask = static_cast<u8>(1 << (key_index % KEYS_PER_BYTE));
if (status) {
entry = entry | mask;
} else {
entry = static_cast<u8>(entry & ~mask);
}
}
void EmulatedDevices::SetKeyboardModifier(const Common::Input::CallbackStatus& callback,
std::size_t index) {
if (index >= device_status.keyboard_moddifier_values.size()) {
return;
}
std::unique_lock lock{mutex};
bool value_changed = false;
const auto new_status = TransformToButton(callback);
auto& current_status = device_status.keyboard_moddifier_values[index];
current_status.toggle = new_status.toggle;
// Update button status with current
if (!current_status.toggle) {
current_status.locked = false;
if (current_status.value != new_status.value) {
current_status.value = new_status.value;
value_changed = true;
}
} else {
// Toggle button and lock status
if (new_status.value && !current_status.locked) {
current_status.locked = true;
current_status.value = !current_status.value;
value_changed = true;
}
// Unlock button ready for next press
if (!new_status.value && current_status.locked) {
current_status.locked = false;
}
}
if (!value_changed) {
return;
}
if (is_configuring) {
lock.unlock();
TriggerOnChange(DeviceTriggerType::KeyboardModdifier);
return;
}
switch (index) {
case Settings::NativeKeyboard::LeftControl:
case Settings::NativeKeyboard::RightControl:
device_status.keyboard_moddifier_state.control.Assign(current_status.value);
break;
case Settings::NativeKeyboard::LeftShift:
case Settings::NativeKeyboard::RightShift:
device_status.keyboard_moddifier_state.shift.Assign(current_status.value);
break;
case Settings::NativeKeyboard::LeftAlt:
device_status.keyboard_moddifier_state.left_alt.Assign(current_status.value);
break;
case Settings::NativeKeyboard::RightAlt:
device_status.keyboard_moddifier_state.right_alt.Assign(current_status.value);
break;
case Settings::NativeKeyboard::CapsLock:
device_status.keyboard_moddifier_state.caps_lock.Assign(current_status.value);
break;
case Settings::NativeKeyboard::ScrollLock:
device_status.keyboard_moddifier_state.scroll_lock.Assign(current_status.value);
break;
case Settings::NativeKeyboard::NumLock:
device_status.keyboard_moddifier_state.num_lock.Assign(current_status.value);
break;
}
lock.unlock();
TriggerOnChange(DeviceTriggerType::KeyboardModdifier);
}
void EmulatedDevices::SetMouseButton(const Common::Input::CallbackStatus& callback,
std::size_t index) {
if (index >= device_status.mouse_button_values.size()) {
return;
}
std::unique_lock lock{mutex};
bool value_changed = false;
const auto new_status = TransformToButton(callback);
auto& current_status = device_status.mouse_button_values[index];
current_status.toggle = new_status.toggle;
// Update button status with current
if (!current_status.toggle) {
current_status.locked = false;
if (current_status.value != new_status.value) {
current_status.value = new_status.value;
value_changed = true;
}
} else {
// Toggle button and lock status
if (new_status.value && !current_status.locked) {
current_status.locked = true;
current_status.value = !current_status.value;
value_changed = true;
}
// Unlock button ready for next press
if (!new_status.value && current_status.locked) {
current_status.locked = false;
}
}
if (!value_changed) {
return;
}
if (is_configuring) {
lock.unlock();
TriggerOnChange(DeviceTriggerType::Mouse);
return;
}
switch (index) {
case Settings::NativeMouseButton::Left:
device_status.mouse_button_state.left.Assign(current_status.value);
break;
case Settings::NativeMouseButton::Right:
device_status.mouse_button_state.right.Assign(current_status.value);
break;
case Settings::NativeMouseButton::Middle:
device_status.mouse_button_state.middle.Assign(current_status.value);
break;
case Settings::NativeMouseButton::Forward:
device_status.mouse_button_state.forward.Assign(current_status.value);
break;
case Settings::NativeMouseButton::Back:
device_status.mouse_button_state.back.Assign(current_status.value);
break;
}
lock.unlock();
TriggerOnChange(DeviceTriggerType::Mouse);
}
void EmulatedDevices::SetMouseWheel(const Common::Input::CallbackStatus& callback,
std::size_t index) {
if (index >= device_status.mouse_wheel_values.size()) {
return;
}
std::unique_lock lock{mutex};
const auto analog_value = TransformToAnalog(callback);
device_status.mouse_wheel_values[index] = analog_value;
if (is_configuring) {
device_status.mouse_wheel_state = {};
lock.unlock();
TriggerOnChange(DeviceTriggerType::Mouse);
return;
}
switch (index) {
case Settings::NativeMouseWheel::X:
device_status.mouse_wheel_state.x = static_cast<s32>(analog_value.value);
break;
case Settings::NativeMouseWheel::Y:
device_status.mouse_wheel_state.y = static_cast<s32>(analog_value.value);
break;
}
lock.unlock();
TriggerOnChange(DeviceTriggerType::Mouse);
}
void EmulatedDevices::SetMousePosition(const Common::Input::CallbackStatus& callback) {
std::unique_lock lock{mutex};
const auto touch_value = TransformToTouch(callback);
device_status.mouse_stick_value = touch_value;
if (is_configuring) {
device_status.mouse_position_state = {};
lock.unlock();
TriggerOnChange(DeviceTriggerType::Mouse);
return;
}
device_status.mouse_position_state.x = touch_value.x.value;
device_status.mouse_position_state.y = touch_value.y.value;
lock.unlock();
TriggerOnChange(DeviceTriggerType::Mouse);
}
KeyboardValues EmulatedDevices::GetKeyboardValues() const {
std::scoped_lock lock{mutex};
return device_status.keyboard_values;
}
KeyboardModifierValues EmulatedDevices::GetKeyboardModdifierValues() const {
std::scoped_lock lock{mutex};
return device_status.keyboard_moddifier_values;
}
MouseButtonValues EmulatedDevices::GetMouseButtonsValues() const {
std::scoped_lock lock{mutex};
return device_status.mouse_button_values;
}
KeyboardKey EmulatedDevices::GetKeyboard() const {
std::scoped_lock lock{mutex};
return device_status.keyboard_state;
}
KeyboardModifier EmulatedDevices::GetKeyboardModifier() const {
std::scoped_lock lock{mutex};
return device_status.keyboard_moddifier_state;
}
MouseButton EmulatedDevices::GetMouseButtons() const {
std::scoped_lock lock{mutex};
return device_status.mouse_button_state;
}
MousePosition EmulatedDevices::GetMousePosition() const {
std::scoped_lock lock{mutex};
return device_status.mouse_position_state;
}
AnalogStickState EmulatedDevices::GetMouseWheel() const {
std::scoped_lock lock{mutex};
return device_status.mouse_wheel_state;
}
void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) {
std::scoped_lock lock{callback_mutex};
for (const auto& poller_pair : callback_list) {
const InterfaceUpdateCallback& poller = poller_pair.second;
if (poller.on_change) {
poller.on_change(type);
}
}
}
int EmulatedDevices::SetCallback(InterfaceUpdateCallback update_callback) {
std::scoped_lock lock{callback_mutex};
callback_list.insert_or_assign(last_callback_key, std::move(update_callback));
return last_callback_key++;
}
void EmulatedDevices::DeleteCallback(int key) {
std::scoped_lock lock{callback_mutex};
const auto& iterator = callback_list.find(key);
if (iterator == callback_list.end()) {
LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
return;
}
callback_list.erase(iterator);
}
} // namespace Core::HID

View file

@ -0,0 +1,212 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <functional>
#include <memory>
#include <mutex>
#include <unordered_map>
#include <vector>
#include "common/common_types.h"
#include "common/input.h"
#include "common/param_package.h"
#include "common/settings.h"
#include "hid_core/hid_types.h"
namespace Core::HID {
using KeyboardDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
Settings::NativeKeyboard::NumKeyboardKeys>;
using KeyboardModifierDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
Settings::NativeKeyboard::NumKeyboardMods>;
using MouseButtonDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
Settings::NativeMouseButton::NumMouseButtons>;
using MouseWheelDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
Settings::NativeMouseWheel::NumMouseWheels>;
using MouseStickDevice = std::unique_ptr<Common::Input::InputDevice>;
using MouseButtonParams =
std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>;
using KeyboardValues =
std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>;
using KeyboardModifierValues =
std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardMods>;
using MouseButtonValues =
std::array<Common::Input::ButtonStatus, Settings::NativeMouseButton::NumMouseButtons>;
using MouseWheelValues =
std::array<Common::Input::AnalogStatus, Settings::NativeMouseWheel::NumMouseWheels>;
using MouseStickValue = Common::Input::TouchStatus;
struct MousePosition {
f32 x;
f32 y;
};
struct DeviceStatus {
// Data from input_common
KeyboardValues keyboard_values{};
KeyboardModifierValues keyboard_moddifier_values{};
MouseButtonValues mouse_button_values{};
MouseWheelValues mouse_wheel_values{};
MouseStickValue mouse_stick_value{};
// Data for HID services
KeyboardKey keyboard_state{};
KeyboardModifier keyboard_moddifier_state{};
MouseButton mouse_button_state{};
MousePosition mouse_position_state{};
AnalogStickState mouse_wheel_state{};
};
enum class DeviceTriggerType {
Keyboard,
KeyboardModdifier,
Mouse,
RingController,
};
struct InterfaceUpdateCallback {
std::function<void(DeviceTriggerType)> on_change;
};
class EmulatedDevices {
public:
/**
* Contains all input data related to external devices that aren't necessarily a controller
* This includes devices such as the keyboard or mouse
*/
explicit EmulatedDevices();
~EmulatedDevices();
YUZU_NON_COPYABLE(EmulatedDevices);
YUZU_NON_MOVEABLE(EmulatedDevices);
/// Removes all callbacks created from input devices
void UnloadInput();
/**
* Sets the emulated devices into configuring mode
* This prevents the modification of the HID state of the emulated devices by input commands
*/
void EnableConfiguration();
/// Returns the emulated devices into normal mode, allowing the modification of the HID state
void DisableConfiguration();
/// Returns true if the emulated device is in configuring mode
bool IsConfiguring() const;
/// Reload all input devices
void ReloadInput();
/// Overrides current mapped devices with the stored configuration and reloads all input devices
void ReloadFromSettings();
/// Saves the current mapped configuration
void SaveCurrentConfig();
/// Reverts any mapped changes made that weren't saved
void RestoreConfig();
/// Returns the latest status of button input from the keyboard with parameters
KeyboardValues GetKeyboardValues() const;
/// Returns the latest status of button input from the keyboard modifiers with parameters
KeyboardModifierValues GetKeyboardModdifierValues() const;
/// Returns the latest status of button input from the mouse with parameters
MouseButtonValues GetMouseButtonsValues() const;
/// Returns the latest status of button input from the keyboard
KeyboardKey GetKeyboard() const;
/// Returns the latest status of button input from the keyboard modifiers
KeyboardModifier GetKeyboardModifier() const;
/// Returns the latest status of button input from the mouse
MouseButton GetMouseButtons() const;
/// Returns the latest mouse coordinates
MousePosition GetMousePosition() const;
/// Returns the latest mouse wheel change
AnalogStickState GetMouseWheel() const;
/**
* Adds a callback to the list of events
* @param update_callback InterfaceUpdateCallback that will be triggered
* @return an unique key corresponding to the callback index in the list
*/
int SetCallback(InterfaceUpdateCallback update_callback);
/**
* Removes a callback from the list stopping any future events to this object
* @param key Key corresponding to the callback index in the list
*/
void DeleteCallback(int key);
private:
/// Helps assigning a value to keyboard_state
void UpdateKey(std::size_t key_index, bool status);
/**
* Updates the touch status of the keyboard device
* @param callback A CallbackStatus containing the key status
* @param index key ID to be updated
*/
void SetKeyboardButton(const Common::Input::CallbackStatus& callback, std::size_t index);
/**
* Updates the keyboard status of the keyboard device
* @param callback A CallbackStatus containing the modifier key status
* @param index modifier key ID to be updated
*/
void SetKeyboardModifier(const Common::Input::CallbackStatus& callback, std::size_t index);
/**
* Updates the mouse button status of the mouse device
* @param callback A CallbackStatus containing the button status
* @param index Button ID to be updated
*/
void SetMouseButton(const Common::Input::CallbackStatus& callback, std::size_t index);
/**
* Updates the mouse wheel status of the mouse device
* @param callback A CallbackStatus containing the wheel status
* @param index wheel ID to be updated
*/
void SetMouseWheel(const Common::Input::CallbackStatus& callback, std::size_t index);
/**
* Updates the mouse position status of the mouse device
* @param callback A CallbackStatus containing the position status
*/
void SetMousePosition(const Common::Input::CallbackStatus& callback);
/**
* Triggers a callback that something has changed on the device status
* @param type Input type of the event to trigger
*/
void TriggerOnChange(DeviceTriggerType type);
bool is_configuring{false};
KeyboardDevices keyboard_devices;
KeyboardModifierDevices keyboard_modifier_devices;
MouseButtonDevices mouse_button_devices;
MouseWheelDevices mouse_wheel_devices;
MouseStickDevice mouse_stick_device;
mutable std::mutex mutex;
mutable std::mutex callback_mutex;
std::unordered_map<int, InterfaceUpdateCallback> callback_list;
int last_callback_key = 0;
// Stores the current status of all external device input
DeviceStatus device_status;
};
} // namespace Core::HID

View file

@ -0,0 +1,436 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <random>
#include "common/input.h"
#include "hid_core/frontend/input_converter.h"
namespace Core::HID {
Common::Input::BatteryStatus TransformToBattery(const Common::Input::CallbackStatus& callback) {
Common::Input::BatteryStatus battery{Common::Input::BatteryStatus::None};
switch (callback.type) {
case Common::Input::InputType::Analog:
case Common::Input::InputType::Trigger: {
const auto value = TransformToTrigger(callback).analog.value;
battery = Common::Input::BatteryLevel::Empty;
if (value > 0.2f) {
battery = Common::Input::BatteryLevel::Critical;
}
if (value > 0.4f) {
battery = Common::Input::BatteryLevel::Low;
}
if (value > 0.6f) {
battery = Common::Input::BatteryLevel::Medium;
}
if (value > 0.8f) {
battery = Common::Input::BatteryLevel::Full;
}
if (value >= 0.95f) {
battery = Common::Input::BatteryLevel::Charging;
}
break;
}
case Common::Input::InputType::Button:
battery = callback.button_status.value ? Common::Input::BatteryLevel::Charging
: Common::Input::BatteryLevel::Critical;
break;
case Common::Input::InputType::Battery:
battery = callback.battery_status;
break;
default:
LOG_ERROR(Input, "Conversion from type {} to battery not implemented", callback.type);
break;
}
return battery;
}
Common::Input::ButtonStatus TransformToButton(const Common::Input::CallbackStatus& callback) {
Common::Input::ButtonStatus status{};
switch (callback.type) {
case Common::Input::InputType::Analog:
status.value = TransformToTrigger(callback).pressed.value;
status.toggle = callback.analog_status.properties.toggle;
status.inverted = callback.analog_status.properties.inverted_button;
break;
case Common::Input::InputType::Trigger:
status.value = TransformToTrigger(callback).pressed.value;
break;
case Common::Input::InputType::Button:
status = callback.button_status;
break;
case Common::Input::InputType::Motion:
status.value = std::abs(callback.motion_status.gyro.x.raw_value) > 1.0f;
break;
default:
LOG_ERROR(Input, "Conversion from type {} to button not implemented", callback.type);
break;
}
if (status.inverted) {
status.value = !status.value;
}
return status;
}
Common::Input::MotionStatus TransformToMotion(const Common::Input::CallbackStatus& callback) {
Common::Input::MotionStatus status{};
switch (callback.type) {
case Common::Input::InputType::Button: {
Common::Input::AnalogProperties properties{
.deadzone = 0.0f,
.range = 1.0f,
.offset = 0.0f,
};
status.delta_timestamp = 1000;
status.force_update = true;
status.accel.x = {
.value = 0.0f,
.raw_value = 0.0f,
.properties = properties,
};
status.accel.y = {
.value = 0.0f,
.raw_value = 0.0f,
.properties = properties,
};
status.accel.z = {
.value = 0.0f,
.raw_value = -1.0f,
.properties = properties,
};
status.gyro.x = {
.value = 0.0f,
.raw_value = 0.0f,
.properties = properties,
};
status.gyro.y = {
.value = 0.0f,
.raw_value = 0.0f,
.properties = properties,
};
status.gyro.z = {
.value = 0.0f,
.raw_value = 0.0f,
.properties = properties,
};
if (TransformToButton(callback).value) {
std::random_device device;
std::mt19937 gen(device());
std::uniform_int_distribution<s16> distribution(-5000, 5000);
status.accel.x.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
status.accel.y.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
status.accel.z.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
status.gyro.x.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
status.gyro.y.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
status.gyro.z.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
}
break;
}
case Common::Input::InputType::Motion:
status = callback.motion_status;
break;
default:
LOG_ERROR(Input, "Conversion from type {} to motion not implemented", callback.type);
break;
}
SanitizeAnalog(status.accel.x, false);
SanitizeAnalog(status.accel.y, false);
SanitizeAnalog(status.accel.z, false);
SanitizeAnalog(status.gyro.x, false);
SanitizeAnalog(status.gyro.y, false);
SanitizeAnalog(status.gyro.z, false);
return status;
}
Common::Input::StickStatus TransformToStick(const Common::Input::CallbackStatus& callback) {
Common::Input::StickStatus status{};
switch (callback.type) {
case Common::Input::InputType::Stick:
status = callback.stick_status;
break;
default:
LOG_ERROR(Input, "Conversion from type {} to stick not implemented", callback.type);
break;
}
SanitizeStick(status.x, status.y, true);
const auto& properties_x = status.x.properties;
const auto& properties_y = status.y.properties;
const float x = status.x.value;
const float y = status.y.value;
// Set directional buttons
status.right = x > properties_x.threshold;
status.left = x < -properties_x.threshold;
status.up = y > properties_y.threshold;
status.down = y < -properties_y.threshold;
return status;
}
Common::Input::TouchStatus TransformToTouch(const Common::Input::CallbackStatus& callback) {
Common::Input::TouchStatus status{};
switch (callback.type) {
case Common::Input::InputType::Touch:
status = callback.touch_status;
break;
case Common::Input::InputType::Stick:
status.x = callback.stick_status.x;
status.y = callback.stick_status.y;
break;
default:
LOG_ERROR(Input, "Conversion from type {} to touch not implemented", callback.type);
break;
}
SanitizeAnalog(status.x, true);
SanitizeAnalog(status.y, true);
float& x = status.x.value;
float& y = status.y.value;
// Adjust if value is inverted
x = status.x.properties.inverted ? 1.0f + x : x;
y = status.y.properties.inverted ? 1.0f + y : y;
// clamp value
x = std::clamp(x, 0.0f, 1.0f);
y = std::clamp(y, 0.0f, 1.0f);
if (status.pressed.inverted) {
status.pressed.value = !status.pressed.value;
}
return status;
}
Common::Input::TriggerStatus TransformToTrigger(const Common::Input::CallbackStatus& callback) {
Common::Input::TriggerStatus status{};
float& raw_value = status.analog.raw_value;
bool calculate_button_value = true;
switch (callback.type) {
case Common::Input::InputType::Analog:
status.analog.properties = callback.analog_status.properties;
raw_value = callback.analog_status.raw_value;
break;
case Common::Input::InputType::Button:
status.analog.properties.range = 1.0f;
status.analog.properties.inverted = callback.button_status.inverted;
raw_value = callback.button_status.value ? 1.0f : 0.0f;
break;
case Common::Input::InputType::Trigger:
status = callback.trigger_status;
calculate_button_value = false;
break;
case Common::Input::InputType::Motion:
status.analog.properties.range = 1.0f;
raw_value = callback.motion_status.accel.x.raw_value;
break;
default:
LOG_ERROR(Input, "Conversion from type {} to trigger not implemented", callback.type);
break;
}
SanitizeAnalog(status.analog, true);
const auto& properties = status.analog.properties;
float& value = status.analog.value;
// Set button status
if (calculate_button_value) {
status.pressed.value = value > properties.threshold;
}
// Adjust if value is inverted
value = properties.inverted ? 1.0f + value : value;
// clamp value
value = std::clamp(value, 0.0f, 1.0f);
return status;
}
Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatus& callback) {
Common::Input::AnalogStatus status{};
switch (callback.type) {
case Common::Input::InputType::Analog:
status.properties = callback.analog_status.properties;
status.raw_value = callback.analog_status.raw_value;
break;
default:
LOG_ERROR(Input, "Conversion from type {} to analog not implemented", callback.type);
break;
}
SanitizeAnalog(status, false);
// Adjust if value is inverted
status.value = status.properties.inverted ? -status.value : status.value;
return status;
}
Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatus& callback) {
Common::Input::CameraStatus camera{};
switch (callback.type) {
case Common::Input::InputType::IrSensor:
camera = {
.format = callback.camera_status,
.data = callback.raw_data,
};
break;
default:
LOG_ERROR(Input, "Conversion from type {} to camera not implemented", callback.type);
break;
}
return camera;
}
Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& callback) {
Common::Input::NfcStatus nfc{};
switch (callback.type) {
case Common::Input::InputType::Nfc:
return callback.nfc_status;
default:
LOG_ERROR(Input, "Conversion from type {} to NFC not implemented", callback.type);
break;
}
return nfc;
}
Common::Input::BodyColorStatus TransformToColor(const Common::Input::CallbackStatus& callback) {
switch (callback.type) {
case Common::Input::InputType::Color:
return callback.color_status;
break;
default:
LOG_ERROR(Input, "Conversion from type {} to color not implemented", callback.type);
return {};
break;
}
}
void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) {
const auto& properties = analog.properties;
float& raw_value = analog.raw_value;
float& value = analog.value;
if (!std::isnormal(raw_value)) {
raw_value = 0;
}
// Apply center offset
raw_value -= properties.offset;
// Set initial values to be formatted
value = raw_value;
// Calculate vector size
const float r = std::abs(value);
// Return zero if value is smaller than the deadzone
if (r <= properties.deadzone || properties.deadzone == 1.0f) {
analog.value = 0;
return;
}
// Adjust range of value
const float deadzone_factor =
1.0f / r * (r - properties.deadzone) / (1.0f - properties.deadzone);
value = value * deadzone_factor / properties.range;
// Invert direction if needed
if (properties.inverted) {
value = -value;
}
// Clamp value
if (clamp_value) {
value = std::clamp(value, -1.0f, 1.0f);
}
}
void SanitizeStick(Common::Input::AnalogStatus& analog_x, Common::Input::AnalogStatus& analog_y,
bool clamp_value) {
const auto& properties_x = analog_x.properties;
const auto& properties_y = analog_y.properties;
float& raw_x = analog_x.raw_value;
float& raw_y = analog_y.raw_value;
float& x = analog_x.value;
float& y = analog_y.value;
if (!std::isnormal(raw_x)) {
raw_x = 0;
}
if (!std::isnormal(raw_y)) {
raw_y = 0;
}
// Apply center offset
raw_x += properties_x.offset;
raw_y += properties_y.offset;
// Apply X scale correction from offset
if (std::abs(properties_x.offset) < 0.75f) {
if (raw_x > 0) {
raw_x /= 1 + properties_x.offset;
} else {
raw_x /= 1 - properties_x.offset;
}
}
// Apply Y scale correction from offset
if (std::abs(properties_y.offset) < 0.75f) {
if (raw_y > 0) {
raw_y /= 1 + properties_y.offset;
} else {
raw_y /= 1 - properties_y.offset;
}
}
// Invert direction if needed
raw_x = properties_x.inverted ? -raw_x : raw_x;
raw_y = properties_y.inverted ? -raw_y : raw_y;
// Set initial values to be formatted
x = raw_x;
y = raw_y;
// Calculate vector size
float r = x * x + y * y;
r = std::sqrt(r);
// TODO(German77): Use deadzone and range of both axis
// Return zero if values are smaller than the deadzone
if (r <= properties_x.deadzone || properties_x.deadzone >= 1.0f) {
x = 0;
y = 0;
return;
}
// Adjust range of joystick
const float deadzone_factor =
1.0f / r * (r - properties_x.deadzone) / (1.0f - properties_x.deadzone);
x = x * deadzone_factor / properties_x.range;
y = y * deadzone_factor / properties_x.range;
r = r * deadzone_factor / properties_x.range;
// Normalize joystick
if (clamp_value && r > 1.0f) {
x /= r;
y /= r;
}
}
} // namespace Core::HID

View file

@ -0,0 +1,119 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
namespace Common::Input {
struct CallbackStatus;
enum class BatteryLevel : u32;
using BatteryStatus = BatteryLevel;
struct AnalogStatus;
struct ButtonStatus;
struct MotionStatus;
struct StickStatus;
struct TouchStatus;
struct TriggerStatus;
}; // namespace Common::Input
namespace Core::HID {
/**
* Converts raw input data into a valid battery status.
*
* @param callback Supported callbacks: Analog, Battery, Trigger.
* @return A valid BatteryStatus object.
*/
Common::Input::BatteryStatus TransformToBattery(const Common::Input::CallbackStatus& callback);
/**
* Converts raw input data into a valid button status. Applies invert properties to the output.
*
* @param callback Supported callbacks: Analog, Button, Trigger.
* @return A valid TouchStatus object.
*/
Common::Input::ButtonStatus TransformToButton(const Common::Input::CallbackStatus& callback);
/**
* Converts raw input data into a valid motion status.
*
* @param callback Supported callbacks: Motion.
* @return A valid TouchStatus object.
*/
Common::Input::MotionStatus TransformToMotion(const Common::Input::CallbackStatus& callback);
/**
* Converts raw input data into a valid stick status. Applies offset, deadzone, range and invert
* properties to the output.
*
* @param callback Supported callbacks: Stick.
* @return A valid StickStatus object.
*/
Common::Input::StickStatus TransformToStick(const Common::Input::CallbackStatus& callback);
/**
* Converts raw input data into a valid touch status.
*
* @param callback Supported callbacks: Touch.
* @return A valid TouchStatus object.
*/
Common::Input::TouchStatus TransformToTouch(const Common::Input::CallbackStatus& callback);
/**
* Converts raw input data into a valid trigger status. Applies offset, deadzone, range and
* invert properties to the output. Button status uses the threshold property if necessary.
*
* @param callback Supported callbacks: Analog, Button, Trigger.
* @return A valid TriggerStatus object.
*/
Common::Input::TriggerStatus TransformToTrigger(const Common::Input::CallbackStatus& callback);
/**
* Converts raw input data into a valid analog status. Applies offset, deadzone, range and
* invert properties to the output.
*
* @param callback Supported callbacks: Analog.
* @return A valid AnalogStatus object.
*/
Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatus& callback);
/**
* Converts raw input data into a valid camera status.
*
* @param callback Supported callbacks: Camera.
* @return A valid CameraObject object.
*/
Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatus& callback);
/**
* Converts raw input data into a valid nfc status.
*
* @param callback Supported callbacks: Nfc.
* @return A valid data tag vector.
*/
Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& callback);
/**
* Converts raw input data into a valid color status.
*
* @param callback Supported callbacks: Color.
* @return A valid Color object.
*/
Common::Input::BodyColorStatus TransformToColor(const Common::Input::CallbackStatus& callback);
/**
* Converts raw analog data into a valid analog value
* @param analog An analog object containing raw data and properties
* @param clamp_value determines if the value needs to be clamped between -1.0f and 1.0f.
*/
void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value);
/**
* Converts raw stick data into a valid stick value
* @param analog_x raw analog data and properties for the x-axis
* @param analog_y raw analog data and properties for the y-axis
* @param clamp_value bool that determines if the value needs to be clamped into the unit circle.
*/
void SanitizeStick(Common::Input::AnalogStatus& analog_x, Common::Input::AnalogStatus& analog_y,
bool clamp_value);
} // namespace Core::HID

View file

@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core.h"
#include "core/hle/service/hid/hid_server.h"
#include "core/hle/service/sm/sm.h"
#include "hid_core/frontend/input_interpreter.h"
#include "hid_core/hid_types.h"
#include "hid_core/resource_manager.h"
#include "hid_core/resources/npad/npad.h"
InputInterpreter::InputInterpreter(Core::System& system)
: npad{system.ServiceManager()
.GetService<Service::HID::IHidServer>("hid")
->GetResourceManager()
->GetNpad()} {
ResetButtonStates();
}
InputInterpreter::~InputInterpreter() = default;
void InputInterpreter::PollInput() {
if (npad == nullptr) {
return;
}
const auto button_state = npad->GetAndResetPressState();
previous_index = current_index;
current_index = (current_index + 1) % button_states.size();
button_states[current_index] = button_state;
}
void InputInterpreter::ResetButtonStates() {
previous_index = 0;
current_index = 0;
button_states[0] = Core::HID::NpadButton::All;
for (std::size_t i = 1; i < button_states.size(); ++i) {
button_states[i] = Core::HID::NpadButton::None;
}
}
bool InputInterpreter::IsButtonPressed(Core::HID::NpadButton button) const {
return True(button_states[current_index] & button);
}
bool InputInterpreter::IsButtonPressedOnce(Core::HID::NpadButton button) const {
const bool current_press = True(button_states[current_index] & button);
const bool previous_press = True(button_states[previous_index] & button);
return current_press && !previous_press;
}
bool InputInterpreter::IsButtonHeld(Core::HID::NpadButton button) const {
Core::HID::NpadButton held_buttons{button_states[0]};
for (std::size_t i = 1; i < button_states.size(); ++i) {
held_buttons &= button_states[i];
}
return True(held_buttons & button);
}

View file

@ -0,0 +1,111 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include "common/common_types.h"
namespace Core {
class System;
}
namespace Core::HID {
enum class NpadButton : u64;
}
namespace Service::HID {
class NPad;
}
/**
* The InputInterpreter class interfaces with HID to retrieve button press states.
* Input is intended to be polled every 50ms so that a button is considered to be
* held down after 400ms has elapsed since the initial button press and subsequent
* repeated presses occur every 50ms.
*/
class InputInterpreter {
public:
explicit InputInterpreter(Core::System& system);
virtual ~InputInterpreter();
/// Gets a button state from HID and inserts it into the array of button states.
void PollInput();
/// Resets all the button states to their defaults.
void ResetButtonStates();
/**
* Checks whether the button is pressed.
*
* @param button The button to check.
*
* @returns True when the button is pressed.
*/
[[nodiscard]] bool IsButtonPressed(Core::HID::NpadButton button) const;
/**
* Checks whether any of the buttons in the parameter list is pressed.
*
* @tparam HIDButton The buttons to check.
*
* @returns True when at least one of the buttons is pressed.
*/
template <Core::HID::NpadButton... T>
[[nodiscard]] bool IsAnyButtonPressed() {
return (IsButtonPressed(T) || ...);
}
/**
* The specified button is considered to be pressed once
* if it is currently pressed and not pressed previously.
*
* @param button The button to check.
*
* @returns True when the button is pressed once.
*/
[[nodiscard]] bool IsButtonPressedOnce(Core::HID::NpadButton button) const;
/**
* Checks whether any of the buttons in the parameter list is pressed once.
*
* @tparam T The buttons to check.
*
* @returns True when at least one of the buttons is pressed once.
*/
template <Core::HID::NpadButton... T>
[[nodiscard]] bool IsAnyButtonPressedOnce() const {
return (IsButtonPressedOnce(T) || ...);
}
/**
* The specified button is considered to be held down if it is pressed in all 9 button states.
*
* @param button The button to check.
*
* @returns True when the button is held down.
*/
[[nodiscard]] bool IsButtonHeld(Core::HID::NpadButton button) const;
/**
* Checks whether any of the buttons in the parameter list is held down.
*
* @tparam T The buttons to check.
*
* @returns True when at least one of the buttons is held down.
*/
template <Core::HID::NpadButton... T>
[[nodiscard]] bool IsAnyButtonHeld() const {
return (IsButtonHeld(T) || ...);
}
private:
std::shared_ptr<Service::HID::NPad> npad;
/// Stores 9 consecutive button states polled from HID.
std::array<Core::HID::NpadButton, 9> button_states{};
std::size_t previous_index{};
std::size_t current_index{};
};

View file

@ -0,0 +1,357 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cmath>
#include "common/math_util.h"
#include "hid_core/frontend/motion_input.h"
namespace Core::HID {
MotionInput::MotionInput() {
// Initialize PID constants with default values
SetPID(0.3f, 0.005f, 0.0f);
SetGyroThreshold(ThresholdStandard);
ResetQuaternion();
ResetRotations();
}
void MotionInput::SetPID(f32 new_kp, f32 new_ki, f32 new_kd) {
kp = new_kp;
ki = new_ki;
kd = new_kd;
}
void MotionInput::SetAcceleration(const Common::Vec3f& acceleration) {
accel = acceleration;
accel.x = std::clamp(accel.x, -AccelMaxValue, AccelMaxValue);
accel.y = std::clamp(accel.y, -AccelMaxValue, AccelMaxValue);
accel.z = std::clamp(accel.z, -AccelMaxValue, AccelMaxValue);
}
void MotionInput::SetGyroscope(const Common::Vec3f& gyroscope) {
gyro = gyroscope - gyro_bias;
gyro.x = std::clamp(gyro.x, -GyroMaxValue, GyroMaxValue);
gyro.y = std::clamp(gyro.y, -GyroMaxValue, GyroMaxValue);
gyro.z = std::clamp(gyro.z, -GyroMaxValue, GyroMaxValue);
// Auto adjust gyro_bias to minimize drift
if (!IsMoving(IsAtRestRelaxed)) {
gyro_bias = (gyro_bias * 0.9999f) + (gyroscope * 0.0001f);
}
// Adjust drift when calibration mode is enabled
if (calibration_mode) {
gyro_bias = (gyro_bias * 0.99f) + (gyroscope * 0.01f);
StopCalibration();
}
if (gyro.Length() < gyro_threshold * user_gyro_threshold) {
gyro = {};
} else {
only_accelerometer = false;
}
}
void MotionInput::SetQuaternion(const Common::Quaternion<f32>& quaternion) {
quat = quaternion;
}
void MotionInput::SetEulerAngles(const Common::Vec3f& euler_angles) {
const float cr = std::cos(euler_angles.x * 0.5f);
const float sr = std::sin(euler_angles.x * 0.5f);
const float cp = std::cos(euler_angles.y * 0.5f);
const float sp = std::sin(euler_angles.y * 0.5f);
const float cy = std::cos(euler_angles.z * 0.5f);
const float sy = std::sin(euler_angles.z * 0.5f);
quat.w = cr * cp * cy + sr * sp * sy;
quat.xyz.x = sr * cp * cy - cr * sp * sy;
quat.xyz.y = cr * sp * cy + sr * cp * sy;
quat.xyz.z = cr * cp * sy - sr * sp * cy;
}
void MotionInput::SetGyroBias(const Common::Vec3f& bias) {
gyro_bias = bias;
}
void MotionInput::SetGyroThreshold(f32 threshold) {
gyro_threshold = threshold;
}
void MotionInput::SetUserGyroThreshold(f32 threshold) {
user_gyro_threshold = threshold / ThresholdStandard;
}
void MotionInput::EnableReset(bool reset) {
reset_enabled = reset;
}
void MotionInput::ResetRotations() {
rotations = {};
}
void MotionInput::ResetQuaternion() {
quat = {{0.0f, 0.0f, -1.0f}, 0.0f};
}
bool MotionInput::IsMoving(f32 sensitivity) const {
return gyro.Length() >= sensitivity || accel.Length() <= 0.9f || accel.Length() >= 1.1f;
}
bool MotionInput::IsCalibrated(f32 sensitivity) const {
return real_error.Length() < sensitivity;
}
void MotionInput::UpdateRotation(u64 elapsed_time) {
const auto sample_period = static_cast<f32>(elapsed_time) / 1000000.0f;
if (sample_period > 0.1f) {
return;
}
rotations += gyro * sample_period;
}
void MotionInput::Calibrate() {
calibration_mode = true;
calibration_counter = 0;
}
void MotionInput::StopCalibration() {
if (calibration_counter++ > CalibrationSamples) {
calibration_mode = false;
ResetQuaternion();
ResetRotations();
}
}
// Based on Madgwick's implementation of Mayhony's AHRS algorithm.
// https://github.com/xioTechnologies/Open-Source-AHRS-With-x-IMU/blob/master/x-IMU%20IMU%20and%20AHRS%20Algorithms/x-IMU%20IMU%20and%20AHRS%20Algorithms/AHRS/MahonyAHRS.cs
void MotionInput::UpdateOrientation(u64 elapsed_time) {
if (!IsCalibrated(0.1f)) {
ResetOrientation();
}
// Short name local variable for readability
f32 q1 = quat.w;
f32 q2 = quat.xyz[0];
f32 q3 = quat.xyz[1];
f32 q4 = quat.xyz[2];
const auto sample_period = static_cast<f32>(elapsed_time) / 1000000.0f;
// Ignore invalid elapsed time
if (sample_period > 0.1f) {
return;
}
const auto normal_accel = accel.Normalized();
auto rad_gyro = gyro * Common::PI * 2;
const f32 swap = rad_gyro.x;
rad_gyro.x = rad_gyro.y;
rad_gyro.y = -swap;
rad_gyro.z = -rad_gyro.z;
// Clear gyro values if there is no gyro present
if (only_accelerometer) {
rad_gyro.x = 0;
rad_gyro.y = 0;
rad_gyro.z = 0;
}
// Ignore drift correction if acceleration is not reliable
if (accel.Length() >= 0.75f && accel.Length() <= 1.25f) {
const f32 ax = -normal_accel.x;
const f32 ay = normal_accel.y;
const f32 az = -normal_accel.z;
// Estimated direction of gravity
const f32 vx = 2.0f * (q2 * q4 - q1 * q3);
const f32 vy = 2.0f * (q1 * q2 + q3 * q4);
const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;
// Error is cross product between estimated direction and measured direction of gravity
const Common::Vec3f new_real_error = {
az * vx - ax * vz,
ay * vz - az * vy,
ax * vy - ay * vx,
};
derivative_error = new_real_error - real_error;
real_error = new_real_error;
// Prevent integral windup
if (ki != 0.0f && !IsCalibrated(0.05f)) {
integral_error += real_error;
} else {
integral_error = {};
}
// Apply feedback terms
if (!only_accelerometer) {
rad_gyro += kp * real_error;
rad_gyro += ki * integral_error;
rad_gyro += kd * derivative_error;
} else {
// Give more weight to accelerometer values to compensate for the lack of gyro
rad_gyro += 35.0f * kp * real_error;
rad_gyro += 10.0f * ki * integral_error;
rad_gyro += 10.0f * kd * derivative_error;
// Emulate gyro values for games that need them
gyro.x = -rad_gyro.y;
gyro.y = rad_gyro.x;
gyro.z = -rad_gyro.z;
UpdateRotation(elapsed_time);
}
}
const f32 gx = rad_gyro.y;
const f32 gy = rad_gyro.x;
const f32 gz = rad_gyro.z;
// Integrate rate of change of quaternion
const f32 pa = q2;
const f32 pb = q3;
const f32 pc = q4;
q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * sample_period);
q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * sample_period);
q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period);
q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period);
quat.w = q1;
quat.xyz[0] = q2;
quat.xyz[1] = q3;
quat.xyz[2] = q4;
quat = quat.Normalized();
}
std::array<Common::Vec3f, 3> MotionInput::GetOrientation() const {
const Common::Quaternion<float> quad{
.xyz = {-quat.xyz[1], -quat.xyz[0], -quat.w},
.w = -quat.xyz[2],
};
const std::array<float, 16> matrix4x4 = quad.ToMatrix();
return {Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]),
Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]),
Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10])};
}
Common::Vec3f MotionInput::GetAcceleration() const {
return accel;
}
Common::Vec3f MotionInput::GetGyroscope() const {
return gyro;
}
Common::Vec3f MotionInput::GetGyroBias() const {
return gyro_bias;
}
Common::Quaternion<f32> MotionInput::GetQuaternion() const {
return quat;
}
Common::Vec3f MotionInput::GetRotations() const {
return rotations;
}
Common::Vec3f MotionInput::GetEulerAngles() const {
// roll (x-axis rotation)
const float sinr_cosp = 2 * (quat.w * quat.xyz.x + quat.xyz.y * quat.xyz.z);
const float cosr_cosp = 1 - 2 * (quat.xyz.x * quat.xyz.x + quat.xyz.y * quat.xyz.y);
// pitch (y-axis rotation)
const float sinp = std::sqrt(1 + 2 * (quat.w * quat.xyz.y - quat.xyz.x * quat.xyz.z));
const float cosp = std::sqrt(1 - 2 * (quat.w * quat.xyz.y - quat.xyz.x * quat.xyz.z));
// yaw (z-axis rotation)
const float siny_cosp = 2 * (quat.w * quat.xyz.z + quat.xyz.x * quat.xyz.y);
const float cosy_cosp = 1 - 2 * (quat.xyz.y * quat.xyz.y + quat.xyz.z * quat.xyz.z);
return {
std::atan2(sinr_cosp, cosr_cosp),
2 * std::atan2(sinp, cosp) - Common::PI / 2,
std::atan2(siny_cosp, cosy_cosp),
};
}
void MotionInput::ResetOrientation() {
if (!reset_enabled || only_accelerometer) {
return;
}
if (!IsMoving(IsAtRestRelaxed) && accel.z <= -0.9f) {
++reset_counter;
if (reset_counter > 900) {
quat.w = 0;
quat.xyz[0] = 0;
quat.xyz[1] = 0;
quat.xyz[2] = -1;
SetOrientationFromAccelerometer();
integral_error = {};
reset_counter = 0;
}
} else {
reset_counter = 0;
}
}
void MotionInput::SetOrientationFromAccelerometer() {
int iterations = 0;
const f32 sample_period = 0.015f;
const auto normal_accel = accel.Normalized();
while (!IsCalibrated(0.01f) && ++iterations < 100) {
// Short name local variable for readability
f32 q1 = quat.w;
f32 q2 = quat.xyz[0];
f32 q3 = quat.xyz[1];
f32 q4 = quat.xyz[2];
Common::Vec3f rad_gyro;
const f32 ax = -normal_accel.x;
const f32 ay = normal_accel.y;
const f32 az = -normal_accel.z;
// Estimated direction of gravity
const f32 vx = 2.0f * (q2 * q4 - q1 * q3);
const f32 vy = 2.0f * (q1 * q2 + q3 * q4);
const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;
// Error is cross product between estimated direction and measured direction of gravity
const Common::Vec3f new_real_error = {
az * vx - ax * vz,
ay * vz - az * vy,
ax * vy - ay * vx,
};
derivative_error = new_real_error - real_error;
real_error = new_real_error;
rad_gyro += 10.0f * kp * real_error;
rad_gyro += 5.0f * ki * integral_error;
rad_gyro += 10.0f * kd * derivative_error;
const f32 gx = rad_gyro.y;
const f32 gy = rad_gyro.x;
const f32 gz = rad_gyro.z;
// Integrate rate of change of quaternion
const f32 pa = q2;
const f32 pb = q3;
const f32 pc = q4;
q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * sample_period);
q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * sample_period);
q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period);
q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period);
quat.w = q1;
quat.xyz[0] = q2;
quat.xyz[1] = q3;
quat.xyz[2] = q4;
quat = quat.Normalized();
}
}
} // namespace Core::HID

View file

@ -0,0 +1,119 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/common_types.h"
#include "common/quaternion.h"
#include "common/vector_math.h"
namespace Core::HID {
class MotionInput {
public:
static constexpr float ThresholdLoose = 0.01f;
static constexpr float ThresholdStandard = 0.007f;
static constexpr float ThresholdThight = 0.002f;
static constexpr float IsAtRestRelaxed = 0.05f;
static constexpr float IsAtRestLoose = 0.02f;
static constexpr float IsAtRestStandard = 0.01f;
static constexpr float IsAtRestThight = 0.005f;
static constexpr float GyroMaxValue = 5.0f;
static constexpr float AccelMaxValue = 7.0f;
static constexpr std::size_t CalibrationSamples = 300;
explicit MotionInput();
MotionInput(const MotionInput&) = default;
MotionInput& operator=(const MotionInput&) = default;
MotionInput(MotionInput&&) = default;
MotionInput& operator=(MotionInput&&) = default;
void SetPID(f32 new_kp, f32 new_ki, f32 new_kd);
void SetAcceleration(const Common::Vec3f& acceleration);
void SetGyroscope(const Common::Vec3f& gyroscope);
void SetQuaternion(const Common::Quaternion<f32>& quaternion);
void SetEulerAngles(const Common::Vec3f& euler_angles);
void SetGyroBias(const Common::Vec3f& bias);
void SetGyroThreshold(f32 threshold);
/// Applies a modifier on top of the normal gyro threshold
void SetUserGyroThreshold(f32 threshold);
void EnableReset(bool reset);
void ResetRotations();
void ResetQuaternion();
void UpdateRotation(u64 elapsed_time);
void UpdateOrientation(u64 elapsed_time);
void Calibrate();
[[nodiscard]] std::array<Common::Vec3f, 3> GetOrientation() const;
[[nodiscard]] Common::Vec3f GetAcceleration() const;
[[nodiscard]] Common::Vec3f GetGyroscope() const;
[[nodiscard]] Common::Vec3f GetGyroBias() const;
[[nodiscard]] Common::Vec3f GetRotations() const;
[[nodiscard]] Common::Quaternion<f32> GetQuaternion() const;
[[nodiscard]] Common::Vec3f GetEulerAngles() const;
[[nodiscard]] bool IsMoving(f32 sensitivity) const;
[[nodiscard]] bool IsCalibrated(f32 sensitivity) const;
private:
void StopCalibration();
void ResetOrientation();
void SetOrientationFromAccelerometer();
// PID constants
f32 kp;
f32 ki;
f32 kd;
// PID errors
Common::Vec3f real_error;
Common::Vec3f integral_error;
Common::Vec3f derivative_error;
// Quaternion containing the device orientation
Common::Quaternion<f32> quat;
// Number of full rotations in each axis
Common::Vec3f rotations;
// Acceleration vector measurement in G force
Common::Vec3f accel;
// Gyroscope vector measurement in radians/s.
Common::Vec3f gyro;
// Vector to be subtracted from gyro measurements
Common::Vec3f gyro_bias;
// Minimum gyro amplitude to detect if the device is moving
f32 gyro_threshold = 0.0f;
// Multiplies gyro_threshold by this value
f32 user_gyro_threshold = 0.0f;
// Number of invalid sequential data
u32 reset_counter = 0;
// If the provided data is invalid the device will be autocalibrated
bool reset_enabled = true;
// Use accelerometer values to calculate position
bool only_accelerometer = true;
// When enabled it will aggressively adjust for gyro drift
bool calibration_mode = false;
// Used to auto disable calibration mode
std::size_t calibration_counter = 0;
};
} // namespace Core::HID

222
src/hid_core/hid_core.cpp Executable file
View file

@ -0,0 +1,222 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "hid_core/frontend/emulated_console.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/frontend/emulated_devices.h"
#include "hid_core/hid_core.h"
#include "hid_core/hid_util.h"
namespace Core::HID {
HIDCore::HIDCore()
: player_1{std::make_unique<EmulatedController>(NpadIdType::Player1)},
player_2{std::make_unique<EmulatedController>(NpadIdType::Player2)},
player_3{std::make_unique<EmulatedController>(NpadIdType::Player3)},
player_4{std::make_unique<EmulatedController>(NpadIdType::Player4)},
player_5{std::make_unique<EmulatedController>(NpadIdType::Player5)},
player_6{std::make_unique<EmulatedController>(NpadIdType::Player6)},
player_7{std::make_unique<EmulatedController>(NpadIdType::Player7)},
player_8{std::make_unique<EmulatedController>(NpadIdType::Player8)},
other{std::make_unique<EmulatedController>(NpadIdType::Other)},
handheld{std::make_unique<EmulatedController>(NpadIdType::Handheld)},
console{std::make_unique<EmulatedConsole>()}, devices{std::make_unique<EmulatedDevices>()} {}
HIDCore::~HIDCore() = default;
EmulatedController* HIDCore::GetEmulatedController(NpadIdType npad_id_type) {
switch (npad_id_type) {
case NpadIdType::Player1:
return player_1.get();
case NpadIdType::Player2:
return player_2.get();
case NpadIdType::Player3:
return player_3.get();
case NpadIdType::Player4:
return player_4.get();
case NpadIdType::Player5:
return player_5.get();
case NpadIdType::Player6:
return player_6.get();
case NpadIdType::Player7:
return player_7.get();
case NpadIdType::Player8:
return player_8.get();
case NpadIdType::Other:
return other.get();
case NpadIdType::Handheld:
return handheld.get();
case NpadIdType::Invalid:
default:
ASSERT_MSG(false, "Invalid NpadIdType={}", npad_id_type);
return nullptr;
}
}
const EmulatedController* HIDCore::GetEmulatedController(NpadIdType npad_id_type) const {
switch (npad_id_type) {
case NpadIdType::Player1:
return player_1.get();
case NpadIdType::Player2:
return player_2.get();
case NpadIdType::Player3:
return player_3.get();
case NpadIdType::Player4:
return player_4.get();
case NpadIdType::Player5:
return player_5.get();
case NpadIdType::Player6:
return player_6.get();
case NpadIdType::Player7:
return player_7.get();
case NpadIdType::Player8:
return player_8.get();
case NpadIdType::Other:
return other.get();
case NpadIdType::Handheld:
return handheld.get();
case NpadIdType::Invalid:
default:
ASSERT_MSG(false, "Invalid NpadIdType={}", npad_id_type);
return nullptr;
}
}
EmulatedConsole* HIDCore::GetEmulatedConsole() {
return console.get();
}
const EmulatedConsole* HIDCore::GetEmulatedConsole() const {
return console.get();
}
EmulatedDevices* HIDCore::GetEmulatedDevices() {
return devices.get();
}
const EmulatedDevices* HIDCore::GetEmulatedDevices() const {
return devices.get();
}
EmulatedController* HIDCore::GetEmulatedControllerByIndex(std::size_t index) {
return GetEmulatedController(Service::HID::IndexToNpadIdType(index));
}
const EmulatedController* HIDCore::GetEmulatedControllerByIndex(std::size_t index) const {
return GetEmulatedController(Service::HID::IndexToNpadIdType(index));
}
void HIDCore::SetSupportedStyleTag(NpadStyleTag style_tag) {
supported_style_tag.raw = style_tag.raw;
player_1->SetSupportedNpadStyleTag(supported_style_tag);
player_2->SetSupportedNpadStyleTag(supported_style_tag);
player_3->SetSupportedNpadStyleTag(supported_style_tag);
player_4->SetSupportedNpadStyleTag(supported_style_tag);
player_5->SetSupportedNpadStyleTag(supported_style_tag);
player_6->SetSupportedNpadStyleTag(supported_style_tag);
player_7->SetSupportedNpadStyleTag(supported_style_tag);
player_8->SetSupportedNpadStyleTag(supported_style_tag);
other->SetSupportedNpadStyleTag(supported_style_tag);
handheld->SetSupportedNpadStyleTag(supported_style_tag);
}
NpadStyleTag HIDCore::GetSupportedStyleTag() const {
return supported_style_tag;
}
s8 HIDCore::GetPlayerCount() const {
s8 active_players = 0;
for (std::size_t player_index = 0; player_index < available_controllers - 2; ++player_index) {
const auto* const controller = GetEmulatedControllerByIndex(player_index);
if (controller->IsConnected()) {
active_players++;
}
}
return active_players;
}
NpadIdType HIDCore::GetFirstNpadId() const {
for (std::size_t player_index = 0; player_index < available_controllers; ++player_index) {
const auto* const controller = GetEmulatedControllerByIndex(player_index);
if (controller->IsConnected()) {
return controller->GetNpadIdType();
}
}
return NpadIdType::Player1;
}
NpadIdType HIDCore::GetFirstDisconnectedNpadId() const {
for (std::size_t player_index = 0; player_index < available_controllers; ++player_index) {
const auto* const controller = GetEmulatedControllerByIndex(player_index);
if (!controller->IsConnected()) {
return controller->GetNpadIdType();
}
}
return NpadIdType::Player1;
}
void HIDCore::SetLastActiveController(NpadIdType npad_id) {
last_active_controller = npad_id;
}
NpadIdType HIDCore::GetLastActiveController() const {
return last_active_controller;
}
void HIDCore::EnableAllControllerConfiguration() {
player_1->EnableConfiguration();
player_2->EnableConfiguration();
player_3->EnableConfiguration();
player_4->EnableConfiguration();
player_5->EnableConfiguration();
player_6->EnableConfiguration();
player_7->EnableConfiguration();
player_8->EnableConfiguration();
other->EnableConfiguration();
handheld->EnableConfiguration();
}
void HIDCore::DisableAllControllerConfiguration() {
player_1->DisableConfiguration();
player_2->DisableConfiguration();
player_3->DisableConfiguration();
player_4->DisableConfiguration();
player_5->DisableConfiguration();
player_6->DisableConfiguration();
player_7->DisableConfiguration();
player_8->DisableConfiguration();
other->DisableConfiguration();
handheld->DisableConfiguration();
}
void HIDCore::ReloadInputDevices() {
player_1->ReloadFromSettings();
player_2->ReloadFromSettings();
player_3->ReloadFromSettings();
player_4->ReloadFromSettings();
player_5->ReloadFromSettings();
player_6->ReloadFromSettings();
player_7->ReloadFromSettings();
player_8->ReloadFromSettings();
other->ReloadFromSettings();
handheld->ReloadFromSettings();
console->ReloadFromSettings();
devices->ReloadFromSettings();
}
void HIDCore::UnloadInputDevices() {
player_1->UnloadInput();
player_2->UnloadInput();
player_3->UnloadInput();
player_4->UnloadInput();
player_5->UnloadInput();
player_6->UnloadInput();
player_7->UnloadInput();
player_8->UnloadInput();
other->UnloadInput();
handheld->UnloadInput();
console->UnloadInput();
devices->UnloadInput();
}
} // namespace Core::HID

89
src/hid_core/hid_core.h Executable file
View file

@ -0,0 +1,89 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include "common/common_funcs.h"
#include "hid_core/hid_types.h"
namespace Core::HID {
class EmulatedConsole;
class EmulatedController;
class EmulatedDevices;
} // namespace Core::HID
namespace Core::HID {
class HIDCore {
public:
explicit HIDCore();
~HIDCore();
YUZU_NON_COPYABLE(HIDCore);
YUZU_NON_MOVEABLE(HIDCore);
EmulatedController* GetEmulatedController(NpadIdType npad_id_type);
const EmulatedController* GetEmulatedController(NpadIdType npad_id_type) const;
EmulatedController* GetEmulatedControllerByIndex(std::size_t index);
const EmulatedController* GetEmulatedControllerByIndex(std::size_t index) const;
EmulatedConsole* GetEmulatedConsole();
const EmulatedConsole* GetEmulatedConsole() const;
EmulatedDevices* GetEmulatedDevices();
const EmulatedDevices* GetEmulatedDevices() const;
void SetSupportedStyleTag(NpadStyleTag style_tag);
NpadStyleTag GetSupportedStyleTag() const;
/// Counts the connected players from P1-P8
s8 GetPlayerCount() const;
/// Returns the first connected npad id
NpadIdType GetFirstNpadId() const;
/// Returns the first disconnected npad id
NpadIdType GetFirstDisconnectedNpadId() const;
/// Sets the npad id of the last active controller
void SetLastActiveController(NpadIdType npad_id);
/// Returns the npad id of the last controller that pushed a button
NpadIdType GetLastActiveController() const;
/// Sets all emulated controllers into configuring mode.
void EnableAllControllerConfiguration();
/// Sets all emulated controllers into normal mode.
void DisableAllControllerConfiguration();
/// Reloads all input devices from settings
void ReloadInputDevices();
/// Removes all callbacks from input common
void UnloadInputDevices();
/// Number of emulated controllers
static constexpr std::size_t available_controllers{10};
private:
std::unique_ptr<EmulatedController> player_1;
std::unique_ptr<EmulatedController> player_2;
std::unique_ptr<EmulatedController> player_3;
std::unique_ptr<EmulatedController> player_4;
std::unique_ptr<EmulatedController> player_5;
std::unique_ptr<EmulatedController> player_6;
std::unique_ptr<EmulatedController> player_7;
std::unique_ptr<EmulatedController> player_8;
std::unique_ptr<EmulatedController> other;
std::unique_ptr<EmulatedController> handheld;
std::unique_ptr<EmulatedConsole> console;
std::unique_ptr<EmulatedDevices> devices;
NpadStyleTag supported_style_tag{NpadStyleSet::All};
NpadIdType last_active_controller{NpadIdType::Handheld};
};
} // namespace Core::HID

59
src/hid_core/hid_result.h Executable file
View file

@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/hle/result.h"
namespace Service::HID {
constexpr Result PalmaResultSuccess{ErrorModule::HID, 0};
constexpr Result NpadInvalidHandle{ErrorModule::HID, 100};
constexpr Result NpadDeviceIndexOutOfRange{ErrorModule::HID, 107};
constexpr Result ResultVibrationNotInitialized{ErrorModule::HID, 121};
constexpr Result ResultVibrationInvalidStyleIndex{ErrorModule::HID, 122};
constexpr Result ResultVibrationInvalidNpadId{ErrorModule::HID, 123};
constexpr Result ResultVibrationDeviceIndexOutOfRange{ErrorModule::HID, 124};
constexpr Result ResultVibrationStrenghtOutOfRange{ErrorModule::HID, 126};
constexpr Result ResultVibrationArraySizeMismatch{ErrorModule::HID, 131};
constexpr Result InvalidSixAxisFusionRange{ErrorModule::HID, 423};
constexpr Result ResultNfcIsNotReady{ErrorModule::HID, 461};
constexpr Result ResultNfcXcdHandleIsNotInitialized{ErrorModule::HID, 464};
constexpr Result ResultIrSensorIsNotReady{ErrorModule::HID, 501};
constexpr Result ResultMcuIsNotReady{ErrorModule::HID, 541};
constexpr Result NpadIsDualJoycon{ErrorModule::HID, 601};
constexpr Result NpadIsSameType{ErrorModule::HID, 602};
constexpr Result ResultNpadIsNotProController{ErrorModule::HID, 604};
constexpr Result ResultInvalidNpadId{ErrorModule::HID, 709};
constexpr Result ResultNpadNotConnected{ErrorModule::HID, 710};
constexpr Result ResultNpadHandlerOverflow{ErrorModule::HID, 711};
constexpr Result ResultNpadHandlerNotInitialized{ErrorModule::HID, 712};
constexpr Result ResultInvalidArraySize{ErrorModule::HID, 715};
constexpr Result ResultUndefinedStyleset{ErrorModule::HID, 716};
constexpr Result ResultMultipleStyleSetSelected{ErrorModule::HID, 717};
constexpr Result ResultAppletResourceOverflow{ErrorModule::HID, 1041};
constexpr Result ResultAppletResourceNotInitialized{ErrorModule::HID, 1042};
constexpr Result ResultSharedMemoryNotInitialized{ErrorModule::HID, 1043};
constexpr Result ResultAruidNoAvailableEntries{ErrorModule::HID, 1044};
constexpr Result ResultAruidAlreadyRegistered{ErrorModule::HID, 1046};
constexpr Result ResultAruidNotRegistered{ErrorModule::HID, 1047};
constexpr Result ResultNpadResourceOverflow{ErrorModule::HID, 2001};
constexpr Result ResultNpadResourceNotInitialized{ErrorModule::HID, 2002};
constexpr Result InvalidPalmaHandle{ErrorModule::HID, 3302};
} // namespace Service::HID
namespace Service::IRS {
constexpr Result InvalidProcessorState{ErrorModule::Irsensor, 78};
constexpr Result InvalidIrCameraHandle{ErrorModule::Irsensor, 204};
} // namespace Service::IRS

736
src/hid_core/hid_types.h Executable file
View file

@ -0,0 +1,736 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/point.h"
#include "common/uuid.h"
#include "common/vector_math.h"
namespace Core::HID {
enum class DeviceIndex : u8 {
Left = 0,
Right = 1,
None = 2,
MaxDeviceIndex = 3,
};
// This is nn::hid::NpadButton
enum class NpadButton : u64 {
None = 0,
A = 1U << 0,
B = 1U << 1,
X = 1U << 2,
Y = 1U << 3,
StickL = 1U << 4,
StickR = 1U << 5,
L = 1U << 6,
R = 1U << 7,
ZL = 1U << 8,
ZR = 1U << 9,
Plus = 1U << 10,
Minus = 1U << 11,
Left = 1U << 12,
Up = 1U << 13,
Right = 1U << 14,
Down = 1U << 15,
StickLLeft = 1U << 16,
StickLUp = 1U << 17,
StickLRight = 1U << 18,
StickLDown = 1U << 19,
StickRLeft = 1U << 20,
StickRUp = 1U << 21,
StickRRight = 1U << 22,
StickRDown = 1U << 23,
LeftSL = 1U << 24,
LeftSR = 1U << 25,
RightSL = 1U << 26,
RightSR = 1U << 27,
Palma = 1U << 28,
Verification = 1U << 29,
HandheldLeftB = 1U << 30,
LagonCLeft = 1U << 31,
LagonCUp = 1ULL << 32,
LagonCRight = 1ULL << 33,
LagonCDown = 1ULL << 34,
All = 0xFFFFFFFFFFFFFFFFULL,
};
DECLARE_ENUM_FLAG_OPERATORS(NpadButton);
enum class KeyboardKeyIndex : u32 {
A = 4,
B = 5,
C = 6,
D = 7,
E = 8,
F = 9,
G = 10,
H = 11,
I = 12,
J = 13,
K = 14,
L = 15,
M = 16,
N = 17,
O = 18,
P = 19,
Q = 20,
R = 21,
S = 22,
T = 23,
U = 24,
V = 25,
W = 26,
X = 27,
Y = 28,
Z = 29,
D1 = 30,
D2 = 31,
D3 = 32,
D4 = 33,
D5 = 34,
D6 = 35,
D7 = 36,
D8 = 37,
D9 = 38,
D0 = 39,
Return = 40,
Escape = 41,
Backspace = 42,
Tab = 43,
Space = 44,
Minus = 45,
Plus = 46,
OpenBracket = 47,
CloseBracket = 48,
Pipe = 49,
Tilde = 50,
Semicolon = 51,
Quote = 52,
Backquote = 53,
Comma = 54,
Period = 55,
Slash = 56,
CapsLock = 57,
F1 = 58,
F2 = 59,
F3 = 60,
F4 = 61,
F5 = 62,
F6 = 63,
F7 = 64,
F8 = 65,
F9 = 66,
F10 = 67,
F11 = 68,
F12 = 69,
PrintScreen = 70,
ScrollLock = 71,
Pause = 72,
Insert = 73,
Home = 74,
PageUp = 75,
Delete = 76,
End = 77,
PageDown = 78,
RightArrow = 79,
LeftArrow = 80,
DownArrow = 81,
UpArrow = 82,
NumLock = 83,
NumPadDivide = 84,
NumPadMultiply = 85,
NumPadSubtract = 86,
NumPadAdd = 87,
NumPadEnter = 88,
NumPad1 = 89,
NumPad2 = 90,
NumPad3 = 91,
NumPad4 = 92,
NumPad5 = 93,
NumPad6 = 94,
NumPad7 = 95,
NumPad8 = 96,
NumPad9 = 97,
NumPad0 = 98,
NumPadDot = 99,
Backslash = 100,
Application = 101,
Power = 102,
NumPadEquals = 103,
F13 = 104,
F14 = 105,
F15 = 106,
F16 = 107,
F17 = 108,
F18 = 109,
F19 = 110,
F20 = 111,
F21 = 112,
F22 = 113,
F23 = 114,
F24 = 115,
NumPadComma = 133,
Ro = 135,
KatakanaHiragana = 136,
Yen = 137,
Henkan = 138,
Muhenkan = 139,
NumPadCommaPc98 = 140,
HangulEnglish = 144,
Hanja = 145,
Katakana = 146,
Hiragana = 147,
ZenkakuHankaku = 148,
LeftControl = 224,
LeftShift = 225,
LeftAlt = 226,
LeftGui = 227,
RightControl = 228,
RightShift = 229,
RightAlt = 230,
RightGui = 231,
};
// This is nn::hid::NpadIdType
enum class NpadIdType : u32 {
Player1 = 0x0,
Player2 = 0x1,
Player3 = 0x2,
Player4 = 0x3,
Player5 = 0x4,
Player6 = 0x5,
Player7 = 0x6,
Player8 = 0x7,
Other = 0x10,
Handheld = 0x20,
Invalid = 0xFFFFFFFF,
};
enum class NpadInterfaceType : u8 {
Bluetooth = 1,
Rail = 2,
Usb = 3,
Embedded = 4,
};
// This is nn::hid::NpadStyleIndex
enum class NpadStyleIndex : u8 {
None = 0,
ProController = 3,
Handheld = 4,
HandheldNES = 4,
JoyconDual = 5,
JoyconLeft = 6,
JoyconRight = 7,
GameCube = 8,
Pokeball = 9,
NES = 10,
SNES = 12,
N64 = 13,
SegaGenesis = 14,
SystemExt = 32,
System = 33,
MaxNpadType = 34,
};
// This is nn::hid::NpadStyleSet
enum class NpadStyleSet : u32 {
None = 0,
Fullkey = 1U << 0,
Handheld = 1U << 1,
JoyDual = 1U << 2,
JoyLeft = 1U << 3,
JoyRight = 1U << 4,
Gc = 1U << 5,
Palma = 1U << 6,
Lark = 1U << 7,
HandheldLark = 1U << 8,
Lucia = 1U << 9,
Lagoon = 1U << 10,
Lager = 1U << 11,
SystemExt = 1U << 29,
System = 1U << 30,
All = 0xFFFFFFFFU,
};
static_assert(sizeof(NpadStyleSet) == 4, "NpadStyleSet is an invalid size");
DECLARE_ENUM_FLAG_OPERATORS(NpadStyleSet)
// This is nn::hid::VibrationDevicePosition
enum class VibrationDevicePosition : u32 {
None = 0,
Left = 1,
Right = 2,
};
// This is nn::hid::VibrationDeviceType
enum class VibrationDeviceType : u32 {
Unknown = 0,
LinearResonantActuator = 1,
GcErm = 2,
N64 = 3,
};
// This is nn::hid::VibrationGcErmCommand
enum class VibrationGcErmCommand : u64 {
Stop = 0,
Start = 1,
StopHard = 2,
};
// This is nn::hid::GyroscopeZeroDriftMode
enum class GyroscopeZeroDriftMode : u32 {
Loose = 0,
Standard = 1,
Tight = 2,
};
// This is nn::settings::system::TouchScreenMode
enum class TouchScreenMode : u32 {
Stylus = 0,
Standard = 1,
};
// This is nn::hid::TouchScreenModeForNx
enum class TouchScreenModeForNx : u8 {
UseSystemSetting,
Finger,
Heat2,
};
// This is nn::hid::system::NpadBatteryLevel
enum class NpadBatteryLevel : u32 {
Empty,
Critical,
Low,
High,
Full,
};
// This is nn::hid::NpadStyleTag
struct NpadStyleTag {
union {
NpadStyleSet raw{};
BitField<0, 1, u32> fullkey;
BitField<1, 1, u32> handheld;
BitField<2, 1, u32> joycon_dual;
BitField<3, 1, u32> joycon_left;
BitField<4, 1, u32> joycon_right;
BitField<5, 1, u32> gamecube;
BitField<6, 1, u32> palma;
BitField<7, 1, u32> lark;
BitField<8, 1, u32> handheld_lark;
BitField<9, 1, u32> lucia;
BitField<10, 1, u32> lagoon;
BitField<11, 1, u32> lager;
BitField<29, 1, u32> system_ext;
BitField<30, 1, u32> system;
};
};
static_assert(sizeof(NpadStyleTag) == 4, "NpadStyleTag is an invalid size");
// This is nn::hid::TouchAttribute
struct TouchAttribute {
union {
u32 raw{};
BitField<0, 1, u32> start_touch;
BitField<1, 1, u32> end_touch;
};
};
static_assert(sizeof(TouchAttribute) == 0x4, "TouchAttribute is an invalid size");
// This is nn::hid::TouchState
struct TouchState {
u64 delta_time{};
TouchAttribute attribute{};
u32 finger{};
Common::Point<u32> position{};
u32 diameter_x{};
u32 diameter_y{};
u32 rotation_angle{};
};
static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size");
struct TouchFinger {
u64 last_touch{};
Common::Point<float> position{};
u32 id{};
TouchAttribute attribute{};
bool pressed{};
};
// This is nn::hid::TouchScreenConfigurationForNx
struct TouchScreenConfigurationForNx {
TouchScreenModeForNx mode{TouchScreenModeForNx::UseSystemSetting};
INSERT_PADDING_BYTES(0xF);
};
static_assert(sizeof(TouchScreenConfigurationForNx) == 0x10,
"TouchScreenConfigurationForNx is an invalid size");
struct NpadColor {
u8 r{};
u8 g{};
u8 b{};
u8 a{};
};
static_assert(sizeof(NpadColor) == 4, "NpadColor is an invalid size");
// This is nn::hid::NpadControllerColor
struct NpadControllerColor {
NpadColor body{};
NpadColor button{};
};
static_assert(sizeof(NpadControllerColor) == 8, "NpadControllerColor is an invalid size");
// This is nn::hid::AnalogStickState
struct AnalogStickState {
s32 x{};
s32 y{};
};
static_assert(sizeof(AnalogStickState) == 8, "AnalogStickState is an invalid size");
// This is nn::hid::server::NpadGcTriggerState
struct NpadGcTriggerState {
s64 sampling_number{};
s32 left{};
s32 right{};
};
static_assert(sizeof(NpadGcTriggerState) == 0x10, "NpadGcTriggerState is an invalid size");
// This is nn::hid::system::NpadPowerInfo
struct NpadPowerInfo {
bool is_powered{};
bool is_charging{};
INSERT_PADDING_BYTES(0x6);
NpadBatteryLevel battery_level{NpadBatteryLevel::Full};
};
static_assert(sizeof(NpadPowerInfo) == 0xC, "NpadPowerInfo is an invalid size");
struct LedPattern {
explicit LedPattern(u64 light1, u64 light2, u64 light3, u64 light4) {
position1.Assign(light1);
position2.Assign(light2);
position3.Assign(light3);
position4.Assign(light4);
}
union {
u64 raw{};
BitField<0, 1, u64> position1;
BitField<1, 1, u64> position2;
BitField<2, 1, u64> position3;
BitField<3, 1, u64> position4;
};
};
struct HomeButtonState {
union {
u64 raw{};
// Buttons
BitField<0, 1, u64> home;
};
};
static_assert(sizeof(HomeButtonState) == 0x8, "HomeButtonState has incorrect size.");
struct CaptureButtonState {
union {
u64 raw{};
// Buttons
BitField<0, 1, u64> capture;
};
};
static_assert(sizeof(CaptureButtonState) == 0x8, "CaptureButtonState has incorrect size.");
struct NpadButtonState {
union {
NpadButton raw{};
// Buttons
BitField<0, 1, u64> a;
BitField<1, 1, u64> b;
BitField<2, 1, u64> x;
BitField<3, 1, u64> y;
BitField<4, 1, u64> stick_l;
BitField<5, 1, u64> stick_r;
BitField<6, 1, u64> l;
BitField<7, 1, u64> r;
BitField<8, 1, u64> zl;
BitField<9, 1, u64> zr;
BitField<10, 1, u64> plus;
BitField<11, 1, u64> minus;
// D-Pad
BitField<12, 1, u64> left;
BitField<13, 1, u64> up;
BitField<14, 1, u64> right;
BitField<15, 1, u64> down;
// Left JoyStick
BitField<16, 1, u64> stick_l_left;
BitField<17, 1, u64> stick_l_up;
BitField<18, 1, u64> stick_l_right;
BitField<19, 1, u64> stick_l_down;
// Right JoyStick
BitField<20, 1, u64> stick_r_left;
BitField<21, 1, u64> stick_r_up;
BitField<22, 1, u64> stick_r_right;
BitField<23, 1, u64> stick_r_down;
BitField<24, 1, u64> left_sl;
BitField<25, 1, u64> left_sr;
BitField<26, 1, u64> right_sl;
BitField<27, 1, u64> right_sr;
BitField<28, 1, u64> palma;
BitField<29, 1, u64> verification;
BitField<30, 1, u64> handheld_left_b;
BitField<31, 1, u64> lagon_c_left;
BitField<32, 1, u64> lagon_c_up;
BitField<33, 1, u64> lagon_c_right;
BitField<34, 1, u64> lagon_c_down;
};
};
static_assert(sizeof(NpadButtonState) == 0x8, "NpadButtonState has incorrect size.");
// This is nn::hid::DebugPadButton
struct DebugPadButton {
union {
u32 raw{};
BitField<0, 1, u32> a;
BitField<1, 1, u32> b;
BitField<2, 1, u32> x;
BitField<3, 1, u32> y;
BitField<4, 1, u32> l;
BitField<5, 1, u32> r;
BitField<6, 1, u32> zl;
BitField<7, 1, u32> zr;
BitField<8, 1, u32> plus;
BitField<9, 1, u32> minus;
BitField<10, 1, u32> d_left;
BitField<11, 1, u32> d_up;
BitField<12, 1, u32> d_right;
BitField<13, 1, u32> d_down;
};
};
static_assert(sizeof(DebugPadButton) == 0x4, "DebugPadButton is an invalid size");
// This is nn::hid::ConsoleSixAxisSensorHandle
struct ConsoleSixAxisSensorHandle {
u8 unknown_1{};
u8 unknown_2{};
INSERT_PADDING_BYTES_NOINIT(2);
};
static_assert(sizeof(ConsoleSixAxisSensorHandle) == 4,
"ConsoleSixAxisSensorHandle is an invalid size");
// This is nn::hid::SixAxisSensorHandle
struct SixAxisSensorHandle {
NpadStyleIndex npad_type{NpadStyleIndex::None};
u8 npad_id{};
DeviceIndex device_index{DeviceIndex::None};
INSERT_PADDING_BYTES_NOINIT(1);
};
static_assert(sizeof(SixAxisSensorHandle) == 4, "SixAxisSensorHandle is an invalid size");
// These parameters seem related to how much gyro/accelerometer is used
struct SixAxisSensorFusionParameters {
f32 parameter1{0.03f}; // Range 0.0 to 1.0, default 0.03
f32 parameter2{0.4f}; // Default 0.4
};
static_assert(sizeof(SixAxisSensorFusionParameters) == 8,
"SixAxisSensorFusionParameters is an invalid size");
// This is nn::hid::server::SixAxisSensorProperties
struct SixAxisSensorProperties {
union {
u8 raw{};
BitField<0, 1, u8> is_newly_assigned;
BitField<1, 1, u8> is_firmware_update_available;
};
};
static_assert(sizeof(SixAxisSensorProperties) == 1, "SixAxisSensorProperties is an invalid size");
// This is nn::hid::SixAxisSensorCalibrationParameter
struct SixAxisSensorCalibrationParameter {
std::array<u8, 0x744> unknown_data{};
};
static_assert(sizeof(SixAxisSensorCalibrationParameter) == 0x744,
"SixAxisSensorCalibrationParameter is an invalid size");
// This is nn::hid::SixAxisSensorIcInformation
struct SixAxisSensorIcInformation {
f32 angular_rate{2000.0f}; // dps
std::array<f32, 6> unknown_gyro_data1{
-10.0f, -10.0f, -10.0f, 10.0f, 10.0f, 10.0f,
}; // dps
std::array<f32, 9> unknown_gyro_data2{
0.95f, -0.003f, -0.003f, -0.003f, 0.95f, -0.003f, -0.003f, -0.003f, 0.95f,
};
std::array<f32, 9> unknown_gyro_data3{
1.05f, 0.003f, 0.003f, 0.003f, 1.05f, 0.003f, 0.003f, 0.003f, 1.05f,
};
f32 acceleration_range{8.0f}; // g force
std::array<f32, 6> unknown_accel_data1{
-0.0612f, -0.0612f, -0.0612f, 0.0612f, 0.0612f, 0.0612f,
}; // g force
std::array<f32, 9> unknown_accel_data2{
0.95f, -0.003f, -0.003f, -0.003f, 0.95f, -0.003f, -0.003f, -0.003f, 0.95f,
};
std::array<f32, 9> unknown_accel_data3{
1.05f, 0.003f, 0.003f, 0.003f, 1.05f, 0.003f, 0.003f, 0.003f, 1.05f,
};
};
static_assert(sizeof(SixAxisSensorIcInformation) == 0xC8,
"SixAxisSensorIcInformation is an invalid size");
// This is nn::hid::SixAxisSensorAttribute
struct SixAxisSensorAttribute {
union {
u32 raw{};
BitField<0, 1, u32> is_connected;
BitField<1, 1, u32> is_interpolated;
};
};
static_assert(sizeof(SixAxisSensorAttribute) == 4, "SixAxisSensorAttribute is an invalid size");
// This is nn::hid::SixAxisSensorState
struct SixAxisSensorState {
s64 delta_time{};
s64 sampling_number{};
Common::Vec3f accel{};
Common::Vec3f gyro{};
Common::Vec3f rotation{};
std::array<Common::Vec3f, 3> orientation{};
SixAxisSensorAttribute attribute{};
INSERT_PADDING_BYTES(4); // Reserved
};
static_assert(sizeof(SixAxisSensorState) == 0x60, "SixAxisSensorState is an invalid size");
// This is nn::hid::VibrationDeviceHandle
struct VibrationDeviceHandle {
NpadStyleIndex npad_type{NpadStyleIndex::None};
u8 npad_id{};
DeviceIndex device_index{DeviceIndex::None};
INSERT_PADDING_BYTES_NOINIT(1);
};
static_assert(sizeof(VibrationDeviceHandle) == 4, "SixAxisSensorHandle is an invalid size");
// This is nn::hid::VibrationValue
struct VibrationValue {
f32 low_amplitude{};
f32 low_frequency{};
f32 high_amplitude{};
f32 high_frequency{};
};
static_assert(sizeof(VibrationValue) == 0x10, "VibrationValue has incorrect size.");
constexpr VibrationValue DEFAULT_VIBRATION_VALUE{
.low_amplitude = 0.0f,
.low_frequency = 160.0f,
.high_amplitude = 0.0f,
.high_frequency = 320.0f,
};
// This is nn::hid::VibrationDeviceInfo
struct VibrationDeviceInfo {
VibrationDeviceType type{};
VibrationDevicePosition position{};
};
static_assert(sizeof(VibrationDeviceInfo) == 0x8, "VibrationDeviceInfo has incorrect size.");
// This is nn::hid::KeyboardModifier
struct KeyboardModifier {
union {
u32 raw{};
BitField<0, 1, u32> control;
BitField<1, 1, u32> shift;
BitField<2, 1, u32> left_alt;
BitField<3, 1, u32> right_alt;
BitField<4, 1, u32> gui;
BitField<8, 1, u32> caps_lock;
BitField<9, 1, u32> scroll_lock;
BitField<10, 1, u32> num_lock;
BitField<11, 1, u32> katakana;
BitField<12, 1, u32> hiragana;
};
};
static_assert(sizeof(KeyboardModifier) == 0x4, "KeyboardModifier is an invalid size");
// This is nn::hid::KeyboardAttribute
struct KeyboardAttribute {
union {
u32 raw{};
BitField<0, 1, u32> is_connected;
};
};
static_assert(sizeof(KeyboardAttribute) == 0x4, "KeyboardAttribute is an invalid size");
// This is nn::hid::KeyboardKey
struct KeyboardKey {
// This should be a 256 bit flag
std::array<u8, 32> key{};
};
static_assert(sizeof(KeyboardKey) == 0x20, "KeyboardKey is an invalid size");
// This is nn::hid::MouseButton
struct MouseButton {
union {
u32_le raw{};
BitField<0, 1, u32> left;
BitField<1, 1, u32> right;
BitField<2, 1, u32> middle;
BitField<3, 1, u32> forward;
BitField<4, 1, u32> back;
};
};
static_assert(sizeof(MouseButton) == 0x4, "MouseButton is an invalid size");
// This is nn::hid::MouseAttribute
struct MouseAttribute {
union {
u32 raw{};
BitField<0, 1, u32> transferable;
BitField<1, 1, u32> is_connected;
};
};
static_assert(sizeof(MouseAttribute) == 0x4, "MouseAttribute is an invalid size");
// This is nn::hid::detail::MouseState
struct MouseState {
s64 sampling_number{};
s32 x{};
s32 y{};
s32 delta_x{};
s32 delta_y{};
// Axis Order in HW is switched for the wheel
s32 delta_wheel_y{};
s32 delta_wheel_x{};
MouseButton button{};
MouseAttribute attribute{};
};
static_assert(sizeof(MouseState) == 0x28, "MouseState is an invalid size");
struct UniquePadId {
u64 id;
};
static_assert(sizeof(UniquePadId) == 0x8, "UniquePadId is an invalid size");
} // namespace Core::HID

146
src/hid_core/hid_util.h Executable file
View file

@ -0,0 +1,146 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "hid_core/hid_result.h"
#include "hid_core/hid_types.h"
namespace Service::HID {
constexpr bool IsNpadIdValid(const Core::HID::NpadIdType npad_id) {
switch (npad_id) {
case Core::HID::NpadIdType::Player1:
case Core::HID::NpadIdType::Player2:
case Core::HID::NpadIdType::Player3:
case Core::HID::NpadIdType::Player4:
case Core::HID::NpadIdType::Player5:
case Core::HID::NpadIdType::Player6:
case Core::HID::NpadIdType::Player7:
case Core::HID::NpadIdType::Player8:
case Core::HID::NpadIdType::Other:
case Core::HID::NpadIdType::Handheld:
return true;
default:
return false;
}
}
constexpr Result IsSixaxisHandleValid(const Core::HID::SixAxisSensorHandle& handle) {
const auto npad_id = IsNpadIdValid(static_cast<Core::HID::NpadIdType>(handle.npad_id));
const bool device_index = handle.device_index < Core::HID::DeviceIndex::MaxDeviceIndex;
if (!npad_id) {
return ResultInvalidNpadId;
}
if (!device_index) {
return NpadDeviceIndexOutOfRange;
}
return ResultSuccess;
}
constexpr Result IsVibrationHandleValid(const Core::HID::VibrationDeviceHandle& handle) {
switch (handle.npad_type) {
case Core::HID::NpadStyleIndex::ProController:
case Core::HID::NpadStyleIndex::Handheld:
case Core::HID::NpadStyleIndex::JoyconDual:
case Core::HID::NpadStyleIndex::JoyconLeft:
case Core::HID::NpadStyleIndex::JoyconRight:
case Core::HID::NpadStyleIndex::GameCube:
case Core::HID::NpadStyleIndex::N64:
case Core::HID::NpadStyleIndex::SystemExt:
case Core::HID::NpadStyleIndex::System:
// These support vibration
break;
default:
return ResultVibrationInvalidStyleIndex;
}
if (!IsNpadIdValid(static_cast<Core::HID::NpadIdType>(handle.npad_id))) {
return ResultVibrationInvalidNpadId;
}
if (handle.device_index >= Core::HID::DeviceIndex::MaxDeviceIndex) {
return ResultVibrationDeviceIndexOutOfRange;
}
return ResultSuccess;
}
/// Converts a Core::HID::NpadIdType to an array index.
constexpr size_t NpadIdTypeToIndex(Core::HID::NpadIdType npad_id_type) {
switch (npad_id_type) {
case Core::HID::NpadIdType::Player1:
return 0;
case Core::HID::NpadIdType::Player2:
return 1;
case Core::HID::NpadIdType::Player3:
return 2;
case Core::HID::NpadIdType::Player4:
return 3;
case Core::HID::NpadIdType::Player5:
return 4;
case Core::HID::NpadIdType::Player6:
return 5;
case Core::HID::NpadIdType::Player7:
return 6;
case Core::HID::NpadIdType::Player8:
return 7;
case Core::HID::NpadIdType::Handheld:
return 8;
case Core::HID::NpadIdType::Other:
return 9;
default:
return 8;
}
}
/// Converts an array index to a Core::HID::NpadIdType
constexpr Core::HID::NpadIdType IndexToNpadIdType(size_t index) {
switch (index) {
case 0:
return Core::HID::NpadIdType::Player1;
case 1:
return Core::HID::NpadIdType::Player2;
case 2:
return Core::HID::NpadIdType::Player3;
case 3:
return Core::HID::NpadIdType::Player4;
case 4:
return Core::HID::NpadIdType::Player5;
case 5:
return Core::HID::NpadIdType::Player6;
case 6:
return Core::HID::NpadIdType::Player7;
case 7:
return Core::HID::NpadIdType::Player8;
case 8:
return Core::HID::NpadIdType::Handheld;
case 9:
return Core::HID::NpadIdType::Other;
default:
return Core::HID::NpadIdType::Invalid;
}
}
constexpr Core::HID::NpadStyleSet GetStylesetByIndex(std::size_t index) {
switch (index) {
case 0:
return Core::HID::NpadStyleSet::Fullkey;
case 1:
return Core::HID::NpadStyleSet::Handheld;
case 2:
return Core::HID::NpadStyleSet::JoyDual;
case 3:
return Core::HID::NpadStyleSet::JoyLeft;
case 4:
return Core::HID::NpadStyleSet::JoyRight;
case 5:
return Core::HID::NpadStyleSet::Palma;
default:
return Core::HID::NpadStyleSet::None;
}
}
} // namespace Service::HID

View file

@ -0,0 +1,73 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/service/kernel_helpers.h"
#include "hid_core/hid_core.h"
#include "hid_core/hidbus/hidbus_base.h"
namespace Service::HID {
HidbusBase::HidbusBase(Core::System& system_, KernelHelpers::ServiceContext& service_context_)
: system(system_), service_context(service_context_) {
send_command_async_event = service_context.CreateEvent("hidbus:SendCommandAsyncEvent");
}
HidbusBase::~HidbusBase() {
service_context.CloseEvent(send_command_async_event);
};
void HidbusBase::ActivateDevice() {
if (is_activated) {
return;
}
is_activated = true;
OnInit();
}
void HidbusBase::DeactivateDevice() {
if (is_activated) {
OnRelease();
}
is_activated = false;
}
bool HidbusBase::IsDeviceActivated() const {
return is_activated;
}
void HidbusBase::Enable(bool enable) {
device_enabled = enable;
}
bool HidbusBase::IsEnabled() const {
return device_enabled;
}
bool HidbusBase::IsPollingMode() const {
return polling_mode_enabled;
}
JoyPollingMode HidbusBase::GetPollingMode() const {
return polling_mode;
}
void HidbusBase::SetPollingMode(JoyPollingMode mode) {
polling_mode = mode;
polling_mode_enabled = true;
}
void HidbusBase::DisablePollingMode() {
polling_mode_enabled = false;
}
void HidbusBase::SetTransferMemoryAddress(Common::ProcessAddress t_mem) {
transfer_memory = t_mem;
}
Kernel::KReadableEvent& HidbusBase::GetSendCommandAsycEvent() const {
return send_command_async_event->GetReadableEvent();
}
} // namespace Service::HID

183
src/hid_core/hidbus/hidbus_base.h Executable file
View file

@ -0,0 +1,183 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <span>
#include "common/typed_address.h"
#include "core/hle/result.h"
namespace Core {
class System;
}
namespace Kernel {
class KEvent;
class KReadableEvent;
} // namespace Kernel
namespace Service::KernelHelpers {
class ServiceContext;
}
namespace Service::HID {
// This is nn::hidbus::JoyPollingMode
enum class JoyPollingMode : u32 {
SixAxisSensorDisable,
SixAxisSensorEnable,
ButtonOnly,
};
struct DataAccessorHeader {
Result result{ResultUnknown};
INSERT_PADDING_WORDS(0x1);
std::array<u8, 0x18> unused{};
u64 latest_entry{};
u64 total_entries{};
};
static_assert(sizeof(DataAccessorHeader) == 0x30, "DataAccessorHeader is an invalid size");
struct JoyDisableSixAxisPollingData {
std::array<u8, 0x26> data;
u8 out_size;
INSERT_PADDING_BYTES(0x1);
u64 sampling_number;
};
static_assert(sizeof(JoyDisableSixAxisPollingData) == 0x30,
"JoyDisableSixAxisPollingData is an invalid size");
struct JoyEnableSixAxisPollingData {
std::array<u8, 0x8> data;
u8 out_size;
INSERT_PADDING_BYTES(0x7);
u64 sampling_number;
};
static_assert(sizeof(JoyEnableSixAxisPollingData) == 0x18,
"JoyEnableSixAxisPollingData is an invalid size");
struct JoyButtonOnlyPollingData {
std::array<u8, 0x2c> data;
u8 out_size;
INSERT_PADDING_BYTES(0x3);
u64 sampling_number;
};
static_assert(sizeof(JoyButtonOnlyPollingData) == 0x38,
"JoyButtonOnlyPollingData is an invalid size");
struct JoyDisableSixAxisPollingEntry {
u64 sampling_number;
JoyDisableSixAxisPollingData polling_data;
};
static_assert(sizeof(JoyDisableSixAxisPollingEntry) == 0x38,
"JoyDisableSixAxisPollingEntry is an invalid size");
struct JoyEnableSixAxisPollingEntry {
u64 sampling_number;
JoyEnableSixAxisPollingData polling_data;
};
static_assert(sizeof(JoyEnableSixAxisPollingEntry) == 0x20,
"JoyEnableSixAxisPollingEntry is an invalid size");
struct JoyButtonOnlyPollingEntry {
u64 sampling_number;
JoyButtonOnlyPollingData polling_data;
};
static_assert(sizeof(JoyButtonOnlyPollingEntry) == 0x40,
"JoyButtonOnlyPollingEntry is an invalid size");
struct JoyDisableSixAxisDataAccessor {
DataAccessorHeader header{};
std::array<JoyDisableSixAxisPollingEntry, 0xb> entries{};
};
static_assert(sizeof(JoyDisableSixAxisDataAccessor) == 0x298,
"JoyDisableSixAxisDataAccessor is an invalid size");
struct JoyEnableSixAxisDataAccessor {
DataAccessorHeader header{};
std::array<JoyEnableSixAxisPollingEntry, 0xb> entries{};
};
static_assert(sizeof(JoyEnableSixAxisDataAccessor) == 0x190,
"JoyEnableSixAxisDataAccessor is an invalid size");
struct ButtonOnlyPollingDataAccessor {
DataAccessorHeader header;
std::array<JoyButtonOnlyPollingEntry, 0xb> entries;
};
static_assert(sizeof(ButtonOnlyPollingDataAccessor) == 0x2F0,
"ButtonOnlyPollingDataAccessor is an invalid size");
class HidbusBase {
public:
explicit HidbusBase(Core::System& system_, KernelHelpers::ServiceContext& service_context_);
virtual ~HidbusBase();
void ActivateDevice();
void DeactivateDevice();
bool IsDeviceActivated() const;
// Enables/disables the device
void Enable(bool enable);
// returns true if device is enabled
bool IsEnabled() const;
// returns true if polling mode is enabled
bool IsPollingMode() const;
// returns polling mode
JoyPollingMode GetPollingMode() const;
// Sets and enables JoyPollingMode
void SetPollingMode(JoyPollingMode mode);
// Disables JoyPollingMode
void DisablePollingMode();
// Called on EnableJoyPollingReceiveMode
void SetTransferMemoryAddress(Common::ProcessAddress t_mem);
Kernel::KReadableEvent& GetSendCommandAsycEvent() const;
virtual void OnInit() {}
virtual void OnRelease() {}
// Updates device transfer memory
virtual void OnUpdate() {}
// Returns the device ID of the joycon
virtual u8 GetDeviceId() const {
return {};
}
// Assigns a command from data
virtual bool SetCommand(std::span<const u8> data) {
return {};
}
// Returns a reply from a command
virtual std::vector<u8> GetReply() const {
return {};
}
protected:
bool is_activated{};
bool device_enabled{};
bool polling_mode_enabled{};
JoyPollingMode polling_mode = {};
// TODO(German77): All data accessors need to be replaced with a ring lifo object
JoyDisableSixAxisDataAccessor disable_sixaxis_data{};
JoyEnableSixAxisDataAccessor enable_sixaxis_data{};
ButtonOnlyPollingDataAccessor button_only_data{};
Common::ProcessAddress transfer_memory{};
Core::System& system;
Kernel::KEvent* send_command_async_event;
KernelHelpers::ServiceContext& service_context;
};
} // namespace Service::HID

292
src/hid_core/hidbus/ringcon.cpp Executable file
View file

@ -0,0 +1,292 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/memory.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "hid_core/hidbus/ringcon.h"
namespace Service::HID {
RingController::RingController(Core::System& system_,
KernelHelpers::ServiceContext& service_context_)
: HidbusBase(system_, service_context_) {
input = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
}
RingController::~RingController() = default;
void RingController::OnInit() {
input->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
Common::Input::PollingMode::Ring);
return;
}
void RingController::OnRelease() {
input->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
Common::Input::PollingMode::Active);
return;
};
void RingController::OnUpdate() {
if (!is_activated) {
return;
}
if (!device_enabled) {
return;
}
if (!polling_mode_enabled || transfer_memory == 0) {
return;
}
// TODO: Increment multitasking counters from motion and sensor data
switch (polling_mode) {
case JoyPollingMode::SixAxisSensorEnable: {
enable_sixaxis_data.header.total_entries = 10;
enable_sixaxis_data.header.result = ResultSuccess;
const auto& last_entry =
enable_sixaxis_data.entries[enable_sixaxis_data.header.latest_entry];
enable_sixaxis_data.header.latest_entry =
(enable_sixaxis_data.header.latest_entry + 1) % 10;
auto& curr_entry = enable_sixaxis_data.entries[enable_sixaxis_data.header.latest_entry];
curr_entry.sampling_number = last_entry.sampling_number + 1;
curr_entry.polling_data.sampling_number = curr_entry.sampling_number;
const RingConData ringcon_value = GetSensorValue();
curr_entry.polling_data.out_size = sizeof(ringcon_value);
std::memcpy(curr_entry.polling_data.data.data(), &ringcon_value, sizeof(ringcon_value));
system.ApplicationMemory().WriteBlock(transfer_memory, &enable_sixaxis_data,
sizeof(enable_sixaxis_data));
break;
}
default:
LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode);
break;
}
}
RingController::RingConData RingController::GetSensorValue() const {
RingConData ringcon_sensor_value{
.status = DataValid::Valid,
.data = 0,
};
const f32 force_value = input->GetRingSensorForce().force * range;
ringcon_sensor_value.data = static_cast<s16>(force_value) + idle_value;
return ringcon_sensor_value;
}
u8 RingController::GetDeviceId() const {
return device_id;
}
std::vector<u8> RingController::GetReply() const {
const RingConCommands current_command = command;
switch (current_command) {
case RingConCommands::GetFirmwareVersion:
return GetFirmwareVersionReply();
case RingConCommands::ReadId:
return GetReadIdReply();
case RingConCommands::c20105:
return GetC020105Reply();
case RingConCommands::ReadUnkCal:
return GetReadUnkCalReply();
case RingConCommands::ReadFactoryCal:
return GetReadFactoryCalReply();
case RingConCommands::ReadUserCal:
return GetReadUserCalReply();
case RingConCommands::ReadRepCount:
return GetReadRepCountReply();
case RingConCommands::ReadTotalPushCount:
return GetReadTotalPushCountReply();
case RingConCommands::ResetRepCount:
return GetResetRepCountReply();
case RingConCommands::SaveCalData:
return GetSaveDataReply();
default:
return GetErrorReply();
}
}
bool RingController::SetCommand(std::span<const u8> data) {
if (data.size() < 4) {
LOG_ERROR(Service_HID, "Command size not supported {}", data.size());
command = RingConCommands::Error;
return false;
}
std::memcpy(&command, data.data(), sizeof(RingConCommands));
switch (command) {
case RingConCommands::GetFirmwareVersion:
case RingConCommands::ReadId:
case RingConCommands::c20105:
case RingConCommands::ReadUnkCal:
case RingConCommands::ReadFactoryCal:
case RingConCommands::ReadUserCal:
case RingConCommands::ReadRepCount:
case RingConCommands::ReadTotalPushCount:
ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes");
send_command_async_event->Signal();
return true;
case RingConCommands::ResetRepCount:
ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes");
total_rep_count = 0;
send_command_async_event->Signal();
return true;
case RingConCommands::SaveCalData: {
ASSERT_MSG(data.size() == 0x14, "data.size is not 0x14 bytes");
SaveCalData save_info{};
std::memcpy(&save_info, data.data(), sizeof(SaveCalData));
user_calibration = save_info.calibration;
send_command_async_event->Signal();
return true;
}
default:
LOG_ERROR(Service_HID, "Command not implemented {}", command);
command = RingConCommands::Error;
// Signal a reply to avoid softlocking the game
send_command_async_event->Signal();
return false;
}
}
std::vector<u8> RingController::GetFirmwareVersionReply() const {
const FirmwareVersionReply reply{
.status = DataValid::Valid,
.firmware = version,
};
return GetDataVector(reply);
}
std::vector<u8> RingController::GetReadIdReply() const {
// The values are hardcoded from a real joycon
const ReadIdReply reply{
.status = DataValid::Valid,
.id_l_x0 = 8,
.id_l_x0_2 = 41,
.id_l_x4 = 22294,
.id_h_x0 = 19777,
.id_h_x0_2 = 13621,
.id_h_x4 = 8245,
};
return GetDataVector(reply);
}
std::vector<u8> RingController::GetC020105Reply() const {
const Cmd020105Reply reply{
.status = DataValid::Valid,
.data = 1,
};
return GetDataVector(reply);
}
std::vector<u8> RingController::GetReadUnkCalReply() const {
const ReadUnkCalReply reply{
.status = DataValid::Valid,
.data = 0,
};
return GetDataVector(reply);
}
std::vector<u8> RingController::GetReadFactoryCalReply() const {
const ReadFactoryCalReply reply{
.status = DataValid::Valid,
.calibration = factory_calibration,
};
return GetDataVector(reply);
}
std::vector<u8> RingController::GetReadUserCalReply() const {
const ReadUserCalReply reply{
.status = DataValid::Valid,
.calibration = user_calibration,
};
return GetDataVector(reply);
}
std::vector<u8> RingController::GetReadRepCountReply() const {
const GetThreeByteReply reply{
.status = DataValid::Valid,
.data = {total_rep_count, 0, 0},
.crc = GetCrcValue({total_rep_count, 0, 0, 0}),
};
return GetDataVector(reply);
}
std::vector<u8> RingController::GetReadTotalPushCountReply() const {
const GetThreeByteReply reply{
.status = DataValid::Valid,
.data = {total_push_count, 0, 0},
.crc = GetCrcValue({total_push_count, 0, 0, 0}),
};
return GetDataVector(reply);
}
std::vector<u8> RingController::GetResetRepCountReply() const {
return GetReadRepCountReply();
}
std::vector<u8> RingController::GetSaveDataReply() const {
const StatusReply reply{
.status = DataValid::Valid,
};
return GetDataVector(reply);
}
std::vector<u8> RingController::GetErrorReply() const {
const ErrorReply reply{
.status = DataValid::BadCRC,
};
return GetDataVector(reply);
}
u8 RingController::GetCrcValue(const std::vector<u8>& data) const {
u8 crc = 0;
for (std::size_t index = 0; index < data.size(); index++) {
for (u8 i = 0x80; i > 0; i >>= 1) {
bool bit = (crc & 0x80) != 0;
if ((data[index] & i) != 0) {
bit = !bit;
}
crc <<= 1;
if (bit) {
crc ^= 0x8d;
}
}
}
return crc;
}
template <typename T>
std::vector<u8> RingController::GetDataVector(const T& reply) const {
static_assert(std::is_trivially_copyable_v<T>);
std::vector<u8> data;
data.resize(sizeof(reply));
std::memcpy(data.data(), &reply, sizeof(reply));
return data;
}
} // namespace Service::HID

253
src/hid_core/hidbus/ringcon.h Executable file
View file

@ -0,0 +1,253 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <span>
#include "common/common_types.h"
#include "hid_core/hidbus/hidbus_base.h"
namespace Core::HID {
class EmulatedController;
} // namespace Core::HID
namespace Service::HID {
class RingController final : public HidbusBase {
public:
explicit RingController(Core::System& system_, KernelHelpers::ServiceContext& service_context_);
~RingController() override;
void OnInit() override;
void OnRelease() override;
// Updates ringcon transfer memory
void OnUpdate() override;
// Returns the device ID of the joycon
u8 GetDeviceId() const override;
// Assigns a command from data
bool SetCommand(std::span<const u8> data) override;
// Returns a reply from a command
std::vector<u8> GetReply() const override;
private:
// These values are obtained from a real ring controller
static constexpr s16 idle_value = 2280;
static constexpr s16 idle_deadzone = 120;
static constexpr s16 range = 2500;
// Most missing command names are leftovers from other firmware versions
enum class RingConCommands : u32 {
GetFirmwareVersion = 0x00020000,
ReadId = 0x00020100,
JoyPolling = 0x00020101,
Unknown1 = 0x00020104,
c20105 = 0x00020105,
Unknown2 = 0x00020204,
Unknown3 = 0x00020304,
Unknown4 = 0x00020404,
ReadUnkCal = 0x00020504,
ReadFactoryCal = 0x00020A04,
Unknown5 = 0x00021104,
Unknown6 = 0x00021204,
Unknown7 = 0x00021304,
ReadUserCal = 0x00021A04,
ReadRepCount = 0x00023104,
ReadTotalPushCount = 0x00023204,
ResetRepCount = 0x04013104,
Unknown8 = 0x04011104,
Unknown9 = 0x04011204,
Unknown10 = 0x04011304,
SaveCalData = 0x10011A04,
Error = 0xFFFFFFFF,
};
enum class DataValid : u32 {
Valid,
BadCRC,
Cal,
};
struct FirmwareVersion {
u8 sub;
u8 main;
};
static_assert(sizeof(FirmwareVersion) == 0x2, "FirmwareVersion is an invalid size");
struct FactoryCalibration {
s32_le os_max;
s32_le hk_max;
s32_le zero_min;
s32_le zero_max;
};
static_assert(sizeof(FactoryCalibration) == 0x10, "FactoryCalibration is an invalid size");
struct CalibrationValue {
s16 value;
u16 crc;
};
static_assert(sizeof(CalibrationValue) == 0x4, "CalibrationValue is an invalid size");
struct UserCalibration {
CalibrationValue os_max;
CalibrationValue hk_max;
CalibrationValue zero;
};
static_assert(sizeof(UserCalibration) == 0xC, "UserCalibration is an invalid size");
struct SaveCalData {
RingConCommands command;
UserCalibration calibration;
INSERT_PADDING_BYTES_NOINIT(4);
};
static_assert(sizeof(SaveCalData) == 0x14, "SaveCalData is an invalid size");
static_assert(std::is_trivially_copyable_v<SaveCalData>,
"SaveCalData must be trivially copyable");
struct FirmwareVersionReply {
DataValid status;
FirmwareVersion firmware;
INSERT_PADDING_BYTES(0x2);
};
static_assert(sizeof(FirmwareVersionReply) == 0x8, "FirmwareVersionReply is an invalid size");
struct Cmd020105Reply {
DataValid status;
u8 data;
INSERT_PADDING_BYTES(0x3);
};
static_assert(sizeof(Cmd020105Reply) == 0x8, "Cmd020105Reply is an invalid size");
struct StatusReply {
DataValid status;
};
static_assert(sizeof(StatusReply) == 0x4, "StatusReply is an invalid size");
struct GetThreeByteReply {
DataValid status;
std::array<u8, 3> data;
u8 crc;
};
static_assert(sizeof(GetThreeByteReply) == 0x8, "GetThreeByteReply is an invalid size");
struct ReadUnkCalReply {
DataValid status;
u16 data;
INSERT_PADDING_BYTES(0x2);
};
static_assert(sizeof(ReadUnkCalReply) == 0x8, "ReadUnkCalReply is an invalid size");
struct ReadFactoryCalReply {
DataValid status;
FactoryCalibration calibration;
};
static_assert(sizeof(ReadFactoryCalReply) == 0x14, "ReadFactoryCalReply is an invalid size");
struct ReadUserCalReply {
DataValid status;
UserCalibration calibration;
INSERT_PADDING_BYTES(0x4);
};
static_assert(sizeof(ReadUserCalReply) == 0x14, "ReadUserCalReply is an invalid size");
struct ReadIdReply {
DataValid status;
u16 id_l_x0;
u16 id_l_x0_2;
u16 id_l_x4;
u16 id_h_x0;
u16 id_h_x0_2;
u16 id_h_x4;
};
static_assert(sizeof(ReadIdReply) == 0x10, "ReadIdReply is an invalid size");
struct ErrorReply {
DataValid status;
INSERT_PADDING_BYTES(0x3);
};
static_assert(sizeof(ErrorReply) == 0x8, "ErrorReply is an invalid size");
struct RingConData {
DataValid status;
s16_le data;
INSERT_PADDING_BYTES(0x2);
};
static_assert(sizeof(RingConData) == 0x8, "RingConData is an invalid size");
// Returns RingConData struct with pressure sensor values
RingConData GetSensorValue() const;
// Returns 8 byte reply with firmware version
std::vector<u8> GetFirmwareVersionReply() const;
// Returns 16 byte reply with ID values
std::vector<u8> GetReadIdReply() const;
// (STUBBED) Returns 8 byte reply
std::vector<u8> GetC020105Reply() const;
// (STUBBED) Returns 8 byte empty reply
std::vector<u8> GetReadUnkCalReply() const;
// Returns 20 byte reply with factory calibration values
std::vector<u8> GetReadFactoryCalReply() const;
// Returns 20 byte reply with user calibration values
std::vector<u8> GetReadUserCalReply() const;
// Returns 8 byte reply
std::vector<u8> GetReadRepCountReply() const;
// Returns 8 byte reply
std::vector<u8> GetReadTotalPushCountReply() const;
// Returns 8 byte reply
std::vector<u8> GetResetRepCountReply() const;
// Returns 4 byte save data reply
std::vector<u8> GetSaveDataReply() const;
// Returns 8 byte error reply
std::vector<u8> GetErrorReply() const;
// Returns 8 bit redundancy check from provided data
u8 GetCrcValue(const std::vector<u8>& data) const;
// Converts structs to an u8 vector equivalent
template <typename T>
std::vector<u8> GetDataVector(const T& reply) const;
RingConCommands command{RingConCommands::Error};
// These counters are used in multitasking mode while the switch is sleeping
// Total steps taken
u8 total_rep_count = 0;
// Total times the ring was pushed
u8 total_push_count = 0;
const u8 device_id = 0x20;
const FirmwareVersion version = {
.sub = 0x0,
.main = 0x2c,
};
const FactoryCalibration factory_calibration = {
.os_max = idle_value + range + idle_deadzone,
.hk_max = idle_value - range - idle_deadzone,
.zero_min = idle_value - idle_deadzone,
.zero_max = idle_value + idle_deadzone,
};
UserCalibration user_calibration = {
.os_max = {.value = range, .crc = 228},
.hk_max = {.value = -range, .crc = 239},
.zero = {.value = idle_value, .crc = 225},
};
Core::HID::EmulatedController* input;
};
} // namespace Service::HID

View file

@ -0,0 +1,50 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "hid_core/hidbus/starlink.h"
namespace Service::HID {
constexpr u8 DEVICE_ID = 0x28;
Starlink::Starlink(Core::System& system_, KernelHelpers::ServiceContext& service_context_)
: HidbusBase(system_, service_context_) {}
Starlink::~Starlink() = default;
void Starlink::OnInit() {
return;
}
void Starlink::OnRelease() {
return;
};
void Starlink::OnUpdate() {
if (!is_activated) {
return;
}
if (!device_enabled) {
return;
}
if (!polling_mode_enabled || transfer_memory == 0) {
return;
}
LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode);
}
u8 Starlink::GetDeviceId() const {
return DEVICE_ID;
}
std::vector<u8> Starlink::GetReply() const {
return {};
}
bool Starlink::SetCommand(std::span<const u8> data) {
LOG_ERROR(Service_HID, "Command not implemented");
return false;
}
} // namespace Service::HID

37
src/hid_core/hidbus/starlink.h Executable file
View file

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/common_types.h"
#include "hid_core/hidbus/hidbus_base.h"
namespace Core::HID {
class EmulatedController;
} // namespace Core::HID
namespace Service::HID {
class Starlink final : public HidbusBase {
public:
explicit Starlink(Core::System& system_, KernelHelpers::ServiceContext& service_context_);
~Starlink() override;
void OnInit() override;
void OnRelease() override;
// Updates ringcon transfer memory
void OnUpdate() override;
// Returns the device ID of the joycon
u8 GetDeviceId() const override;
// Assigns a command from data
bool SetCommand(std::span<const u8> data) override;
// Returns a reply from a command
std::vector<u8> GetReply() const override;
};
} // namespace Service::HID

50
src/hid_core/hidbus/stubbed.cpp Executable file
View file

@ -0,0 +1,50 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "hid_core/hidbus/stubbed.h"
namespace Service::HID {
constexpr u8 DEVICE_ID = 0xFF;
HidbusStubbed::HidbusStubbed(Core::System& system_, KernelHelpers::ServiceContext& service_context_)
: HidbusBase(system_, service_context_) {}
HidbusStubbed::~HidbusStubbed() = default;
void HidbusStubbed::OnInit() {
return;
}
void HidbusStubbed::OnRelease() {
return;
};
void HidbusStubbed::OnUpdate() {
if (!is_activated) {
return;
}
if (!device_enabled) {
return;
}
if (!polling_mode_enabled || transfer_memory == 0) {
return;
}
LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode);
}
u8 HidbusStubbed::GetDeviceId() const {
return DEVICE_ID;
}
std::vector<u8> HidbusStubbed::GetReply() const {
return {};
}
bool HidbusStubbed::SetCommand(std::span<const u8> data) {
LOG_ERROR(Service_HID, "Command not implemented");
return false;
}
} // namespace Service::HID

37
src/hid_core/hidbus/stubbed.h Executable file
View file

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/common_types.h"
#include "hid_core/hidbus/hidbus_base.h"
namespace Core::HID {
class EmulatedController;
} // namespace Core::HID
namespace Service::HID {
class HidbusStubbed final : public HidbusBase {
public:
explicit HidbusStubbed(Core::System& system_, KernelHelpers::ServiceContext& service_context_);
~HidbusStubbed() override;
void OnInit() override;
void OnRelease() override;
// Updates ringcon transfer memory
void OnUpdate() override;
// Returns the device ID of the joycon
u8 GetDeviceId() const override;
// Assigns a command from data
bool SetCommand(std::span<const u8> data) override;
// Returns a reply from a command
std::vector<u8> GetReply() const override;
};
} // namespace Service::HID

View file

@ -0,0 +1,267 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <queue>
#include "core/core.h"
#include "core/core_timing.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "hid_core/irsensor/clustering_processor.h"
namespace Service::IRS {
ClusteringProcessor::ClusteringProcessor(Core::System& system_,
Core::IrSensor::DeviceFormat& device_format,
std::size_t npad_index)
: device{device_format}, system{system_} {
npad_device = system.HIDCore().GetEmulatedControllerByIndex(npad_index);
device.mode = Core::IrSensor::IrSensorMode::ClusteringProcessor;
device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
SetDefaultConfig();
shared_memory = std::construct_at(
reinterpret_cast<ClusteringSharedMemory*>(&device_format.state.processor_raw_data));
Core::HID::ControllerUpdateCallback engine_callback{
.on_change = [this](Core::HID::ControllerTriggerType type) { OnControllerUpdate(type); },
.is_npad_service = true,
};
callback_key = npad_device->SetCallback(engine_callback);
}
ClusteringProcessor::~ClusteringProcessor() {
npad_device->DeleteCallback(callback_key);
};
void ClusteringProcessor::StartProcessor() {
device.camera_status = Core::IrSensor::IrCameraStatus::Available;
device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Ready;
}
void ClusteringProcessor::SuspendProcessor() {}
void ClusteringProcessor::StopProcessor() {}
void ClusteringProcessor::OnControllerUpdate(Core::HID::ControllerTriggerType type) {
if (type != Core::HID::ControllerTriggerType::IrSensor) {
return;
}
next_state = {};
const auto& camera_data = npad_device->GetCamera();
auto filtered_image = camera_data.data;
RemoveLowIntensityData(filtered_image);
const auto window_start_x = static_cast<std::size_t>(current_config.window_of_interest.x);
const auto window_start_y = static_cast<std::size_t>(current_config.window_of_interest.y);
const auto window_end_x =
window_start_x + static_cast<std::size_t>(current_config.window_of_interest.width);
const auto window_end_y =
window_start_y + static_cast<std::size_t>(current_config.window_of_interest.height);
for (std::size_t y = window_start_y; y < window_end_y; y++) {
for (std::size_t x = window_start_x; x < window_end_x; x++) {
u8 pixel = GetPixel(filtered_image, x, y);
if (pixel == 0) {
continue;
}
const auto cluster = GetClusterProperties(filtered_image, x, y);
if (cluster.pixel_count > current_config.pixel_count_max) {
continue;
}
if (cluster.pixel_count < current_config.pixel_count_min) {
continue;
}
// Cluster object limit reached
if (next_state.object_count >= next_state.data.size()) {
continue;
}
next_state.data[next_state.object_count] = cluster;
next_state.object_count++;
}
}
next_state.sampling_number = camera_data.sample;
next_state.timestamp = system.CoreTiming().GetGlobalTimeNs().count();
next_state.ambient_noise_level = Core::IrSensor::CameraAmbientNoiseLevel::Low;
shared_memory->clustering_lifo.WriteNextEntry(next_state);
if (!IsProcessorActive()) {
StartProcessor();
}
}
void ClusteringProcessor::RemoveLowIntensityData(std::vector<u8>& data) {
for (u8& pixel : data) {
if (pixel < current_config.pixel_count_min) {
pixel = 0;
}
}
}
ClusteringProcessor::ClusteringData ClusteringProcessor::GetClusterProperties(std::vector<u8>& data,
std::size_t x,
std::size_t y) {
using DataPoint = Common::Point<std::size_t>;
std::queue<DataPoint> search_points{};
ClusteringData current_cluster = GetPixelProperties(data, x, y);
SetPixel(data, x, y, 0);
search_points.emplace<DataPoint>({x, y});
while (!search_points.empty()) {
const auto point = search_points.front();
search_points.pop();
// Avoid negative numbers
if (point.x == 0 || point.y == 0) {
continue;
}
std::array<DataPoint, 4> new_points{
DataPoint{point.x - 1, point.y},
{point.x, point.y - 1},
{point.x + 1, point.y},
{point.x, point.y + 1},
};
for (const auto new_point : new_points) {
if (new_point.x >= width) {
continue;
}
if (new_point.y >= height) {
continue;
}
if (GetPixel(data, new_point.x, new_point.y) < current_config.object_intensity_min) {
continue;
}
const ClusteringData cluster = GetPixelProperties(data, new_point.x, new_point.y);
current_cluster = MergeCluster(current_cluster, cluster);
SetPixel(data, new_point.x, new_point.y, 0);
search_points.emplace<DataPoint>({new_point.x, new_point.y});
}
}
return current_cluster;
}
ClusteringProcessor::ClusteringData ClusteringProcessor::GetPixelProperties(
const std::vector<u8>& data, std::size_t x, std::size_t y) const {
return {
.average_intensity = GetPixel(data, x, y) / 255.0f,
.centroid =
{
.x = static_cast<f32>(x),
.y = static_cast<f32>(y),
},
.pixel_count = 1,
.bound =
{
.x = static_cast<s16>(x),
.y = static_cast<s16>(y),
.width = 1,
.height = 1,
},
};
}
ClusteringProcessor::ClusteringData ClusteringProcessor::MergeCluster(
const ClusteringData a, const ClusteringData b) const {
const f32 a_pixel_count = static_cast<f32>(a.pixel_count);
const f32 b_pixel_count = static_cast<f32>(b.pixel_count);
const f32 pixel_count = a_pixel_count + b_pixel_count;
const f32 average_intensity =
(a.average_intensity * a_pixel_count + b.average_intensity * b_pixel_count) / pixel_count;
const Core::IrSensor::IrsCentroid centroid = {
.x = (a.centroid.x * a_pixel_count + b.centroid.x * b_pixel_count) / pixel_count,
.y = (a.centroid.y * a_pixel_count + b.centroid.y * b_pixel_count) / pixel_count,
};
s16 bound_start_x = a.bound.x < b.bound.x ? a.bound.x : b.bound.x;
s16 bound_start_y = a.bound.y < b.bound.y ? a.bound.y : b.bound.y;
s16 a_bound_end_x = a.bound.x + a.bound.width;
s16 a_bound_end_y = a.bound.y + a.bound.height;
s16 b_bound_end_x = b.bound.x + b.bound.width;
s16 b_bound_end_y = b.bound.y + b.bound.height;
const Core::IrSensor::IrsRect bound = {
.x = bound_start_x,
.y = bound_start_y,
.width = a_bound_end_x > b_bound_end_x ? static_cast<s16>(a_bound_end_x - bound_start_x)
: static_cast<s16>(b_bound_end_x - bound_start_x),
.height = a_bound_end_y > b_bound_end_y ? static_cast<s16>(a_bound_end_y - bound_start_y)
: static_cast<s16>(b_bound_end_y - bound_start_y),
};
return {
.average_intensity = average_intensity,
.centroid = centroid,
.pixel_count = static_cast<u32>(pixel_count),
.bound = bound,
};
}
u8 ClusteringProcessor::GetPixel(const std::vector<u8>& data, std::size_t x, std::size_t y) const {
if ((y * width) + x >= data.size()) {
return 0;
}
return data[(y * width) + x];
}
void ClusteringProcessor::SetPixel(std::vector<u8>& data, std::size_t x, std::size_t y, u8 value) {
if ((y * width) + x >= data.size()) {
return;
}
data[(y * width) + x] = value;
}
void ClusteringProcessor::SetDefaultConfig() {
using namespace std::literals::chrono_literals;
current_config.camera_config.exposure_time = std::chrono::microseconds(200ms).count();
current_config.camera_config.gain = 2;
current_config.camera_config.is_negative_used = false;
current_config.camera_config.light_target = Core::IrSensor::CameraLightTarget::BrightLeds;
current_config.window_of_interest = {
.x = 0,
.y = 0,
.width = width,
.height = height,
};
current_config.pixel_count_min = 3;
current_config.pixel_count_max = static_cast<u32>(GetDataSize(format));
current_config.is_external_light_filter_enabled = true;
current_config.object_intensity_min = 150;
npad_device->SetCameraFormat(format);
}
void ClusteringProcessor::SetConfig(Core::IrSensor::PackedClusteringProcessorConfig config) {
current_config.camera_config.exposure_time = config.camera_config.exposure_time;
current_config.camera_config.gain = config.camera_config.gain;
current_config.camera_config.is_negative_used = config.camera_config.is_negative_used;
current_config.camera_config.light_target =
static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target);
current_config.window_of_interest = config.window_of_interest;
current_config.pixel_count_min = config.pixel_count_min;
current_config.pixel_count_max = config.pixel_count_max;
current_config.is_external_light_filter_enabled = config.is_external_light_filter_enabled;
current_config.object_intensity_min = config.object_intensity_min;
LOG_INFO(Service_IRS,
"Processor config, exposure_time={}, gain={}, is_negative_used={}, "
"light_target={}, window_of_interest=({}, {}, {}, {}), pixel_count_min={}, "
"pixel_count_max={}, is_external_light_filter_enabled={}, object_intensity_min={}",
current_config.camera_config.exposure_time, current_config.camera_config.gain,
current_config.camera_config.is_negative_used,
current_config.camera_config.light_target, current_config.window_of_interest.x,
current_config.window_of_interest.y, current_config.window_of_interest.width,
current_config.window_of_interest.height, current_config.pixel_count_min,
current_config.pixel_count_max, current_config.is_external_light_filter_enabled,
current_config.object_intensity_min);
npad_device->SetCameraFormat(format);
}
} // namespace Service::IRS

View file

@ -0,0 +1,115 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "common/common_types.h"
#include "hid_core/irsensor/irs_types.h"
#include "hid_core/irsensor/processor_base.h"
#include "hid_core/resources/irs_ring_lifo.h"
namespace Core {
class System;
}
namespace Core::HID {
class EmulatedController;
} // namespace Core::HID
namespace Service::IRS {
class ClusteringProcessor final : public ProcessorBase {
public:
explicit ClusteringProcessor(Core::System& system_, Core::IrSensor::DeviceFormat& device_format,
std::size_t npad_index);
~ClusteringProcessor() override;
// Called when the processor is initialized
void StartProcessor() override;
// Called when the processor is suspended
void SuspendProcessor() override;
// Called when the processor is stopped
void StopProcessor() override;
// Sets config parameters of the camera
void SetConfig(Core::IrSensor::PackedClusteringProcessorConfig config);
private:
static constexpr auto format = Core::IrSensor::ImageTransferProcessorFormat::Size320x240;
static constexpr std::size_t width = 320;
static constexpr std::size_t height = 240;
// This is nn::irsensor::ClusteringProcessorConfig
struct ClusteringProcessorConfig {
Core::IrSensor::CameraConfig camera_config;
Core::IrSensor::IrsRect window_of_interest;
u32 pixel_count_min;
u32 pixel_count_max;
u32 object_intensity_min;
bool is_external_light_filter_enabled;
INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(ClusteringProcessorConfig) == 0x30,
"ClusteringProcessorConfig is an invalid size");
// This is nn::irsensor::AdaptiveClusteringProcessorConfig
struct AdaptiveClusteringProcessorConfig {
Core::IrSensor::AdaptiveClusteringMode mode;
Core::IrSensor::AdaptiveClusteringTargetDistance target_distance;
};
static_assert(sizeof(AdaptiveClusteringProcessorConfig) == 0x8,
"AdaptiveClusteringProcessorConfig is an invalid size");
// This is nn::irsensor::ClusteringData
struct ClusteringData {
f32 average_intensity;
Core::IrSensor::IrsCentroid centroid;
u32 pixel_count;
Core::IrSensor::IrsRect bound;
};
static_assert(sizeof(ClusteringData) == 0x18, "ClusteringData is an invalid size");
// This is nn::irsensor::ClusteringProcessorState
struct ClusteringProcessorState {
s64 sampling_number;
u64 timestamp;
u8 object_count;
INSERT_PADDING_BYTES(3);
Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level;
std::array<ClusteringData, 0x10> data;
};
static_assert(sizeof(ClusteringProcessorState) == 0x198,
"ClusteringProcessorState is an invalid size");
struct ClusteringSharedMemory {
Service::IRS::Lifo<ClusteringProcessorState, 6> clustering_lifo;
static_assert(sizeof(clustering_lifo) == 0x9A0, "clustering_lifo is an invalid size");
INSERT_PADDING_WORDS(0x11F);
};
static_assert(sizeof(ClusteringSharedMemory) == 0xE20,
"ClusteringSharedMemory is an invalid size");
void OnControllerUpdate(Core::HID::ControllerTriggerType type);
void RemoveLowIntensityData(std::vector<u8>& data);
ClusteringData GetClusterProperties(std::vector<u8>& data, std::size_t x, std::size_t y);
ClusteringData GetPixelProperties(const std::vector<u8>& data, std::size_t x,
std::size_t y) const;
ClusteringData MergeCluster(const ClusteringData a, const ClusteringData b) const;
u8 GetPixel(const std::vector<u8>& data, std::size_t x, std::size_t y) const;
void SetPixel(std::vector<u8>& data, std::size_t x, std::size_t y, u8 value);
// Sets config parameters of the camera
void SetDefaultConfig();
ClusteringSharedMemory* shared_memory = nullptr;
ClusteringProcessorState next_state{};
ClusteringProcessorConfig current_config{};
Core::IrSensor::DeviceFormat& device;
Core::HID::EmulatedController* npad_device;
int callback_key{};
Core::System& system;
};
} // namespace Service::IRS

View file

@ -0,0 +1,155 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "core/core.h"
#include "core/memory.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "hid_core/irsensor/image_transfer_processor.h"
namespace Service::IRS {
ImageTransferProcessor::ImageTransferProcessor(Core::System& system_,
Core::IrSensor::DeviceFormat& device_format,
std::size_t npad_index)
: device{device_format}, system{system_} {
npad_device = system.HIDCore().GetEmulatedControllerByIndex(npad_index);
Core::HID::ControllerUpdateCallback engine_callback{
.on_change = [this](Core::HID::ControllerTriggerType type) { OnControllerUpdate(type); },
.is_npad_service = true,
};
callback_key = npad_device->SetCallback(engine_callback);
device.mode = Core::IrSensor::IrSensorMode::ImageTransferProcessor;
device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
}
ImageTransferProcessor::~ImageTransferProcessor() {
npad_device->DeleteCallback(callback_key);
};
void ImageTransferProcessor::StartProcessor() {
is_active = true;
device.camera_status = Core::IrSensor::IrCameraStatus::Available;
device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Ready;
processor_state.sampling_number = 0;
processor_state.ambient_noise_level = Core::IrSensor::CameraAmbientNoiseLevel::Low;
}
void ImageTransferProcessor::SuspendProcessor() {}
void ImageTransferProcessor::StopProcessor() {}
void ImageTransferProcessor::OnControllerUpdate(Core::HID::ControllerTriggerType type) {
if (type != Core::HID::ControllerTriggerType::IrSensor) {
return;
}
if (transfer_memory == 0) {
return;
}
const auto& camera_data = npad_device->GetCamera();
// This indicates how much ambient light is present
processor_state.ambient_noise_level = Core::IrSensor::CameraAmbientNoiseLevel::Low;
processor_state.sampling_number = camera_data.sample;
if (camera_data.format != current_config.origin_format) {
LOG_WARNING(Service_IRS, "Wrong Input format {} expected {}", camera_data.format,
current_config.origin_format);
system.ApplicationMemory().ZeroBlock(transfer_memory,
GetDataSize(current_config.trimming_format));
return;
}
if (current_config.origin_format > current_config.trimming_format) {
LOG_WARNING(Service_IRS, "Origin format {} is smaller than trimming format {}",
current_config.origin_format, current_config.trimming_format);
system.ApplicationMemory().ZeroBlock(transfer_memory,
GetDataSize(current_config.trimming_format));
return;
}
std::vector<u8> window_data{};
const auto origin_width = GetDataWidth(current_config.origin_format);
const auto origin_height = GetDataHeight(current_config.origin_format);
const auto trimming_width = GetDataWidth(current_config.trimming_format);
const auto trimming_height = GetDataHeight(current_config.trimming_format);
window_data.resize(GetDataSize(current_config.trimming_format));
if (trimming_width + current_config.trimming_start_x > origin_width ||
trimming_height + current_config.trimming_start_y > origin_height) {
LOG_WARNING(Service_IRS,
"Trimming area ({}, {}, {}, {}) is outside of origin area ({}, {})",
current_config.trimming_start_x, current_config.trimming_start_y,
trimming_width, trimming_height, origin_width, origin_height);
system.ApplicationMemory().ZeroBlock(transfer_memory,
GetDataSize(current_config.trimming_format));
return;
}
for (std::size_t y = 0; y < trimming_height; y++) {
for (std::size_t x = 0; x < trimming_width; x++) {
const std::size_t window_index = (y * trimming_width) + x;
const std::size_t origin_index =
((y + current_config.trimming_start_y) * origin_width) + x +
current_config.trimming_start_x;
window_data[window_index] = camera_data.data[origin_index];
}
}
system.ApplicationMemory().WriteBlock(transfer_memory, window_data.data(),
GetDataSize(current_config.trimming_format));
if (!IsProcessorActive()) {
StartProcessor();
}
}
void ImageTransferProcessor::SetConfig(Core::IrSensor::PackedImageTransferProcessorConfig config) {
current_config.camera_config.exposure_time = config.camera_config.exposure_time;
current_config.camera_config.gain = config.camera_config.gain;
current_config.camera_config.is_negative_used = config.camera_config.is_negative_used;
current_config.camera_config.light_target =
static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target);
current_config.origin_format =
static_cast<Core::IrSensor::ImageTransferProcessorFormat>(config.format);
current_config.trimming_format =
static_cast<Core::IrSensor::ImageTransferProcessorFormat>(config.format);
current_config.trimming_start_x = 0;
current_config.trimming_start_y = 0;
npad_device->SetCameraFormat(current_config.origin_format);
}
void ImageTransferProcessor::SetConfig(
Core::IrSensor::PackedImageTransferProcessorExConfig config) {
current_config.camera_config.exposure_time = config.camera_config.exposure_time;
current_config.camera_config.gain = config.camera_config.gain;
current_config.camera_config.is_negative_used = config.camera_config.is_negative_used;
current_config.camera_config.light_target =
static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target);
current_config.origin_format =
static_cast<Core::IrSensor::ImageTransferProcessorFormat>(config.origin_format);
current_config.trimming_format =
static_cast<Core::IrSensor::ImageTransferProcessorFormat>(config.trimming_format);
current_config.trimming_start_x = config.trimming_start_x;
current_config.trimming_start_y = config.trimming_start_y;
npad_device->SetCameraFormat(current_config.origin_format);
}
void ImageTransferProcessor::SetTransferMemoryAddress(Common::ProcessAddress t_mem) {
transfer_memory = t_mem;
}
Core::IrSensor::ImageTransferProcessorState ImageTransferProcessor::GetState(
std::vector<u8>& data) const {
const auto size = GetDataSize(current_config.trimming_format);
data.resize(size);
system.ApplicationMemory().ReadBlock(transfer_memory, data.data(), size);
return processor_state;
}
} // namespace Service::IRS

View file

@ -0,0 +1,77 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "common/typed_address.h"
#include "hid_core/irsensor/irs_types.h"
#include "hid_core/irsensor/processor_base.h"
namespace Core {
class System;
}
namespace Core::HID {
class EmulatedController;
} // namespace Core::HID
namespace Service::IRS {
class ImageTransferProcessor final : public ProcessorBase {
public:
explicit ImageTransferProcessor(Core::System& system_,
Core::IrSensor::DeviceFormat& device_format,
std::size_t npad_index);
~ImageTransferProcessor() override;
// Called when the processor is initialized
void StartProcessor() override;
// Called when the processor is suspended
void SuspendProcessor() override;
// Called when the processor is stopped
void StopProcessor() override;
// Sets config parameters of the camera
void SetConfig(Core::IrSensor::PackedImageTransferProcessorConfig config);
void SetConfig(Core::IrSensor::PackedImageTransferProcessorExConfig config);
// Transfer memory where the image data will be stored
void SetTransferMemoryAddress(Common::ProcessAddress t_mem);
Core::IrSensor::ImageTransferProcessorState GetState(std::vector<u8>& data) const;
private:
// This is nn::irsensor::ImageTransferProcessorConfig
struct ImageTransferProcessorConfig {
Core::IrSensor::CameraConfig camera_config;
Core::IrSensor::ImageTransferProcessorFormat format;
};
static_assert(sizeof(ImageTransferProcessorConfig) == 0x20,
"ImageTransferProcessorConfig is an invalid size");
// This is nn::irsensor::ImageTransferProcessorExConfig
struct ImageTransferProcessorExConfig {
Core::IrSensor::CameraConfig camera_config;
Core::IrSensor::ImageTransferProcessorFormat origin_format;
Core::IrSensor::ImageTransferProcessorFormat trimming_format;
u16 trimming_start_x;
u16 trimming_start_y;
bool is_external_light_filter_enabled;
INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(ImageTransferProcessorExConfig) == 0x28,
"ImageTransferProcessorExConfig is an invalid size");
void OnControllerUpdate(Core::HID::ControllerTriggerType type);
ImageTransferProcessorExConfig current_config{};
Core::IrSensor::ImageTransferProcessorState processor_state{};
Core::IrSensor::DeviceFormat& device;
Core::HID::EmulatedController* npad_device;
int callback_key{};
Core::System& system;
Common::ProcessAddress transfer_memory{};
};
} // namespace Service::IRS

View file

@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "hid_core/irsensor/ir_led_processor.h"
namespace Service::IRS {
IrLedProcessor::IrLedProcessor(Core::IrSensor::DeviceFormat& device_format)
: device(device_format) {
device.mode = Core::IrSensor::IrSensorMode::IrLedProcessor;
device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
}
IrLedProcessor::~IrLedProcessor() = default;
void IrLedProcessor::StartProcessor() {}
void IrLedProcessor::SuspendProcessor() {}
void IrLedProcessor::StopProcessor() {}
void IrLedProcessor::SetConfig(Core::IrSensor::PackedIrLedProcessorConfig config) {
current_config.light_target =
static_cast<Core::IrSensor::CameraLightTarget>(config.light_target);
}
} // namespace Service::IRS

View file

@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "common/bit_field.h"
#include "common/common_types.h"
#include "hid_core/irsensor/irs_types.h"
#include "hid_core/irsensor/processor_base.h"
namespace Service::IRS {
class IrLedProcessor final : public ProcessorBase {
public:
explicit IrLedProcessor(Core::IrSensor::DeviceFormat& device_format);
~IrLedProcessor() override;
// Called when the processor is initialized
void StartProcessor() override;
// Called when the processor is suspended
void SuspendProcessor() override;
// Called when the processor is stopped
void StopProcessor() override;
// Sets config parameters of the camera
void SetConfig(Core::IrSensor::PackedIrLedProcessorConfig config);
private:
// This is nn::irsensor::IrLedProcessorConfig
struct IrLedProcessorConfig {
Core::IrSensor::CameraLightTarget light_target;
};
static_assert(sizeof(IrLedProcessorConfig) == 0x4, "IrLedProcessorConfig is an invalid size");
struct IrLedProcessorState {
s64 sampling_number;
u64 timestamp;
std::array<u8, 0x8> data;
};
static_assert(sizeof(IrLedProcessorState) == 0x18, "IrLedProcessorState is an invalid size");
IrLedProcessorConfig current_config{};
Core::IrSensor::DeviceFormat& device;
};
} // namespace Service::IRS

301
src/hid_core/irsensor/irs_types.h Executable file
View file

@ -0,0 +1,301 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "hid_core/hid_types.h"
namespace Core::IrSensor {
// This is nn::irsensor::CameraAmbientNoiseLevel
enum class CameraAmbientNoiseLevel : u32 {
Low,
Medium,
High,
Unknown3, // This level can't be reached
};
// This is nn::irsensor::CameraLightTarget
enum class CameraLightTarget : u32 {
AllLeds,
BrightLeds,
DimLeds,
None,
};
// This is nn::irsensor::PackedCameraLightTarget
enum class PackedCameraLightTarget : u8 {
AllLeds,
BrightLeds,
DimLeds,
None,
};
// This is nn::irsensor::AdaptiveClusteringMode
enum class AdaptiveClusteringMode : u32 {
StaticFov,
DynamicFov,
};
// This is nn::irsensor::AdaptiveClusteringTargetDistance
enum class AdaptiveClusteringTargetDistance : u32 {
Near,
Middle,
Far,
};
// This is nn::irsensor::ImageTransferProcessorFormat
enum class ImageTransferProcessorFormat : u32 {
Size320x240,
Size160x120,
Size80x60,
Size40x30,
Size20x15,
};
// This is nn::irsensor::PackedImageTransferProcessorFormat
enum class PackedImageTransferProcessorFormat : u8 {
Size320x240,
Size160x120,
Size80x60,
Size40x30,
Size20x15,
};
// This is nn::irsensor::IrCameraStatus
enum class IrCameraStatus : u32 {
Available,
Unsupported,
Unconnected,
};
// This is nn::irsensor::IrCameraInternalStatus
enum class IrCameraInternalStatus : u32 {
Stopped,
FirmwareUpdateNeeded,
Unknown2,
Unknown3,
Unknown4,
FirmwareVersionRequested,
FirmwareVersionIsInvalid,
Ready,
Setting,
};
// This is nn::irsensor::detail::StatusManager::IrSensorMode
enum class IrSensorMode : u64 {
None,
MomentProcessor,
ClusteringProcessor,
ImageTransferProcessor,
PointingProcessorMarker,
TeraPluginProcessor,
IrLedProcessor,
};
// This is nn::irsensor::ImageProcessorStatus
enum ImageProcessorStatus : u32 {
Stopped,
Running,
};
// This is nn::irsensor::HandAnalysisMode
enum class HandAnalysisMode : u32 {
None,
Silhouette,
Image,
SilhoueteAndImage,
SilhuetteOnly,
};
// This is nn::irsensor::IrSensorFunctionLevel
enum class IrSensorFunctionLevel : u8 {
unknown0,
unknown1,
unknown2,
unknown3,
unknown4,
};
// This is nn::irsensor::MomentProcessorPreprocess
enum class MomentProcessorPreprocess : u32 {
Unknown0,
Unknown1,
};
// This is nn::irsensor::PackedMomentProcessorPreprocess
enum class PackedMomentProcessorPreprocess : u8 {
Unknown0,
Unknown1,
};
// This is nn::irsensor::PointingStatus
enum class PointingStatus : u32 {
Unknown0,
Unknown1,
};
struct IrsRect {
s16 x;
s16 y;
s16 width;
s16 height;
};
struct IrsCentroid {
f32 x;
f32 y;
};
struct CameraConfig {
u64 exposure_time;
CameraLightTarget light_target;
u32 gain;
bool is_negative_used;
INSERT_PADDING_BYTES(7);
};
static_assert(sizeof(CameraConfig) == 0x18, "CameraConfig is an invalid size");
struct PackedCameraConfig {
u64 exposure_time;
PackedCameraLightTarget light_target;
u8 gain;
bool is_negative_used;
INSERT_PADDING_BYTES(5);
};
static_assert(sizeof(PackedCameraConfig) == 0x10, "PackedCameraConfig is an invalid size");
// This is nn::irsensor::IrCameraHandle
struct IrCameraHandle {
u8 npad_id{};
Core::HID::NpadStyleIndex npad_type{Core::HID::NpadStyleIndex::None};
INSERT_PADDING_BYTES(2);
};
static_assert(sizeof(IrCameraHandle) == 4, "IrCameraHandle is an invalid size");
// This is nn::irsensor::PackedMcuVersion
struct PackedMcuVersion {
u16 major;
u16 minor;
};
static_assert(sizeof(PackedMcuVersion) == 4, "PackedMcuVersion is an invalid size");
// This is nn::irsensor::PackedMomentProcessorConfig
struct PackedMomentProcessorConfig {
PackedCameraConfig camera_config;
IrsRect window_of_interest;
PackedMcuVersion required_mcu_version;
PackedMomentProcessorPreprocess preprocess;
u8 preprocess_intensity_threshold;
INSERT_PADDING_BYTES(2);
};
static_assert(sizeof(PackedMomentProcessorConfig) == 0x20,
"PackedMomentProcessorConfig is an invalid size");
// This is nn::irsensor::PackedClusteringProcessorConfig
struct PackedClusteringProcessorConfig {
PackedCameraConfig camera_config;
IrsRect window_of_interest;
PackedMcuVersion required_mcu_version;
u32 pixel_count_min;
u32 pixel_count_max;
u8 object_intensity_min;
bool is_external_light_filter_enabled;
INSERT_PADDING_BYTES(2);
};
static_assert(sizeof(PackedClusteringProcessorConfig) == 0x28,
"PackedClusteringProcessorConfig is an invalid size");
// This is nn::irsensor::PackedImageTransferProcessorConfig
struct PackedImageTransferProcessorConfig {
PackedCameraConfig camera_config;
PackedMcuVersion required_mcu_version;
PackedImageTransferProcessorFormat format;
INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(PackedImageTransferProcessorConfig) == 0x18,
"PackedImageTransferProcessorConfig is an invalid size");
// This is nn::irsensor::PackedTeraPluginProcessorConfig
struct PackedTeraPluginProcessorConfig {
PackedMcuVersion required_mcu_version;
u8 mode;
u8 unknown_1;
u8 unknown_2;
u8 unknown_3;
};
static_assert(sizeof(PackedTeraPluginProcessorConfig) == 0x8,
"PackedTeraPluginProcessorConfig is an invalid size");
// This is nn::irsensor::PackedPointingProcessorConfig
struct PackedPointingProcessorConfig {
IrsRect window_of_interest;
PackedMcuVersion required_mcu_version;
};
static_assert(sizeof(PackedPointingProcessorConfig) == 0xC,
"PackedPointingProcessorConfig is an invalid size");
// This is nn::irsensor::PackedFunctionLevel
struct PackedFunctionLevel {
IrSensorFunctionLevel function_level;
INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(PackedFunctionLevel) == 0x4, "PackedFunctionLevel is an invalid size");
// This is nn::irsensor::PackedImageTransferProcessorExConfig
struct PackedImageTransferProcessorExConfig {
PackedCameraConfig camera_config;
PackedMcuVersion required_mcu_version;
PackedImageTransferProcessorFormat origin_format;
PackedImageTransferProcessorFormat trimming_format;
u16 trimming_start_x;
u16 trimming_start_y;
bool is_external_light_filter_enabled;
INSERT_PADDING_BYTES(5);
};
static_assert(sizeof(PackedImageTransferProcessorExConfig) == 0x20,
"PackedImageTransferProcessorExConfig is an invalid size");
// This is nn::irsensor::PackedIrLedProcessorConfig
struct PackedIrLedProcessorConfig {
PackedMcuVersion required_mcu_version;
u8 light_target;
INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(PackedIrLedProcessorConfig) == 0x8,
"PackedIrLedProcessorConfig is an invalid size");
// This is nn::irsensor::HandAnalysisConfig
struct HandAnalysisConfig {
HandAnalysisMode mode;
};
static_assert(sizeof(HandAnalysisConfig) == 0x4, "HandAnalysisConfig is an invalid size");
// This is nn::irsensor::detail::ProcessorState contents are different for each processor
struct ProcessorState {
std::array<u8, 0xE20> processor_raw_data{};
};
static_assert(sizeof(ProcessorState) == 0xE20, "ProcessorState is an invalid size");
// This is nn::irsensor::detail::DeviceFormat
struct DeviceFormat {
Core::IrSensor::IrCameraStatus camera_status{Core::IrSensor::IrCameraStatus::Unconnected};
Core::IrSensor::IrCameraInternalStatus camera_internal_status{
Core::IrSensor::IrCameraInternalStatus::Ready};
Core::IrSensor::IrSensorMode mode{Core::IrSensor::IrSensorMode::None};
ProcessorState state{};
};
static_assert(sizeof(DeviceFormat) == 0xE30, "DeviceFormat is an invalid size");
// This is nn::irsensor::ImageTransferProcessorState
struct ImageTransferProcessorState {
u64 sampling_number;
Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level;
INSERT_PADDING_BYTES(4);
};
static_assert(sizeof(ImageTransferProcessorState) == 0x10,
"ImageTransferProcessorState is an invalid size");
} // namespace Core::IrSensor

View file

@ -0,0 +1,149 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "core/core.h"
#include "core/core_timing.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "hid_core/irsensor/moment_processor.h"
namespace Service::IRS {
static constexpr auto format = Core::IrSensor::ImageTransferProcessorFormat::Size40x30;
static constexpr std::size_t ImageWidth = 40;
static constexpr std::size_t ImageHeight = 30;
MomentProcessor::MomentProcessor(Core::System& system_, Core::IrSensor::DeviceFormat& device_format,
std::size_t npad_index)
: device(device_format), system{system_} {
npad_device = system.HIDCore().GetEmulatedControllerByIndex(npad_index);
device.mode = Core::IrSensor::IrSensorMode::MomentProcessor;
device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
shared_memory = std::construct_at(
reinterpret_cast<MomentSharedMemory*>(&device_format.state.processor_raw_data));
Core::HID::ControllerUpdateCallback engine_callback{
.on_change = [this](Core::HID::ControllerTriggerType type) { OnControllerUpdate(type); },
.is_npad_service = true,
};
callback_key = npad_device->SetCallback(engine_callback);
}
MomentProcessor::~MomentProcessor() {
npad_device->DeleteCallback(callback_key);
};
void MomentProcessor::StartProcessor() {
device.camera_status = Core::IrSensor::IrCameraStatus::Available;
device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Ready;
}
void MomentProcessor::SuspendProcessor() {}
void MomentProcessor::StopProcessor() {}
void MomentProcessor::OnControllerUpdate(Core::HID::ControllerTriggerType type) {
if (type != Core::HID::ControllerTriggerType::IrSensor) {
return;
}
next_state = {};
const auto& camera_data = npad_device->GetCamera();
const auto window_width = static_cast<std::size_t>(current_config.window_of_interest.width);
const auto window_height = static_cast<std::size_t>(current_config.window_of_interest.height);
const auto window_start_x = static_cast<std::size_t>(current_config.window_of_interest.x);
const auto window_start_y = static_cast<std::size_t>(current_config.window_of_interest.y);
const std::size_t block_width = window_width / Columns;
const std::size_t block_height = window_height / Rows;
for (std::size_t row = 0; row < Rows; row++) {
for (std::size_t column = 0; column < Columns; column++) {
const size_t x_pos = (column * block_width) + window_start_x;
const size_t y_pos = (row * block_height) + window_start_y;
auto& statistic = next_state.statistic[column + (row * Columns)];
statistic = GetStatistic(camera_data.data, x_pos, y_pos, block_width, block_height);
}
}
next_state.sampling_number = camera_data.sample;
next_state.timestamp = system.CoreTiming().GetGlobalTimeNs().count();
next_state.ambient_noise_level = Core::IrSensor::CameraAmbientNoiseLevel::Low;
shared_memory->moment_lifo.WriteNextEntry(next_state);
if (!IsProcessorActive()) {
StartProcessor();
}
}
u8 MomentProcessor::GetPixel(const std::vector<u8>& data, std::size_t x, std::size_t y) const {
if ((y * ImageWidth) + x >= data.size()) {
return 0;
}
return data[(y * ImageWidth) + x];
}
MomentProcessor::MomentStatistic MomentProcessor::GetStatistic(const std::vector<u8>& data,
std::size_t start_x,
std::size_t start_y,
std::size_t width,
std::size_t height) const {
// The actual implementation is always 320x240
static constexpr std::size_t RealWidth = 320;
static constexpr std::size_t RealHeight = 240;
static constexpr std::size_t Threshold = 30;
MomentStatistic statistic{};
std::size_t active_points{};
// Sum all data points on the block that meet with the threshold
for (std::size_t y = 0; y < width; y++) {
for (std::size_t x = 0; x < height; x++) {
const size_t x_pos = x + start_x;
const size_t y_pos = y + start_y;
const auto pixel =
GetPixel(data, x_pos * ImageWidth / RealWidth, y_pos * ImageHeight / RealHeight);
if (pixel < Threshold) {
continue;
}
statistic.average_intensity += pixel;
statistic.centroid.x += static_cast<float>(x_pos);
statistic.centroid.y += static_cast<float>(y_pos);
active_points++;
}
}
// Return an empty field if no points were available
if (active_points == 0) {
return {};
}
// Finally calculate the actual centroid and average intensity
statistic.centroid.x /= static_cast<float>(active_points);
statistic.centroid.y /= static_cast<float>(active_points);
statistic.average_intensity /= static_cast<f32>(width * height);
return statistic;
}
void MomentProcessor::SetConfig(Core::IrSensor::PackedMomentProcessorConfig config) {
current_config.camera_config.exposure_time = config.camera_config.exposure_time;
current_config.camera_config.gain = config.camera_config.gain;
current_config.camera_config.is_negative_used = config.camera_config.is_negative_used;
current_config.camera_config.light_target =
static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target);
current_config.window_of_interest = config.window_of_interest;
current_config.preprocess =
static_cast<Core::IrSensor::MomentProcessorPreprocess>(config.preprocess);
current_config.preprocess_intensity_threshold = config.preprocess_intensity_threshold;
npad_device->SetCameraFormat(format);
}
} // namespace Service::IRS

View file

@ -0,0 +1,91 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "common/bit_field.h"
#include "common/common_types.h"
#include "hid_core/irsensor/irs_types.h"
#include "hid_core/irsensor/processor_base.h"
#include "hid_core/resources/irs_ring_lifo.h"
namespace Core {
class System;
}
namespace Core::HID {
class EmulatedController;
} // namespace Core::HID
namespace Service::IRS {
class MomentProcessor final : public ProcessorBase {
public:
explicit MomentProcessor(Core::System& system_, Core::IrSensor::DeviceFormat& device_format,
std::size_t npad_index);
~MomentProcessor() override;
// Called when the processor is initialized
void StartProcessor() override;
// Called when the processor is suspended
void SuspendProcessor() override;
// Called when the processor is stopped
void StopProcessor() override;
// Sets config parameters of the camera
void SetConfig(Core::IrSensor::PackedMomentProcessorConfig config);
private:
static constexpr std::size_t Columns = 8;
static constexpr std::size_t Rows = 6;
// This is nn::irsensor::MomentProcessorConfig
struct MomentProcessorConfig {
Core::IrSensor::CameraConfig camera_config;
Core::IrSensor::IrsRect window_of_interest;
Core::IrSensor::MomentProcessorPreprocess preprocess;
u32 preprocess_intensity_threshold;
};
static_assert(sizeof(MomentProcessorConfig) == 0x28,
"MomentProcessorConfig is an invalid size");
// This is nn::irsensor::MomentStatistic
struct MomentStatistic {
f32 average_intensity;
Core::IrSensor::IrsCentroid centroid;
};
static_assert(sizeof(MomentStatistic) == 0xC, "MomentStatistic is an invalid size");
// This is nn::irsensor::MomentProcessorState
struct MomentProcessorState {
s64 sampling_number;
u64 timestamp;
Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level;
INSERT_PADDING_BYTES(4);
std::array<MomentStatistic, Columns * Rows> statistic;
};
static_assert(sizeof(MomentProcessorState) == 0x258, "MomentProcessorState is an invalid size");
struct MomentSharedMemory {
Service::IRS::Lifo<MomentProcessorState, 6> moment_lifo;
};
static_assert(sizeof(MomentSharedMemory) == 0xE20, "MomentSharedMemory is an invalid size");
void OnControllerUpdate(Core::HID::ControllerTriggerType type);
u8 GetPixel(const std::vector<u8>& data, std::size_t x, std::size_t y) const;
MomentStatistic GetStatistic(const std::vector<u8>& data, std::size_t start_x,
std::size_t start_y, std::size_t width, std::size_t height) const;
MomentSharedMemory* shared_memory = nullptr;
MomentProcessorState next_state{};
MomentProcessorConfig current_config{};
Core::IrSensor::DeviceFormat& device;
Core::HID::EmulatedController* npad_device;
int callback_key{};
Core::System& system;
};
} // namespace Service::IRS

View file

@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "hid_core/irsensor/pointing_processor.h"
namespace Service::IRS {
PointingProcessor::PointingProcessor(Core::IrSensor::DeviceFormat& device_format)
: device(device_format) {
device.mode = Core::IrSensor::IrSensorMode::PointingProcessorMarker;
device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
}
PointingProcessor::~PointingProcessor() = default;
void PointingProcessor::StartProcessor() {}
void PointingProcessor::SuspendProcessor() {}
void PointingProcessor::StopProcessor() {}
void PointingProcessor::SetConfig(Core::IrSensor::PackedPointingProcessorConfig config) {
current_config.window_of_interest = config.window_of_interest;
}
} // namespace Service::IRS

View file

@ -0,0 +1,61 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "common/common_types.h"
#include "hid_core/irsensor/irs_types.h"
#include "hid_core/irsensor/processor_base.h"
namespace Service::IRS {
class PointingProcessor final : public ProcessorBase {
public:
explicit PointingProcessor(Core::IrSensor::DeviceFormat& device_format);
~PointingProcessor() override;
// Called when the processor is initialized
void StartProcessor() override;
// Called when the processor is suspended
void SuspendProcessor() override;
// Called when the processor is stopped
void StopProcessor() override;
// Sets config parameters of the camera
void SetConfig(Core::IrSensor::PackedPointingProcessorConfig config);
private:
// This is nn::irsensor::PointingProcessorConfig
struct PointingProcessorConfig {
Core::IrSensor::IrsRect window_of_interest;
};
static_assert(sizeof(PointingProcessorConfig) == 0x8,
"PointingProcessorConfig is an invalid size");
struct PointingProcessorMarkerData {
u8 pointing_status;
INSERT_PADDING_BYTES(3);
u32 unknown;
float unknown_float1;
float position_x;
float position_y;
float unknown_float2;
Core::IrSensor::IrsRect window_of_interest;
};
static_assert(sizeof(PointingProcessorMarkerData) == 0x20,
"PointingProcessorMarkerData is an invalid size");
struct PointingProcessorMarkerState {
s64 sampling_number;
u64 timestamp;
std::array<PointingProcessorMarkerData, 0x3> data;
};
static_assert(sizeof(PointingProcessorMarkerState) == 0x70,
"PointingProcessorMarkerState is an invalid size");
PointingProcessorConfig current_config{};
Core::IrSensor::DeviceFormat& device;
};
} // namespace Service::IRS

View file

@ -0,0 +1,67 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "hid_core/irsensor/processor_base.h"
namespace Service::IRS {
ProcessorBase::ProcessorBase() {}
ProcessorBase::~ProcessorBase() = default;
bool ProcessorBase::IsProcessorActive() const {
return is_active;
}
std::size_t ProcessorBase::GetDataSize(Core::IrSensor::ImageTransferProcessorFormat format) const {
switch (format) {
case Core::IrSensor::ImageTransferProcessorFormat::Size320x240:
return 320 * 240;
case Core::IrSensor::ImageTransferProcessorFormat::Size160x120:
return 160 * 120;
case Core::IrSensor::ImageTransferProcessorFormat::Size80x60:
return 80 * 60;
case Core::IrSensor::ImageTransferProcessorFormat::Size40x30:
return 40 * 30;
case Core::IrSensor::ImageTransferProcessorFormat::Size20x15:
return 20 * 15;
default:
return 0;
}
}
std::size_t ProcessorBase::GetDataWidth(Core::IrSensor::ImageTransferProcessorFormat format) const {
switch (format) {
case Core::IrSensor::ImageTransferProcessorFormat::Size320x240:
return 320;
case Core::IrSensor::ImageTransferProcessorFormat::Size160x120:
return 160;
case Core::IrSensor::ImageTransferProcessorFormat::Size80x60:
return 80;
case Core::IrSensor::ImageTransferProcessorFormat::Size40x30:
return 40;
case Core::IrSensor::ImageTransferProcessorFormat::Size20x15:
return 20;
default:
return 0;
}
}
std::size_t ProcessorBase::GetDataHeight(
Core::IrSensor::ImageTransferProcessorFormat format) const {
switch (format) {
case Core::IrSensor::ImageTransferProcessorFormat::Size320x240:
return 240;
case Core::IrSensor::ImageTransferProcessorFormat::Size160x120:
return 120;
case Core::IrSensor::ImageTransferProcessorFormat::Size80x60:
return 60;
case Core::IrSensor::ImageTransferProcessorFormat::Size40x30:
return 30;
case Core::IrSensor::ImageTransferProcessorFormat::Size20x15:
return 15;
default:
return 0;
}
}
} // namespace Service::IRS

View file

@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "common/common_types.h"
#include "hid_core/irsensor/irs_types.h"
namespace Service::IRS {
class ProcessorBase {
public:
explicit ProcessorBase();
virtual ~ProcessorBase();
virtual void StartProcessor() = 0;
virtual void SuspendProcessor() = 0;
virtual void StopProcessor() = 0;
bool IsProcessorActive() const;
protected:
/// Returns the number of bytes the image uses
std::size_t GetDataSize(Core::IrSensor::ImageTransferProcessorFormat format) const;
/// Returns the width of the image
std::size_t GetDataWidth(Core::IrSensor::ImageTransferProcessorFormat format) const;
/// Returns the height of the image
std::size_t GetDataHeight(Core::IrSensor::ImageTransferProcessorFormat format) const;
bool is_active{false};
};
} // namespace Service::IRS

View file

@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "hid_core/irsensor/tera_plugin_processor.h"
namespace Service::IRS {
TeraPluginProcessor::TeraPluginProcessor(Core::IrSensor::DeviceFormat& device_format)
: device(device_format) {
device.mode = Core::IrSensor::IrSensorMode::TeraPluginProcessor;
device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
}
TeraPluginProcessor::~TeraPluginProcessor() = default;
void TeraPluginProcessor::StartProcessor() {}
void TeraPluginProcessor::SuspendProcessor() {}
void TeraPluginProcessor::StopProcessor() {}
void TeraPluginProcessor::SetConfig(Core::IrSensor::PackedTeraPluginProcessorConfig config) {
current_config.mode = config.mode;
current_config.unknown_1 = config.unknown_1;
current_config.unknown_2 = config.unknown_2;
current_config.unknown_3 = config.unknown_3;
}
} // namespace Service::IRS

View file

@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "common/bit_field.h"
#include "common/common_types.h"
#include "hid_core/irsensor/irs_types.h"
#include "hid_core/irsensor/processor_base.h"
namespace Service::IRS {
class TeraPluginProcessor final : public ProcessorBase {
public:
explicit TeraPluginProcessor(Core::IrSensor::DeviceFormat& device_format);
~TeraPluginProcessor() override;
// Called when the processor is initialized
void StartProcessor() override;
// Called when the processor is suspended
void SuspendProcessor() override;
// Called when the processor is stopped
void StopProcessor() override;
// Sets config parameters of the camera
void SetConfig(Core::IrSensor::PackedTeraPluginProcessorConfig config);
private:
// This is nn::irsensor::TeraPluginProcessorConfig
struct TeraPluginProcessorConfig {
u8 mode;
u8 unknown_1;
u8 unknown_2;
u8 unknown_3;
};
static_assert(sizeof(TeraPluginProcessorConfig) == 0x4,
"TeraPluginProcessorConfig is an invalid size");
struct TeraPluginProcessorState {
s64 sampling_number;
u64 timestamp;
Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level;
std::array<u8, 0x12c> data;
};
static_assert(sizeof(TeraPluginProcessorState) == 0x140,
"TeraPluginProcessorState is an invalid size");
TeraPluginProcessorConfig current_config{};
Core::IrSensor::DeviceFormat& device;
};
} // namespace Service::IRS

View file

@ -0,0 +1,6 @@
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/common_precompiled_headers.h"

362
src/hid_core/resource_manager.cpp Executable file
View file

@ -0,0 +1,362 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "common/logging/log.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/kernel/k_shared_memory.h"
#include "core/hle/service/ipc_helpers.h"
#include "hid_core/hid_core.h"
#include "hid_core/resource_manager.h"
#include "hid_core/resources/applet_resource.h"
#include "hid_core/resources/debug_pad/debug_pad.h"
#include "hid_core/resources/digitizer/digitizer.h"
#include "hid_core/resources/keyboard/keyboard.h"
#include "hid_core/resources/mouse/debug_mouse.h"
#include "hid_core/resources/mouse/mouse.h"
#include "hid_core/resources/npad/npad.h"
#include "hid_core/resources/palma/palma.h"
#include "hid_core/resources/shared_memory_format.h"
#include "hid_core/resources/six_axis/console_six_axis.h"
#include "hid_core/resources/six_axis/seven_six_axis.h"
#include "hid_core/resources/six_axis/six_axis.h"
#include "hid_core/resources/system_buttons/capture_button.h"
#include "hid_core/resources/system_buttons/home_button.h"
#include "hid_core/resources/system_buttons/sleep_button.h"
#include "hid_core/resources/touch_screen/gesture.h"
#include "hid_core/resources/touch_screen/touch_screen.h"
#include "hid_core/resources/unique_pad/unique_pad.h"
namespace Service::HID {
// Updating period for each HID device.
// Period time is obtained by measuring the number of samples in a second on HW using a homebrew
// Correct npad_update_ns is 4ms this is overclocked to lower input lag
constexpr auto npad_update_ns = std::chrono::nanoseconds{1 * 1000 * 1000}; // (1ms, 1000Hz)
constexpr auto default_update_ns = std::chrono::nanoseconds{4 * 1000 * 1000}; // (4ms, 1000Hz)
constexpr auto mouse_keyboard_update_ns = std::chrono::nanoseconds{8 * 1000 * 1000}; // (8ms, 125Hz)
constexpr auto motion_update_ns = std::chrono::nanoseconds{5 * 1000 * 1000}; // (5ms, 200Hz)
ResourceManager::ResourceManager(Core::System& system_)
: system{system_}, service_context{system_, "hid"} {
applet_resource = std::make_shared<AppletResource>(system);
}
ResourceManager::~ResourceManager() = default;
void ResourceManager::Initialize() {
if (is_initialized) {
return;
}
system.HIDCore().ReloadInputDevices();
InitializeHidCommonSampler();
InitializeTouchScreenSampler();
InitializeConsoleSixAxisSampler();
InitializeAHidSampler();
is_initialized = true;
}
std::shared_ptr<AppletResource> ResourceManager::GetAppletResource() const {
return applet_resource;
}
std::shared_ptr<CaptureButton> ResourceManager::GetCaptureButton() const {
return capture_button;
}
std::shared_ptr<ConsoleSixAxis> ResourceManager::GetConsoleSixAxis() const {
return console_six_axis;
}
std::shared_ptr<DebugMouse> ResourceManager::GetDebugMouse() const {
return debug_mouse;
}
std::shared_ptr<DebugPad> ResourceManager::GetDebugPad() const {
return debug_pad;
}
std::shared_ptr<Digitizer> ResourceManager::GetDigitizer() const {
return digitizer;
}
std::shared_ptr<Gesture> ResourceManager::GetGesture() const {
return gesture;
}
std::shared_ptr<HomeButton> ResourceManager::GetHomeButton() const {
return home_button;
}
std::shared_ptr<Keyboard> ResourceManager::GetKeyboard() const {
return keyboard;
}
std::shared_ptr<Mouse> ResourceManager::GetMouse() const {
return mouse;
}
std::shared_ptr<NPad> ResourceManager::GetNpad() const {
return npad;
}
std::shared_ptr<Palma> ResourceManager::GetPalma() const {
return palma;
}
std::shared_ptr<SevenSixAxis> ResourceManager::GetSevenSixAxis() const {
return seven_six_axis;
}
std::shared_ptr<SixAxis> ResourceManager::GetSixAxis() const {
return six_axis;
}
std::shared_ptr<SleepButton> ResourceManager::GetSleepButton() const {
return sleep_button;
}
std::shared_ptr<TouchScreen> ResourceManager::GetTouchScreen() const {
return touch_screen;
}
std::shared_ptr<UniquePad> ResourceManager::GetUniquePad() const {
return unique_pad;
}
Result ResourceManager::CreateAppletResource(u64 aruid) {
if (aruid == SystemAruid) {
const auto result = RegisterCoreAppletResource();
if (result.IsError()) {
return result;
}
return GetNpad()->ActivateNpadResource();
}
const auto result = CreateAppletResourceImpl(aruid);
if (result.IsError()) {
return result;
}
// Homebrew doesn't try to activate some controllers, so we activate them by default
npad->Activate();
six_axis->Activate();
touch_screen->Activate();
return GetNpad()->ActivateNpadResource(aruid);
}
Result ResourceManager::CreateAppletResourceImpl(u64 aruid) {
std::scoped_lock lock{shared_mutex};
return applet_resource->CreateAppletResource(aruid);
}
void ResourceManager::InitializeHidCommonSampler() {
debug_pad = std::make_shared<DebugPad>(system.HIDCore());
mouse = std::make_shared<Mouse>(system.HIDCore());
debug_mouse = std::make_shared<DebugMouse>(system.HIDCore());
keyboard = std::make_shared<Keyboard>(system.HIDCore());
unique_pad = std::make_shared<UniquePad>(system.HIDCore());
npad = std::make_shared<NPad>(system.HIDCore(), service_context);
gesture = std::make_shared<Gesture>(system.HIDCore());
home_button = std::make_shared<HomeButton>(system.HIDCore());
sleep_button = std::make_shared<SleepButton>(system.HIDCore());
capture_button = std::make_shared<CaptureButton>(system.HIDCore());
digitizer = std::make_shared<Digitizer>(system.HIDCore());
palma = std::make_shared<Palma>(system.HIDCore(), service_context);
six_axis = std::make_shared<SixAxis>(system.HIDCore(), npad);
debug_pad->SetAppletResource(applet_resource, &shared_mutex);
digitizer->SetAppletResource(applet_resource, &shared_mutex);
keyboard->SetAppletResource(applet_resource, &shared_mutex);
npad->SetNpadExternals(applet_resource, &shared_mutex);
six_axis->SetAppletResource(applet_resource, &shared_mutex);
mouse->SetAppletResource(applet_resource, &shared_mutex);
debug_mouse->SetAppletResource(applet_resource, &shared_mutex);
home_button->SetAppletResource(applet_resource, &shared_mutex);
sleep_button->SetAppletResource(applet_resource, &shared_mutex);
capture_button->SetAppletResource(applet_resource, &shared_mutex);
}
void ResourceManager::InitializeTouchScreenSampler() {
gesture = std::make_shared<Gesture>(system.HIDCore());
touch_screen = std::make_shared<TouchScreen>(system.HIDCore());
touch_screen->SetAppletResource(applet_resource, &shared_mutex);
gesture->SetAppletResource(applet_resource, &shared_mutex);
}
void ResourceManager::InitializeConsoleSixAxisSampler() {
console_six_axis = std::make_shared<ConsoleSixAxis>(system.HIDCore());
seven_six_axis = std::make_shared<SevenSixAxis>(system);
console_six_axis->SetAppletResource(applet_resource, &shared_mutex);
}
void ResourceManager::InitializeAHidSampler() {
// TODO
}
Result ResourceManager::RegisterCoreAppletResource() {
std::scoped_lock lock{shared_mutex};
return applet_resource->RegisterCoreAppletResource();
}
Result ResourceManager::UnregisterCoreAppletResource() {
std::scoped_lock lock{shared_mutex};
return applet_resource->UnregisterCoreAppletResource();
}
Result ResourceManager::RegisterAppletResourceUserId(u64 aruid, bool bool_value) {
std::scoped_lock lock{shared_mutex};
auto result = applet_resource->RegisterAppletResourceUserId(aruid, bool_value);
if (result.IsSuccess()) {
result = npad->RegisterAppletResourceUserId(aruid);
}
return result;
}
void ResourceManager::UnregisterAppletResourceUserId(u64 aruid) {
std::scoped_lock lock{shared_mutex};
applet_resource->UnregisterAppletResourceUserId(aruid);
}
Result ResourceManager::GetSharedMemoryHandle(Kernel::KSharedMemory** out_handle, u64 aruid) {
std::scoped_lock lock{shared_mutex};
return applet_resource->GetSharedMemoryHandle(out_handle, aruid);
}
void ResourceManager::FreeAppletResourceId(u64 aruid) {
std::scoped_lock lock{shared_mutex};
applet_resource->FreeAppletResourceId(aruid);
}
void ResourceManager::EnableInput(u64 aruid, bool is_enabled) {
std::scoped_lock lock{shared_mutex};
applet_resource->EnableInput(aruid, is_enabled);
}
void ResourceManager::EnableSixAxisSensor(u64 aruid, bool is_enabled) {
std::scoped_lock lock{shared_mutex};
applet_resource->EnableSixAxisSensor(aruid, is_enabled);
}
void ResourceManager::EnablePadInput(u64 aruid, bool is_enabled) {
std::scoped_lock lock{shared_mutex};
applet_resource->EnablePadInput(aruid, is_enabled);
}
void ResourceManager::EnableTouchScreen(u64 aruid, bool is_enabled) {
std::scoped_lock lock{shared_mutex};
applet_resource->EnableTouchScreen(aruid, is_enabled);
}
void ResourceManager::UpdateControllers(std::chrono::nanoseconds ns_late) {
auto& core_timing = system.CoreTiming();
debug_pad->OnUpdate(core_timing);
digitizer->OnUpdate(core_timing);
unique_pad->OnUpdate(core_timing);
gesture->OnUpdate(core_timing);
touch_screen->OnUpdate(core_timing);
palma->OnUpdate(core_timing);
home_button->OnUpdate(core_timing);
sleep_button->OnUpdate(core_timing);
capture_button->OnUpdate(core_timing);
}
void ResourceManager::UpdateNpad(std::chrono::nanoseconds ns_late) {
auto& core_timing = system.CoreTiming();
npad->OnUpdate(core_timing);
}
void ResourceManager::UpdateMouseKeyboard(std::chrono::nanoseconds ns_late) {
auto& core_timing = system.CoreTiming();
mouse->OnUpdate(core_timing);
debug_mouse->OnUpdate(core_timing);
keyboard->OnUpdate(core_timing);
}
void ResourceManager::UpdateMotion(std::chrono::nanoseconds ns_late) {
auto& core_timing = system.CoreTiming();
six_axis->OnUpdate(core_timing);
seven_six_axis->OnUpdate(core_timing);
console_six_axis->OnUpdate(core_timing);
}
IAppletResource::IAppletResource(Core::System& system_, std::shared_ptr<ResourceManager> resource,
u64 applet_resource_user_id)
: ServiceFramework{system_, "IAppletResource"}, aruid{applet_resource_user_id},
resource_manager{resource} {
static const FunctionInfo functions[] = {
{0, &IAppletResource::GetSharedMemoryHandle, "GetSharedMemoryHandle"},
};
RegisterHandlers(functions);
// Register update callbacks
npad_update_event = Core::Timing::CreateEvent(
"HID::UpdatePadCallback",
[this, resource](
s64 time, std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto guard = LockService();
resource->UpdateNpad(ns_late);
return std::nullopt;
});
default_update_event = Core::Timing::CreateEvent(
"HID::UpdateDefaultCallback",
[this, resource](
s64 time, std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto guard = LockService();
resource->UpdateControllers(ns_late);
return std::nullopt;
});
mouse_keyboard_update_event = Core::Timing::CreateEvent(
"HID::UpdateMouseKeyboardCallback",
[this, resource](
s64 time, std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto guard = LockService();
resource->UpdateMouseKeyboard(ns_late);
return std::nullopt;
});
motion_update_event = Core::Timing::CreateEvent(
"HID::UpdateMotionCallback",
[this, resource](
s64 time, std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto guard = LockService();
resource->UpdateMotion(ns_late);
return std::nullopt;
});
system.CoreTiming().ScheduleLoopingEvent(npad_update_ns, npad_update_ns, npad_update_event);
system.CoreTiming().ScheduleLoopingEvent(default_update_ns, default_update_ns,
default_update_event);
system.CoreTiming().ScheduleLoopingEvent(mouse_keyboard_update_ns, mouse_keyboard_update_ns,
mouse_keyboard_update_event);
system.CoreTiming().ScheduleLoopingEvent(motion_update_ns, motion_update_ns,
motion_update_event);
}
IAppletResource::~IAppletResource() {
system.CoreTiming().UnscheduleEvent(npad_update_event);
system.CoreTiming().UnscheduleEvent(default_update_event);
system.CoreTiming().UnscheduleEvent(mouse_keyboard_update_event);
system.CoreTiming().UnscheduleEvent(motion_update_event);
resource_manager->FreeAppletResourceId(aruid);
}
void IAppletResource::GetSharedMemoryHandle(HLERequestContext& ctx) {
Kernel::KSharedMemory* handle;
const auto result = resource_manager->GetSharedMemoryHandle(&handle, aruid);
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, result=0x{:X}", aruid, result.raw);
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(result);
rb.PushCopyObjects(handle);
}
} // namespace Service::HID

149
src/hid_core/resource_manager.h Executable file
View file

@ -0,0 +1,149 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Core::Timing {
struct EventType;
}
namespace Kernel {
class KSharedMemory;
}
namespace Service::HID {
class AppletResource;
class CaptureButton;
class Controller_Stubbed;
class ConsoleSixAxis;
class DebugMouse;
class DebugPad;
class Digitizer;
class Gesture;
class HomeButton;
class Keyboard;
class Mouse;
class NPad;
class Palma;
class SevenSixAxis;
class SixAxis;
class SleepButton;
class TouchScreen;
class UniquePad;
class ResourceManager {
public:
explicit ResourceManager(Core::System& system_);
~ResourceManager();
void Initialize();
std::shared_ptr<AppletResource> GetAppletResource() const;
std::shared_ptr<CaptureButton> GetCaptureButton() const;
std::shared_ptr<ConsoleSixAxis> GetConsoleSixAxis() const;
std::shared_ptr<DebugMouse> GetDebugMouse() const;
std::shared_ptr<DebugPad> GetDebugPad() const;
std::shared_ptr<Digitizer> GetDigitizer() const;
std::shared_ptr<Gesture> GetGesture() const;
std::shared_ptr<HomeButton> GetHomeButton() const;
std::shared_ptr<Keyboard> GetKeyboard() const;
std::shared_ptr<Mouse> GetMouse() const;
std::shared_ptr<NPad> GetNpad() const;
std::shared_ptr<Palma> GetPalma() const;
std::shared_ptr<SevenSixAxis> GetSevenSixAxis() const;
std::shared_ptr<SixAxis> GetSixAxis() const;
std::shared_ptr<SleepButton> GetSleepButton() const;
std::shared_ptr<TouchScreen> GetTouchScreen() const;
std::shared_ptr<UniquePad> GetUniquePad() const;
Result CreateAppletResource(u64 aruid);
Result RegisterCoreAppletResource();
Result UnregisterCoreAppletResource();
Result RegisterAppletResourceUserId(u64 aruid, bool bool_value);
void UnregisterAppletResourceUserId(u64 aruid);
Result GetSharedMemoryHandle(Kernel::KSharedMemory** out_handle, u64 aruid);
void FreeAppletResourceId(u64 aruid);
void EnableInput(u64 aruid, bool is_enabled);
void EnableSixAxisSensor(u64 aruid, bool is_enabled);
void EnablePadInput(u64 aruid, bool is_enabled);
void EnableTouchScreen(u64 aruid, bool is_enabled);
void UpdateControllers(std::chrono::nanoseconds ns_late);
void UpdateNpad(std::chrono::nanoseconds ns_late);
void UpdateMouseKeyboard(std::chrono::nanoseconds ns_late);
void UpdateMotion(std::chrono::nanoseconds ns_late);
private:
Result CreateAppletResourceImpl(u64 aruid);
void InitializeHidCommonSampler();
void InitializeTouchScreenSampler();
void InitializeConsoleSixAxisSampler();
void InitializeAHidSampler();
bool is_initialized{false};
mutable std::recursive_mutex shared_mutex;
std::shared_ptr<AppletResource> applet_resource = nullptr;
std::shared_ptr<CaptureButton> capture_button = nullptr;
std::shared_ptr<ConsoleSixAxis> console_six_axis = nullptr;
std::shared_ptr<DebugMouse> debug_mouse = nullptr;
std::shared_ptr<DebugPad> debug_pad = nullptr;
std::shared_ptr<Digitizer> digitizer = nullptr;
std::shared_ptr<Gesture> gesture = nullptr;
std::shared_ptr<HomeButton> home_button = nullptr;
std::shared_ptr<Keyboard> keyboard = nullptr;
std::shared_ptr<Mouse> mouse = nullptr;
std::shared_ptr<NPad> npad = nullptr;
std::shared_ptr<Palma> palma = nullptr;
std::shared_ptr<SevenSixAxis> seven_six_axis = nullptr;
std::shared_ptr<SixAxis> six_axis = nullptr;
std::shared_ptr<SleepButton> sleep_button = nullptr;
std::shared_ptr<TouchScreen> touch_screen = nullptr;
std::shared_ptr<UniquePad> unique_pad = nullptr;
// TODO: Create these resources
// std::shared_ptr<AudioControl> audio_control = nullptr;
// std::shared_ptr<ButtonConfig> button_config = nullptr;
// std::shared_ptr<Config> config = nullptr;
// std::shared_ptr<Connection> connection = nullptr;
// std::shared_ptr<CustomConfig> custom_config = nullptr;
// std::shared_ptr<Digitizer> digitizer = nullptr;
// std::shared_ptr<Hdls> hdls = nullptr;
// std::shared_ptr<PlayReport> play_report = nullptr;
// std::shared_ptr<Rail> rail = nullptr;
Core::System& system;
KernelHelpers::ServiceContext service_context;
};
class IAppletResource final : public ServiceFramework<IAppletResource> {
public:
explicit IAppletResource(Core::System& system_, std::shared_ptr<ResourceManager> resource,
u64 applet_resource_user_id);
~IAppletResource() override;
private:
void GetSharedMemoryHandle(HLERequestContext& ctx);
std::shared_ptr<Core::Timing::EventType> npad_update_event;
std::shared_ptr<Core::Timing::EventType> default_update_event;
std::shared_ptr<Core::Timing::EventType> mouse_keyboard_update_event;
std::shared_ptr<Core::Timing::EventType> motion_update_event;
u64 aruid;
std::shared_ptr<ResourceManager> resource_manager;
};
} // namespace Service::HID

View file

@ -0,0 +1,329 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "core/core.h"
#include "core/hle/kernel/k_shared_memory.h"
#include "hid_core/hid_result.h"
#include "hid_core/resources/applet_resource.h"
#include "hid_core/resources/shared_memory_format.h"
namespace Service::HID {
AppletResource::AppletResource(Core::System& system_) : system{system_} {}
AppletResource::~AppletResource() = default;
Result AppletResource::CreateAppletResource(u64 aruid) {
const u64 index = GetIndexFromAruid(aruid);
if (index >= AruidIndexMax) {
return ResultAruidNotRegistered;
}
if (data[index].flag.is_assigned) {
return ResultAruidAlreadyRegistered;
}
auto& shared_memory = shared_memory_holder[index];
if (!shared_memory.IsMapped()) {
const Result result = shared_memory.Initialize(system);
if (result.IsError()) {
return result;
}
if (shared_memory.GetAddress() == nullptr) {
shared_memory.Finalize();
return ResultSharedMemoryNotInitialized;
}
}
auto* shared_memory_format = shared_memory.GetAddress();
if (shared_memory_format != nullptr) {
shared_memory_format->Initialize();
}
data[index].shared_memory_format = shared_memory_format;
data[index].flag.is_assigned.Assign(true);
// TODO: InitializeSixAxisControllerConfig(false);
active_aruid = aruid;
return ResultSuccess;
}
Result AppletResource::RegisterAppletResourceUserId(u64 aruid, bool enable_input) {
const u64 index = GetIndexFromAruid(aruid);
if (index < AruidIndexMax) {
return ResultAruidAlreadyRegistered;
}
std::size_t data_index = AruidIndexMax;
for (std::size_t i = 0; i < AruidIndexMax; i++) {
if (!data[i].flag.is_initialized) {
data_index = i;
break;
}
}
if (data_index == AruidIndexMax) {
return ResultAruidNoAvailableEntries;
}
AruidData& aruid_data = data[data_index];
aruid_data.aruid = aruid;
aruid_data.flag.is_initialized.Assign(true);
if (enable_input) {
aruid_data.flag.enable_pad_input.Assign(true);
aruid_data.flag.enable_six_axis_sensor.Assign(true);
aruid_data.flag.bit_18.Assign(true);
aruid_data.flag.enable_touchscreen.Assign(true);
}
data_index = AruidIndexMax;
for (std::size_t i = 0; i < AruidIndexMax; i++) {
if (registration_list.flag[i] == RegistrationStatus::Initialized) {
if (registration_list.aruid[i] != aruid) {
continue;
}
data_index = i;
break;
}
if (registration_list.flag[i] == RegistrationStatus::None) {
data_index = i;
break;
}
}
if (data_index == AruidIndexMax) {
return ResultSuccess;
}
registration_list.flag[data_index] = RegistrationStatus::Initialized;
registration_list.aruid[data_index] = aruid;
return ResultSuccess;
}
void AppletResource::UnregisterAppletResourceUserId(u64 aruid) {
u64 index = GetIndexFromAruid(aruid);
if (index < AruidIndexMax) {
if (data[index].flag.is_assigned) {
data[index].shared_memory_format = nullptr;
data[index].flag.is_assigned.Assign(false);
}
}
index = GetIndexFromAruid(aruid);
if (index < AruidIndexMax) {
DestroySevenSixAxisTransferMemory();
data[index].flag.raw = 0;
data[index].aruid = 0;
index = GetIndexFromAruid(aruid);
if (index < AruidIndexMax) {
registration_list.flag[index] = RegistrationStatus::PendingDelete;
}
}
}
void AppletResource::FreeAppletResourceId(u64 aruid) {
u64 index = GetIndexFromAruid(aruid);
if (index >= AruidIndexMax) {
return;
}
auto& aruid_data = data[index];
if (aruid_data.flag.is_assigned) {
aruid_data.shared_memory_format = nullptr;
aruid_data.flag.is_assigned.Assign(false);
}
}
u64 AppletResource::GetActiveAruid() {
return active_aruid;
}
Result AppletResource::GetSharedMemoryHandle(Kernel::KSharedMemory** out_handle, u64 aruid) {
u64 index = GetIndexFromAruid(aruid);
if (index >= AruidIndexMax) {
return ResultAruidNotRegistered;
}
*out_handle = shared_memory_holder[index].GetHandle();
return ResultSuccess;
}
Result AppletResource::GetSharedMemoryFormat(SharedMemoryFormat** out_shared_memory_format,
u64 aruid) {
u64 index = GetIndexFromAruid(aruid);
if (index >= AruidIndexMax) {
return ResultAruidNotRegistered;
}
*out_shared_memory_format = data[index].shared_memory_format;
return ResultSuccess;
}
AruidData* AppletResource::GetAruidData(u64 aruid) {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index == AruidIndexMax) {
return nullptr;
}
return &data[aruid_index];
}
AruidData* AppletResource::GetAruidDataByIndex(std::size_t aruid_index) {
return &data[aruid_index];
}
bool AppletResource::IsVibrationAruidActive(u64 aruid) const {
return aruid == 0 || aruid == active_vibration_aruid;
}
u64 AppletResource::GetIndexFromAruid(u64 aruid) {
for (std::size_t i = 0; i < AruidIndexMax; i++) {
if (registration_list.flag[i] == RegistrationStatus::Initialized &&
registration_list.aruid[i] == aruid) {
return i;
}
}
return AruidIndexMax;
}
Result AppletResource::DestroySevenSixAxisTransferMemory() {
// TODO
return ResultSuccess;
}
void AppletResource::EnableInput(u64 aruid, bool is_enabled) {
const u64 index = GetIndexFromAruid(aruid);
if (index >= AruidIndexMax) {
return;
}
data[index].flag.enable_pad_input.Assign(is_enabled);
data[index].flag.enable_touchscreen.Assign(is_enabled);
}
void AppletResource::EnableSixAxisSensor(u64 aruid, bool is_enabled) {
const u64 index = GetIndexFromAruid(aruid);
if (index >= AruidIndexMax) {
return;
}
data[index].flag.enable_six_axis_sensor.Assign(is_enabled);
}
void AppletResource::EnablePadInput(u64 aruid, bool is_enabled) {
const u64 index = GetIndexFromAruid(aruid);
if (index >= AruidIndexMax) {
return;
}
data[index].flag.enable_pad_input.Assign(is_enabled);
}
void AppletResource::EnableTouchScreen(u64 aruid, bool is_enabled) {
const u64 index = GetIndexFromAruid(aruid);
if (index >= AruidIndexMax) {
return;
}
data[index].flag.enable_touchscreen.Assign(is_enabled);
}
void AppletResource::SetIsPalmaConnectable(u64 aruid, bool is_connectable) {
const u64 index = GetIndexFromAruid(aruid);
if (index >= AruidIndexMax) {
return;
}
data[index].flag.is_palma_connectable.Assign(is_connectable);
}
void AppletResource::EnablePalmaBoostMode(u64 aruid, bool is_enabled) {
const u64 index = GetIndexFromAruid(aruid);
if (index >= AruidIndexMax) {
return;
}
data[index].flag.enable_palma_boost_mode.Assign(is_enabled);
}
Result AppletResource::RegisterCoreAppletResource() {
if (ref_counter == std::numeric_limits<s32>::max() - 1) {
return ResultAppletResourceOverflow;
}
if (ref_counter == 0) {
const u64 index = GetIndexFromAruid(0);
if (index < AruidIndexMax) {
return ResultAruidAlreadyRegistered;
}
std::size_t data_index = AruidIndexMax;
for (std::size_t i = 0; i < AruidIndexMax; i++) {
if (!data[i].flag.is_initialized) {
data_index = i;
break;
}
}
if (data_index == AruidIndexMax) {
return ResultAruidNoAvailableEntries;
}
AruidData& aruid_data = data[data_index];
aruid_data.aruid = 0;
aruid_data.flag.is_initialized.Assign(true);
aruid_data.flag.enable_pad_input.Assign(true);
aruid_data.flag.enable_six_axis_sensor.Assign(true);
aruid_data.flag.bit_18.Assign(true);
aruid_data.flag.enable_touchscreen.Assign(true);
data_index = AruidIndexMax;
for (std::size_t i = 0; i < AruidIndexMax; i++) {
if (registration_list.flag[i] == RegistrationStatus::Initialized) {
if (registration_list.aruid[i] != 0) {
continue;
}
data_index = i;
break;
}
if (registration_list.flag[i] == RegistrationStatus::None) {
data_index = i;
break;
}
}
Result result = ResultSuccess;
if (data_index == AruidIndexMax) {
result = CreateAppletResource(0);
} else {
registration_list.flag[data_index] = RegistrationStatus::Initialized;
registration_list.aruid[data_index] = 0;
}
if (result.IsError()) {
UnregisterAppletResourceUserId(0);
return result;
}
}
ref_counter++;
return ResultSuccess;
}
Result AppletResource::UnregisterCoreAppletResource() {
if (ref_counter == 0) {
return ResultAppletResourceNotInitialized;
}
if (--ref_counter == 0) {
UnregisterAppletResourceUserId(0);
}
return ResultSuccess;
}
} // namespace Service::HID

View file

@ -0,0 +1,123 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include <mutex>
#include "common/bit_field.h"
#include "common/common_types.h"
#include "core/hle/result.h"
#include "hid_core/resources/shared_memory_holder.h"
namespace Core {
class System;
}
namespace Kernel {
class KSharedMemory;
}
namespace Service::HID {
struct SharedMemoryFormat;
class AppletResource;
class NPadResource;
static constexpr std::size_t AruidIndexMax = 0x20;
static constexpr u64 SystemAruid = 0;
enum class RegistrationStatus : u32 {
None,
Initialized,
PendingDelete,
};
struct DataStatusFlag {
union {
u32 raw{};
BitField<0, 1, u32> is_initialized;
BitField<1, 1, u32> is_assigned;
BitField<16, 1, u32> enable_pad_input;
BitField<17, 1, u32> enable_six_axis_sensor;
BitField<18, 1, u32> bit_18;
BitField<19, 1, u32> is_palma_connectable;
BitField<20, 1, u32> enable_palma_boost_mode;
BitField<21, 1, u32> enable_touchscreen;
};
};
struct AruidRegisterList {
std::array<RegistrationStatus, AruidIndexMax> flag{};
std::array<u64, AruidIndexMax> aruid{};
};
static_assert(sizeof(AruidRegisterList) == 0x180, "AruidRegisterList is an invalid size");
struct AruidData {
DataStatusFlag flag{};
u64 aruid{};
SharedMemoryFormat* shared_memory_format{nullptr};
};
struct HandheldConfig {
bool is_handheld_hid_enabled;
bool is_force_handheld;
bool is_joycon_rail_enabled;
bool is_force_handheld_style_vibration;
};
static_assert(sizeof(HandheldConfig) == 0x4, "HandheldConfig is an invalid size");
struct AppletResourceHolder {
std::shared_ptr<AppletResource> applet_resource{nullptr};
std::recursive_mutex* shared_mutex{nullptr};
NPadResource* shared_npad_resource{nullptr};
std::shared_ptr<HandheldConfig> handheld_config{nullptr};
long* handle_1;
};
class AppletResource {
public:
explicit AppletResource(Core::System& system_);
~AppletResource();
Result CreateAppletResource(u64 aruid);
Result RegisterAppletResourceUserId(u64 aruid, bool enable_input);
void UnregisterAppletResourceUserId(u64 aruid);
void FreeAppletResourceId(u64 aruid);
u64 GetActiveAruid();
Result GetSharedMemoryHandle(Kernel::KSharedMemory** out_handle, u64 aruid);
Result GetSharedMemoryFormat(SharedMemoryFormat** out_shared_memory_format, u64 aruid);
AruidData* GetAruidData(u64 aruid);
AruidData* GetAruidDataByIndex(std::size_t aruid_index);
bool IsVibrationAruidActive(u64 aruid) const;
u64 GetIndexFromAruid(u64 aruid);
Result DestroySevenSixAxisTransferMemory();
void EnableInput(u64 aruid, bool is_enabled);
void EnableSixAxisSensor(u64 aruid, bool is_enabled);
void EnablePadInput(u64 aruid, bool is_enabled);
void EnableTouchScreen(u64 aruid, bool is_enabled);
void SetIsPalmaConnectable(u64 aruid, bool is_connectable);
void EnablePalmaBoostMode(u64 aruid, bool is_enabled);
Result RegisterCoreAppletResource();
Result UnregisterCoreAppletResource();
private:
u64 active_aruid{};
AruidRegisterList registration_list{};
std::array<AruidData, AruidIndexMax> data{};
std::array<SharedMemoryHolder, AruidIndexMax> shared_memory_holder{};
s32 ref_counter{};
u64 active_vibration_aruid;
Core::System& system;
};
} // namespace Service::HID

View file

@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "hid_core/resources/controller_base.h"
namespace Service::HID {
ControllerBase::ControllerBase(Core::HID::HIDCore& hid_core_) : hid_core(hid_core_) {}
ControllerBase::~ControllerBase() = default;
Result ControllerBase::Activate() {
if (is_activated) {
return ResultSuccess;
}
is_activated = true;
OnInit();
return ResultSuccess;
}
Result ControllerBase::Activate(u64 aruid) {
return Activate();
}
void ControllerBase::DeactivateController() {
if (is_activated) {
OnRelease();
}
is_activated = false;
}
bool ControllerBase::IsControllerActivated() const {
return is_activated;
}
void ControllerBase::SetAppletResource(std::shared_ptr<AppletResource> resource,
std::recursive_mutex* resource_mutex) {
applet_resource = resource;
shared_mutex = resource_mutex;
}
} // namespace Service::HID

View file

@ -0,0 +1,55 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include "common/common_types.h"
#include "core/hle/result.h"
#include "hid_core/resources/applet_resource.h"
namespace Core::Timing {
class CoreTiming;
}
namespace Core::HID {
class HIDCore;
} // namespace Core::HID
namespace Service::HID {
class ControllerBase {
public:
explicit ControllerBase(Core::HID::HIDCore& hid_core_);
virtual ~ControllerBase();
// Called when the controller is initialized
virtual void OnInit() = 0;
// When the controller is released
virtual void OnRelease() = 0;
// When the controller is requesting an update for the shared memory
virtual void OnUpdate(const Core::Timing::CoreTiming& core_timing) = 0;
// When the controller is requesting a motion update for the shared memory
virtual void OnMotionUpdate(const Core::Timing::CoreTiming& core_timing) {}
Result Activate();
Result Activate(u64 aruid);
void DeactivateController();
bool IsControllerActivated() const;
void SetAppletResource(std::shared_ptr<AppletResource> resource,
std::recursive_mutex* resource_mutex);
protected:
bool is_activated{false};
std::shared_ptr<AppletResource> applet_resource{nullptr};
std::recursive_mutex* shared_mutex{nullptr};
Core::HID::HIDCore& hid_core;
};
} // namespace Service::HID

View file

@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/settings.h"
#include "core/core_timing.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "hid_core/hid_types.h"
#include "hid_core/resources/applet_resource.h"
#include "hid_core/resources/debug_pad/debug_pad.h"
#include "hid_core/resources/shared_memory_format.h"
namespace Service::HID {
DebugPad::DebugPad(Core::HID::HIDCore& hid_core_) : ControllerBase{hid_core_} {
controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Other);
}
DebugPad::~DebugPad() = default;
void DebugPad::OnInit() {}
void DebugPad::OnRelease() {}
void DebugPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
std::scoped_lock shared_lock{*shared_mutex};
const u64 aruid = applet_resource->GetActiveAruid();
auto* data = applet_resource->GetAruidData(aruid);
if (data == nullptr || !data->flag.is_assigned) {
return;
}
DebugPadSharedMemoryFormat& shared_memory = data->shared_memory_format->debug_pad;
if (!IsControllerActivated()) {
shared_memory.debug_pad_lifo.buffer_count = 0;
shared_memory.debug_pad_lifo.buffer_tail = 0;
return;
}
const auto& last_entry = shared_memory.debug_pad_lifo.ReadCurrentEntry().state;
next_state.sampling_number = last_entry.sampling_number + 1;
if (Settings::values.debug_pad_enabled) {
next_state.attribute.connected.Assign(1);
const auto& button_state = controller->GetDebugPadButtons();
const auto& stick_state = controller->GetSticks();
next_state.pad_state = button_state;
next_state.l_stick = stick_state.left;
next_state.r_stick = stick_state.right;
}
shared_memory.debug_pad_lifo.WriteNextEntry(next_state);
}
} // namespace Service::HID

View file

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "hid_core/resources/controller_base.h"
#include "hid_core/resources/debug_pad/debug_pad_types.h"
namespace Core::HID {
class HIDCore;
class EmulatedController;
} // namespace Core::HID
namespace Core::Timing {
class CoreTiming;
}
namespace Service::HID {
class DebugPad final : public ControllerBase {
public:
explicit DebugPad(Core::HID::HIDCore& hid_core_);
~DebugPad() override;
// Called when the controller is initialized
void OnInit() override;
// When the controller is released
void OnRelease() override;
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
private:
DebugPadState next_state{};
Core::HID::EmulatedController* controller = nullptr;
};
} // namespace Service::HID

View file

@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "common/bit_field.h"
#include "common/common_types.h"
#include "hid_core/hid_types.h"
namespace Service::HID {
// This is nn::hid::DebugPadAttribute
struct DebugPadAttribute {
union {
u32 raw{};
BitField<0, 1, u32> connected;
};
};
static_assert(sizeof(DebugPadAttribute) == 0x4, "DebugPadAttribute is an invalid size");
// This is nn::hid::DebugPadState
struct DebugPadState {
s64 sampling_number{};
DebugPadAttribute attribute{};
Core::HID::DebugPadButton pad_state{};
Core::HID::AnalogStickState r_stick{};
Core::HID::AnalogStickState l_stick{};
};
static_assert(sizeof(DebugPadState) == 0x20, "DebugPadState is an invalid state");
} // namespace Service::HID

View file

@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core_timing.h"
#include "hid_core/resources/applet_resource.h"
#include "hid_core/resources/digitizer/digitizer.h"
#include "hid_core/resources/shared_memory_format.h"
namespace Service::HID {
Digitizer::Digitizer(Core::HID::HIDCore& hid_core_) : ControllerBase{hid_core_} {}
Digitizer::~Digitizer() = default;
void Digitizer::OnInit() {}
void Digitizer::OnRelease() {}
void Digitizer::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
if (!smart_update) {
return;
}
std::scoped_lock shared_lock{*shared_mutex};
const u64 aruid = applet_resource->GetActiveAruid();
auto* data = applet_resource->GetAruidData(aruid);
if (data == nullptr || !data->flag.is_assigned) {
return;
}
auto& header = data->shared_memory_format->digitizer.header;
header.timestamp = core_timing.GetGlobalTimeNs().count();
header.total_entry_count = 17;
header.entry_count = 0;
header.last_entry_index = 0;
}
} // namespace Service::HID

View file

@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "hid_core/resources/controller_base.h"
namespace Service::HID {
class Digitizer final : public ControllerBase {
public:
explicit Digitizer(Core::HID::HIDCore& hid_core_);
~Digitizer() override;
// Called when the controller is initialized
void OnInit() override;
// When the controller is released
void OnRelease() override;
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
private:
bool smart_update{};
};
} // namespace Service::HID

View file

@ -0,0 +1,99 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "hid_core/resources/hid_firmware_settings.h"
namespace Service::HID {
HidFirmwareSettings::HidFirmwareSettings() {
LoadSettings(true);
}
void HidFirmwareSettings::Reload() {
LoadSettings(true);
}
void HidFirmwareSettings::LoadSettings(bool reload_config) {
if (is_initalized && !reload_config) {
return;
}
// TODO: Use nn::settings::fwdbg::GetSettingsItemValue to load config values
is_debug_pad_enabled = true;
is_device_managed = true;
is_touch_i2c_managed = is_device_managed;
is_future_devices_emulated = false;
is_mcu_hardware_error_emulated = false;
is_rail_enabled = true;
is_firmware_update_failure_emulated = false;
is_firmware_update_failure = {};
is_ble_disabled = false;
is_dscale_disabled = false;
is_handheld_forced = true;
features_per_id_disabled = {};
is_touch_firmware_auto_update_disabled = false;
is_initalized = true;
}
bool HidFirmwareSettings::IsDebugPadEnabled() {
LoadSettings(false);
return is_debug_pad_enabled;
}
bool HidFirmwareSettings::IsDeviceManaged() {
LoadSettings(false);
return is_device_managed;
}
bool HidFirmwareSettings::IsEmulateFutureDevice() {
LoadSettings(false);
return is_future_devices_emulated;
}
bool HidFirmwareSettings::IsTouchI2cManaged() {
LoadSettings(false);
return is_touch_i2c_managed;
}
bool HidFirmwareSettings::IsHandheldForced() {
LoadSettings(false);
return is_handheld_forced;
}
bool HidFirmwareSettings::IsRailEnabled() {
LoadSettings(false);
return is_rail_enabled;
}
bool HidFirmwareSettings::IsHardwareErrorEmulated() {
LoadSettings(false);
return is_mcu_hardware_error_emulated;
}
bool HidFirmwareSettings::IsBleDisabled() {
LoadSettings(false);
return is_ble_disabled;
}
bool HidFirmwareSettings::IsDscaleDisabled() {
LoadSettings(false);
return is_dscale_disabled;
}
bool HidFirmwareSettings::IsTouchAutoUpdateDisabled() {
LoadSettings(false);
return is_touch_firmware_auto_update_disabled;
}
HidFirmwareSettings::FirmwareSetting HidFirmwareSettings::GetFirmwareUpdateFailure() {
LoadSettings(false);
return is_firmware_update_failure;
}
HidFirmwareSettings::FeaturesPerId HidFirmwareSettings::FeaturesDisabledPerId() {
LoadSettings(false);
return features_per_id_disabled;
}
} // namespace Service::HID

View file

@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "common/common_types.h"
namespace Service::HID {
/// Loads firmware config from nn::settings::fwdbg
class HidFirmwareSettings {
public:
using FirmwareSetting = std::array<u8, 4>;
using FeaturesPerId = std::array<bool, 0xA8>;
HidFirmwareSettings();
void Reload();
void LoadSettings(bool reload_config);
bool IsDebugPadEnabled();
bool IsDeviceManaged();
bool IsEmulateFutureDevice();
bool IsTouchI2cManaged();
bool IsHandheldForced();
bool IsRailEnabled();
bool IsHardwareErrorEmulated();
bool IsBleDisabled();
bool IsDscaleDisabled();
bool IsTouchAutoUpdateDisabled();
FirmwareSetting GetFirmwareUpdateFailure();
FeaturesPerId FeaturesDisabledPerId();
private:
bool is_initalized{};
// Debug settings
bool is_debug_pad_enabled{};
bool is_device_managed{};
bool is_touch_i2c_managed{};
bool is_future_devices_emulated{};
bool is_mcu_hardware_error_emulated{};
bool is_rail_enabled{};
bool is_firmware_update_failure_emulated{};
bool is_ble_disabled{};
bool is_dscale_disabled{};
bool is_handheld_forced{};
bool is_touch_firmware_auto_update_disabled{};
FirmwareSetting is_firmware_update_failure{};
FeaturesPerId features_per_id_disabled{};
};
} // namespace Service::HID

View file

@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include "common/common_types.h"
namespace Service::IRS {
template <typename State, std::size_t max_buffer_size>
struct Lifo {
s64 sampling_number{};
s64 buffer_count{};
std::array<State, max_buffer_size> entries{};
const State& ReadCurrentEntry() const {
return entries[GetBufferTail()];
}
const State& ReadPreviousEntry() const {
return entries[GetPreviousEntryIndex()];
}
s64 GetBufferTail() const {
return sampling_number % max_buffer_size;
}
std::size_t GetPreviousEntryIndex() const {
return static_cast<size_t>((GetBufferTail() + max_buffer_size - 1) % max_buffer_size);
}
std::size_t GetNextEntryIndex() const {
return static_cast<size_t>((GetBufferTail() + 1) % max_buffer_size);
}
void WriteNextEntry(const State& new_state) {
if (buffer_count < static_cast<s64>(max_buffer_size)) {
buffer_count++;
}
sampling_number++;
entries[GetBufferTail()] = new_state;
}
};
} // namespace Service::IRS

View file

@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/settings.h"
#include "core/core_timing.h"
#include "hid_core/frontend/emulated_devices.h"
#include "hid_core/hid_core.h"
#include "hid_core/resources/applet_resource.h"
#include "hid_core/resources/keyboard/keyboard.h"
#include "hid_core/resources/shared_memory_format.h"
namespace Service::HID {
Keyboard::Keyboard(Core::HID::HIDCore& hid_core_) : ControllerBase{hid_core_} {
emulated_devices = hid_core.GetEmulatedDevices();
}
Keyboard::~Keyboard() = default;
void Keyboard::OnInit() {}
void Keyboard::OnRelease() {}
void Keyboard::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
std::scoped_lock shared_lock{*shared_mutex};
const u64 aruid = applet_resource->GetActiveAruid();
auto* data = applet_resource->GetAruidData(aruid);
if (data == nullptr || !data->flag.is_assigned) {
return;
}
KeyboardSharedMemoryFormat& shared_memory = data->shared_memory_format->keyboard;
if (!IsControllerActivated()) {
shared_memory.keyboard_lifo.buffer_count = 0;
shared_memory.keyboard_lifo.buffer_tail = 0;
return;
}
const auto& last_entry = shared_memory.keyboard_lifo.ReadCurrentEntry().state;
next_state.sampling_number = last_entry.sampling_number + 1;
if (Settings::values.keyboard_enabled) {
const auto& keyboard_state = emulated_devices->GetKeyboard();
const auto& keyboard_modifier_state = emulated_devices->GetKeyboardModifier();
next_state.key = keyboard_state;
next_state.modifier = keyboard_modifier_state;
next_state.attribute.is_connected.Assign(1);
}
shared_memory.keyboard_lifo.WriteNextEntry(next_state);
}
} // namespace Service::HID

View file

@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "hid_core/resources/controller_base.h"
#include "hid_core/resources/keyboard/keyboard_types.h"
namespace Core::HID {
class HIDCore;
class EmulatedDevices;
} // namespace Core::HID
namespace Service::HID {
class Keyboard final : public ControllerBase {
public:
explicit Keyboard(Core::HID::HIDCore& hid_core_);
~Keyboard() override;
// Called when the controller is initialized
void OnInit() override;
// When the controller is released
void OnRelease() override;
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
private:
KeyboardState next_state{};
Core::HID::EmulatedDevices* emulated_devices = nullptr;
};
} // namespace Service::HID

View file

@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "common/common_types.h"
#include "hid_core/hid_types.h"
namespace Service::HID {
// This is nn::hid::detail::KeyboardState
struct KeyboardState {
s64 sampling_number{};
Core::HID::KeyboardModifier modifier{};
Core::HID::KeyboardAttribute attribute{};
Core::HID::KeyboardKey key{};
};
static_assert(sizeof(KeyboardState) == 0x30, "KeyboardState is an invalid size");
} // namespace Service::HID

View file

@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core_timing.h"
#include "core/frontend/emu_window.h"
#include "hid_core/frontend/emulated_devices.h"
#include "hid_core/hid_core.h"
#include "hid_core/resources/applet_resource.h"
#include "hid_core/resources/mouse/debug_mouse.h"
#include "hid_core/resources/shared_memory_format.h"
namespace Service::HID {
DebugMouse::DebugMouse(Core::HID::HIDCore& hid_core_) : ControllerBase{hid_core_} {
emulated_devices = hid_core.GetEmulatedDevices();
}
DebugMouse::~DebugMouse() = default;
void DebugMouse::OnInit() {}
void DebugMouse::OnRelease() {}
void DebugMouse::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
std::scoped_lock shared_lock{*shared_mutex};
const u64 aruid = applet_resource->GetActiveAruid();
auto* data = applet_resource->GetAruidData(aruid);
if (data == nullptr || !data->flag.is_assigned) {
return;
}
MouseSharedMemoryFormat& shared_memory = data->shared_memory_format->debug_mouse;
if (!IsControllerActivated()) {
shared_memory.mouse_lifo.buffer_count = 0;
shared_memory.mouse_lifo.buffer_tail = 0;
return;
}
next_state = {};
const auto& last_entry = shared_memory.mouse_lifo.ReadCurrentEntry().state;
next_state.sampling_number = last_entry.sampling_number + 1;
if (Settings::values.mouse_enabled) {
const auto& mouse_button_state = emulated_devices->GetMouseButtons();
const auto& mouse_position_state = emulated_devices->GetMousePosition();
const auto& mouse_wheel_state = emulated_devices->GetMouseWheel();
next_state.attribute.is_connected.Assign(1);
next_state.x = static_cast<s32>(mouse_position_state.x * Layout::ScreenUndocked::Width);
next_state.y = static_cast<s32>(mouse_position_state.y * Layout::ScreenUndocked::Height);
next_state.delta_x = next_state.x - last_entry.x;
next_state.delta_y = next_state.y - last_entry.y;
next_state.delta_wheel_x = mouse_wheel_state.x - last_mouse_wheel_state.x;
next_state.delta_wheel_y = mouse_wheel_state.y - last_mouse_wheel_state.y;
last_mouse_wheel_state = mouse_wheel_state;
next_state.button = mouse_button_state;
}
shared_memory.mouse_lifo.WriteNextEntry(next_state);
}
} // namespace Service::HID

View file

@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "hid_core/hid_types.h"
#include "hid_core/resources/controller_base.h"
namespace Core::HID {
class HIDCore;
class EmulatedDevices;
} // namespace Core::HID
namespace Service::HID {
class DebugMouse final : public ControllerBase {
public:
explicit DebugMouse(Core::HID::HIDCore& hid_core_);
~DebugMouse() override;
// Called when the controller is initialized
void OnInit() override;
// When the controller is released
void OnRelease() override;
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
private:
Core::HID::MouseState next_state{};
Core::HID::AnalogStickState last_mouse_wheel_state{};
Core::HID::EmulatedDevices* emulated_devices = nullptr;
};
} // namespace Service::HID

View file

@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core_timing.h"
#include "core/frontend/emu_window.h"
#include "hid_core/frontend/emulated_devices.h"
#include "hid_core/hid_core.h"
#include "hid_core/resources/applet_resource.h"
#include "hid_core/resources/mouse/mouse.h"
#include "hid_core/resources/shared_memory_format.h"
namespace Service::HID {
Mouse::Mouse(Core::HID::HIDCore& hid_core_) : ControllerBase{hid_core_} {
emulated_devices = hid_core.GetEmulatedDevices();
}
Mouse::~Mouse() = default;
void Mouse::OnInit() {}
void Mouse::OnRelease() {}
void Mouse::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
std::scoped_lock shared_lock{*shared_mutex};
const u64 aruid = applet_resource->GetActiveAruid();
auto* data = applet_resource->GetAruidData(aruid);
if (data == nullptr || !data->flag.is_assigned) {
return;
}
MouseSharedMemoryFormat& shared_memory = data->shared_memory_format->mouse;
if (!IsControllerActivated()) {
shared_memory.mouse_lifo.buffer_count = 0;
shared_memory.mouse_lifo.buffer_tail = 0;
return;
}
next_state = {};
const auto& last_entry = shared_memory.mouse_lifo.ReadCurrentEntry().state;
next_state.sampling_number = last_entry.sampling_number + 1;
if (Settings::values.mouse_enabled) {
const auto& mouse_button_state = emulated_devices->GetMouseButtons();
const auto& mouse_position_state = emulated_devices->GetMousePosition();
const auto& mouse_wheel_state = emulated_devices->GetMouseWheel();
next_state.attribute.is_connected.Assign(1);
next_state.x = static_cast<s32>(mouse_position_state.x * Layout::ScreenUndocked::Width);
next_state.y = static_cast<s32>(mouse_position_state.y * Layout::ScreenUndocked::Height);
next_state.delta_x = next_state.x - last_entry.x;
next_state.delta_y = next_state.y - last_entry.y;
next_state.delta_wheel_x = mouse_wheel_state.x - last_mouse_wheel_state.x;
next_state.delta_wheel_y = mouse_wheel_state.y - last_mouse_wheel_state.y;
last_mouse_wheel_state = mouse_wheel_state;
next_state.button = mouse_button_state;
}
shared_memory.mouse_lifo.WriteNextEntry(next_state);
}
} // namespace Service::HID

View file

@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "hid_core/hid_types.h"
#include "hid_core/resources/controller_base.h"
namespace Core::HID {
class HIDCore;
class EmulatedDevices;
} // namespace Core::HID
namespace Service::HID {
class Mouse final : public ControllerBase {
public:
explicit Mouse(Core::HID::HIDCore& hid_core_);
~Mouse() override;
// Called when the controller is initialized
void OnInit() override;
// When the controller is released
void OnRelease() override;
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
private:
Core::HID::MouseState next_state{};
Core::HID::AnalogStickState last_mouse_wheel_state{};
Core::HID::EmulatedDevices* emulated_devices = nullptr;
};
} // namespace Service::HID

View file

@ -0,0 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "common/common_types.h"
namespace Service::HID {} // namespace Service::HID

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,214 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <atomic>
#include <mutex>
#include <span>
#include "common/common_types.h"
#include "hid_core/hid_types.h"
#include "hid_core/resources/controller_base.h"
#include "hid_core/resources/npad/npad_resource.h"
#include "hid_core/resources/npad/npad_types.h"
namespace Core::HID {
class EmulatedController;
enum class ControllerTriggerType;
} // namespace Core::HID
namespace Kernel {
class KEvent;
class KReadableEvent;
} // namespace Kernel
namespace Service::KernelHelpers {
class ServiceContext;
} // namespace Service::KernelHelpers
union Result;
namespace Service::HID {
class AppletResource;
struct NpadInternalState;
struct NpadSixAxisSensorLifo;
struct NpadSharedMemoryFormat;
class NPad final {
public:
explicit NPad(Core::HID::HIDCore& hid_core_, KernelHelpers::ServiceContext& service_context_);
~NPad();
Result Activate();
Result Activate(u64 aruid);
Result ActivateNpadResource();
Result ActivateNpadResource(u64 aruid);
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing);
Result SetSupportedNpadStyleSet(u64 aruid, Core::HID::NpadStyleSet supported_style_set);
Result GetSupportedNpadStyleSet(u64 aruid,
Core::HID::NpadStyleSet& out_supported_style_set) const;
Result GetMaskedSupportedNpadStyleSet(u64 aruid,
Core::HID::NpadStyleSet& out_supported_style_set) const;
Result SetSupportedNpadIdType(u64 aruid,
std::span<const Core::HID::NpadIdType> supported_npad_list);
Result SetNpadJoyHoldType(u64 aruid, NpadJoyHoldType hold_type);
Result GetNpadJoyHoldType(u64 aruid, NpadJoyHoldType& out_hold_type) const;
Result SetNpadHandheldActivationMode(u64 aruid, NpadHandheldActivationMode mode);
Result GetNpadHandheldActivationMode(u64 aruid, NpadHandheldActivationMode& out_mode) const;
bool SetNpadMode(u64 aruid, Core::HID::NpadIdType& new_npad_id, Core::HID::NpadIdType npad_id,
NpadJoyDeviceType npad_device_type, NpadJoyAssignmentMode assignment_mode);
bool VibrateControllerAtIndex(u64 aruid, Core::HID::NpadIdType npad_id,
std::size_t device_index,
const Core::HID::VibrationValue& vibration_value);
void VibrateController(u64 aruid,
const Core::HID::VibrationDeviceHandle& vibration_device_handle,
const Core::HID::VibrationValue& vibration_value);
void VibrateControllers(
u64 aruid, std::span<const Core::HID::VibrationDeviceHandle> vibration_device_handles,
std::span<const Core::HID::VibrationValue> vibration_values);
Core::HID::VibrationValue GetLastVibration(
u64 aruid, const Core::HID::VibrationDeviceHandle& vibration_device_handle) const;
void InitializeVibrationDevice(const Core::HID::VibrationDeviceHandle& vibration_device_handle);
void InitializeVibrationDeviceAtIndex(u64 aruid, Core::HID::NpadIdType npad_id,
std::size_t device_index);
void SetPermitVibrationSession(bool permit_vibration_session);
bool IsVibrationDeviceMounted(
u64 aruid, const Core::HID::VibrationDeviceHandle& vibration_device_handle) const;
Result AcquireNpadStyleSetUpdateEventHandle(u64 aruid, Kernel::KReadableEvent** out_event,
Core::HID::NpadIdType npad_id);
// Adds a new controller at an index.
void AddNewControllerAt(u64 aruid, Core::HID::NpadStyleIndex controller,
Core::HID::NpadIdType npad_id);
// Adds a new controller at an index with connection status.
void UpdateControllerAt(u64 aruid, Core::HID::NpadStyleIndex controller,
Core::HID::NpadIdType npad_id, bool connected);
Result DisconnectNpad(u64 aruid, Core::HID::NpadIdType npad_id);
Result IsFirmwareUpdateAvailableForSixAxisSensor(
u64 aruid, const Core::HID::SixAxisSensorHandle& sixaxis_handle,
bool& is_firmware_available) const;
Result ResetIsSixAxisSensorDeviceNewlyAssigned(
u64 aruid, const Core::HID::SixAxisSensorHandle& sixaxis_handle);
Result GetLedPattern(Core::HID::NpadIdType npad_id, Core::HID::LedPattern& pattern) const;
Result IsUnintendedHomeButtonInputProtectionEnabled(bool& out_is_enabled, u64 aruid,
Core::HID::NpadIdType npad_id) const;
Result EnableUnintendedHomeButtonInputProtection(u64 aruid, Core::HID::NpadIdType npad_id,
bool is_enabled);
void SetNpadAnalogStickUseCenterClamp(u64 aruid, bool is_enabled);
void ClearAllConnectedControllers();
void DisconnectAllConnectedControllers();
void ConnectAllDisconnectedControllers();
void ClearAllControllers();
Result MergeSingleJoyAsDualJoy(u64 aruid, Core::HID::NpadIdType npad_id_1,
Core::HID::NpadIdType npad_id_2);
Result StartLrAssignmentMode(u64 aruid);
Result StopLrAssignmentMode(u64 aruid);
Result SwapNpadAssignment(u64 aruid, Core::HID::NpadIdType npad_id_1,
Core::HID::NpadIdType npad_id_2);
// Logical OR for all buttons presses on all controllers
// Specifically for cheat engine and other features.
Core::HID::NpadButton GetAndResetPressState();
Result ApplyNpadSystemCommonPolicy(u64 aruid);
Result ApplyNpadSystemCommonPolicyFull(u64 aruid);
Result ClearNpadSystemCommonPolicy(u64 aruid);
void SetRevision(u64 aruid, NpadRevision revision);
NpadRevision GetRevision(u64 aruid);
Result RegisterAppletResourceUserId(u64 aruid);
void UnregisterAppletResourceUserId(u64 aruid);
void SetNpadExternals(std::shared_ptr<AppletResource> resource,
std::recursive_mutex* shared_mutex);
AppletDetailedUiType GetAppletDetailedUiType(Core::HID::NpadIdType npad_id);
private:
struct VibrationData {
bool device_mounted{};
Core::HID::VibrationValue latest_vibration_value{};
std::chrono::steady_clock::time_point last_vibration_timepoint{};
};
struct NpadControllerData {
NpadInternalState* shared_memory = nullptr;
Core::HID::EmulatedController* device = nullptr;
std::array<VibrationData, 2> vibration{};
bool is_connected{};
// Dual joycons can have only one side connected
bool is_dual_left_connected{true};
bool is_dual_right_connected{true};
// Current pad state
NPadGenericState npad_pad_state{};
NPadGenericState npad_libnx_state{};
NpadGcTriggerState npad_trigger_state{};
int callback_key{};
};
void ControllerUpdate(Core::HID::ControllerTriggerType type, std::size_t controller_idx);
void InitNewlyAddedController(u64 aruid, Core::HID::NpadIdType npad_id);
void RequestPadStateUpdate(u64 aruid, Core::HID::NpadIdType npad_id);
void WriteEmptyEntry(NpadInternalState* npad);
NpadControllerData& GetControllerFromHandle(
u64 aruid, const Core::HID::VibrationDeviceHandle& device_handle);
const NpadControllerData& GetControllerFromHandle(
u64 aruid, const Core::HID::VibrationDeviceHandle& device_handle) const;
NpadControllerData& GetControllerFromHandle(
u64 aruid, const Core::HID::SixAxisSensorHandle& device_handle);
const NpadControllerData& GetControllerFromHandle(
u64 aruid, const Core::HID::SixAxisSensorHandle& device_handle) const;
NpadControllerData& GetControllerFromNpadIdType(u64 aruid, Core::HID::NpadIdType npad_id);
const NpadControllerData& GetControllerFromNpadIdType(u64 aruid,
Core::HID::NpadIdType npad_id) const;
Core::HID::SixAxisSensorProperties& GetSixaxisProperties(
u64 aruid, const Core::HID::SixAxisSensorHandle& device_handle);
const Core::HID::SixAxisSensorProperties& GetSixaxisProperties(
u64 aruid, const Core::HID::SixAxisSensorHandle& device_handle) const;
Core::HID::HIDCore& hid_core;
KernelHelpers::ServiceContext& service_context;
s32 ref_counter{};
mutable std::mutex mutex;
NPadResource npad_resource;
AppletResourceHolder applet_resource_holder{};
Kernel::KEvent* input_event{nullptr};
std::mutex* input_mutex{nullptr};
std::atomic<u64> press_state{};
bool permit_vibration_session_enabled;
std::array<std::array<NpadControllerData, MaxSupportedNpadIdTypes>, AruidIndexMax>
controller_data{};
};
} // namespace Service::HID

View file

@ -0,0 +1,228 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "hid_core/hid_util.h"
#include "hid_core/resources/npad/npad_data.h"
namespace Service::HID {
NPadData::NPadData() {
ClearNpadSystemCommonPolicy();
}
NPadData::~NPadData() = default;
NpadStatus NPadData::GetNpadStatus() const {
return status;
}
void NPadData::SetNpadAnalogStickUseCenterClamp(bool is_enabled) {
status.use_center_clamp.Assign(is_enabled);
}
bool NPadData::GetNpadAnalogStickUseCenterClamp() const {
return status.use_center_clamp.As<bool>();
}
void NPadData::SetNpadSystemExtStateEnabled(bool is_enabled) {
status.system_ext_state.Assign(is_enabled);
}
bool NPadData::GetNpadSystemExtState() const {
return status.system_ext_state.As<bool>();
}
Result NPadData::SetSupportedNpadIdType(std::span<const Core::HID::NpadIdType> list) {
// Note: Real limit is 11. But array size is 10. N's bug?
if (list.size() > MaxSupportedNpadIdTypes) {
return ResultInvalidArraySize;
}
supported_npad_id_types_count = list.size();
memcpy(supported_npad_id_types.data(), list.data(),
list.size() * sizeof(Core::HID::NpadIdType));
return ResultSuccess;
}
std::size_t NPadData::GetSupportedNpadIdType(std::span<Core::HID::NpadIdType> out_list) const {
std::size_t out_size = std::min(supported_npad_id_types_count, out_list.size());
memcpy(out_list.data(), supported_npad_id_types.data(),
out_size * sizeof(Core::HID::NpadIdType));
return out_size;
}
bool NPadData::IsNpadIdTypeSupported(Core::HID::NpadIdType npad_id) const {
for (std::size_t i = 0; i < supported_npad_id_types_count; i++) {
if (supported_npad_id_types[i] == npad_id) {
return true;
}
}
return false;
}
void NPadData::SetNpadSystemCommonPolicy(bool is_full_policy) {
supported_npad_style_set = Core::HID::NpadStyleSet::Fullkey | Core::HID::NpadStyleSet::JoyDual |
Core::HID::NpadStyleSet::SystemExt | Core::HID::NpadStyleSet::System;
handheld_activation_mode = NpadHandheldActivationMode::Dual;
status.is_supported_styleset_set.Assign(true);
status.is_hold_type_set.Assign(true);
status.lr_assignment_mode.Assign(false);
status.is_policy.Assign(true);
if (is_full_policy) {
status.is_full_policy.Assign(true);
}
supported_npad_id_types_count = 10;
supported_npad_id_types[0] = Core::HID::NpadIdType::Player1;
supported_npad_id_types[1] = Core::HID::NpadIdType::Player2;
supported_npad_id_types[2] = Core::HID::NpadIdType::Player3;
supported_npad_id_types[3] = Core::HID::NpadIdType::Player4;
supported_npad_id_types[4] = Core::HID::NpadIdType::Player5;
supported_npad_id_types[5] = Core::HID::NpadIdType::Player6;
supported_npad_id_types[6] = Core::HID::NpadIdType::Player7;
supported_npad_id_types[7] = Core::HID::NpadIdType::Player8;
supported_npad_id_types[8] = Core::HID::NpadIdType::Other;
supported_npad_id_types[9] = Core::HID::NpadIdType::Handheld;
for (auto& input_protection : is_unintended_home_button_input_protection) {
input_protection = true;
}
}
void NPadData::ClearNpadSystemCommonPolicy() {
status.raw = 0;
supported_npad_style_set = Core::HID::NpadStyleSet::All;
npad_hold_type = NpadJoyHoldType::Vertical;
handheld_activation_mode = NpadHandheldActivationMode::Dual;
for (auto& button_assignment : npad_button_assignment) {
button_assignment = Core::HID::NpadButton::None;
}
supported_npad_id_types_count = 10;
supported_npad_id_types[0] = Core::HID::NpadIdType::Player1;
supported_npad_id_types[1] = Core::HID::NpadIdType::Player2;
supported_npad_id_types[2] = Core::HID::NpadIdType::Player3;
supported_npad_id_types[3] = Core::HID::NpadIdType::Player4;
supported_npad_id_types[4] = Core::HID::NpadIdType::Player5;
supported_npad_id_types[5] = Core::HID::NpadIdType::Player6;
supported_npad_id_types[6] = Core::HID::NpadIdType::Player7;
supported_npad_id_types[7] = Core::HID::NpadIdType::Player8;
supported_npad_id_types[8] = Core::HID::NpadIdType::Other;
supported_npad_id_types[9] = Core::HID::NpadIdType::Handheld;
for (auto& input_protection : is_unintended_home_button_input_protection) {
input_protection = true;
}
}
void NPadData::SetNpadJoyHoldType(NpadJoyHoldType hold_type) {
npad_hold_type = hold_type;
status.is_hold_type_set.Assign(true);
}
NpadJoyHoldType NPadData::GetNpadJoyHoldType() const {
return npad_hold_type;
}
void NPadData::SetHandheldActivationMode(NpadHandheldActivationMode activation_mode) {
handheld_activation_mode = activation_mode;
}
NpadHandheldActivationMode NPadData::GetHandheldActivationMode() const {
return handheld_activation_mode;
}
void NPadData::SetSupportedNpadStyleSet(Core::HID::NpadStyleSet style_set) {
supported_npad_style_set = style_set;
status.is_supported_styleset_set.Assign(true);
status.is_hold_type_set.Assign(true);
}
Core::HID::NpadStyleSet NPadData::GetSupportedNpadStyleSet() const {
return supported_npad_style_set;
}
bool NPadData::IsNpadStyleIndexSupported(Core::HID::NpadStyleIndex style_index) const {
Core::HID::NpadStyleTag style = {supported_npad_style_set};
switch (style_index) {
case Core::HID::NpadStyleIndex::ProController:
return style.fullkey.As<bool>();
case Core::HID::NpadStyleIndex::Handheld:
return style.handheld.As<bool>();
case Core::HID::NpadStyleIndex::JoyconDual:
return style.joycon_dual.As<bool>();
case Core::HID::NpadStyleIndex::JoyconLeft:
return style.joycon_left.As<bool>();
case Core::HID::NpadStyleIndex::JoyconRight:
return style.joycon_right.As<bool>();
case Core::HID::NpadStyleIndex::GameCube:
return style.gamecube.As<bool>();
case Core::HID::NpadStyleIndex::Pokeball:
return style.palma.As<bool>();
case Core::HID::NpadStyleIndex::NES:
return style.lark.As<bool>();
case Core::HID::NpadStyleIndex::SNES:
return style.lucia.As<bool>();
case Core::HID::NpadStyleIndex::N64:
return style.lagoon.As<bool>();
case Core::HID::NpadStyleIndex::SegaGenesis:
return style.lager.As<bool>();
default:
return false;
}
}
void NPadData::SetLrAssignmentMode(bool is_enabled) {
status.lr_assignment_mode.Assign(is_enabled);
}
bool NPadData::GetLrAssignmentMode() const {
return status.lr_assignment_mode.As<bool>();
}
void NPadData::SetAssigningSingleOnSlSrPress(bool is_enabled) {
status.assigning_single_on_sl_sr_press.Assign(is_enabled);
}
bool NPadData::GetAssigningSingleOnSlSrPress() const {
return status.assigning_single_on_sl_sr_press.As<bool>();
}
void NPadData::SetHomeProtectionEnabled(bool is_enabled, Core::HID::NpadIdType npad_id) {
is_unintended_home_button_input_protection[NpadIdTypeToIndex(npad_id)] = is_enabled;
}
bool NPadData::GetHomeProtectionEnabled(Core::HID::NpadIdType npad_id) const {
return is_unintended_home_button_input_protection[NpadIdTypeToIndex(npad_id)];
}
void NPadData::SetCaptureButtonAssignment(Core::HID::NpadButton button_assignment,
std::size_t style_index) {
npad_button_assignment[style_index] = button_assignment;
}
Core::HID::NpadButton NPadData::GetCaptureButtonAssignment(std::size_t style_index) const {
return npad_button_assignment[style_index];
}
std::size_t NPadData::GetNpadCaptureButtonAssignmentList(
std::span<Core::HID::NpadButton> out_list) const {
for (std::size_t i = 0; i < out_list.size(); i++) {
Core::HID::NpadStyleSet style_set = GetStylesetByIndex(i);
if ((style_set & supported_npad_style_set) == Core::HID::NpadStyleSet::None ||
npad_button_assignment[i] == Core::HID::NpadButton::None) {
return i;
}
out_list[i] = npad_button_assignment[i];
}
return out_list.size();
}
} // namespace Service::HID

View file

@ -0,0 +1,88 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include <span>
#include "common/common_types.h"
#include "core/hle/result.h"
#include "hid_core/hid_types.h"
#include "hid_core/resources/npad/npad_types.h"
namespace Service::HID {
struct NpadStatus {
union {
u32 raw{};
BitField<0, 1, u32> is_supported_styleset_set;
BitField<1, 1, u32> is_hold_type_set;
BitField<2, 1, u32> lr_assignment_mode;
BitField<3, 1, u32> assigning_single_on_sl_sr_press;
BitField<4, 1, u32> is_full_policy;
BitField<5, 1, u32> is_policy;
BitField<6, 1, u32> use_center_clamp;
BitField<7, 1, u32> system_ext_state;
};
};
static_assert(sizeof(NpadStatus) == 4, "NpadStatus is an invalid size");
/// Handles Npad request from HID interfaces
class NPadData final {
public:
explicit NPadData();
~NPadData();
NpadStatus GetNpadStatus() const;
void SetNpadAnalogStickUseCenterClamp(bool is_enabled);
bool GetNpadAnalogStickUseCenterClamp() const;
void SetNpadSystemExtStateEnabled(bool is_enabled);
bool GetNpadSystemExtState() const;
Result SetSupportedNpadIdType(std::span<const Core::HID::NpadIdType> list);
std::size_t GetSupportedNpadIdType(std::span<Core::HID::NpadIdType> out_list) const;
bool IsNpadIdTypeSupported(Core::HID::NpadIdType npad_id) const;
void SetNpadSystemCommonPolicy(bool is_full_policy);
void ClearNpadSystemCommonPolicy();
void SetNpadJoyHoldType(NpadJoyHoldType hold_type);
NpadJoyHoldType GetNpadJoyHoldType() const;
void SetHandheldActivationMode(NpadHandheldActivationMode activation_mode);
NpadHandheldActivationMode GetHandheldActivationMode() const;
void SetSupportedNpadStyleSet(Core::HID::NpadStyleSet style_set);
Core::HID::NpadStyleSet GetSupportedNpadStyleSet() const;
bool IsNpadStyleIndexSupported(Core::HID::NpadStyleIndex style_index) const;
void SetLrAssignmentMode(bool is_enabled);
bool GetLrAssignmentMode() const;
void SetAssigningSingleOnSlSrPress(bool is_enabled);
bool GetAssigningSingleOnSlSrPress() const;
void SetHomeProtectionEnabled(bool is_enabled, Core::HID::NpadIdType npad_id);
bool GetHomeProtectionEnabled(Core::HID::NpadIdType npad_id) const;
void SetCaptureButtonAssignment(Core::HID::NpadButton button_assignment,
std::size_t style_index);
Core::HID::NpadButton GetCaptureButtonAssignment(std::size_t style_index) const;
std::size_t GetNpadCaptureButtonAssignmentList(std::span<Core::HID::NpadButton> out_list) const;
private:
NpadStatus status{};
Core::HID::NpadStyleSet supported_npad_style_set{Core::HID::NpadStyleSet::All};
NpadJoyHoldType npad_hold_type{NpadJoyHoldType::Vertical};
NpadHandheldActivationMode handheld_activation_mode{};
std::array<Core::HID::NpadIdType, MaxSupportedNpadIdTypes> supported_npad_id_types{};
std::array<Core::HID::NpadButton, StyleIndexCount> npad_button_assignment{};
std::size_t supported_npad_id_types_count{};
std::array<bool, MaxSupportedNpadIdTypes> is_unintended_home_button_input_protection{};
};
} // namespace Service::HID

View file

@ -0,0 +1,685 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_readable_event.h"
#include "hid_core/hid_result.h"
#include "hid_core/hid_util.h"
#include "hid_core/resources/npad/npad_resource.h"
#include "hid_core/resources/npad/npad_types.h"
namespace Service::HID {
NPadResource::NPadResource(KernelHelpers::ServiceContext& context) : service_context{context} {}
NPadResource::~NPadResource() = default;
Result NPadResource::RegisterAppletResourceUserId(u64 aruid) {
const auto aruid_index = GetIndexFromAruid(aruid);
if (aruid_index < AruidIndexMax) {
return ResultAruidAlreadyRegistered;
}
std::size_t data_index = AruidIndexMax;
for (std::size_t i = 0; i < AruidIndexMax; i++) {
if (!state[i].flag.is_initialized) {
data_index = i;
break;
}
}
if (data_index == AruidIndexMax) {
return ResultAruidNoAvailableEntries;
}
auto& aruid_data = state[data_index];
aruid_data.aruid = aruid;
aruid_data.flag.is_initialized.Assign(true);
data_index = AruidIndexMax;
for (std::size_t i = 0; i < AruidIndexMax; i++) {
if (registration_list.flag[i] == RegistrationStatus::Initialized) {
if (registration_list.aruid[i] != aruid) {
continue;
}
data_index = i;
break;
}
if (registration_list.flag[i] == RegistrationStatus::None) {
data_index = i;
break;
}
}
if (data_index == AruidIndexMax) {
return ResultSuccess;
}
registration_list.flag[data_index] = RegistrationStatus::Initialized;
registration_list.aruid[data_index] = aruid;
return ResultSuccess;
}
void NPadResource::UnregisterAppletResourceUserId(u64 aruid) {
const u64 aruid_index = GetIndexFromAruid(aruid);
DestroyStyleSetUpdateEvents(aruid);
if (aruid_index < AruidIndexMax) {
state[aruid_index] = {};
registration_list.flag[aruid_index] = RegistrationStatus::PendingDelete;
}
}
void NPadResource::DestroyStyleSetUpdateEvents(u64 aruid) {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return;
}
for (auto& controller_state : state[aruid_index].controller_state) {
if (!controller_state.is_styleset_update_event_initialized) {
continue;
}
service_context.CloseEvent(controller_state.style_set_update_event);
controller_state.is_styleset_update_event_initialized = false;
}
}
Result NPadResource::Activate(u64 aruid) {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultSuccess;
}
auto& state_data = state[aruid_index];
if (state_data.flag.is_assigned) {
return ResultAruidAlreadyRegistered;
}
state_data.flag.is_assigned.Assign(true);
state_data.data.ClearNpadSystemCommonPolicy();
state_data.npad_revision = NpadRevision::Revision0;
state_data.button_config = {};
if (active_data_aruid == aruid) {
default_hold_type = active_data.GetNpadJoyHoldType();
active_data.SetNpadJoyHoldType(default_hold_type);
}
return ResultSuccess;
}
Result NPadResource::Activate() {
if (ref_counter == std::numeric_limits<s32>::max() - 1) {
return ResultAppletResourceOverflow;
}
if (ref_counter == 0) {
RegisterAppletResourceUserId(SystemAruid);
Activate(SystemAruid);
}
ref_counter++;
return ResultSuccess;
}
Result NPadResource::Deactivate() {
if (ref_counter == 0) {
return ResultAppletResourceNotInitialized;
}
UnregisterAppletResourceUserId(SystemAruid);
ref_counter--;
return ResultSuccess;
}
NPadData* NPadResource::GetActiveData() {
return &active_data;
}
u64 NPadResource::GetActiveDataAruid() {
return active_data_aruid;
}
void NPadResource::SetAppletResourceUserId(u64 aruid) {
if (active_data_aruid == aruid) {
return;
}
active_data_aruid = aruid;
default_hold_type = active_data.GetNpadJoyHoldType();
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return;
}
auto& data = state[aruid_index].data;
if (data.GetNpadStatus().is_policy || data.GetNpadStatus().is_full_policy) {
data.SetNpadJoyHoldType(default_hold_type);
}
active_data = data;
if (data.GetNpadStatus().is_hold_type_set) {
active_data.SetNpadJoyHoldType(default_hold_type);
}
}
std::size_t NPadResource::GetIndexFromAruid(u64 aruid) const {
for (std::size_t i = 0; i < AruidIndexMax; i++) {
if (registration_list.flag[i] == RegistrationStatus::Initialized &&
registration_list.aruid[i] == aruid) {
return i;
}
}
return AruidIndexMax;
}
Result NPadResource::ApplyNpadSystemCommonPolicy(u64 aruid, bool is_full_policy) {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultNpadNotConnected;
}
auto& data = state[aruid_index].data;
data.SetNpadSystemCommonPolicy(is_full_policy);
data.SetNpadJoyHoldType(default_hold_type);
if (active_data_aruid == aruid) {
active_data.SetNpadSystemCommonPolicy(is_full_policy);
active_data.SetNpadJoyHoldType(default_hold_type);
}
return ResultSuccess;
}
Result NPadResource::ClearNpadSystemCommonPolicy(u64 aruid) {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultNpadNotConnected;
}
state[aruid_index].data.ClearNpadSystemCommonPolicy();
if (active_data_aruid == aruid) {
active_data.ClearNpadSystemCommonPolicy();
}
return ResultSuccess;
}
Result NPadResource::SetSupportedNpadStyleSet(u64 aruid, Core::HID::NpadStyleSet style_set) {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultNpadNotConnected;
}
auto& data = state[aruid_index].data;
data.SetSupportedNpadStyleSet(style_set);
if (active_data_aruid == aruid) {
active_data.SetSupportedNpadStyleSet(style_set);
active_data.SetNpadJoyHoldType(data.GetNpadJoyHoldType());
}
return ResultSuccess;
}
Result NPadResource::GetSupportedNpadStyleSet(Core::HID::NpadStyleSet& out_style_Set,
u64 aruid) const {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultNpadNotConnected;
}
auto& data = state[aruid_index].data;
if (!data.GetNpadStatus().is_supported_styleset_set) {
return ResultUndefinedStyleset;
}
out_style_Set = data.GetSupportedNpadStyleSet();
return ResultSuccess;
}
Result NPadResource::GetMaskedSupportedNpadStyleSet(Core::HID::NpadStyleSet& out_style_set,
u64 aruid) const {
if (aruid == SystemAruid) {
out_style_set = Core::HID::NpadStyleSet::Fullkey | Core::HID::NpadStyleSet::Handheld |
Core::HID::NpadStyleSet::JoyDual | Core::HID::NpadStyleSet::JoyLeft |
Core::HID::NpadStyleSet::JoyRight | Core::HID::NpadStyleSet::Palma |
Core::HID::NpadStyleSet::SystemExt | Core::HID::NpadStyleSet::System;
return ResultSuccess;
}
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultNpadNotConnected;
}
auto& data = state[aruid_index].data;
if (!data.GetNpadStatus().is_supported_styleset_set) {
return ResultUndefinedStyleset;
}
Core::HID::NpadStyleSet mask{Core::HID::NpadStyleSet::None};
out_style_set = data.GetSupportedNpadStyleSet();
switch (state[aruid_index].npad_revision) {
case NpadRevision::Revision1:
mask = Core::HID::NpadStyleSet::Fullkey | Core::HID::NpadStyleSet::Handheld |
Core::HID::NpadStyleSet::JoyDual | Core::HID::NpadStyleSet::JoyLeft |
Core::HID::NpadStyleSet::JoyRight | Core::HID::NpadStyleSet::Gc |
Core::HID::NpadStyleSet::Palma | Core::HID::NpadStyleSet::SystemExt |
Core::HID::NpadStyleSet::System;
break;
case NpadRevision::Revision2:
mask = Core::HID::NpadStyleSet::Fullkey | Core::HID::NpadStyleSet::Handheld |
Core::HID::NpadStyleSet::JoyDual | Core::HID::NpadStyleSet::JoyLeft |
Core::HID::NpadStyleSet::JoyRight | Core::HID::NpadStyleSet::Gc |
Core::HID::NpadStyleSet::Palma | Core::HID::NpadStyleSet::Lark |
Core::HID::NpadStyleSet::SystemExt | Core::HID::NpadStyleSet::System;
break;
case NpadRevision::Revision3:
mask = Core::HID::NpadStyleSet::Fullkey | Core::HID::NpadStyleSet::Handheld |
Core::HID::NpadStyleSet::JoyDual | Core::HID::NpadStyleSet::JoyLeft |
Core::HID::NpadStyleSet::JoyRight | Core::HID::NpadStyleSet::Gc |
Core::HID::NpadStyleSet::Palma | Core::HID::NpadStyleSet::Lark |
Core::HID::NpadStyleSet::HandheldLark | Core::HID::NpadStyleSet::Lucia |
Core::HID::NpadStyleSet::Lagoon | Core::HID::NpadStyleSet::Lager |
Core::HID::NpadStyleSet::SystemExt | Core::HID::NpadStyleSet::System;
break;
default:
mask = Core::HID::NpadStyleSet::Fullkey | Core::HID::NpadStyleSet::Handheld |
Core::HID::NpadStyleSet::JoyDual | Core::HID::NpadStyleSet::JoyLeft |
Core::HID::NpadStyleSet::JoyRight | Core::HID::NpadStyleSet::SystemExt |
Core::HID::NpadStyleSet::System;
break;
}
out_style_set = out_style_set & mask;
return ResultSuccess;
}
Result NPadResource::GetAvailableStyleset(Core::HID::NpadStyleSet& out_style_set, u64 aruid) const {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultNpadNotConnected;
}
auto& data = state[aruid_index].data;
if (!data.GetNpadStatus().is_supported_styleset_set) {
return ResultUndefinedStyleset;
}
Core::HID::NpadStyleSet mask{Core::HID::NpadStyleSet::None};
out_style_set = data.GetSupportedNpadStyleSet();
switch (state[aruid_index].npad_revision) {
case NpadRevision::Revision1:
mask = Core::HID::NpadStyleSet::Fullkey | Core::HID::NpadStyleSet::Handheld |
Core::HID::NpadStyleSet::JoyDual | Core::HID::NpadStyleSet::JoyLeft |
Core::HID::NpadStyleSet::JoyRight | Core::HID::NpadStyleSet::Gc |
Core::HID::NpadStyleSet::Palma | Core::HID::NpadStyleSet::SystemExt |
Core::HID::NpadStyleSet::System;
break;
case NpadRevision::Revision2:
mask = Core::HID::NpadStyleSet::Fullkey | Core::HID::NpadStyleSet::Handheld |
Core::HID::NpadStyleSet::JoyDual | Core::HID::NpadStyleSet::JoyLeft |
Core::HID::NpadStyleSet::JoyRight | Core::HID::NpadStyleSet::Gc |
Core::HID::NpadStyleSet::Palma | Core::HID::NpadStyleSet::Lark |
Core::HID::NpadStyleSet::SystemExt | Core::HID::NpadStyleSet::System;
break;
case NpadRevision::Revision3:
mask = Core::HID::NpadStyleSet::Fullkey | Core::HID::NpadStyleSet::Handheld |
Core::HID::NpadStyleSet::JoyDual | Core::HID::NpadStyleSet::JoyLeft |
Core::HID::NpadStyleSet::JoyRight | Core::HID::NpadStyleSet::Gc |
Core::HID::NpadStyleSet::Palma | Core::HID::NpadStyleSet::Lark |
Core::HID::NpadStyleSet::HandheldLark | Core::HID::NpadStyleSet::Lucia |
Core::HID::NpadStyleSet::Lagoon | Core::HID::NpadStyleSet::Lager |
Core::HID::NpadStyleSet::SystemExt | Core::HID::NpadStyleSet::System;
break;
default:
mask = Core::HID::NpadStyleSet::Fullkey | Core::HID::NpadStyleSet::Handheld |
Core::HID::NpadStyleSet::JoyDual | Core::HID::NpadStyleSet::JoyLeft |
Core::HID::NpadStyleSet::JoyRight | Core::HID::NpadStyleSet::SystemExt |
Core::HID::NpadStyleSet::System;
break;
}
out_style_set = out_style_set & mask;
return ResultSuccess;
}
NpadRevision NPadResource::GetNpadRevision(u64 aruid) const {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return NpadRevision::Revision0;
}
return state[aruid_index].npad_revision;
}
Result NPadResource::IsSupportedNpadStyleSet(bool& is_set, u64 aruid) {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultNpadNotConnected;
}
is_set = state[aruid_index].data.GetNpadStatus().is_supported_styleset_set.Value() != 0;
return ResultSuccess;
}
Result NPadResource::SetNpadJoyHoldType(u64 aruid, NpadJoyHoldType hold_type) {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultNpadNotConnected;
}
state[aruid_index].data.SetNpadJoyHoldType(hold_type);
if (active_data_aruid == aruid) {
active_data.SetNpadJoyHoldType(hold_type);
}
return ResultSuccess;
}
Result NPadResource::GetNpadJoyHoldType(NpadJoyHoldType& hold_type, u64 aruid) const {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultNpadNotConnected;
}
auto& data = state[aruid_index].data;
if (data.GetNpadStatus().is_policy || data.GetNpadStatus().is_full_policy) {
hold_type = active_data.GetNpadJoyHoldType();
return ResultSuccess;
}
hold_type = data.GetNpadJoyHoldType();
return ResultSuccess;
}
Result NPadResource::SetNpadHandheldActivationMode(u64 aruid,
NpadHandheldActivationMode activation_mode) {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultNpadNotConnected;
}
state[aruid_index].data.SetHandheldActivationMode(activation_mode);
if (active_data_aruid == aruid) {
active_data.SetHandheldActivationMode(activation_mode);
}
return ResultSuccess;
}
Result NPadResource::GetNpadHandheldActivationMode(NpadHandheldActivationMode& activation_mode,
u64 aruid) const {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultNpadNotConnected;
}
activation_mode = state[aruid_index].data.GetHandheldActivationMode();
return ResultSuccess;
}
Result NPadResource::SetSupportedNpadIdType(
u64 aruid, std::span<const Core::HID::NpadIdType> supported_npad_list) {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultNpadNotConnected;
}
if (supported_npad_list.size() > MaxSupportedNpadIdTypes) {
return ResultInvalidArraySize;
}
Result result = state[aruid_index].data.SetSupportedNpadIdType(supported_npad_list);
if (result.IsSuccess() && active_data_aruid == aruid) {
result = active_data.SetSupportedNpadIdType(supported_npad_list);
}
return result;
}
bool NPadResource::IsControllerSupported(u64 aruid, Core::HID::NpadStyleIndex style_index) const {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return false;
}
return state[aruid_index].data.IsNpadStyleIndexSupported(style_index);
}
Result NPadResource::SetLrAssignmentMode(u64 aruid, bool is_enabled) {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultNpadNotConnected;
}
state[aruid_index].data.SetLrAssignmentMode(is_enabled);
if (active_data_aruid == aruid) {
active_data.SetLrAssignmentMode(is_enabled);
}
return ResultSuccess;
}
Result NPadResource::GetLrAssignmentMode(bool& is_enabled, u64 aruid) const {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultNpadNotConnected;
}
is_enabled = state[aruid_index].data.GetLrAssignmentMode();
return ResultSuccess;
}
Result NPadResource::SetAssigningSingleOnSlSrPress(u64 aruid, bool is_enabled) {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultNpadNotConnected;
}
state[aruid_index].data.SetAssigningSingleOnSlSrPress(is_enabled);
if (active_data_aruid == aruid) {
active_data.SetAssigningSingleOnSlSrPress(is_enabled);
}
return ResultSuccess;
}
Result NPadResource::IsAssigningSingleOnSlSrPressEnabled(bool& is_enabled, u64 aruid) const {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultNpadNotConnected;
}
is_enabled = state[aruid_index].data.GetAssigningSingleOnSlSrPress();
return ResultSuccess;
}
Result NPadResource::AcquireNpadStyleSetUpdateEventHandle(u64 aruid,
Kernel::KReadableEvent** out_event,
Core::HID::NpadIdType npad_id) {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultNpadNotConnected;
}
auto& controller_state = state[aruid_index].controller_state[NpadIdTypeToIndex(npad_id)];
if (!controller_state.is_styleset_update_event_initialized) {
// Auto clear = true
controller_state.style_set_update_event =
service_context.CreateEvent("NpadResource:StylesetUpdateEvent");
// Assume creating the event succeeds otherwise crash the system here
controller_state.is_styleset_update_event_initialized = true;
}
*out_event = &controller_state.style_set_update_event->GetReadableEvent();
if (controller_state.is_styleset_update_event_initialized) {
controller_state.style_set_update_event->Signal();
}
return ResultSuccess;
}
Result NPadResource::SignalStyleSetUpdateEvent(u64 aruid, Core::HID::NpadIdType npad_id) {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultNpadNotConnected;
}
auto controller = state[aruid_index].controller_state[NpadIdTypeToIndex(npad_id)];
if (controller.is_styleset_update_event_initialized) {
controller.style_set_update_event->Signal();
}
return ResultSuccess;
}
Result NPadResource::GetHomeProtectionEnabled(bool& is_enabled, u64 aruid,
Core::HID::NpadIdType npad_id) const {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultNpadNotConnected;
}
is_enabled = state[aruid_index].data.GetHomeProtectionEnabled(npad_id);
return ResultSuccess;
}
Result NPadResource::SetHomeProtectionEnabled(u64 aruid, Core::HID::NpadIdType npad_id,
bool is_enabled) {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultNpadNotConnected;
}
state[aruid_index].data.SetHomeProtectionEnabled(is_enabled, npad_id);
if (active_data_aruid == aruid) {
active_data.SetHomeProtectionEnabled(is_enabled, npad_id);
}
return ResultSuccess;
}
Result NPadResource::SetNpadAnalogStickUseCenterClamp(u64 aruid, bool is_enabled) {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultNpadNotConnected;
}
state[aruid_index].data.SetNpadAnalogStickUseCenterClamp(is_enabled);
if (active_data_aruid == aruid) {
active_data.SetNpadAnalogStickUseCenterClamp(is_enabled);
}
return ResultSuccess;
}
Result NPadResource::SetButtonConfig(u64 aruid, Core::HID::NpadIdType npad_id, std::size_t index,
Core::HID::NpadButton button_config) {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultNpadNotConnected;
}
state[aruid_index].button_config[NpadIdTypeToIndex(npad_id)][index] = button_config;
return ResultSuccess;
}
Core::HID::NpadButton NPadResource::GetButtonConfig(u64 aruid, Core::HID::NpadIdType npad_id,
std::size_t index, Core::HID::NpadButton mask,
bool is_enabled) {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return Core::HID::NpadButton::None;
}
auto& button_config = state[aruid_index].button_config[NpadIdTypeToIndex(npad_id)][index];
if (is_enabled) {
button_config = button_config | mask;
return button_config;
}
button_config = Core::HID::NpadButton::None;
return Core::HID::NpadButton::None;
}
void NPadResource::ResetButtonConfig() {
for (auto& selected_state : state) {
selected_state.button_config = {};
}
}
Result NPadResource::SetNpadCaptureButtonAssignment(u64 aruid,
Core::HID::NpadStyleSet npad_style_set,
Core::HID::NpadButton button_assignment) {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultNpadNotConnected;
}
// Must be a power of two
const auto raw_styleset = static_cast<u32>(npad_style_set);
if (raw_styleset == 0 && (raw_styleset & (raw_styleset - 1)) != 0) {
return ResultMultipleStyleSetSelected;
}
std::size_t style_index{};
Core::HID::NpadStyleSet style_selected{};
for (style_index = 0; style_index < StyleIndexCount; ++style_index) {
style_selected = GetStylesetByIndex(style_index);
if (npad_style_set == style_selected) {
break;
}
}
if (style_selected == Core::HID::NpadStyleSet::None) {
return ResultMultipleStyleSetSelected;
}
state[aruid_index].data.SetCaptureButtonAssignment(button_assignment, style_index);
if (active_data_aruid == aruid) {
active_data.SetCaptureButtonAssignment(button_assignment, style_index);
}
return ResultSuccess;
}
Result NPadResource::ClearNpadCaptureButtonAssignment(u64 aruid) {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultNpadNotConnected;
}
for (std::size_t i = 0; i < StyleIndexCount; i++) {
state[aruid_index].data.SetCaptureButtonAssignment(Core::HID::NpadButton::None, i);
if (active_data_aruid == aruid) {
active_data.SetCaptureButtonAssignment(Core::HID::NpadButton::None, i);
}
}
return ResultSuccess;
}
std::size_t NPadResource::GetNpadCaptureButtonAssignment(std::span<Core::HID::NpadButton> out_list,
u64 aruid) const {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return 0;
}
return state[aruid_index].data.GetNpadCaptureButtonAssignmentList(out_list);
}
void NPadResource::SetNpadRevision(u64 aruid, NpadRevision revision) {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return;
}
state[aruid_index].npad_revision = revision;
}
Result NPadResource::SetNpadSystemExtStateEnabled(u64 aruid, bool is_enabled) {
const u64 aruid_index = GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultNpadNotConnected;
}
state[aruid_index].data.SetNpadAnalogStickUseCenterClamp(is_enabled);
if (active_data_aruid == aruid) {
active_data.SetNpadAnalogStickUseCenterClamp(is_enabled);
}
return ResultSuccess;
}
} // namespace Service::HID

View file

@ -0,0 +1,132 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include <mutex>
#include <span>
#include "common/common_types.h"
#include "core/hle/result.h"
#include "core/hle/service/kernel_helpers.h"
#include "hid_core/hid_types.h"
#include "hid_core/resources/applet_resource.h"
#include "hid_core/resources/npad/npad_data.h"
#include "hid_core/resources/npad/npad_types.h"
namespace Core {
class System;
}
namespace Kernel {
class KReadableEvent;
}
namespace Service::HID {
struct DataStatusFlag;
struct NpadControllerState {
bool is_styleset_update_event_initialized{};
INSERT_PADDING_BYTES(0x7);
Kernel::KEvent* style_set_update_event{nullptr};
INSERT_PADDING_BYTES(0x27);
};
struct NpadState {
DataStatusFlag flag{};
u64 aruid{};
NPadData data{};
std::array<std::array<Core::HID::NpadButton, StyleIndexCount>, MaxSupportedNpadIdTypes>
button_config;
std::array<NpadControllerState, MaxSupportedNpadIdTypes> controller_state;
NpadRevision npad_revision;
};
/// Handles Npad request from HID interfaces
class NPadResource final {
public:
explicit NPadResource(KernelHelpers::ServiceContext& context);
~NPadResource();
NPadData* GetActiveData();
u64 GetActiveDataAruid();
Result RegisterAppletResourceUserId(u64 aruid);
void UnregisterAppletResourceUserId(u64 aruid);
void DestroyStyleSetUpdateEvents(u64 aruid);
Result Activate(u64 aruid);
Result Activate();
Result Deactivate();
void SetAppletResourceUserId(u64 aruid);
std::size_t GetIndexFromAruid(u64 aruid) const;
Result ApplyNpadSystemCommonPolicy(u64 aruid, bool is_full_policy);
Result ClearNpadSystemCommonPolicy(u64 aruid);
Result SetSupportedNpadStyleSet(u64 aruid, Core::HID::NpadStyleSet style_set);
Result GetSupportedNpadStyleSet(Core::HID::NpadStyleSet& out_style_Set, u64 aruid) const;
Result GetMaskedSupportedNpadStyleSet(Core::HID::NpadStyleSet& out_style_set, u64 aruid) const;
Result GetAvailableStyleset(Core::HID::NpadStyleSet& out_style_set, u64 aruid) const;
NpadRevision GetNpadRevision(u64 aruid) const;
void SetNpadRevision(u64 aruid, NpadRevision revision);
Result IsSupportedNpadStyleSet(bool& is_set, u64 aruid);
Result SetNpadJoyHoldType(u64 aruid, NpadJoyHoldType hold_type);
Result GetNpadJoyHoldType(NpadJoyHoldType& hold_type, u64 aruid) const;
Result SetNpadHandheldActivationMode(u64 aruid, NpadHandheldActivationMode activation_mode);
Result GetNpadHandheldActivationMode(NpadHandheldActivationMode& activation_mode,
u64 aruid) const;
Result SetSupportedNpadIdType(u64 aruid,
std::span<const Core::HID::NpadIdType> supported_npad_list);
bool IsControllerSupported(u64 aruid, Core::HID::NpadStyleIndex style_index) const;
Result SetLrAssignmentMode(u64 aruid, bool is_enabled);
Result GetLrAssignmentMode(bool& is_enabled, u64 aruid) const;
Result SetAssigningSingleOnSlSrPress(u64 aruid, bool is_enabled);
Result IsAssigningSingleOnSlSrPressEnabled(bool& is_enabled, u64 aruid) const;
Result AcquireNpadStyleSetUpdateEventHandle(u64 aruid, Kernel::KReadableEvent** out_event,
Core::HID::NpadIdType npad_id);
Result SignalStyleSetUpdateEvent(u64 aruid, Core::HID::NpadIdType npad_id);
Result GetHomeProtectionEnabled(bool& is_enabled, u64 aruid,
Core::HID::NpadIdType npad_id) const;
Result SetHomeProtectionEnabled(u64 aruid, Core::HID::NpadIdType npad_id, bool is_enabled);
Result SetNpadAnalogStickUseCenterClamp(u64 aruid, bool is_enabled);
Result SetButtonConfig(u64 aruid, Core::HID::NpadIdType npad_id, std::size_t index,
Core::HID::NpadButton button_config);
Core::HID::NpadButton GetButtonConfig(u64 aruid, Core::HID::NpadIdType npad_id,
std::size_t index, Core::HID::NpadButton mask,
bool is_enabled);
void ResetButtonConfig();
Result SetNpadCaptureButtonAssignment(u64 aruid, Core::HID::NpadStyleSet npad_style_set,
Core::HID::NpadButton button_assignment);
Result ClearNpadCaptureButtonAssignment(u64 aruid);
std::size_t GetNpadCaptureButtonAssignment(std::span<Core::HID::NpadButton> out_list,
u64 aruid) const;
Result SetNpadSystemExtStateEnabled(u64 aruid, bool is_enabled);
private:
NPadData active_data{};
AruidRegisterList registration_list{};
std::array<NpadState, AruidIndexMax> state{};
u64 active_data_aruid{};
NpadJoyHoldType default_hold_type{};
s32 ref_counter{};
KernelHelpers::ServiceContext& service_context;
};
} // namespace Service::HID

View file

@ -0,0 +1,255 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "hid_core/hid_types.h"
namespace Service::HID {
static constexpr std::size_t MaxSupportedNpadIdTypes = 10;
static constexpr std::size_t StyleIndexCount = 7;
// This is nn::hid::NpadJoyHoldType
enum class NpadJoyHoldType : u64 {
Vertical = 0,
Horizontal = 1,
};
// This is nn::hid::NpadJoyAssignmentMode
enum class NpadJoyAssignmentMode : u32 {
Dual = 0,
Single = 1,
};
// This is nn::hid::NpadJoyDeviceType
enum class NpadJoyDeviceType : s64 {
Left = 0,
Right = 1,
};
// This is nn::hid::NpadHandheldActivationMode
enum class NpadHandheldActivationMode : u64 {
Dual = 0,
Single = 1,
None = 2,
MaxActivationMode = 3,
};
// This is nn::hid::system::AppletFooterUiAttributesSet
struct AppletFooterUiAttributes {
INSERT_PADDING_BYTES(0x4);
};
// This is nn::hid::system::AppletFooterUiType
enum class AppletFooterUiType : u8 {
None = 0,
HandheldNone = 1,
HandheldJoyConLeftOnly = 2,
HandheldJoyConRightOnly = 3,
HandheldJoyConLeftJoyConRight = 4,
JoyDual = 5,
JoyDualLeftOnly = 6,
JoyDualRightOnly = 7,
JoyLeftHorizontal = 8,
JoyLeftVertical = 9,
JoyRightHorizontal = 10,
JoyRightVertical = 11,
SwitchProController = 12,
CompatibleProController = 13,
CompatibleJoyCon = 14,
LarkHvc1 = 15,
LarkHvc2 = 16,
LarkNesLeft = 17,
LarkNesRight = 18,
Lucia = 19,
Verification = 20,
Lagon = 21,
};
using AppletFooterUiVariant = u8;
// This is "nn::hid::system::AppletDetailedUiType".
struct AppletDetailedUiType {
AppletFooterUiVariant ui_variant;
INSERT_PADDING_BYTES(0x2);
AppletFooterUiType footer;
};
static_assert(sizeof(AppletDetailedUiType) == 0x4, "AppletDetailedUiType is an invalid size");
// This is nn::hid::NpadCommunicationMode
enum class NpadCommunicationMode : u64 {
Mode_5ms = 0,
Mode_10ms = 1,
Mode_15ms = 2,
Default = 3,
};
enum class NpadRevision : u32 {
Revision0 = 0,
Revision1 = 1,
Revision2 = 2,
Revision3 = 3,
};
// This is nn::hid::detail::ColorAttribute
enum class ColorAttribute : u32 {
Ok = 0,
ReadError = 1,
NoController = 2,
};
static_assert(sizeof(ColorAttribute) == 4, "ColorAttribute is an invalid size");
// This is nn::hid::detail::NpadFullKeyColorState
struct NpadFullKeyColorState {
ColorAttribute attribute{ColorAttribute::NoController};
Core::HID::NpadControllerColor fullkey{};
};
static_assert(sizeof(NpadFullKeyColorState) == 0xC, "NpadFullKeyColorState is an invalid size");
// This is nn::hid::detail::NpadJoyColorState
struct NpadJoyColorState {
ColorAttribute attribute{ColorAttribute::NoController};
Core::HID::NpadControllerColor left{};
Core::HID::NpadControllerColor right{};
};
static_assert(sizeof(NpadJoyColorState) == 0x14, "NpadJoyColorState is an invalid size");
// This is nn::hid::NpadAttribute
struct NpadAttribute {
union {
u32 raw{};
BitField<0, 1, u32> is_connected;
BitField<1, 1, u32> is_wired;
BitField<2, 1, u32> is_left_connected;
BitField<3, 1, u32> is_left_wired;
BitField<4, 1, u32> is_right_connected;
BitField<5, 1, u32> is_right_wired;
};
};
static_assert(sizeof(NpadAttribute) == 4, "NpadAttribute is an invalid size");
// This is nn::hid::NpadFullKeyState
// This is nn::hid::NpadHandheldState
// This is nn::hid::NpadJoyDualState
// This is nn::hid::NpadJoyLeftState
// This is nn::hid::NpadJoyRightState
// This is nn::hid::NpadPalmaState
// This is nn::hid::NpadSystemExtState
struct NPadGenericState {
s64_le sampling_number{};
Core::HID::NpadButtonState npad_buttons{};
Core::HID::AnalogStickState l_stick{};
Core::HID::AnalogStickState r_stick{};
NpadAttribute connection_status{};
INSERT_PADDING_BYTES(4); // Reserved
};
static_assert(sizeof(NPadGenericState) == 0x28, "NPadGenericState is an invalid size");
// This is nn::hid::server::NpadGcTriggerState
struct NpadGcTriggerState {
s64 sampling_number{};
s32 l_analog{};
s32 r_analog{};
};
static_assert(sizeof(NpadGcTriggerState) == 0x10, "NpadGcTriggerState is an invalid size");
// This is nn::hid::NpadSystemProperties
struct NPadSystemProperties {
union {
s64 raw{};
BitField<0, 1, s64> is_charging_joy_dual;
BitField<1, 1, s64> is_charging_joy_left;
BitField<2, 1, s64> is_charging_joy_right;
BitField<3, 1, s64> is_powered_joy_dual;
BitField<4, 1, s64> is_powered_joy_left;
BitField<5, 1, s64> is_powered_joy_right;
BitField<9, 1, s64> is_system_unsupported_button;
BitField<10, 1, s64> is_system_ext_unsupported_button;
BitField<11, 1, s64> is_vertical;
BitField<12, 1, s64> is_horizontal;
BitField<13, 1, s64> use_plus;
BitField<14, 1, s64> use_minus;
BitField<15, 1, s64> use_directional_buttons;
};
};
static_assert(sizeof(NPadSystemProperties) == 0x8, "NPadSystemProperties is an invalid size");
// This is nn::hid::NpadSystemButtonProperties
struct NpadSystemButtonProperties {
union {
s32 raw{};
BitField<0, 1, s32> is_home_button_protection_enabled;
};
};
static_assert(sizeof(NpadSystemButtonProperties) == 0x4, "NPadButtonProperties is an invalid size");
// This is nn::hid::system::DeviceType
struct DeviceType {
union {
u32 raw{};
BitField<0, 1, s32> fullkey;
BitField<1, 1, s32> debug_pad;
BitField<2, 1, s32> handheld_left;
BitField<3, 1, s32> handheld_right;
BitField<4, 1, s32> joycon_left;
BitField<5, 1, s32> joycon_right;
BitField<6, 1, s32> palma;
BitField<7, 1, s32> lark_hvc_left;
BitField<8, 1, s32> lark_hvc_right;
BitField<9, 1, s32> lark_nes_left;
BitField<10, 1, s32> lark_nes_right;
BitField<11, 1, s32> handheld_lark_hvc_left;
BitField<12, 1, s32> handheld_lark_hvc_right;
BitField<13, 1, s32> handheld_lark_nes_left;
BitField<14, 1, s32> handheld_lark_nes_right;
BitField<15, 1, s32> lucia;
BitField<16, 1, s32> lagon;
BitField<17, 1, s32> lager;
BitField<31, 1, s32> system;
};
};
// This is nn::hid::detail::NfcXcdDeviceHandleStateImpl
struct NfcXcdDeviceHandleStateImpl {
u64 handle{};
bool is_available{};
bool is_activated{};
INSERT_PADDING_BYTES(0x6); // Reserved
u64 sampling_number{};
};
static_assert(sizeof(NfcXcdDeviceHandleStateImpl) == 0x18,
"NfcXcdDeviceHandleStateImpl is an invalid size");
// This is nn::hid::NpadLarkType
enum class NpadLarkType : u32 {
Invalid,
H1,
H2,
NL,
NR,
};
// This is nn::hid::NpadLuciaType
enum class NpadLuciaType : u32 {
Invalid,
J,
E,
U,
};
// This is nn::hid::NpadLagonType
enum class NpadLagonType : u32 {
Invalid,
};
// This is nn::hid::NpadLagerType
enum class NpadLagerType : u32 {
Invalid,
J,
E,
U,
};
} // namespace Service::HID

View file

@ -0,0 +1,225 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core_timing.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/service/kernel_helpers.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "hid_core/resources/palma/palma.h"
namespace Service::HID {
Palma::Palma(Core::HID::HIDCore& hid_core_, KernelHelpers::ServiceContext& service_context_)
: ControllerBase{hid_core_}, service_context{service_context_} {
controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Other);
operation_complete_event = service_context.CreateEvent("hid:PalmaOperationCompleteEvent");
}
Palma::~Palma() {
service_context.CloseEvent(operation_complete_event);
};
void Palma::OnInit() {}
void Palma::OnRelease() {}
void Palma::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
if (!IsControllerActivated()) {
return;
}
}
Result Palma::GetPalmaConnectionHandle(Core::HID::NpadIdType npad_id,
PalmaConnectionHandle& handle) {
active_handle.npad_id = npad_id;
handle = active_handle;
return ResultSuccess;
}
Result Palma::InitializePalma(const PalmaConnectionHandle& handle) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
Activate();
return ResultSuccess;
}
Kernel::KReadableEvent& Palma::AcquirePalmaOperationCompleteEvent(
const PalmaConnectionHandle& handle) const {
if (handle.npad_id != active_handle.npad_id) {
LOG_ERROR(Service_HID, "Invalid npad id {}", handle.npad_id);
}
return operation_complete_event->GetReadableEvent();
}
Result Palma::GetPalmaOperationInfo(const PalmaConnectionHandle& handle,
PalmaOperationType& operation_type,
PalmaOperationData& data) const {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
operation_type = operation.operation;
data = operation.data;
return ResultSuccess;
}
Result Palma::PlayPalmaActivity(const PalmaConnectionHandle& handle, u64 palma_activity) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
operation.operation = PalmaOperationType::PlayActivity;
operation.result = PalmaResultSuccess;
operation.data = {};
operation_complete_event->Signal();
return ResultSuccess;
}
Result Palma::SetPalmaFrModeType(const PalmaConnectionHandle& handle, PalmaFrModeType fr_mode_) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
fr_mode = fr_mode_;
return ResultSuccess;
}
Result Palma::ReadPalmaStep(const PalmaConnectionHandle& handle) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
operation.operation = PalmaOperationType::ReadStep;
operation.result = PalmaResultSuccess;
operation.data = {};
operation_complete_event->Signal();
return ResultSuccess;
}
Result Palma::EnablePalmaStep(const PalmaConnectionHandle& handle, bool is_enabled) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
return ResultSuccess;
}
Result Palma::ResetPalmaStep(const PalmaConnectionHandle& handle) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
return ResultSuccess;
}
void Palma::ReadPalmaApplicationSection() {}
void Palma::WritePalmaApplicationSection() {}
Result Palma::ReadPalmaUniqueCode(const PalmaConnectionHandle& handle) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
operation.operation = PalmaOperationType::ReadUniqueCode;
operation.result = PalmaResultSuccess;
operation.data = {};
operation_complete_event->Signal();
return ResultSuccess;
}
Result Palma::SetPalmaUniqueCodeInvalid(const PalmaConnectionHandle& handle) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
operation.operation = PalmaOperationType::SetUniqueCodeInvalid;
operation.result = PalmaResultSuccess;
operation.data = {};
operation_complete_event->Signal();
return ResultSuccess;
}
void Palma::WritePalmaActivityEntry() {}
Result Palma::WritePalmaRgbLedPatternEntry(const PalmaConnectionHandle& handle, u64 unknown) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
operation.operation = PalmaOperationType::WriteRgbLedPatternEntry;
operation.result = PalmaResultSuccess;
operation.data = {};
operation_complete_event->Signal();
return ResultSuccess;
}
Result Palma::WritePalmaWaveEntry(const PalmaConnectionHandle& handle, PalmaWaveSet wave,
Common::ProcessAddress t_mem, u64 size) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
operation.operation = PalmaOperationType::WriteWaveEntry;
operation.result = PalmaResultSuccess;
operation.data = {};
operation_complete_event->Signal();
return ResultSuccess;
}
Result Palma::SetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& handle,
s32 database_id_version_) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
database_id_version = database_id_version_;
operation.operation = PalmaOperationType::ReadDataBaseIdentificationVersion;
operation.result = PalmaResultSuccess;
operation.data[0] = {};
operation_complete_event->Signal();
return ResultSuccess;
}
Result Palma::GetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& handle) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
operation.operation = PalmaOperationType::ReadDataBaseIdentificationVersion;
operation.result = PalmaResultSuccess;
operation.data = {};
operation.data[0] = static_cast<u8>(database_id_version);
operation_complete_event->Signal();
return ResultSuccess;
}
void Palma::SuspendPalmaFeature() {}
Result Palma::GetPalmaOperationResult(const PalmaConnectionHandle& handle) const {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
return operation.result;
}
void Palma::ReadPalmaPlayLog() {}
void Palma::ResetPalmaPlayLog() {}
void Palma::SetIsPalmaAllConnectable(bool is_all_connectable) {
// If true controllers are able to be paired
is_connectable = is_all_connectable;
}
void Palma::SetIsPalmaPairedConnectable() {}
Result Palma::PairPalma(const PalmaConnectionHandle& handle) {
if (handle.npad_id != active_handle.npad_id) {
return InvalidPalmaHandle;
}
// TODO: Do something
return ResultSuccess;
}
void Palma::SetPalmaBoostMode(bool boost_mode) {}
void Palma::CancelWritePalmaWaveEntry() {}
void Palma::EnablePalmaBoostMode() {}
void Palma::GetPalmaBluetoothAddress() {}
void Palma::SetDisallowedPalmaConnection() {}
} // namespace Service::HID

View file

@ -0,0 +1,163 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include "common/common_funcs.h"
#include "common/typed_address.h"
#include "hid_core/hid_result.h"
#include "hid_core/hid_types.h"
#include "hid_core/resources/controller_base.h"
namespace Kernel {
class KEvent;
class KReadableEvent;
} // namespace Kernel
namespace Service::KernelHelpers {
class ServiceContext;
}
namespace Core::HID {
class EmulatedController;
} // namespace Core::HID
namespace Service::HID {
class Palma final : public ControllerBase {
public:
using PalmaOperationData = std::array<u8, 0x140>;
// This is nn::hid::PalmaOperationType
enum class PalmaOperationType {
PlayActivity,
SetFrModeType,
ReadStep,
EnableStep,
ResetStep,
ReadApplicationSection,
WriteApplicationSection,
ReadUniqueCode,
SetUniqueCodeInvalid,
WriteActivityEntry,
WriteRgbLedPatternEntry,
WriteWaveEntry,
ReadDataBaseIdentificationVersion,
WriteDataBaseIdentificationVersion,
SuspendFeature,
ReadPlayLog,
ResetPlayLog,
};
// This is nn::hid::PalmaWaveSet
enum class PalmaWaveSet : u64 {
Small,
Medium,
Large,
};
// This is nn::hid::PalmaFrModeType
enum class PalmaFrModeType : u64 {
Off,
B01,
B02,
B03,
Downloaded,
};
// This is nn::hid::PalmaFeature
enum class PalmaFeature : u64 {
FrMode,
RumbleFeedback,
Step,
MuteSwitch,
};
// This is nn::hid::PalmaOperationInfo
struct PalmaOperationInfo {
PalmaOperationType operation{};
Result result{PalmaResultSuccess};
PalmaOperationData data{};
};
static_assert(sizeof(PalmaOperationInfo) == 0x148, "PalmaOperationInfo is an invalid size");
// This is nn::hid::PalmaActivityEntry
struct PalmaActivityEntry {
u32 rgb_led_pattern_index;
INSERT_PADDING_BYTES(2);
PalmaWaveSet wave_set;
u32 wave_index;
INSERT_PADDING_BYTES(12);
};
static_assert(sizeof(PalmaActivityEntry) == 0x20, "PalmaActivityEntry is an invalid size");
struct PalmaConnectionHandle {
Core::HID::NpadIdType npad_id;
INSERT_PADDING_BYTES(4); // Unknown
};
static_assert(sizeof(PalmaConnectionHandle) == 0x8,
"PalmaConnectionHandle has incorrect size.");
explicit Palma(Core::HID::HIDCore& hid_core_, KernelHelpers::ServiceContext& service_context_);
~Palma() override;
// Called when the controller is initialized
void OnInit() override;
// When the controller is released
void OnRelease() override;
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
Result GetPalmaConnectionHandle(Core::HID::NpadIdType npad_id, PalmaConnectionHandle& handle);
Result InitializePalma(const PalmaConnectionHandle& handle);
Kernel::KReadableEvent& AcquirePalmaOperationCompleteEvent(
const PalmaConnectionHandle& handle) const;
Result GetPalmaOperationInfo(const PalmaConnectionHandle& handle,
PalmaOperationType& operation_type,
PalmaOperationData& data) const;
Result PlayPalmaActivity(const PalmaConnectionHandle& handle, u64 palma_activity);
Result SetPalmaFrModeType(const PalmaConnectionHandle& handle, PalmaFrModeType fr_mode_);
Result ReadPalmaStep(const PalmaConnectionHandle& handle);
Result EnablePalmaStep(const PalmaConnectionHandle& handle, bool is_enabled);
Result ResetPalmaStep(const PalmaConnectionHandle& handle);
Result ReadPalmaUniqueCode(const PalmaConnectionHandle& handle);
Result SetPalmaUniqueCodeInvalid(const PalmaConnectionHandle& handle);
Result WritePalmaRgbLedPatternEntry(const PalmaConnectionHandle& handle, u64 unknown);
Result WritePalmaWaveEntry(const PalmaConnectionHandle& handle, PalmaWaveSet wave,
Common::ProcessAddress t_mem, u64 size);
Result SetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& handle,
s32 database_id_version_);
Result GetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& handle);
Result GetPalmaOperationResult(const PalmaConnectionHandle& handle) const;
void SetIsPalmaAllConnectable(bool is_all_connectable);
Result PairPalma(const PalmaConnectionHandle& handle);
void SetPalmaBoostMode(bool boost_mode);
private:
void ReadPalmaApplicationSection();
void WritePalmaApplicationSection();
void WritePalmaActivityEntry();
void SuspendPalmaFeature();
void ReadPalmaPlayLog();
void ResetPalmaPlayLog();
void SetIsPalmaPairedConnectable();
void CancelWritePalmaWaveEntry();
void EnablePalmaBoostMode();
void GetPalmaBluetoothAddress();
void SetDisallowedPalmaConnection();
bool is_connectable{};
s32 database_id_version{};
PalmaOperationInfo operation{};
PalmaFrModeType fr_mode{};
PalmaConnectionHandle active_handle{};
Core::HID::EmulatedController* controller;
Kernel::KEvent* operation_complete_event;
KernelHelpers::ServiceContext& service_context;
};
} // namespace Service::HID

View file

@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include "common/common_types.h"
namespace Service::HID {
template <typename State>
struct AtomicStorage {
s64 sampling_number;
State state;
};
template <typename State, std::size_t max_buffer_size>
struct Lifo {
s64 timestamp{};
s64 total_buffer_count = static_cast<s64>(max_buffer_size);
s64 buffer_tail{};
s64 buffer_count{};
std::array<AtomicStorage<State>, max_buffer_size> entries{};
const AtomicStorage<State>& ReadCurrentEntry() const {
return entries[buffer_tail];
}
const AtomicStorage<State>& ReadPreviousEntry() const {
return entries[GetPreviousEntryIndex()];
}
std::size_t GetPreviousEntryIndex() const {
return static_cast<size_t>((buffer_tail + max_buffer_size - 1) % max_buffer_size);
}
std::size_t GetNextEntryIndex() const {
return static_cast<size_t>((buffer_tail + 1) % max_buffer_size);
}
void WriteNextEntry(const State& new_state) {
if (buffer_count < static_cast<s64>(max_buffer_size) - 1) {
buffer_count++;
}
buffer_tail = GetNextEntryIndex();
const auto& previous_entry = ReadPreviousEntry();
entries[buffer_tail].sampling_number = previous_entry.sampling_number + 1;
entries[buffer_tail].state = new_state;
}
};
} // namespace Service::HID

View file

@ -0,0 +1,240 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/vector_math.h"
#include "hid_core/hid_types.h"
#include "hid_core/resources/debug_pad/debug_pad_types.h"
#include "hid_core/resources/keyboard/keyboard_types.h"
#include "hid_core/resources/mouse/mouse_types.h"
#include "hid_core/resources/npad/npad_types.h"
#include "hid_core/resources/ring_lifo.h"
#include "hid_core/resources/touch_screen/touch_types.h"
namespace Service::HID {
static const std::size_t HidEntryCount = 17;
struct CommonHeader {
s64 timestamp{};
s64 total_entry_count{};
s64 last_entry_index{};
s64 entry_count{};
};
static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size");
// This is nn::hid::detail::DebugPadSharedMemoryFormat
struct DebugPadSharedMemoryFormat {
// This is nn::hid::detail::DebugPadLifo
Lifo<DebugPadState, HidEntryCount> debug_pad_lifo{};
static_assert(sizeof(debug_pad_lifo) == 0x2C8, "debug_pad_lifo is an invalid size");
INSERT_PADDING_WORDS(0x4E);
};
static_assert(sizeof(DebugPadSharedMemoryFormat) == 0x400,
"DebugPadSharedMemoryFormat is an invalid size");
// This is nn::hid::detail::TouchScreenSharedMemoryFormat
struct TouchScreenSharedMemoryFormat {
// This is nn::hid::detail::TouchScreenLifo
Lifo<TouchScreenState, HidEntryCount> touch_screen_lifo{};
static_assert(sizeof(touch_screen_lifo) == 0x2C38, "touch_screen_lifo is an invalid size");
INSERT_PADDING_WORDS(0xF2);
};
static_assert(sizeof(TouchScreenSharedMemoryFormat) == 0x3000,
"TouchScreenSharedMemoryFormat is an invalid size");
// This is nn::hid::detail::MouseSharedMemoryFormat
struct MouseSharedMemoryFormat {
// This is nn::hid::detail::MouseLifo
Lifo<Core::HID::MouseState, HidEntryCount> mouse_lifo{};
static_assert(sizeof(mouse_lifo) == 0x350, "mouse_lifo is an invalid size");
INSERT_PADDING_WORDS(0x2C);
};
static_assert(sizeof(MouseSharedMemoryFormat) == 0x400,
"MouseSharedMemoryFormat is an invalid size");
// This is nn::hid::detail::KeyboardSharedMemoryFormat
struct KeyboardSharedMemoryFormat {
// This is nn::hid::detail::KeyboardLifo
Lifo<KeyboardState, HidEntryCount> keyboard_lifo{};
static_assert(sizeof(keyboard_lifo) == 0x3D8, "keyboard_lifo is an invalid size");
INSERT_PADDING_WORDS(0xA);
};
static_assert(sizeof(KeyboardSharedMemoryFormat) == 0x400,
"KeyboardSharedMemoryFormat is an invalid size");
// This is nn::hid::detail::DigitizerSharedMemoryFormat
struct DigitizerSharedMemoryFormat {
CommonHeader header;
INSERT_PADDING_BYTES(0xFE0);
};
static_assert(sizeof(DigitizerSharedMemoryFormat) == 0x1000,
"DigitizerSharedMemoryFormat is an invalid size");
// This is nn::hid::detail::HomeButtonSharedMemoryFormat
struct HomeButtonSharedMemoryFormat {
CommonHeader header;
INSERT_PADDING_BYTES(0x1E0);
};
static_assert(sizeof(HomeButtonSharedMemoryFormat) == 0x200,
"HomeButtonSharedMemoryFormat is an invalid size");
// This is nn::hid::detail::SleepButtonSharedMemoryFormat
struct SleepButtonSharedMemoryFormat {
CommonHeader header;
INSERT_PADDING_BYTES(0x1E0);
};
static_assert(sizeof(SleepButtonSharedMemoryFormat) == 0x200,
"SleepButtonSharedMemoryFormat is an invalid size");
// This is nn::hid::detail::CaptureButtonSharedMemoryFormat
struct CaptureButtonSharedMemoryFormat {
CommonHeader header;
INSERT_PADDING_BYTES(0x1E0);
};
static_assert(sizeof(CaptureButtonSharedMemoryFormat) == 0x200,
"CaptureButtonSharedMemoryFormat is an invalid size");
// This is nn::hid::detail::InputDetectorSharedMemoryFormat
struct InputDetectorSharedMemoryFormat {
CommonHeader header;
INSERT_PADDING_BYTES(0x7E0);
};
static_assert(sizeof(InputDetectorSharedMemoryFormat) == 0x800,
"InputDetectorSharedMemoryFormat is an invalid size");
// This is nn::hid::detail::UniquePadSharedMemoryFormat
struct UniquePadSharedMemoryFormat {
CommonHeader header;
INSERT_PADDING_BYTES(0x3FE0);
};
static_assert(sizeof(UniquePadSharedMemoryFormat) == 0x4000,
"UniquePadSharedMemoryFormat is an invalid size");
// This is nn::hid::detail::NpadSixAxisSensorLifo
struct NpadSixAxisSensorLifo {
Lifo<Core::HID::SixAxisSensorState, HidEntryCount> lifo;
};
// This is nn::hid::detail::NpadInternalState
struct NpadInternalState {
Core::HID::NpadStyleTag style_tag{Core::HID::NpadStyleSet::None};
NpadJoyAssignmentMode assignment_mode{NpadJoyAssignmentMode::Dual};
NpadFullKeyColorState fullkey_color{};
NpadJoyColorState joycon_color{};
Lifo<NPadGenericState, HidEntryCount> fullkey_lifo{};
Lifo<NPadGenericState, HidEntryCount> handheld_lifo{};
Lifo<NPadGenericState, HidEntryCount> joy_dual_lifo{};
Lifo<NPadGenericState, HidEntryCount> joy_left_lifo{};
Lifo<NPadGenericState, HidEntryCount> joy_right_lifo{};
Lifo<NPadGenericState, HidEntryCount> palma_lifo{};
Lifo<NPadGenericState, HidEntryCount> system_ext_lifo{};
NpadSixAxisSensorLifo sixaxis_fullkey_lifo{};
NpadSixAxisSensorLifo sixaxis_handheld_lifo{};
NpadSixAxisSensorLifo sixaxis_dual_left_lifo{};
NpadSixAxisSensorLifo sixaxis_dual_right_lifo{};
NpadSixAxisSensorLifo sixaxis_left_lifo{};
NpadSixAxisSensorLifo sixaxis_right_lifo{};
DeviceType device_type{};
INSERT_PADDING_BYTES(0x4); // Reserved
NPadSystemProperties system_properties{};
NpadSystemButtonProperties button_properties{};
Core::HID::NpadBatteryLevel battery_level_dual{};
Core::HID::NpadBatteryLevel battery_level_left{};
Core::HID::NpadBatteryLevel battery_level_right{};
AppletFooterUiAttributes applet_footer_attributes{};
AppletFooterUiType applet_footer_type{AppletFooterUiType::None};
INSERT_PADDING_BYTES(0x5B); // Reserved
INSERT_PADDING_BYTES(0x20); // Unknown
Lifo<NpadGcTriggerState, HidEntryCount> gc_trigger_lifo{};
NpadLarkType lark_type_l_and_main{};
NpadLarkType lark_type_r{};
NpadLuciaType lucia_type{};
NpadLagerType lager_type{};
Core::HID::SixAxisSensorProperties sixaxis_fullkey_properties;
Core::HID::SixAxisSensorProperties sixaxis_handheld_properties;
Core::HID::SixAxisSensorProperties sixaxis_dual_left_properties;
Core::HID::SixAxisSensorProperties sixaxis_dual_right_properties;
Core::HID::SixAxisSensorProperties sixaxis_left_properties;
Core::HID::SixAxisSensorProperties sixaxis_right_properties;
};
static_assert(sizeof(NpadInternalState) == 0x43F8, "NpadInternalState is an invalid size");
// This is nn::hid::detail::NpadSharedMemoryEntry
struct NpadSharedMemoryEntry {
NpadInternalState internal_state;
INSERT_PADDING_BYTES(0xC08);
};
static_assert(sizeof(NpadSharedMemoryEntry) == 0x5000, "NpadSharedMemoryEntry is an invalid size");
// This is nn::hid::detail::NpadSharedMemoryFormat
struct NpadSharedMemoryFormat {
std::array<NpadSharedMemoryEntry, MaxSupportedNpadIdTypes> npad_entry;
};
static_assert(sizeof(NpadSharedMemoryFormat) == 0x32000,
"NpadSharedMemoryFormat is an invalid size");
// This is nn::hid::detail::GestureSharedMemoryFormat
struct GestureSharedMemoryFormat {
// This is nn::hid::detail::GestureLifo
Lifo<GestureState, HidEntryCount> gesture_lifo{};
static_assert(sizeof(gesture_lifo) == 0x708, "gesture_lifo is an invalid size");
INSERT_PADDING_WORDS(0x3E);
};
static_assert(sizeof(GestureSharedMemoryFormat) == 0x800,
"GestureSharedMemoryFormat is an invalid size");
// This is nn::hid::detail::ConsoleSixAxisSensorSharedMemoryFormat
struct ConsoleSixAxisSensorSharedMemoryFormat {
u64 sampling_number{};
bool is_seven_six_axis_sensor_at_rest{};
INSERT_PADDING_BYTES(3); // padding
f32 verticalization_error{};
Common::Vec3f gyro_bias{};
INSERT_PADDING_BYTES(4); // padding
};
static_assert(sizeof(ConsoleSixAxisSensorSharedMemoryFormat) == 0x20,
"ConsoleSixAxisSensorSharedMemoryFormat is an invalid size");
// This is nn::hid::detail::SharedMemoryFormat
struct SharedMemoryFormat {
void Initialize() {}
DebugPadSharedMemoryFormat debug_pad;
TouchScreenSharedMemoryFormat touch_screen;
MouseSharedMemoryFormat mouse;
KeyboardSharedMemoryFormat keyboard;
DigitizerSharedMemoryFormat digitizer;
HomeButtonSharedMemoryFormat home_button;
SleepButtonSharedMemoryFormat sleep_button;
CaptureButtonSharedMemoryFormat capture_button;
InputDetectorSharedMemoryFormat input_detector;
UniquePadSharedMemoryFormat unique_pad;
NpadSharedMemoryFormat npad;
GestureSharedMemoryFormat gesture;
ConsoleSixAxisSensorSharedMemoryFormat console;
INSERT_PADDING_BYTES(0x19E0);
MouseSharedMemoryFormat debug_mouse;
INSERT_PADDING_BYTES(0x2000);
};
static_assert(offsetof(SharedMemoryFormat, debug_pad) == 0x0, "debug_pad has wrong offset");
static_assert(offsetof(SharedMemoryFormat, touch_screen) == 0x400, "touch_screen has wrong offset");
static_assert(offsetof(SharedMemoryFormat, mouse) == 0x3400, "mouse has wrong offset");
static_assert(offsetof(SharedMemoryFormat, keyboard) == 0x3800, "keyboard has wrong offset");
static_assert(offsetof(SharedMemoryFormat, digitizer) == 0x3C00, "digitizer has wrong offset");
static_assert(offsetof(SharedMemoryFormat, home_button) == 0x4C00, "home_button has wrong offset");
static_assert(offsetof(SharedMemoryFormat, sleep_button) == 0x4E00,
"sleep_button has wrong offset");
static_assert(offsetof(SharedMemoryFormat, capture_button) == 0x5000,
"capture_button has wrong offset");
static_assert(offsetof(SharedMemoryFormat, input_detector) == 0x5200,
"input_detector has wrong offset");
static_assert(offsetof(SharedMemoryFormat, npad) == 0x9A00, "npad has wrong offset");
static_assert(offsetof(SharedMemoryFormat, gesture) == 0x3BA00, "gesture has wrong offset");
static_assert(offsetof(SharedMemoryFormat, console) == 0x3C200, "console has wrong offset");
static_assert(offsetof(SharedMemoryFormat, debug_mouse) == 0x3DC00, "debug_mouse has wrong offset");
static_assert(sizeof(SharedMemoryFormat) == 0x40000, "SharedMemoryFormat is an invalid size");
} // namespace Service::HID

View file

@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "core/core.h"
#include "core/hle/kernel/k_shared_memory.h"
#include "hid_core/hid_result.h"
#include "hid_core/resources/applet_resource.h"
#include "hid_core/resources/shared_memory_format.h"
#include "hid_core/resources/shared_memory_holder.h"
namespace Service::HID {
SharedMemoryHolder::SharedMemoryHolder() {}
SharedMemoryHolder::~SharedMemoryHolder() {
Finalize();
}
Result SharedMemoryHolder::Initialize(Core::System& system) {
shared_memory = Kernel::KSharedMemory::Create(system.Kernel());
const Result result = shared_memory->Initialize(
system.DeviceMemory(), nullptr, Kernel::Svc::MemoryPermission::None,
Kernel::Svc::MemoryPermission::Read, sizeof(SharedMemoryFormat));
if (result.IsError()) {
return result;
}
Kernel::KSharedMemory::Register(system.Kernel(), shared_memory);
is_created = true;
is_mapped = true;
address = std::construct_at(reinterpret_cast<SharedMemoryFormat*>(shared_memory->GetPointer()));
return ResultSuccess;
}
void SharedMemoryHolder::Finalize() {
if (address != nullptr) {
shared_memory->Close();
}
is_created = false;
is_mapped = false;
address = nullptr;
}
bool SharedMemoryHolder::IsMapped() {
return is_mapped;
}
SharedMemoryFormat* SharedMemoryHolder::GetAddress() {
return address;
}
Kernel::KSharedMemory* SharedMemoryHolder::GetHandle() {
return shared_memory;
}
} // namespace Service::HID

Some files were not shown because too many files have changed in this diff Show more