From 919cc325739103d4638d4f210404de945abd609d Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Tue, 3 Nov 2026 19:05:19 -0500 Subject: [PATCH 01/14] am: use Switchbrew's names for the Switch 2 commands Renames only, no behavior change. Only entries the Switch 2 work added; commands upstream Eden also lists keep their names. - ISelfController 22-24: GetScreenShotAcdIndex, GetScreenShotApparentPlatform, GetScreenShotApplicationProperty. - ISelfController 2000: GetGraphicsSupervisor; 2100-2401: SetRecordingLayerEnabled, SetSharingLayerEnabled, SetCopyrightLayerEnabled, SetRtcScreenSharingAudioEnabled, SetStreamPlayMirroringAudioEnabled. - ICommonStateGetter 650/651: Begin/EndUsingCameraResource; 2000: SetRtcModeChangedMessageEnabled; 2010: GetCurrentDisplayLayoutRatio. - IApplicationFunctions 230: IsTensorRtSupported. - appletOE 10: OpenApplicationProxy2; 2000: OpenCompatApplicationFunctions. - IGraphicsScaleController is nn::am::service:: IGraphicsScalingParametersFunctions (header renamed to match), and its command 10 is GetGraphicsScalingParameters. Switchbrew leaves IApplicationProxy 40 and 60 unnamed; they are named after the interfaces they return (IPerformanceFunctions, this one). --- src/core/CMakeLists.txt | 2 +- .../am/service/application_functions.cpp | 2 +- .../service/am/service/application_proxy.cpp | 15 +++++++------ .../service/am/service/application_proxy.h | 5 +++-- .../am/service/application_proxy_service.cpp | 6 ++--- .../am/service/common_state_getter.cpp | 8 +++---- ...> graphics_scaling_parameters_functions.h} | 19 +++++++++------- .../service/am/service/self_controller.cpp | 22 +++++++++---------- .../hle/service/am/service/self_controller.h | 2 +- 9 files changed, 43 insertions(+), 38 deletions(-) rename src/core/hle/service/am/service/{graphics_scale_controller.h => graphics_scaling_parameters_functions.h} (50%) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 86e8bd6027..42adb14f84 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -465,7 +465,7 @@ add_library(core STATIC hle/service/am/service/application_functions.cpp hle/service/am/service/application_functions.h hle/service/am/service/application_proxy.cpp - hle/service/am/service/graphics_scale_controller.h + hle/service/am/service/graphics_scaling_parameters_functions.h hle/service/am/service/application_proxy.h hle/service/am/service/application_proxy_service.cpp hle/service/am/service/application_proxy_service.h diff --git a/src/core/hle/service/am/service/application_functions.cpp b/src/core/hle/service/am/service/application_functions.cpp index cd5be56363..5a2d68535b 100644 --- a/src/core/hle/service/am/service/application_functions.cpp +++ b/src/core/hle/service/am/service/application_functions.cpp @@ -122,7 +122,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_, std::shared_ {200, &IApplicationFunctions::StubReplyZeroed, "GetLastApplicationExitReason"}, {210, D<&IApplicationFunctions::GetOperationModeRefreshEvent>, "GetOperationModeRefreshEvent (guessed)"}, {220, &IApplicationFunctions::StubReplyZeroed, "Unknown220"}, // [20.0.0+] - {230, &IApplicationFunctions::StubReplyZeroed, "Unknown230"}, + {230, &IApplicationFunctions::StubReplyZeroed, "IsTensorRtSupported"}, {300, &IApplicationFunctions::StubReplyInterface, "CreateMovieWriter"}, // [19.0.0+] {310, &IApplicationFunctions::StubReplyZeroed, "Unknown310"}, // [20.0.0+] {320, D<&IApplicationFunctions::Unknown320>, "Unknown320"}, // [20.0.0+] diff --git a/src/core/hle/service/am/service/application_proxy.cpp b/src/core/hle/service/am/service/application_proxy.cpp index 430f3c6888..fd0679b69b 100644 --- a/src/core/hle/service/am/service/application_proxy.cpp +++ b/src/core/hle/service/am/service/application_proxy.cpp @@ -11,7 +11,7 @@ #include "core/hle/service/am/service/common_state_getter.h" #include "core/hle/service/am/service/debug_functions.h" #include "core/hle/service/am/service/display_controller.h" -#include "core/hle/service/am/service/graphics_scale_controller.h" +#include "core/hle/service/am/service/graphics_scaling_parameters_functions.h" #include "core/hle/service/am/service/library_applet_creator.h" #include "core/hle/service/am/service/process_winding_controller.h" #include "core/hle/service/am/service/self_controller.h" @@ -35,10 +35,11 @@ IApplicationProxy::IApplicationProxy(Core::System& system_, std::shared_ptr, "GetProcessWindingController"}, {11, D<&IApplicationProxy::GetLibraryAppletCreator>, "GetLibraryAppletCreator"}, {20, D<&IApplicationProxy::GetApplicationFunctions>, "GetApplicationFunctions"}, - // Switch 2-only getters; see nx2_stub_interface.h. - {40, &IApplicationProxy::GetNx2Interface, "Nx2Getter40"}, + // Switch 2-only getters; see nx2_stub_interface.h. Switchbrew leaves 40 and 60 unnamed: + // their names follow the interfaces they return. + {40, &IApplicationProxy::GetNx2Interface, "GetPerformanceFunctions"}, {50, &IApplicationProxy::GetNx2Interface, "Nx2Getter50"}, - {60, D<&IApplicationProxy::GetGraphicsScaleController>, "GetGraphicsScaleController (Switch 2)"}, + {60, D<&IApplicationProxy::GetGraphicsScalingParametersFunctions>, "GetGraphicsScalingParametersFunctions"}, {70, &IApplicationProxy::GetNx2Interface, "Nx2Getter70"}, {80, &IApplicationProxy::GetNx2Interface, "Nx2Getter80"}, {90, &IApplicationProxy::GetNx2Interface, "Nx2Getter90"}, @@ -51,10 +52,10 @@ IApplicationProxy::IApplicationProxy(Core::System& system_, std::shared_ptr> out_controller) { +Result IApplicationProxy::GetGraphicsScalingParametersFunctions( + Out> out_functions) { LOG_DEBUG(Service_AM, "called"); - *out_controller = std::make_shared(system); + *out_functions = std::make_shared(system); R_SUCCEED(); } diff --git a/src/core/hle/service/am/service/application_proxy.h b/src/core/hle/service/am/service/application_proxy.h index 0a706be4fd..68de11b5e9 100644 --- a/src/core/hle/service/am/service/application_proxy.h +++ b/src/core/hle/service/am/service/application_proxy.h @@ -17,7 +17,7 @@ class IApplicationFunctions; class ICommonStateGetter; class IDebugFunctions; class IDisplayController; -class IGraphicsScaleController; +class IGraphicsScalingParametersFunctions; class ILibraryAppletCreator; class IProcessWindingController; class ISelfController; @@ -32,7 +32,8 @@ class IApplicationProxy final : public ServiceFramework { private: void GetNx2Interface(HLERequestContext& ctx); - Result GetGraphicsScaleController(Out> out_controller); + Result GetGraphicsScalingParametersFunctions( + Out> out_functions); Result GetAudioController(Out> out_audio_controller); Result GetDisplayController(Out> out_display_controller); Result GetProcessWindingController( diff --git a/src/core/hle/service/am/service/application_proxy_service.cpp b/src/core/hle/service/am/service/application_proxy_service.cpp index 9d332798b4..67030c0c7f 100644 --- a/src/core/hle/service/am/service/application_proxy_service.cpp +++ b/src/core/hle/service/am/service/application_proxy_service.cpp @@ -19,9 +19,9 @@ IApplicationProxyService::IApplicationProxyService(Core::System& system_, : ServiceFramework{system_, "appletOE"}, m_window_system{window_system} { static const FunctionInfo functions[] = { {0, D<&IApplicationProxyService::OpenApplicationProxy>, "OpenApplicationProxy"}, - // Switch 2 SDKs open the proxy with command 10, same arguments. - {10, D<&IApplicationProxyService::OpenApplicationProxy>, "OpenApplicationProxy (Switch 2)"}, - {2000, &IApplicationProxyService::StubReplyInterface, "Unknown2000"}, + // Switch 2 SDKs open the proxy with OpenApplicationProxy2, same arguments. + {10, D<&IApplicationProxyService::OpenApplicationProxy>, "OpenApplicationProxy2"}, + {2000, &IApplicationProxyService::StubReplyInterface, "OpenCompatApplicationFunctions"}, }; RegisterHandlers(functions); } diff --git a/src/core/hle/service/am/service/common_state_getter.cpp b/src/core/hle/service/am/service/common_state_getter.cpp index 2c5e536414..7191bf7ec4 100644 --- a/src/core/hle/service/am/service/common_state_getter.cpp +++ b/src/core/hle/service/am/service/common_state_getter.cpp @@ -79,8 +79,8 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_, std::shared_ptr, "Unknown610"}, //21.0.0+ {611, D<&ICommonStateGetter::Unknown611>, "Unknown611"}, //22.0.0+ - {650, &ICommonStateGetter::StubReplyZeroed, "Unknown650"}, - {651, &ICommonStateGetter::StubReplyZeroed, "Unknown651"}, + {650, &ICommonStateGetter::StubReplyZeroed, "BeginUsingCameraResource"}, + {651, &ICommonStateGetter::StubReplyZeroed, "EndUsingCameraResource"}, {900, D<&ICommonStateGetter::SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled>, "SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled"}, //11.0.0+ {910, &ICommonStateGetter::StubReplyZeroed, "GetLaunchRequiredTick"}, //17.0.0+ {1000, D<&ICommonStateGetter::BeginVrMode3d>, "BeginVrMode3d"}, //19.0.0+ @@ -89,8 +89,8 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_, std::shared_ptr, "GetVrLaboGoggleViewport"}, //21.0.0+ {1004, D<&ICommonStateGetter::GetPanelPhysicalSizeForSpecificTitle>, "GetPanelPhysicalSizeForSpecificTitle"}, //21.0.0+ {1005, D<&ICommonStateGetter::GetPanelResolutionForSpecificTitle>, "GetPanelResolutionForSpecificTitle"}, //21.0.0+ - {2000, &ICommonStateGetter::StubReplyZeroed, "Unknown2000"}, - {2010, &ICommonStateGetter::StubReplyZeroed, "Unknown2010"}, + {2000, &ICommonStateGetter::StubReplyZeroed, "SetRtcModeChangedMessageEnabled"}, + {2010, &ICommonStateGetter::StubReplyZeroed, "GetCurrentDisplayLayoutRatio"}, }; // clang-format on diff --git a/src/core/hle/service/am/service/graphics_scale_controller.h b/src/core/hle/service/am/service/graphics_scaling_parameters_functions.h similarity index 50% rename from src/core/hle/service/am/service/graphics_scale_controller.h rename to src/core/hle/service/am/service/graphics_scaling_parameters_functions.h index b31858d3ed..4d061aeb16 100644 --- a/src/core/hle/service/am/service/graphics_scale_controller.h +++ b/src/core/hle/service/am/service/graphics_scaling_parameters_functions.h @@ -9,16 +9,18 @@ namespace Service::AM { -// Switch 2 interface returned by IApplicationProxy command 60 (name is a guess). nn::oe's -// GetCurrentGraphicsScale reads command 10 and accepts 10, 20 or 30 (scale enum 0, 1, 2). -// Commands without a handler are auto-stubbed because the name carries the nx2: prefix. -class IGraphicsScaleController final : public ServiceFramework { +// nn::am::service::IGraphicsScalingParametersFunctions (Switch 2), returned by IApplicationProxy +// command 60. nn::oe's GetCurrentGraphicsScale reads command 10 and accepts 10, 20 or 30 (scale +// enum 0, 1, 2). Commands without a handler are auto-stubbed because the name carries the nx2: +// prefix. +class IGraphicsScalingParametersFunctions final + : public ServiceFramework { public: - explicit IGraphicsScaleController(Core::System& system_) - : ServiceFramework{system_, "nx2:IGraphicsScaleController"} { + explicit IGraphicsScalingParametersFunctions(Core::System& system_) + : ServiceFramework{system_, "nx2:IGraphicsScalingParametersFunctions"} { // clang-format off static const FunctionInfo functions[] = { - {10, D<&IGraphicsScaleController::GetCurrentGraphicsScale>, "GetCurrentGraphicsScale"}, + {10, D<&IGraphicsScalingParametersFunctions::GetGraphicsScalingParameters>, "GetGraphicsScalingParameters"}, }; // clang-format on RegisterHandlers(functions); @@ -27,7 +29,8 @@ class IGraphicsScaleController final : public ServiceFramework out_scale, Out out_unknown1, Out out_unknown2) { + Result GetGraphicsScalingParameters(Out out_scale, Out out_unknown1, + Out out_unknown2) { LOG_WARNING(Service_AM, "(STUBBED) called, returning 10"); *out_scale = 10; *out_unknown1 = 0; diff --git a/src/core/hle/service/am/service/self_controller.cpp b/src/core/hle/service/am/service/self_controller.cpp index d67e1b32cd..2616dee609 100644 --- a/src/core/hle/service/am/service/self_controller.cpp +++ b/src/core/hle/service/am/service/self_controller.cpp @@ -42,9 +42,9 @@ ISelfController::ISelfController(Core::System& system_, std::shared_ptr {19, D<&ISelfController::SetAlbumImageOrientation>, "SetAlbumImageOrientation"}, {20, &ISelfController::StubReplyZeroed, "SetDesirableKeyboardLayout"}, {21, &ISelfController::StubReplyZeroed, "GetScreenShotProgramId"}, - {22, &ISelfController::StubReplyZeroed, "Unknown22"}, - {23, &ISelfController::StubReplyZeroed, "Unknown23"}, - {24, &ISelfController::StubReplyZeroed, "Unknown24"}, + {22, &ISelfController::StubReplyZeroed, "GetScreenShotAcdIndex"}, + {23, &ISelfController::StubReplyZeroed, "GetScreenShotApparentPlatform"}, + {24, &ISelfController::StubReplyZeroed, "GetScreenShotApplicationProperty"}, {40, D<&ISelfController::CreateManagedDisplayLayer>, "CreateManagedDisplayLayer"}, {41, D<&ISelfController::IsSystemBufferSharingEnabled>, "IsSystemBufferSharingEnabled"}, {42, D<&ISelfController::GetSystemSharedLayerHandle>, "GetSystemSharedLayerHandle"}, @@ -83,12 +83,12 @@ ISelfController::ISelfController(Core::System& system_, std::shared_ptr {230, D<&ISelfController::Unknown230>, "Unknown230"}, {240, D<&ISelfController::Unknown240>, "Unknown240"}, {1000, &ISelfController::StubReplyInterface, "GetDebugStorageChannel"}, - {2000, &ISelfController::GetNx2GraphicsSystemSession, "GetGraphicsSystemSession (Switch 2, guessed)"}, - {2100, &ISelfController::StubReplyZeroed, "Unknown2100"}, - {2200, &ISelfController::StubReplyZeroed, "Unknown2200"}, - {2300, &ISelfController::StubReplyZeroed, "Unknown2300"}, - {2400, &ISelfController::StubReplyZeroed, "Unknown2400"}, - {2401, &ISelfController::StubReplyZeroed, "Unknown2401"}, + {2000, &ISelfController::GetGraphicsSupervisor, "GetGraphicsSupervisor"}, + {2100, &ISelfController::StubReplyZeroed, "SetRecordingLayerEnabled"}, + {2200, &ISelfController::StubReplyZeroed, "SetSharingLayerEnabled"}, + {2300, &ISelfController::StubReplyZeroed, "SetCopyrightLayerEnabled"}, + {2400, &ISelfController::StubReplyZeroed, "SetRtcScreenSharingAudioEnabled"}, + {2401, &ISelfController::StubReplyZeroed, "SetStreamPlayMirroringAudioEnabled"}, {2402, &ISelfController::StubReplyZeroed, "Unknown2402"}, }; // clang-format on @@ -100,9 +100,9 @@ ISelfController::ISelfController(Core::System& system_, std::shared_ptr m_applet->library_applet_mode); } -void ISelfController::GetNx2GraphicsSystemSession(HLERequestContext& ctx) { +void ISelfController::GetGraphicsSupervisor(HLERequestContext& ctx) { // nn::gsv::BootWithRemoteMemory sends TIPC on the handle this returns. - LOG_WARNING(Service_AM, "(STUBBED) Switch 2 ISelfController::2000, returning a nx2:gsv session"); + LOG_WARNING(Service_AM, "(STUBBED) GetGraphicsSupervisor, returning a nx2:gsv session"); ReturnRawSession(system, ctx, std::make_shared(system)); } diff --git a/src/core/hle/service/am/service/self_controller.h b/src/core/hle/service/am/service/self_controller.h index 221fbb0981..7cc0a156fb 100644 --- a/src/core/hle/service/am/service/self_controller.h +++ b/src/core/hle/service/am/service/self_controller.h @@ -30,7 +30,7 @@ class ISelfController final : public ServiceFramework { ~ISelfController() override; private: - void GetNx2GraphicsSystemSession(HLERequestContext& ctx); + void GetGraphicsSupervisor(HLERequestContext& ctx); Result Exit(); Result LockExit(); Result UnlockExit(); From eb4afcd4b8f68fc45e01cf5d0069a089ca2318b5 Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Tue, 3 Nov 2026 19:06:33 -0500 Subject: [PATCH 02/14] hid: use Switchbrew's names for the commands added for Switch 2 titles Renames only, no behavior change. Covers the IHidServer entries the Switch 2 work added as UnknownNNN (upstream Eden does not list them): 137-160, 311/312, 322-324, 550, 1005, the Jcms commands 3110-3113 and the Nmouse commands 3130-3142. 213, 214 and 220 have handlers, so their functions are renamed too: SendVibrationValueInMode, SendVibrationValuesInMode (was SendNx2VibrationValues) and IsOunceVibrationDeviceMounted. 3114, 3134 and 3143-3149 are not on Switchbrew and keep their placeholder names. --- src/core/hle/service/hid/hid_server.cpp | 72 ++++++++++++------------- src/core/hle/service/hid/hid_server.h | 10 ++-- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/core/hle/service/hid/hid_server.cpp b/src/core/hle/service/hid/hid_server.cpp index f2a7b7d04a..a8cbc37f4e 100644 --- a/src/core/hle/service/hid/hid_server.cpp +++ b/src/core/hle/service/hid/hid_server.cpp @@ -120,13 +120,13 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr r {134, C<&IHidServer::SetNpadAnalogStickUseCenterClamp>, "SetNpadAnalogStickUseCenterClamp"}, {135, C<&IHidServer::SetNpadCaptureButtonAssignment>, "SetNpadCaptureButtonAssignment"}, {136, C<&IHidServer::ClearNpadCaptureButtonAssignment>, "ClearNpadCaptureButtonAssignment"}, - {137, &IHidServer::StubReplyZeroed, "Unknown137"}, - {138, &IHidServer::StubReplyZeroed, "Unknown138"}, - {139, &IHidServer::StubReplyZeroed, "Unknown139"}, - {140, &IHidServer::StubReplyZeroed, "Unknown140"}, - {150, &IHidServer::StubReplyZeroed, "Unknown150"}, - {151, &IHidServer::StubReplyZeroed, "Unknown151"}, - {160, &IHidServer::StubReplyZeroed, "Unknown160"}, + {137, &IHidServer::StubReplyZeroed, "SetNpadGcAnalogStick8bitRawValue"}, + {138, &IHidServer::StubReplyZeroed, "SetSceneIdForPrivateApp"}, + {139, &IHidServer::StubReplyZeroed, "GetSceneIdForPrivateApp"}, + {140, &IHidServer::StubReplyZeroed, "IsFirmwareUpdateAvailableForDebug"}, + {150, &IHidServer::StubReplyZeroed, "GetSixAxisSensorIcSettings"}, + {151, &IHidServer::StubReplyZeroed, "SetSixAxisSensorIcSettings"}, + {160, &IHidServer::StubReplyZeroed, "GetSixAxisSensorIcAccelerationOffset"}, {200, C<&IHidServer::GetVibrationDeviceInfo>, "GetVibrationDeviceInfo"}, {201, C<&IHidServer::SendVibrationValue>, "SendVibrationValue"}, {202, C<&IHidServer::GetActualVibrationValue>, "GetActualVibrationValue"}, @@ -140,9 +140,9 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr r {210, C<&IHidServer::EndPermitVibrationSession>, "EndPermitVibrationSession"}, {211, C<&IHidServer::IsVibrationDeviceMounted>, "IsVibrationDeviceMounted"}, {212, C<&IHidServer::SendVibrationValueInBool>, "SendVibrationValueInBool"}, - {213, C<&IHidServer::Unknown213>, "Unknown213"}, - {214, C<&IHidServer::SendNx2VibrationValues>, "SendNx2VibrationValues (guessed)"}, - {220, C<&IHidServer::Unknown220>, "Unknown220"}, + {213, C<&IHidServer::SendVibrationValueInMode>, "SendVibrationValueInMode"}, + {214, C<&IHidServer::SendVibrationValuesInMode>, "SendVibrationValuesInMode"}, + {220, C<&IHidServer::IsOunceVibrationDeviceMounted>, "IsOunceVibrationDeviceMounted"}, {300, C<&IHidServer::ActivateConsoleSixAxisSensor>, "ActivateConsoleSixAxisSensor"}, {301, C<&IHidServer::StartConsoleSixAxisSensor>, "StartConsoleSixAxisSensor"}, {302, C<&IHidServer::StopConsoleSixAxisSensor>, "StopConsoleSixAxisSensor"}, @@ -154,13 +154,13 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr r {308, &IHidServer::StubReplyZeroed, "SetSevenSixAxisSensorFusionStrength"}, {309, &IHidServer::StubReplyZeroed, "GetSevenSixAxisSensorFusionStrength"}, {310, C<&IHidServer::ResetSevenSixAxisSensorTimestamp>, "ResetSevenSixAxisSensorTimestamp"}, - {311, &IHidServer::StubReplyZeroed, "Unknown311"}, - {312, &IHidServer::StubReplyZeroed, "Unknown312"}, + {311, &IHidServer::StubReplyZeroed, "ForceActivateConsoleSixAxisSensor"}, + {312, &IHidServer::StubReplyZeroed, "ForceDeactivateConsoleSixAxisSensor"}, {320, &IHidServer::StubReplyZeroed, "EnableNpadImu"}, //21.0.0+ {321, &IHidServer::StubReplyZeroed, "DisableNpadImu"}, //21.0.0+ - {322, &IHidServer::StubReplyZeroed, "Unknown322"}, - {323, &IHidServer::StubReplyZeroed, "Unknown323"}, - {324, &IHidServer::StubReplyZeroed, "Unknown324"}, + {322, &IHidServer::StubReplyZeroed, "SetNpadImuSupportedFeatures"}, + {323, &IHidServer::StubReplyZeroed, "EnableNpadImuHandle"}, + {324, &IHidServer::StubReplyZeroed, "DisableNpadImuHandle"}, {400, C<&IHidServer::IsUsbFullKeyControllerEnabled>, "IsUsbFullKeyControllerEnabled"}, {401, &IHidServer::StubReplyZeroed, "EnableUsbFullKeyController"}, {402, &IHidServer::StubReplyZeroed, "IsUsbFullKeyControllerConnected"}, @@ -200,13 +200,13 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr r {527, C<&IHidServer::EnablePalmaBoostMode>, "EnablePalmaBoostMode"}, {528, C<&IHidServer::GetPalmaBluetoothAddress>, "GetPalmaBluetoothAddress"}, {529, C<&IHidServer::SetDisallowedPalmaConnection>, "SetDisallowedPalmaConnection"}, - {550, &IHidServer::StubReplyZeroed, "Unknown550"}, + {550, &IHidServer::StubReplyZeroed, "GetGlGrButtonConfigState"}, {1000, C<&IHidServer::SetNpadCommunicationMode>, "SetNpadCommunicationMode"}, {1001, C<&IHidServer::GetNpadCommunicationMode>, "GetNpadCommunicationMode"}, {1002, C<&IHidServer::SetTouchScreenConfiguration>, "SetTouchScreenConfiguration"}, {1003, C<&IHidServer::IsFirmwareUpdateNeededForNotification>, "IsFirmwareUpdateNeededForNotification"}, {1004, C<&IHidServer::SetTouchScreenResolution>, "SetTouchScreenResolution"}, - {1005, &IHidServer::StubReplyZeroed, "Unknown1005"}, + {1005, &IHidServer::StubReplyZeroed, "EnableNxTouchScreenEmulationForTouchEnter"}, {1270, nullptr, "DeleteButtonConfigStorageRight"}, {1271, nullptr, "IsUsingCustomButtonConfig"}, {1272, nullptr, "IsAnyCustomButtonConfigEnabled"}, @@ -255,24 +255,24 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr r {3013, &IHidServer::StubReplyZeroed, "SetDebugPadGenericPadMap"}, //21.0.0+ {3014, &IHidServer::StubReplyZeroed, "GetDebugPadKeyboardMap"}, //21.0.0+ {3015, &IHidServer::StubReplyZeroed, "SetDebugPadKeyboardMap"}, //21.0.0+ - {3110, &IHidServer::StubReplyZeroed, "Unknown3110"}, - {3111, &IHidServer::StubReplyZeroed, "Unknown3111"}, - {3112, &IHidServer::StubReplyZeroed, "Unknown3112"}, - {3113, &IHidServer::StubReplyZeroed, "Unknown3113"}, + {3110, &IHidServer::StubReplyZeroed, "InitializeJcms"}, + {3111, &IHidServer::StubReplyZeroed, "FinalizeJcms"}, + {3112, &IHidServer::StubReplyZeroed, "StartJcms"}, + {3113, &IHidServer::StubReplyZeroed, "StopJcms"}, {3114, &IHidServer::StubReplyZeroed, "Unknown3114"}, - {3130, &IHidServer::StubReplyZeroed, "Unknown3130"}, - {3131, &IHidServer::StubReplyZeroed, "Unknown3131"}, - {3132, &IHidServer::StubReplyZeroed, "Unknown3132"}, - {3133, &IHidServer::StubReplyZeroed, "Unknown3133"}, + {3130, &IHidServer::StubReplyZeroed, "InitializeNmouse"}, + {3131, &IHidServer::StubReplyZeroed, "FinalizeNmouse"}, + {3132, &IHidServer::StubReplyZeroed, "SetNmousePointerRanges"}, + {3133, &IHidServer::StubReplyZeroed, "GetNmousePointerRanges"}, {3134, &IHidServer::StubReplyZeroed, "Unknown3134"}, - {3135, &IHidServer::StubReplyZeroed, "Unknown3135"}, - {3136, &IHidServer::StubReplyZeroed, "Unknown3136"}, - {3137, &IHidServer::StubReplyZeroed, "Unknown3137"}, - {3138, &IHidServer::StubReplyZeroed, "Unknown3138"}, - {3139, &IHidServer::StubReplyZeroed, "Unknown3139"}, - {3140, &IHidServer::StubReplyZeroed, "Unknown3140"}, - {3141, &IHidServer::StubReplyZeroed, "Unknown3141"}, - {3142, &IHidServer::StubReplyZeroed, "Unknown3142"}, + {3135, &IHidServer::StubReplyZeroed, "EnableShowingCursor"}, + {3136, &IHidServer::StubReplyZeroed, "SetNmouseButtonConfigJoyLeftJcms"}, + {3137, &IHidServer::StubReplyZeroed, "SetNmouseButtonConfigJoyRightJcms"}, + {3138, &IHidServer::StubReplyZeroed, "SetMergedNmouseSourceTypes"}, + {3139, &IHidServer::StubReplyZeroed, "InitializeNmouse2"}, + {3140, &IHidServer::StubReplyZeroed, "SetNmousePointerPosition"}, + {3141, &IHidServer::StubReplyZeroed, "EnableNmousePointer"}, + {3142, &IHidServer::StubReplyZeroed, "RestrictNmousePointer"}, {3143, &IHidServer::StubReplyZeroed, "Unknown3143"}, {3144, &IHidServer::StubReplyZeroed, "Unknown3144"}, {3145, &IHidServer::StubReplyZeroed, "Unknown3145"}, @@ -1113,7 +1113,7 @@ Result IHidServer::IsVibrationDeviceMounted( R_SUCCEED(); } -Result IHidServer::SendNx2VibrationValues( +Result IHidServer::SendVibrationValuesInMode( u64 unknown, AppletResourceUserId aruid, InArray vibration_handles, InArray vibration_values) { @@ -1134,12 +1134,12 @@ Result IHidServer::SendNx2VibrationValues( R_SUCCEED(); } -Result IHidServer::Unknown213() { +Result IHidServer::SendVibrationValueInMode() { LOG_DEBUG(Service_HID, "(STUBBED) called"); R_SUCCEED(); } -Result IHidServer::Unknown220(Out out) { +Result IHidServer::IsOunceVibrationDeviceMounted(Out out) { LOG_DEBUG(Service_HID, "(STUBBED) called"); *out = 0; R_SUCCEED(); diff --git a/src/core/hle/service/hid/hid_server.h b/src/core/hle/service/hid/hid_server.h index 9bb62fabc4..79dbbfc9fa 100644 --- a/src/core/hle/service/hid/hid_server.h +++ b/src/core/hle/service/hid/hid_server.h @@ -191,16 +191,16 @@ class IHidServer final : public ServiceFramework { ClientAppletResourceUserId aruid); // Switch 2 sends vibration every frame with 214: pointer buffers of device handles and values // like 206, but the aruid is raw data after an unknown u64 instead of a sent process id. - Result SendNx2VibrationValues( + Result SendVibrationValuesInMode( u64 unknown, AppletResourceUserId aruid, InArray vibration_handles, InArray vibration_values); // 213 (32 bytes, aruid): sent per controller every frame by Kirby Air Riders (tens of // thousands of auto stub log lines a run); no reply data. Accepted and dropped. - Result Unknown213(); - // 220 (16 bytes, aruid): replies one byte (meaning unknown); answer 0 instead of leaving it to - // the message buffer's leftovers. - Result Unknown220(Out out); + Result SendVibrationValueInMode(); + // 220 (VibrationDeviceHandle, aruid): whether the Switch 2 vibration device is mounted. + // Answers 0 instead of leaving it to the message buffer's leftovers. + Result IsOunceVibrationDeviceMounted(Out out); Result ActivateConsoleSixAxisSensor(ClientAppletResourceUserId aruid); Result StartConsoleSixAxisSensor(Core::HID::ConsoleSixAxisSensorHandle console_sixaxis_handle, ClientAppletResourceUserId aruid); From 3c510606088cf8cbf902db4fc5d8806a2a76bedc Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Tue, 3 Nov 2026 19:13:24 -0500 Subject: [PATCH 03/14] am: accept IGraphicsScalingParametersFunctions 1050 SetGraphicsScaleChangedNotificationEnabled(bool) is called at boot by every Switch 2 title in the 11-02/11-03 run logs (MKW included) and was left to the auto stub. The graphics scale never changes here, so there is never a change message to send: accepting the flag is the whole job. --- .../am/service/graphics_scaling_parameters_functions.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/core/hle/service/am/service/graphics_scaling_parameters_functions.h b/src/core/hle/service/am/service/graphics_scaling_parameters_functions.h index 4d061aeb16..11234c5fbe 100644 --- a/src/core/hle/service/am/service/graphics_scaling_parameters_functions.h +++ b/src/core/hle/service/am/service/graphics_scaling_parameters_functions.h @@ -21,6 +21,7 @@ class IGraphicsScalingParametersFunctions final // clang-format off static const FunctionInfo functions[] = { {10, D<&IGraphicsScalingParametersFunctions::GetGraphicsScalingParameters>, "GetGraphicsScalingParameters"}, + {1050, D<&IGraphicsScalingParametersFunctions::SetGraphicsScaleChangedNotificationEnabled>, "SetGraphicsScaleChangedNotificationEnabled"}, }; // clang-format on RegisterHandlers(functions); @@ -37,6 +38,13 @@ class IGraphicsScalingParametersFunctions final *out_unknown2 = 0; R_SUCCEED(); } + + // 1050(bool): every title calls it at boot. The scale never changes here, so there is never a + // change message to send either way. + Result SetGraphicsScaleChangedNotificationEnabled(bool enabled) { + LOG_DEBUG(Service_AM, "called, enabled={}", enabled); + R_SUCCEED(); + } }; } // namespace Service::AM From fd2ac8ae753bf206650345e6845ceb1533f52c1b Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Tue, 3 Nov 2026 19:13:25 -0500 Subject: [PATCH 04/14] chat: return chat:u's objects and events instead of empty replies MKW calls chat:u 3 and the chat session's 2 at boot; both were auto-stubbed, so the title got no object and no event handle back. Per Switchbrew (Switch 2: Chat services): - chat:u 1 returns the same IUserShimScopedObject as 0. - chat:u 2, 3 and 4 return IUserLayoutCustomizationShimScopedObject, IVirtualCameraShimScopedObject and IUserSelectiveTalkShimScopedObject. Nothing implements them yet, so each is an nx2 stub interface whose commands succeed empty. - The session's 2-4 (AttachToRoomInfoChangedEvent, AttachToLocalCameraImageProcessingModeChangedEvent, AttachToLocalCameraZoomMagnificationChangedEvent) take nothing and return an event. None of these changes outside a GameChat room, so each gets its own event that is never signalled. --- src/core/hle/service/friend/friend.cpp | 57 +++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp index bfb2d6b503..cbda822501 100644 --- a/src/core/hle/service/friend/friend.cpp +++ b/src/core/hle/service/friend/friend.cpp @@ -4,6 +4,7 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include "common/logging.h" #include "common/uuid.h" @@ -14,6 +15,7 @@ #include "core/hle/service/friend/friend_interface.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/kernel_helpers.h" +#include "core/hle/service/nx2_stub_interface.h" #include "core/hle/service/server_manager.h" namespace Service::Friend { @@ -558,21 +560,38 @@ class IServiceForSystem final : public ServiceFramework { } }; -// Session returned by chat:u 0. Command 1 hands back a handle the title waits on, so it gets an -// event that is never signalled; everything else falls through to the nx2 auto stub. +// Session returned by chat:u 0 and 1. Command 1 hands back a handle the title waits on, so it gets +// an event that is never signalled. 2-4 attach to the room info, local camera image processing +// mode and local camera zoom change events (Switchbrew: no input, an event out); none of them +// changes outside a GameChat room, so they never fire. Everything else falls through to the nx2 +// auto stub. class IChatSession final : public ServiceFramework { public: explicit IChatSession(Core::System& system_) : ServiceFramework{system_, "nx2:chat:u::0"}, service_context{system_, "IChatSession"} { static const FunctionInfo functions[] = { {1, &IChatSession::GetEvent, "GetEvent (guessed)"}, + {2, &IChatSession::AttachToChangedEvent, "AttachToRoomInfoChangedEvent"}, + {3, &IChatSession::AttachToChangedEvent, "AttachToLocalCameraImageProcessingModeChangedEvent"}, + {4, &IChatSession::AttachToChangedEvent, "AttachToLocalCameraZoomMagnificationChangedEvent"}, }; RegisterHandlers(functions); event = service_context.CreateEvent("IChatSession:Event"); + static constexpr std::array changed_event_names{ + "IChatSession:RoomInfoChanged", + "IChatSession:LocalCameraImageProcessingModeChanged", + "IChatSession:LocalCameraZoomMagnificationChanged", + }; + for (size_t i = 0; i < changed_events.size(); ++i) { + changed_events[i] = service_context.CreateEvent(changed_event_names[i]); + } } ~IChatSession() override { service_context.CloseEvent(event); + for (auto* changed_event : changed_events) { + service_context.CloseEvent(changed_event); + } } private: @@ -583,28 +602,56 @@ class IChatSession final : public ServiceFramework { rb.PushCopyObjects(ctx, event->GetReadableEvent()); } + void AttachToChangedEvent(HLERequestContext& ctx) { + LOG_DEBUG(Service_Friend, "chat:u::0 {}", ctx.GetCommand()); + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(ctx, changed_events[ctx.GetCommand() - 2]->GetReadableEvent()); + } + KernelHelpers::ServiceContext service_context; Kernel::KEvent* event; + std::array changed_events{}; }; -// Switch 2 GameChat (chat:u). Command 0 opens a session the title keeps using, so it has to -// return an interface; everything else falls through to the nx2 auto stub. +// Switch 2 GameChat (chat:u). 0 and 1 open the session the title keeps using. 2-4 return objects +// nothing here implements yet (layout customization, virtual camera, selective talk), so each +// gets an nx2 stub whose commands all succeed empty; MKW calls 3 at boot. Switchbrew leaves 0-4 +// unnamed: 2-4 are named after what they return. Everything else falls through to the nx2 auto +// stub. class IChatService final : public ServiceFramework { public: explicit IChatService(Core::System& system_) : ServiceFramework{system_, "nx2:chat:u"} { static const FunctionInfo functions[] = { {0, &IChatService::OpenSession, "OpenSession"}, + {1, &IChatService::OpenSession, "OpenSession (u32)"}, + {2, &IChatService::OpenStubObject, "OpenUserLayoutCustomizationShimScopedObject"}, + {3, &IChatService::OpenStubObject, "OpenVirtualCameraShimScopedObject"}, + {4, &IChatService::OpenStubObject, "OpenUserSelectiveTalkShimScopedObject"}, }; RegisterHandlers(functions); } private: void OpenSession(HLERequestContext& ctx) { - LOG_WARNING(Service_Friend, "(STUBBED) chat:u 0, returning a chat session"); + LOG_WARNING(Service_Friend, "(STUBBED) chat:u {}, returning a chat session", ctx.GetCommand()); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); rb.PushIpcInterface(ctx, system); } + + void OpenStubObject(HLERequestContext& ctx) { + static constexpr std::array names{ + "nx2:chat:IUserLayoutCustomizationShimScopedObject", + "nx2:chat:IVirtualCameraShimScopedObject", + "nx2:chat:IUserSelectiveTalkShimScopedObject", + }; + const char* name = names[ctx.GetCommand() - 2]; + LOG_WARNING(Service_Friend, "(STUBBED) chat:u {}, returning {}", ctx.GetCommand(), name); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(ctx, system, name); + } }; void LoopProcess(Core::System& system) { From 975dab0b0b76d8c5dc7b2b4f5a83e7f445801c5d Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Tue, 3 Nov 2026 19:17:17 -0500 Subject: [PATCH 05/14] audio: name the Switch 2 audio sessions after their nn:: interfaces Renames only, no behavior change. Switchbrew (Audio services) gives the interface names of the Switch 2 audio services: - aud:u is nn::audio::IUserClientSession (was INx2AudioUserClient). - auddmg:u is nn::audio::devmgr::IUserSessionManager, and its command 0 returns nn::audio::devmgr::IUserSession (were INx2AudioDeviceManagerService and INx2AudioVirtualDeviceManager). - audsmx:u is nn::audio::sysmix::IUserSessionManager, returning nn::audio::sysmix::IUserSession, whose command 1 returns nn::audio::sysmix::IExternalStreamSession (were INx2AudioSoundMixerService, INx2SysmixUserSession and INx2SysmixStreamSession). The devmgr and sysmix classes share names, so they live in Devmgr and Sysmix namespaces. auddmg:u 0 is OpenUserSession like audsmx:u 0 (Switchbrew leaves both unnamed). The command IDs of every session match Switchbrew's lists; command names stay (most come from SDK symbols). Session name strings keep their nx2: prefix, which drives the auto stub. --- src/core/hle/service/audio/audio.cpp | 6 +- .../service/audio/nx2_audio_device_manager.h | 111 ++++++++++-------- .../hle/service/audio/nx2_audio_user_client.h | 12 +- 3 files changed, 70 insertions(+), 59 deletions(-) diff --git a/src/core/hle/service/audio/audio.cpp b/src/core/hle/service/audio/audio.cpp index eb1511857b..f41ccff9ac 100644 --- a/src/core/hle/service/audio/audio.cpp +++ b/src/core/hle/service/audio/audio.cpp @@ -206,9 +206,9 @@ void LoopProcess(Core::System& system) { server_manager->RegisterNamedService("audrec:u", std::make_shared(system), 30); server_manager->RegisterNamedService("audren:u", std::make_shared(system), 30); // Switch 2 audio entry point (nn::audio::CreateUserClientSessionByHipc). - server_manager->RegisterNamedService("aud:u", std::make_shared(system), 30); - server_manager->RegisterNamedService("auddmg:u", std::make_shared(system), 30); - server_manager->RegisterNamedService("audsmx:u", std::make_shared(system), 30); + server_manager->RegisterNamedService("aud:u", std::make_shared(system), 30); + server_manager->RegisterNamedService("auddmg:u", std::make_shared(system), 30); + server_manager->RegisterNamedService("audsmx:u", std::make_shared(system), 30); server_manager->RegisterNamedService("audren:a", std::make_shared(system), 30); server_manager->RegisterNamedService("hwopus", std::make_shared(system), 25); ServerManager::RunServer(std::move(server_manager)); diff --git a/src/core/hle/service/audio/nx2_audio_device_manager.h b/src/core/hle/service/audio/nx2_audio_device_manager.h index 675012b0ab..f9bcb4b3af 100644 --- a/src/core/hle/service/audio/nx2_audio_device_manager.h +++ b/src/core/hle/service/audio/nx2_audio_device_manager.h @@ -72,10 +72,12 @@ inline std::shared_ptr Nx2SysmixStreamList() { return list; } -// Switch 2 virtual output devices (nn::audio::ListVirtualOutputDevices and friends), reached -// through the object auddmg:u command 0 returns. Eden plays everything through one stereo 48 kHz -// output, so there is one virtual device. The nx2: name keeps the other commands auto-stubbed and -// logged. +namespace Devmgr { + +// nn::audio::devmgr::IUserSession: Switch 2 virtual output devices +// (nn::audio::ListVirtualOutputDevices and friends), reached through the object auddmg:u command 0 +// returns. Eden plays everything through one stereo 48 kHz output, so there is one virtual device. +// The nx2: name keeps the other commands auto-stubbed and logged. // - 0(u32): u32 count, the device names in the out buffer (0x100 bytes each). // - 100(u32): the default device's name in the out buffer. // - 300(16-byte stream format, name buffer): whether the format is supported (result only). @@ -86,27 +88,27 @@ inline std::shared_ptr Nx2SysmixStreamList() { // - 700(u32 stream id, u32 device functionality, name buffer): attach a sysmix stream to the // device, which starts it (nn::audio::devmgr::AttachStream, behind OutputStream Start). // - 800(u32 stream id): detach it again (Stop). -class INx2AudioVirtualDeviceManager final : public ServiceFramework { +class IUserSession final : public ServiceFramework { public: - explicit INx2AudioVirtualDeviceManager(Core::System& system_) + explicit IUserSession(Core::System& system_) : ServiceFramework{system_, "nx2:auddmg:u:manager"}, service_context{system_, "nx2:auddmg:u:manager"}, state_event{service_context.CreateEvent("nx2:auddmg:u:StateEvent")} { // clang-format off static const FunctionInfo functions[] = { - {0, &INx2AudioVirtualDeviceManager::ListVirtualOutputDevices, "ListVirtualOutputDevices"}, - {100, &INx2AudioVirtualDeviceManager::GetDefaultVirtualOutputDevice, "GetDefaultVirtualOutputDevice"}, - {300, &INx2AudioVirtualDeviceManager::IsStreamFormatSupported, "IsVirtualOutputDeviceStreamFormatSupported"}, - {401, &INx2AudioVirtualDeviceManager::GetVirtualOutputDeviceProperties, "GetVirtualOutputDeviceProperties"}, - {500, &INx2AudioVirtualDeviceManager::AcquireStateChangeEvent, "AcquireVirtualOutputDeviceStateEvent (guessed)"}, - {700, &INx2AudioVirtualDeviceManager::AttachStream, "AttachStream"}, - {800, &INx2AudioVirtualDeviceManager::DetachStream, "DetachStream"}, + {0, &IUserSession::ListVirtualOutputDevices, "ListVirtualOutputDevices"}, + {100, &IUserSession::GetDefaultVirtualOutputDevice, "GetDefaultVirtualOutputDevice"}, + {300, &IUserSession::IsStreamFormatSupported, "IsVirtualOutputDeviceStreamFormatSupported"}, + {401, &IUserSession::GetVirtualOutputDeviceProperties, "GetVirtualOutputDeviceProperties"}, + {500, &IUserSession::AcquireStateChangeEvent, "AcquireVirtualOutputDeviceStateEvent (guessed)"}, + {700, &IUserSession::AttachStream, "AttachStream"}, + {800, &IUserSession::DetachStream, "DetachStream"}, }; // clang-format on RegisterHandlers(functions); } - ~INx2AudioVirtualDeviceManager() override { + ~IUserSession() override { service_context.CloseEvent(state_event); } @@ -213,38 +215,44 @@ class INx2AudioVirtualDeviceManager final : public ServiceFramework { +// Switch 2 "auddmg:u" (nn::audio::devmgr::IUserSessionManager): command 0 (16 bytes and the pid) +// returns the user session. +class IUserSessionManager final : public ServiceFramework { public: - explicit INx2AudioDeviceManagerService(Core::System& system_) + explicit IUserSessionManager(Core::System& system_) : ServiceFramework{system_, "nx2:auddmg:u"} { // clang-format off static const FunctionInfo functions[] = { - {0, &INx2AudioDeviceManagerService::OpenVirtualDeviceManager, "OpenVirtualDeviceManager (guessed)"}, + {0, &IUserSessionManager::OpenUserSession, "OpenUserSession"}, }; // clang-format on RegisterHandlers(functions); } private: - void OpenVirtualDeviceManager(HLERequestContext& ctx) { + void OpenUserSession(HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(ctx, system); + rb.PushIpcInterface(ctx, system); } }; -// The stream object IUserSession command 1 returns. Commands (ids, request -> reply): +} // namespace Devmgr + +namespace Sysmix { + +// nn::audio::sysmix::IExternalStreamSession, the stream object IUserSession command 1 returns. +// Commands (ids, request -> reply): // - 0(u32 sample type, u32 channels, u32 sample rate, u32 frames, u32 prefill, u32 flags): open. // - 1: close. 2: u32 state. 3: f32 gain. 4(f32 gain, s64 ramp ns): set the gain. // - 5 / 6: acquire (copy handle) / release the state-change event. // - 7(u32 mode, s32 threshold) / 8: acquire (copy handle) / release the data event. // - 9(s64 ns): clock the stream. 10(s32 frames) -> u32: prime. 11(s32 frames) -> u32: purge. // - 12(name buffer): rename. -class INx2SysmixStreamSession final : public ServiceFramework { +class IExternalStreamSession final : public ServiceFramework { public: - explicit INx2SysmixStreamSession(Core::System& system_, + explicit IExternalStreamSession(Core::System& system_, std::shared_ptr streams_, std::shared_ptr stream_) : ServiceFramework{system_, "nx2:audsmx:u:stream"}, @@ -252,19 +260,19 @@ class INx2SysmixStreamSession final : public ServiceFrameworkdata_event = service_context.CreateEvent("nx2:audsmx:u:StreamDataEvent"); } - ~INx2SysmixStreamSession() override { + ~IExternalStreamSession() override { // The drain timer signals these events under the lock: unlist the stream first. { std::scoped_lock lk{streams->mutex}; @@ -431,19 +439,19 @@ class INx2SysmixStreamSession final : public ServiceFramework stream; }; -// The session audsmx:u command 0 returns. Commands: +// nn::audio::sysmix::IUserSession, the session audsmx:u command 0 returns. Commands: // - 1(u32, u32 creation flags, u64 pid, u64 buffer address, u64 buffer size, name buffer): // u32 stream id and the stream object. // - 2 / 3(buffer of u32 stream ids): engage / disengage those streams. -class INx2SysmixUserSession final : public ServiceFramework { +class IUserSession final : public ServiceFramework { public: - explicit INx2SysmixUserSession(Core::System& system_) + explicit IUserSession(Core::System& system_) : ServiceFramework{system_, "nx2:audsmx:u:session"} { // clang-format off static const FunctionInfo functions[] = { - {1, &INx2SysmixUserSession::CreateStream, "CreateStream"}, - {2, &INx2SysmixUserSession::EngageStreams, "EngageStreams"}, - {3, &INx2SysmixUserSession::DisengageStreams, "DisengageStreams"}, + {1, &IUserSession::CreateStream, "CreateStream"}, + {2, &IUserSession::EngageStreams, "EngageStreams"}, + {3, &IUserSession::DisengageStreams, "DisengageStreams"}, }; // clang-format on RegisterHandlers(functions); @@ -457,7 +465,7 @@ class INx2SysmixUserSession final : public ServiceFrameworkid); - rb.PushIpcInterface(ctx, system, streams, stream); + rb.PushIpcInterface(ctx, system, streams, stream); } void SetEngaged(HLERequestContext& ctx, bool engaged) { @@ -614,14 +622,15 @@ class INx2SysmixUserSession final : public ServiceFramework drain_timer; }; -// Switch 2 "audsmx:u": command 0 (16 bytes, the pid and a copy handle) returns the user session. -class INx2AudioSoundMixerService final : public ServiceFramework { +// Switch 2 "audsmx:u" (nn::audio::sysmix::IUserSessionManager): command 0 (16 bytes, the pid and +// a copy handle) returns the user session. +class IUserSessionManager final : public ServiceFramework { public: - explicit INx2AudioSoundMixerService(Core::System& system_) + explicit IUserSessionManager(Core::System& system_) : ServiceFramework{system_, "nx2:audsmx:u"} { // clang-format off static const FunctionInfo functions[] = { - {0, &INx2AudioSoundMixerService::OpenUserSession, "OpenUserSession"}, + {0, &IUserSessionManager::OpenUserSession, "OpenUserSession"}, }; // clang-format on RegisterHandlers(functions); @@ -632,8 +641,10 @@ class INx2AudioSoundMixerService final : public ServiceFramework(ctx, system); + rb.PushIpcInterface(ctx, system); } }; +} // namespace Sysmix + } // namespace Service::Audio diff --git a/src/core/hle/service/audio/nx2_audio_user_client.h b/src/core/hle/service/audio/nx2_audio_user_client.h index f053ad62af..3392535160 100644 --- a/src/core/hle/service/audio/nx2_audio_user_client.h +++ b/src/core/hle/service/audio/nx2_audio_user_client.h @@ -11,20 +11,20 @@ namespace Service::Audio { -// Switch 2 "aud:u" (nn::audio::CreateUserClientSessionByHipc). The SDK reaches the Switch 1 -// audio interfaces through it: command 2 returns an object used exactly like +// Switch 2 "aud:u", nn::audio::IUserClientSession (nn::audio::CreateUserClientSessionByHipc). +// The SDK reaches the Switch 1 audio interfaces through it: command 2 returns an object used like // IAudioRendererManager (vtable slot 1 = GetWorkBufferSize with a Switch 1 parameter block), so // it is backed by Eden's audren:u implementation. Command 1 is nn::audio::OpenAudioOut's // manager, called like IAudioOutManager (slot 1 = OpenAudioOut), backed by audout:u. The nx2: name // keeps unknown commands auto-stubbed and logged. -class INx2AudioUserClient final : public ServiceFramework { +class IUserClientSession final : public ServiceFramework { public: - explicit INx2AudioUserClient(Core::System& system_) + explicit IUserClientSession(Core::System& system_) : ServiceFramework{system_, "nx2:aud:u"} { // clang-format off static const FunctionInfo functions[] = { - {1, &INx2AudioUserClient::GetAudioOutManager, "GetAudioOutManager (guessed)"}, - {2, &INx2AudioUserClient::GetAudioRendererManager, "GetAudioRendererManager (guessed)"}, + {1, &IUserClientSession::GetAudioOutManager, "GetAudioOutManager (guessed)"}, + {2, &IUserClientSession::GetAudioRendererManager, "GetAudioRendererManager (guessed)"}, }; // clang-format on RegisterHandlers(functions); From ca77be1838e1105c6d78bc74296ae0311f7be649 Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Tue, 3 Nov 2026 19:17:59 -0500 Subject: [PATCH 06/14] nvdrv: name the Switch 2 AS ioctl 0x0B after NVGPU_AS_IOCTL_GET_SYNC_RO_MAP Renames only, no behavior change. Switchbrew (NV services) lists 0x8010410B as the Switch 2 ioctl NVGPU_AS_IOCTL_GET_SYNC_RO_MAP with the fields base_gpuva, sync_size and num_syncpoints, so Nx2GetSyncpointShimInfo / IoctlNx2SyncpointShimInfo {address, stride, count} become GetSyncRoMap / IoctlGetSyncRoMap with those field names. --- .../service/nvdrv/devices/nvhost_as_gpu.cpp | 19 ++++++++++--------- .../hle/service/nvdrv/devices/nvhost_as_gpu.h | 12 ++++++------ 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp index b6aa71dda6..8cd9b16f7e 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -86,7 +86,7 @@ NvResult nvhost_as_gpu::Ioctl1(DeviceFD fd, Ioctl command, std::span i } case 0xb: if (g_nx2_driver) { - return WrapFixed(this, &nvhost_as_gpu::Nx2GetSyncpointShimInfo, input, output); + return WrapFixed(this, &nvhost_as_gpu::GetSyncRoMap, input, output); } break; case 0x14: @@ -308,10 +308,11 @@ void nvhost_as_gpu::Nx2SparseTile(u64 gpu_address, u64 cpu_address, u64 size) { void nvhost_as_gpu::OnClose(DeviceFD fd) {} -// Switch 2 NvRm (with characteristics flag 26) asks for the read-only syncpoint shim: a GPU page -// whose u32 at id * stride reads as that syncpoint's value; NVN waits on syncpoints through it and -// asserts without one. Same scheme as changes.patch, placed on the last page of the VA range. -NvResult nvhost_as_gpu::Nx2GetSyncpointShimInfo(IoctlNx2SyncpointShimInfo& params) { +// NVGPU_AS_IOCTL_GET_SYNC_RO_MAP: Switch 2 NvRm (with characteristics flag 26) asks for the +// read-only syncpoint shim, a GPU page whose u32 at id * sync_size reads as that syncpoint's +// value; NVN waits on syncpoints through it and asserts without one. Same scheme as +// changes.patch, placed on the last page of the VA range. +NvResult nvhost_as_gpu::GetSyncRoMap(IoctlGetSyncRoMap& params) { std::scoped_lock lock(mutex); if (!vm.initialised || !gmmu) { return NvResult::NotSupported; @@ -321,11 +322,11 @@ NvResult nvhost_as_gpu::Nx2GetSyncpointShimInfo(IoctlNx2SyncpointShimInfo& param nx2_syncpoint_shim = true; gmmu->SetNativeSyncpointShim(address); } - params.address = address; - params.stride = sizeof(u32); - params.count = Tegra::MemoryManager::NumShimSyncpoints; + params.base_gpuva = address; + params.sync_size = sizeof(u32); + params.num_syncpoints = Tegra::MemoryManager::NumShimSyncpoints; LOG_INFO(Service_NVDRV, "Switch 2 syncpoint shim at {:#x} ({} syncpoints)", address, - params.count); + params.num_syncpoints); return NvResult::Success; } diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h index 37b6d0d4c4..786c37c822 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h @@ -222,13 +222,13 @@ class nvhost_as_gpu final : public nvdevice { } vm; std::unique_ptr gmmu; - struct IoctlNx2SyncpointShimInfo { - u64_le address; - u32_le stride; - u32_le count; + struct IoctlGetSyncRoMap { + u64_le base_gpuva; + u32_le sync_size; + u32_le num_syncpoints; }; - static_assert(sizeof(IoctlNx2SyncpointShimInfo) == 16); - NvResult Nx2GetSyncpointShimInfo(IoctlNx2SyncpointShimInfo& params); + static_assert(sizeof(IoctlGetSyncRoMap) == 16); + NvResult GetSyncRoMap(IoctlGetSyncRoMap& params); bool nx2_syncpoint_shim{}; // Switch 2: maps GPU-accessible process memory (CPU range) at a GPU address. From d1a6663f37d2c91a7a2180bd9d65e14ee54d4378 Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Tue, 3 Nov 2026 19:28:24 -0500 Subject: [PATCH 07/14] am: answer IsTensorRtSupported with true IApplicationFunctions 230 (Switchbrew: IsTensorRtSupported, Switch 2, 20.0.0+) was a zeroed stub, which told titles the console can't run TensorRT networks. Switch 2 hardware can, and the DLSS network work (#465) now handles what a title runs next, so answer true like the console. --- .../hle/service/am/service/application_functions.cpp | 9 ++++++++- src/core/hle/service/am/service/application_functions.h | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/core/hle/service/am/service/application_functions.cpp b/src/core/hle/service/am/service/application_functions.cpp index 5a2d68535b..26250bb4b5 100644 --- a/src/core/hle/service/am/service/application_functions.cpp +++ b/src/core/hle/service/am/service/application_functions.cpp @@ -122,7 +122,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_, std::shared_ {200, &IApplicationFunctions::StubReplyZeroed, "GetLastApplicationExitReason"}, {210, D<&IApplicationFunctions::GetOperationModeRefreshEvent>, "GetOperationModeRefreshEvent (guessed)"}, {220, &IApplicationFunctions::StubReplyZeroed, "Unknown220"}, // [20.0.0+] - {230, &IApplicationFunctions::StubReplyZeroed, "IsTensorRtSupported"}, + {230, D<&IApplicationFunctions::IsTensorRtSupported>, "IsTensorRtSupported"}, {300, &IApplicationFunctions::StubReplyInterface, "CreateMovieWriter"}, // [19.0.0+] {310, &IApplicationFunctions::StubReplyZeroed, "Unknown310"}, // [20.0.0+] {320, D<&IApplicationFunctions::Unknown320>, "Unknown320"}, // [20.0.0+] @@ -576,6 +576,13 @@ Result IApplicationFunctions::GetOperationModeRefreshEvent( R_SUCCEED(); } +Result IApplicationFunctions::IsTensorRtSupported(Out out_is_supported) { + // Switch 2 hardware runs TensorRT networks (DLSS); the zeroed stub told titles it can't. + LOG_DEBUG(Service_AM, "called"); + *out_is_supported = true; + R_SUCCEED(); +} + Result IApplicationFunctions::Unknown320(Out out) { // Called by Switch 2 titles during nn::oe::Initialize. It returns two bytes of data (meaning // unknown); a reply without them left the caller reading stale message words. diff --git a/src/core/hle/service/am/service/application_functions.h b/src/core/hle/service/am/service/application_functions.h index 8acccab293..e3d048588d 100644 --- a/src/core/hle/service/am/service/application_functions.h +++ b/src/core/hle/service/am/service/application_functions.h @@ -85,6 +85,7 @@ class IApplicationFunctions final : public ServiceFramework out_event); Result CreateApplicationAndRequestToStart(u64 application_id); Result GetOperationModeRefreshEvent(OutCopyHandle out_event); + Result IsTensorRtSupported(Out out_is_supported); Result Unknown320(Out out); Result Unknown321(Out out); Result Unknown330(Out out); From 1be1c998cd76590150b5e5ded6cd3e20c53f2ae8 Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Tue, 3 Nov 2026 19:28:27 -0500 Subject: [PATCH 08/14] hid: answer IsOunceVibrationDeviceMounted like IsVibrationDeviceMounted 220 (Switchbrew: IsOunceVibrationDeviceMounted, Switch 2; a PID, a VibrationDeviceHandle and an aruid in, a bool out) always answered 0, so titles saw the Switch 2 vibration device as absent. It now reads the handle and answers whether that device is mounted, the same way 211 does for Switch 1 devices. A handle the vibration code doesn't recognise reads as not mounted rather than failing, as 214 already treats them. --- src/core/hle/service/hid/hid_server.cpp | 22 +++++++++++++++++++--- src/core/hle/service/hid/hid_server.h | 8 +++++--- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/core/hle/service/hid/hid_server.cpp b/src/core/hle/service/hid/hid_server.cpp index a8cbc37f4e..de7fbce716 100644 --- a/src/core/hle/service/hid/hid_server.cpp +++ b/src/core/hle/service/hid/hid_server.cpp @@ -1139,9 +1139,25 @@ Result IHidServer::SendVibrationValueInMode() { R_SUCCEED(); } -Result IHidServer::IsOunceVibrationDeviceMounted(Out out) { - LOG_DEBUG(Service_HID, "(STUBBED) called"); - *out = 0; +Result IHidServer::IsOunceVibrationDeviceMounted( + Out out_is_mounted, Core::HID::VibrationDeviceHandle vibration_device_handle, + ClientAppletResourceUserId aruid) { + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + vibration_device_handle.npad_type, vibration_device_handle.npad_id, + vibration_device_handle.device_index, aruid.pid); + + // Like IsVibrationDeviceMounted, but a handle this doesn't recognise reads as not mounted + // instead of failing: the Switch 2 handle layout is only known from captures (see 214). + *out_is_mounted = false; + if (IsVibrationHandleValid(vibration_device_handle).IsSuccess()) { + const NpadVibrationBase* device = + GetResourceManager()->GetVibrationDevice(vibration_device_handle); + if (device != nullptr) { + *out_is_mounted = device->IsVibrationMounted(); + } + } + R_SUCCEED(); } diff --git a/src/core/hle/service/hid/hid_server.h b/src/core/hle/service/hid/hid_server.h index 79dbbfc9fa..6b2f7bc8ed 100644 --- a/src/core/hle/service/hid/hid_server.h +++ b/src/core/hle/service/hid/hid_server.h @@ -198,9 +198,11 @@ class IHidServer final : public ServiceFramework { // 213 (32 bytes, aruid): sent per controller every frame by Kirby Air Riders (tens of // thousands of auto stub log lines a run); no reply data. Accepted and dropped. Result SendVibrationValueInMode(); - // 220 (VibrationDeviceHandle, aruid): whether the Switch 2 vibration device is mounted. - // Answers 0 instead of leaving it to the message buffer's leftovers. - Result IsOunceVibrationDeviceMounted(Out out); + // 220: whether the Switch 2 vibration device behind a handle is mounted, as 211 answers it for + // Switch 1 devices. + Result IsOunceVibrationDeviceMounted(Out out_is_mounted, + Core::HID::VibrationDeviceHandle vibration_device_handle, + ClientAppletResourceUserId aruid); Result ActivateConsoleSixAxisSensor(ClientAppletResourceUserId aruid); Result StartConsoleSixAxisSensor(Core::HID::ConsoleSixAxisSensorHandle console_sixaxis_handle, ClientAppletResourceUserId aruid); From c952e189b61462263d21857936fc18d14387a20f Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Tue, 3 Nov 2026 19:45:55 -0500 Subject: [PATCH 09/14] chat: implement the chat:u calls Switchbrew documents - GetRoomInfo (11) writes the 0xF0-byte nn::chat::RoomInfo as all zeroes, the console's answer outside a GameChat room (ChattingUid at 0x18 invalid). The auto stub left the title's buffer as it was. - chat:u 2 returns a real IUserLayoutCustomizationShimScopedObject: its command 1 hands back an event official software takes right after Initialize, which the nx2 stub couldn't. The event never fires; the layout never changes outside a room. - The commands Switchbrew names but gives no layout for (local camera image processing / zoom, screen sharing, realtime audio levels, the audio stream work buffer and sharing key on chat:u; camera streaming and audio stream recording on the session) are registered under those names and answer 0, as the auto stub did. --- src/core/hle/service/friend/friend.cpp | 95 +++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 9 deletions(-) diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp index cbda822501..56757fb7e2 100644 --- a/src/core/hle/service/friend/friend.cpp +++ b/src/core/hle/service/friend/friend.cpp @@ -574,6 +574,18 @@ class IChatSession final : public ServiceFramework { {2, &IChatSession::AttachToChangedEvent, "AttachToRoomInfoChangedEvent"}, {3, &IChatSession::AttachToChangedEvent, "AttachToLocalCameraImageProcessingModeChangedEvent"}, {4, &IChatSession::AttachToChangedEvent, "AttachToLocalCameraZoomMagnificationChangedEvent"}, + {10, &IChatSession::StubReplyZeroed, "StartLocalCameraStreaming"}, + {11, &IChatSession::StubReplyZeroed, "StopLocalCameraStreaming"}, + {20, &IChatSession::StubReplyZeroed, "StartRemoteCameraStreaming"}, + {21, &IChatSession::StubReplyZeroed, "StopRemoteCameraStreaming"}, + {30, &IChatSession::StubReplyZeroed, "CreateAudioStreamWorkBuffer"}, + {31, &IChatSession::StubReplyZeroed, "DestroyAudioStreamWorkBuffer"}, + {32, &IChatSession::StubReplyZeroed, "GetAudioStreamCapacityFrameCount"}, + {33, &IChatSession::StubReplyZeroed, "StartAudioStreamRecording"}, + {34, &IChatSession::StubReplyZeroed, "StopAudioStreamRecording"}, + {35, &IChatSession::StubReplyZeroed, "GetAudioStreamFreeFrameCount"}, + {36, &IChatSession::StubReplyZeroed, "GetAudioStreamFilledFrameCount"}, + {37, &IChatSession::StubReplyZeroed, "ReadAudioStream"}, }; RegisterHandlers(functions); event = service_context.CreateEvent("IChatSession:Event"); @@ -614,20 +626,68 @@ class IChatSession final : public ServiceFramework { std::array changed_events{}; }; -// Switch 2 GameChat (chat:u). 0 and 1 open the session the title keeps using. 2-4 return objects -// nothing here implements yet (layout customization, virtual camera, selective talk), so each -// gets an nx2 stub whose commands all succeed empty; MKW calls 3 at boot. Switchbrew leaves 0-4 -// unnamed: 2-4 are named after what they return. Everything else falls through to the nx2 auto -// stub. +// nn::chat::IUserLayoutCustomizationShimScopedObject, returned by chat:u 2. Per Switchbrew: +// 0 Initialize(pid, u64), then 1 hands back an event official software takes right after it; +// 10, 11 (u64 out), 13 (u8 and 16 bytes out) and 20 (u8 out) are unnamed, 12 ends the full-screen +// section. Outside a GameChat room the layout never changes: the event never fires and the rest +// answer 0. +class IUserLayoutCustomizationShimScopedObject final + : public ServiceFramework { +public: + explicit IUserLayoutCustomizationShimScopedObject(Core::System& system_) + : ServiceFramework{system_, "nx2:chat:IUserLayoutCustomizationShimScopedObject"}, + service_context{system_, "IUserLayoutCustomizationShimScopedObject"} { + static const FunctionInfo functions[] = { + {0, &IUserLayoutCustomizationShimScopedObject::StubReplyZeroed, "Initialize"}, + {1, &IUserLayoutCustomizationShimScopedObject::GetEvent, "GetEvent (guessed)"}, + {12, &IUserLayoutCustomizationShimScopedObject::StubReplyZeroed, "EndFullScreenWithLocalCameraAvailableSection"}, + }; + RegisterHandlers(functions); + event = service_context.CreateEvent("IUserLayoutCustomizationShimScopedObject:Event"); + } + + ~IUserLayoutCustomizationShimScopedObject() override { + service_context.CloseEvent(event); + } + +private: + void GetEvent(HLERequestContext& ctx) { + LOG_DEBUG(Service_Friend, "called"); + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(ctx, event->GetReadableEvent()); + } + + KernelHelpers::ServiceContext service_context; + Kernel::KEvent* event; +}; + +// Switch 2 GameChat (chat:u, nn::chat::IUserService). 0 and 1 open the session the title keeps +// using, 2 the layout customization object. 3 and 4 return objects nothing here implements yet +// (virtual camera, selective talk), so each gets an nx2 stub whose commands all succeed empty; MKW +// calls 3 at boot. Switchbrew leaves 0-4 unnamed: 2-4 are named after what they return. The named +// settings commands answer 0, which is what a console outside a GameChat room reports; everything +// else falls through to the nx2 auto stub. class IChatService final : public ServiceFramework { public: explicit IChatService(Core::System& system_) : ServiceFramework{system_, "nx2:chat:u"} { static const FunctionInfo functions[] = { {0, &IChatService::OpenSession, "OpenSession"}, {1, &IChatService::OpenSession, "OpenSession (u32)"}, - {2, &IChatService::OpenStubObject, "OpenUserLayoutCustomizationShimScopedObject"}, + {2, &IChatService::OpenUserLayoutCustomizationShimScopedObject, "OpenUserLayoutCustomizationShimScopedObject"}, {3, &IChatService::OpenStubObject, "OpenVirtualCameraShimScopedObject"}, {4, &IChatService::OpenStubObject, "OpenUserSelectiveTalkShimScopedObject"}, + {11, &IChatService::GetRoomInfo, "GetRoomInfo"}, + {14, &IChatService::StubReplyZeroed, "GetLocalCameraImageProcessingMode"}, + {15, &IChatService::StubReplyZeroed, "SetLocalCameraZoomMagnification"}, + {16, &IChatService::StubReplyZeroed, "GetLocalCameraZoomMagnification"}, + {21, &IChatService::StubReplyZeroed, "SetScreenSharingDisallowList"}, + {22, &IChatService::StubReplyZeroed, "ClearScreenSharingDisallowList"}, + {23, &IChatService::StubReplyZeroed, "SetScreenSharingMode"}, + {40, &IChatService::StubReplyZeroed, "GetRealtimeAudioLevels"}, + {51, &IChatService::StubReplyZeroed, "CalculateRequiredAudioStreamWorkBufferSize"}, + {60, &IChatService::StubReplyZeroed, "SetSharingKey"}, + {61, &IChatService::StubReplyZeroed, "ClearSharingKey"}, }; RegisterHandlers(functions); } @@ -640,13 +700,30 @@ class IChatService final : public ServiceFramework { rb.PushIpcInterface(ctx, system); } + void OpenUserLayoutCustomizationShimScopedObject(HLERequestContext& ctx) { + LOG_DEBUG(Service_Friend, "called"); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(ctx, system); + } + + // 11: the nn::chat::RoomInfo (0xF0 bytes) of the room the user chats in. Nobody is ever in a + // room here, so it is all zeroes, ChattingUid (0x18) included. The auto stub left the title's + // buffer as it was. + void GetRoomInfo(HLERequestContext& ctx) { + LOG_DEBUG(Service_Friend, "called"); + constexpr std::array room_info{}; + ctx.WriteBuffer(room_info); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + void OpenStubObject(HLERequestContext& ctx) { - static constexpr std::array names{ - "nx2:chat:IUserLayoutCustomizationShimScopedObject", + static constexpr std::array names{ "nx2:chat:IVirtualCameraShimScopedObject", "nx2:chat:IUserSelectiveTalkShimScopedObject", }; - const char* name = names[ctx.GetCommand() - 2]; + const char* name = names[ctx.GetCommand() - 3]; LOG_WARNING(Service_Friend, "(STUBBED) chat:u {}, returning {}", ctx.GetCommand(), name); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); From e7bc83db58fcc5baeaf07641de53274a96c2aab7 Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Tue, 3 Nov 2026 19:45:57 -0500 Subject: [PATCH 10/14] hid: read back the Switch 2 scene id and mouse pointer ranges Zeroed stubs answered the getters 0 whatever the title had set: - 138/139 SetSceneIdForPrivateApp / GetSceneIdForPrivateApp (Switchbrew: aruid and a u64 in; aruid in, u64 out) now store and return the id. - InitializeNmouse (3130), InitializeNmouse2 (3139, MKW calls it) and SetNmousePointerRanges (3132) take the pointer ranges (width, height); GetNmousePointerRanges (3133) returns them instead of 0 x 0. No mouse is ever attached, so the shared memory the title hands over is left as it is. --- src/core/hle/service/hid/hid_server.cpp | 66 ++++++++++++++++++++++--- src/core/hle/service/hid/hid_server.h | 19 +++++++ 2 files changed, 79 insertions(+), 6 deletions(-) diff --git a/src/core/hle/service/hid/hid_server.cpp b/src/core/hle/service/hid/hid_server.cpp index de7fbce716..1916b41ab3 100644 --- a/src/core/hle/service/hid/hid_server.cpp +++ b/src/core/hle/service/hid/hid_server.cpp @@ -121,8 +121,8 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr r {135, C<&IHidServer::SetNpadCaptureButtonAssignment>, "SetNpadCaptureButtonAssignment"}, {136, C<&IHidServer::ClearNpadCaptureButtonAssignment>, "ClearNpadCaptureButtonAssignment"}, {137, &IHidServer::StubReplyZeroed, "SetNpadGcAnalogStick8bitRawValue"}, - {138, &IHidServer::StubReplyZeroed, "SetSceneIdForPrivateApp"}, - {139, &IHidServer::StubReplyZeroed, "GetSceneIdForPrivateApp"}, + {138, C<&IHidServer::SetSceneIdForPrivateApp>, "SetSceneIdForPrivateApp"}, + {139, C<&IHidServer::GetSceneIdForPrivateApp>, "GetSceneIdForPrivateApp"}, {140, &IHidServer::StubReplyZeroed, "IsFirmwareUpdateAvailableForDebug"}, {150, &IHidServer::StubReplyZeroed, "GetSixAxisSensorIcSettings"}, {151, &IHidServer::StubReplyZeroed, "SetSixAxisSensorIcSettings"}, @@ -260,16 +260,16 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr r {3112, &IHidServer::StubReplyZeroed, "StartJcms"}, {3113, &IHidServer::StubReplyZeroed, "StopJcms"}, {3114, &IHidServer::StubReplyZeroed, "Unknown3114"}, - {3130, &IHidServer::StubReplyZeroed, "InitializeNmouse"}, + {3130, C<&IHidServer::InitializeNmouse>, "InitializeNmouse"}, {3131, &IHidServer::StubReplyZeroed, "FinalizeNmouse"}, - {3132, &IHidServer::StubReplyZeroed, "SetNmousePointerRanges"}, - {3133, &IHidServer::StubReplyZeroed, "GetNmousePointerRanges"}, + {3132, C<&IHidServer::SetNmousePointerRanges>, "SetNmousePointerRanges"}, + {3133, C<&IHidServer::GetNmousePointerRanges>, "GetNmousePointerRanges"}, {3134, &IHidServer::StubReplyZeroed, "Unknown3134"}, {3135, &IHidServer::StubReplyZeroed, "EnableShowingCursor"}, {3136, &IHidServer::StubReplyZeroed, "SetNmouseButtonConfigJoyLeftJcms"}, {3137, &IHidServer::StubReplyZeroed, "SetNmouseButtonConfigJoyRightJcms"}, {3138, &IHidServer::StubReplyZeroed, "SetMergedNmouseSourceTypes"}, - {3139, &IHidServer::StubReplyZeroed, "InitializeNmouse2"}, + {3139, C<&IHidServer::InitializeNmouse2>, "InitializeNmouse2"}, {3140, &IHidServer::StubReplyZeroed, "SetNmousePointerPosition"}, {3141, &IHidServer::StubReplyZeroed, "EnableNmousePointer"}, {3142, &IHidServer::StubReplyZeroed, "RestrictNmousePointer"}, @@ -1161,6 +1161,60 @@ Result IHidServer::IsOunceVibrationDeviceMounted( R_SUCCEED(); } +Result IHidServer::SetSceneIdForPrivateApp(ClientAppletResourceUserId aruid, u64 scene_id) { + LOG_DEBUG(Service_HID, "called, scene_id={:#x}, applet_resource_user_id={}", scene_id, aruid.pid); + scene_id_for_private_app = scene_id; + R_SUCCEED(); +} + +Result IHidServer::GetSceneIdForPrivateApp(Out out_scene_id, + ClientAppletResourceUserId aruid) { + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", aruid.pid); + *out_scene_id = scene_id_for_private_app; + R_SUCCEED(); +} + +Result IHidServer::InitializeNmouse(u8 pointer_type, s32 width, s32 height, + ClientAppletResourceUserId aruid, u64 t_mem_size, + InCopyHandle t_mem) { + LOG_WARNING(Service_HID, + "(STUBBED) called, pointer_type={}, width={}, height={}, applet_resource_user_id={}, " + "t_mem_size={:#x}", + pointer_type, width, height, aruid.pid, t_mem_size); + nmouse_pointer_width = width; + nmouse_pointer_height = height; + R_SUCCEED(); +} + +Result IHidServer::InitializeNmouse2(u32 pointer_type, s32 width, s32 height, u32 unknown, + ClientAppletResourceUserId aruid, u64 t_mem_size, + InCopyHandle t_mem) { + LOG_WARNING(Service_HID, + "(STUBBED) called, pointer_type={}, width={}, height={}, unknown={:#x}, " + "applet_resource_user_id={}, t_mem_size={:#x}", + pointer_type, width, height, unknown, aruid.pid, t_mem_size); + nmouse_pointer_width = width; + nmouse_pointer_height = height; + R_SUCCEED(); +} + +Result IHidServer::SetNmousePointerRanges(s32 width, s32 height, + ClientAppletResourceUserId aruid) { + LOG_DEBUG(Service_HID, "called, width={}, height={}, applet_resource_user_id={}", width, + height, aruid.pid); + nmouse_pointer_width = width; + nmouse_pointer_height = height; + R_SUCCEED(); +} + +Result IHidServer::GetNmousePointerRanges(Out out_width, Out out_height, + ClientAppletResourceUserId aruid) { + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", aruid.pid); + *out_width = nmouse_pointer_width; + *out_height = nmouse_pointer_height; + R_SUCCEED(); +} + Result IHidServer::SendVibrationValueInBool( bool is_vibrating, Core::HID::VibrationDeviceHandle vibration_device_handle, ClientAppletResourceUserId aruid) { diff --git a/src/core/hle/service/hid/hid_server.h b/src/core/hle/service/hid/hid_server.h index 6b2f7bc8ed..57e296af43 100644 --- a/src/core/hle/service/hid/hid_server.h +++ b/src/core/hle/service/hid/hid_server.h @@ -203,6 +203,22 @@ class IHidServer final : public ServiceFramework { Result IsOunceVibrationDeviceMounted(Out out_is_mounted, Core::HID::VibrationDeviceHandle vibration_device_handle, ClientAppletResourceUserId aruid); + // 138/139 (Switch 2): a scene id the title stores and reads back. + Result SetSceneIdForPrivateApp(ClientAppletResourceUserId aruid, u64 scene_id); + Result GetSceneIdForPrivateApp(Out out_scene_id, ClientAppletResourceUserId aruid); + // Switch 2 mouse mode (Nmouse). The pointer ranges (the screen size the pointer moves in) set by + // InitializeNmouse, InitializeNmouse2 and SetNmousePointerRanges are what + // GetNmousePointerRanges reads back. No mouse is ever attached, so the shared memory the title + // hands over stays as it is. + Result InitializeNmouse(u8 pointer_type, s32 width, s32 height, + ClientAppletResourceUserId aruid, u64 t_mem_size, + InCopyHandle t_mem); + Result InitializeNmouse2(u32 pointer_type, s32 width, s32 height, u32 unknown, + ClientAppletResourceUserId aruid, u64 t_mem_size, + InCopyHandle t_mem); + Result SetNmousePointerRanges(s32 width, s32 height, ClientAppletResourceUserId aruid); + Result GetNmousePointerRanges(Out out_width, Out out_height, + ClientAppletResourceUserId aruid); Result ActivateConsoleSixAxisSensor(ClientAppletResourceUserId aruid); Result StartConsoleSixAxisSensor(Core::HID::ConsoleSixAxisSensorHandle console_sixaxis_handle, ClientAppletResourceUserId aruid); @@ -281,6 +297,9 @@ class IHidServer final : public ServiceFramework { std::shared_ptr resource_manager; std::shared_ptr firmware_settings; + u64 scene_id_for_private_app{}; + s32 nmouse_pointer_width{}; + s32 nmouse_pointer_height{}; }; } // namespace Service::HID From 8e8ab3208f228562b65670be5e020907cfda61e8 Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Tue, 3 Nov 2026 19:45:58 -0500 Subject: [PATCH 11/14] am: give IApplicationProxy 40 its IPerformanceFunctions Command 40 returned an nx2 stub, so IsCpuOverclockEnabled always read 0 even after SetCpuOverclockEnabled(true). IPerformanceFunctions (Switchbrew, Switch 2) now keeps the title's request and reads it back, and also reports enabled when Eden's CPU clock setting is Boost or Overclock. The title's request doesn't change that setting: it scales the clock the guest reads, which would jump under a running title. --- src/core/CMakeLists.txt | 1 + .../service/am/service/application_proxy.cpp | 10 +++- .../service/am/service/application_proxy.h | 2 + .../am/service/performance_functions.h | 48 +++++++++++++++++++ 4 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 src/core/hle/service/am/service/performance_functions.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 42adb14f84..360cc86796 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -466,6 +466,7 @@ add_library(core STATIC hle/service/am/service/application_functions.h hle/service/am/service/application_proxy.cpp hle/service/am/service/graphics_scaling_parameters_functions.h + hle/service/am/service/performance_functions.h hle/service/am/service/application_proxy.h hle/service/am/service/application_proxy_service.cpp hle/service/am/service/application_proxy_service.h diff --git a/src/core/hle/service/am/service/application_proxy.cpp b/src/core/hle/service/am/service/application_proxy.cpp index fd0679b69b..ec0475233f 100644 --- a/src/core/hle/service/am/service/application_proxy.cpp +++ b/src/core/hle/service/am/service/application_proxy.cpp @@ -13,6 +13,7 @@ #include "core/hle/service/am/service/display_controller.h" #include "core/hle/service/am/service/graphics_scaling_parameters_functions.h" #include "core/hle/service/am/service/library_applet_creator.h" +#include "core/hle/service/am/service/performance_functions.h" #include "core/hle/service/am/service/process_winding_controller.h" #include "core/hle/service/am/service/self_controller.h" #include "core/hle/service/am/service/window_controller.h" @@ -37,7 +38,7 @@ IApplicationProxy::IApplicationProxy(Core::System& system_, std::shared_ptr, "GetApplicationFunctions"}, // Switch 2-only getters; see nx2_stub_interface.h. Switchbrew leaves 40 and 60 unnamed: // their names follow the interfaces they return. - {40, &IApplicationProxy::GetNx2Interface, "GetPerformanceFunctions"}, + {40, D<&IApplicationProxy::GetPerformanceFunctions>, "GetPerformanceFunctions"}, {50, &IApplicationProxy::GetNx2Interface, "Nx2Getter50"}, {60, D<&IApplicationProxy::GetGraphicsScalingParametersFunctions>, "GetGraphicsScalingParametersFunctions"}, {70, &IApplicationProxy::GetNx2Interface, "Nx2Getter70"}, @@ -52,6 +53,13 @@ IApplicationProxy::IApplicationProxy(Core::System& system_, std::shared_ptr> out_functions) { + LOG_DEBUG(Service_AM, "called"); + *out_functions = std::make_shared(system); + R_SUCCEED(); +} + Result IApplicationProxy::GetGraphicsScalingParametersFunctions( Out> out_functions) { LOG_DEBUG(Service_AM, "called"); diff --git a/src/core/hle/service/am/service/application_proxy.h b/src/core/hle/service/am/service/application_proxy.h index 68de11b5e9..ceed41b792 100644 --- a/src/core/hle/service/am/service/application_proxy.h +++ b/src/core/hle/service/am/service/application_proxy.h @@ -19,6 +19,7 @@ class IDebugFunctions; class IDisplayController; class IGraphicsScalingParametersFunctions; class ILibraryAppletCreator; +class IPerformanceFunctions; class IProcessWindingController; class ISelfController; class IWindowController; @@ -32,6 +33,7 @@ class IApplicationProxy final : public ServiceFramework { private: void GetNx2Interface(HLERequestContext& ctx); + Result GetPerformanceFunctions(Out> out_functions); Result GetGraphicsScalingParametersFunctions( Out> out_functions); Result GetAudioController(Out> out_audio_controller); diff --git a/src/core/hle/service/am/service/performance_functions.h b/src/core/hle/service/am/service/performance_functions.h new file mode 100644 index 0000000000..b51962670d --- /dev/null +++ b/src/core/hle/service/am/service/performance_functions.h @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/settings.h" +#include "core/hle/service/cmif_types.h" +#include "core/hle/service/nx2_stub_interface.h" +#include "core/hle/service/service.h" + +namespace Service::AM { + +// nn::am::service::IPerformanceFunctions (Switch 2), returned by IApplicationProxy command 40. The +// title's own request is kept and read back, and Eden's CPU clock setting (Boost or Overclock) +// also reads as enabled. The request doesn't change that setting: it scales the guest clock, which +// would jump under a running title. Commands without a handler are auto-stubbed because the name +// carries the nx2: prefix. +class IPerformanceFunctions final : public ServiceFramework { +public: + explicit IPerformanceFunctions(Core::System& system_) + : ServiceFramework{system_, "nx2:IPerformanceFunctions"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, D<&IPerformanceFunctions::IsCpuOverclockEnabled>, "IsCpuOverclockEnabled"}, + {1, D<&IPerformanceFunctions::SetCpuOverclockEnabled>, "SetCpuOverclockEnabled"}, + }; + // clang-format on + RegisterHandlers(functions); + } + +private: + Result IsCpuOverclockEnabled(Out out_is_enabled) { + *out_is_enabled = m_cpu_overclock_requested || + Settings::values.cpu_clock.GetValue() != Settings::CpuClock::Normal; + LOG_DEBUG(Service_AM, "called, enabled={}", *out_is_enabled); + R_SUCCEED(); + } + + Result SetCpuOverclockEnabled(bool enabled) { + LOG_DEBUG(Service_AM, "called, enabled={}", enabled); + m_cpu_overclock_requested = enabled; + R_SUCCEED(); + } + + bool m_cpu_overclock_requested{}; +}; + +} // namespace Service::AM From 20a67976d4922fc6548d27a7a139f639b1166782 Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Tue, 3 Nov 2026 20:09:33 -0500 Subject: [PATCH 12/14] am: keep the overclock request on the applet IApplicationProxy 40 makes a new IPerformanceFunctions per call, so a SetCpuOverclockEnabled(true) on one object read back as false through the next. The request now lives on the Applet like the other application settings (media playback state, game play recording), under its lock. Also drops the nx2_stub_interface.h include both Switch 2 AM interface headers carried without using it. --- src/core/hle/service/am/applet.h | 1 + .../service/am/service/application_proxy.cpp | 2 +- .../graphics_scaling_parameters_functions.h | 1 - .../am/service/performance_functions.h | 26 ++++++++++++------- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/core/hle/service/am/applet.h b/src/core/hle/service/am/applet.h index 571fa65219..3ab467f549 100644 --- a/src/core/hle/service/am/applet.h +++ b/src/core/hle/service/am/applet.h @@ -84,6 +84,7 @@ struct Applet { // Application functions bool game_play_recording_supported{}; bool media_playback_state{}; + bool cpu_overclock_requested{}; ///< Switch 2 IPerformanceFunctions GamePlayRecordingState game_play_recording_state{GamePlayRecordingState::Disabled}; bool jit_service_launched{}; bool application_crash_report_enabled{}; diff --git a/src/core/hle/service/am/service/application_proxy.cpp b/src/core/hle/service/am/service/application_proxy.cpp index ec0475233f..d5814a073a 100644 --- a/src/core/hle/service/am/service/application_proxy.cpp +++ b/src/core/hle/service/am/service/application_proxy.cpp @@ -56,7 +56,7 @@ IApplicationProxy::~IApplicationProxy() = default; Result IApplicationProxy::GetPerformanceFunctions( Out> out_functions) { LOG_DEBUG(Service_AM, "called"); - *out_functions = std::make_shared(system); + *out_functions = std::make_shared(system, m_applet); R_SUCCEED(); } diff --git a/src/core/hle/service/am/service/graphics_scaling_parameters_functions.h b/src/core/hle/service/am/service/graphics_scaling_parameters_functions.h index 11234c5fbe..e3632cba45 100644 --- a/src/core/hle/service/am/service/graphics_scaling_parameters_functions.h +++ b/src/core/hle/service/am/service/graphics_scaling_parameters_functions.h @@ -4,7 +4,6 @@ #pragma once #include "core/hle/service/cmif_types.h" -#include "core/hle/service/nx2_stub_interface.h" #include "core/hle/service/service.h" namespace Service::AM { diff --git a/src/core/hle/service/am/service/performance_functions.h b/src/core/hle/service/am/service/performance_functions.h index b51962670d..1eac4ddd0e 100644 --- a/src/core/hle/service/am/service/performance_functions.h +++ b/src/core/hle/service/am/service/performance_functions.h @@ -3,22 +3,26 @@ #pragma once +#include +#include + #include "common/settings.h" +#include "core/hle/service/am/applet.h" #include "core/hle/service/cmif_types.h" -#include "core/hle/service/nx2_stub_interface.h" #include "core/hle/service/service.h" namespace Service::AM { // nn::am::service::IPerformanceFunctions (Switch 2), returned by IApplicationProxy command 40. The -// title's own request is kept and read back, and Eden's CPU clock setting (Boost or Overclock) -// also reads as enabled. The request doesn't change that setting: it scales the guest clock, which -// would jump under a running title. Commands without a handler are auto-stubbed because the name -// carries the nx2: prefix. +// application's own request is kept on its applet (so it survives the interface being opened +// again) and read back, and Eden's CPU clock setting (Boost or Overclock) also reads as enabled. +// The request doesn't change that setting: it scales the guest clock, which would jump under a +// running title. Commands without a handler are auto-stubbed because the name carries the nx2: +// prefix. class IPerformanceFunctions final : public ServiceFramework { public: - explicit IPerformanceFunctions(Core::System& system_) - : ServiceFramework{system_, "nx2:IPerformanceFunctions"} { + explicit IPerformanceFunctions(Core::System& system_, std::shared_ptr applet) + : ServiceFramework{system_, "nx2:IPerformanceFunctions"}, m_applet{std::move(applet)} { // clang-format off static const FunctionInfo functions[] = { {0, D<&IPerformanceFunctions::IsCpuOverclockEnabled>, "IsCpuOverclockEnabled"}, @@ -30,7 +34,8 @@ class IPerformanceFunctions final : public ServiceFramework out_is_enabled) { - *out_is_enabled = m_cpu_overclock_requested || + std::scoped_lock lk{m_applet->lock}; + *out_is_enabled = m_applet->cpu_overclock_requested || Settings::values.cpu_clock.GetValue() != Settings::CpuClock::Normal; LOG_DEBUG(Service_AM, "called, enabled={}", *out_is_enabled); R_SUCCEED(); @@ -38,11 +43,12 @@ class IPerformanceFunctions final : public ServiceFrameworklock}; + m_applet->cpu_overclock_requested = enabled; R_SUCCEED(); } - bool m_cpu_overclock_requested{}; + const std::shared_ptr m_applet; }; } // namespace Service::AM From 62a118c56e27327e56c3902663bae42208ae27a9 Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Tue, 3 Nov 2026 20:09:35 -0500 Subject: [PATCH 13/14] hid: keep scene ids and pointer ranges per application, log 213 once - The scene id (138/139) and the Nmouse pointer ranges are kept per applet resource user id, so another applet can't overwrite what the application reads back. - InitializeNmouse (3130) goes back to a placeholder: none of the 312 Switch 2 boots in the logs on this machine call it, so its Switchbrew layout has no capture behind it. InitializeNmouse2 (3139, MKW) stays. - SendVibrationValueInMode (213) is still dropped: two layouts fit its 32 bytes and no capture says which. It now logs its first request so the next run of Kirby Air Riders, the only title seen sending it, settles that. MKW rumbles through 214, which is forwarded. --- src/core/hle/service/hid/hid_server.cpp | 51 +++++++++++++------------ src/core/hle/service/hid/hid_server.h | 29 ++++++++------ 2 files changed, 43 insertions(+), 37 deletions(-) diff --git a/src/core/hle/service/hid/hid_server.cpp b/src/core/hle/service/hid/hid_server.cpp index 1916b41ab3..7830927459 100644 --- a/src/core/hle/service/hid/hid_server.cpp +++ b/src/core/hle/service/hid/hid_server.cpp @@ -6,6 +6,7 @@ #include #include +#include #include "common/common_types.h" #include "common/logging.h" @@ -140,7 +141,7 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr r {210, C<&IHidServer::EndPermitVibrationSession>, "EndPermitVibrationSession"}, {211, C<&IHidServer::IsVibrationDeviceMounted>, "IsVibrationDeviceMounted"}, {212, C<&IHidServer::SendVibrationValueInBool>, "SendVibrationValueInBool"}, - {213, C<&IHidServer::SendVibrationValueInMode>, "SendVibrationValueInMode"}, + {213, &IHidServer::SendVibrationValueInMode, "SendVibrationValueInMode"}, {214, C<&IHidServer::SendVibrationValuesInMode>, "SendVibrationValuesInMode"}, {220, C<&IHidServer::IsOunceVibrationDeviceMounted>, "IsOunceVibrationDeviceMounted"}, {300, C<&IHidServer::ActivateConsoleSixAxisSensor>, "ActivateConsoleSixAxisSensor"}, @@ -260,7 +261,7 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr r {3112, &IHidServer::StubReplyZeroed, "StartJcms"}, {3113, &IHidServer::StubReplyZeroed, "StopJcms"}, {3114, &IHidServer::StubReplyZeroed, "Unknown3114"}, - {3130, C<&IHidServer::InitializeNmouse>, "InitializeNmouse"}, + {3130, &IHidServer::StubReplyZeroed, "InitializeNmouse"}, {3131, &IHidServer::StubReplyZeroed, "FinalizeNmouse"}, {3132, C<&IHidServer::SetNmousePointerRanges>, "SetNmousePointerRanges"}, {3133, C<&IHidServer::GetNmousePointerRanges>, "GetNmousePointerRanges"}, @@ -1134,9 +1135,19 @@ Result IHidServer::SendVibrationValuesInMode( R_SUCCEED(); } -Result IHidServer::SendVibrationValueInMode() { - LOG_DEBUG(Service_HID, "(STUBBED) called"); - R_SUCCEED(); +void IHidServer::SendVibrationValueInMode(HLERequestContext& ctx) { + // Switchbrew names 213 but gives no layout, and two fit its 32 bytes (the mode before the + // handle, or after the value as in 206's padding). Log the first request so a run shows which. + static std::once_flag logged; + std::call_once(logged, [&ctx] { + IPC::RequestParser rp{ctx}; + const auto w = rp.PopRaw>(); + LOG_INFO(Service_HID, + "(STUBBED) first request: {:08x} {:08x} {:08x} {:08x} {:08x} {:08x} {:08x} {:08x}", + w[0], w[1], w[2], w[3], w[4], w[5], w[6], w[7]); + }); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); } Result IHidServer::IsOunceVibrationDeviceMounted( @@ -1163,26 +1174,15 @@ Result IHidServer::IsOunceVibrationDeviceMounted( Result IHidServer::SetSceneIdForPrivateApp(ClientAppletResourceUserId aruid, u64 scene_id) { LOG_DEBUG(Service_HID, "called, scene_id={:#x}, applet_resource_user_id={}", scene_id, aruid.pid); - scene_id_for_private_app = scene_id; + scene_ids_for_private_app[aruid.pid] = scene_id; R_SUCCEED(); } Result IHidServer::GetSceneIdForPrivateApp(Out out_scene_id, ClientAppletResourceUserId aruid) { LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", aruid.pid); - *out_scene_id = scene_id_for_private_app; - R_SUCCEED(); -} - -Result IHidServer::InitializeNmouse(u8 pointer_type, s32 width, s32 height, - ClientAppletResourceUserId aruid, u64 t_mem_size, - InCopyHandle t_mem) { - LOG_WARNING(Service_HID, - "(STUBBED) called, pointer_type={}, width={}, height={}, applet_resource_user_id={}, " - "t_mem_size={:#x}", - pointer_type, width, height, aruid.pid, t_mem_size); - nmouse_pointer_width = width; - nmouse_pointer_height = height; + const auto it = scene_ids_for_private_app.find(aruid.pid); + *out_scene_id = it != scene_ids_for_private_app.end() ? it->second : 0; R_SUCCEED(); } @@ -1193,8 +1193,7 @@ Result IHidServer::InitializeNmouse2(u32 pointer_type, s32 width, s32 height, u3 "(STUBBED) called, pointer_type={}, width={}, height={}, unknown={:#x}, " "applet_resource_user_id={}, t_mem_size={:#x}", pointer_type, width, height, unknown, aruid.pid, t_mem_size); - nmouse_pointer_width = width; - nmouse_pointer_height = height; + nmouse_pointer_ranges[aruid.pid] = {width, height}; R_SUCCEED(); } @@ -1202,16 +1201,18 @@ Result IHidServer::SetNmousePointerRanges(s32 width, s32 height, ClientAppletResourceUserId aruid) { LOG_DEBUG(Service_HID, "called, width={}, height={}, applet_resource_user_id={}", width, height, aruid.pid); - nmouse_pointer_width = width; - nmouse_pointer_height = height; + nmouse_pointer_ranges[aruid.pid] = {width, height}; R_SUCCEED(); } Result IHidServer::GetNmousePointerRanges(Out out_width, Out out_height, ClientAppletResourceUserId aruid) { LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", aruid.pid); - *out_width = nmouse_pointer_width; - *out_height = nmouse_pointer_height; + const auto it = nmouse_pointer_ranges.find(aruid.pid); + const NmousePointerRanges ranges = + it != nmouse_pointer_ranges.end() ? it->second : NmousePointerRanges{}; + *out_width = ranges.width; + *out_height = ranges.height; R_SUCCEED(); } diff --git a/src/core/hle/service/hid/hid_server.h b/src/core/hle/service/hid/hid_server.h index 57e296af43..7b65ca9b35 100644 --- a/src/core/hle/service/hid/hid_server.h +++ b/src/core/hle/service/hid/hid_server.h @@ -6,6 +6,8 @@ #pragma once +#include + #include "core/hle/service/cmif_types.h" #include "core/hle/service/service.h" #include "hid_core/hid_types.h" @@ -196,23 +198,22 @@ class IHidServer final : public ServiceFramework { InArray vibration_handles, InArray vibration_values); // 213 (32 bytes, aruid): sent per controller every frame by Kirby Air Riders (tens of - // thousands of auto stub log lines a run); no reply data. Accepted and dropped. - Result SendVibrationValueInMode(); + // thousands of auto stub log lines a run); no reply data. Accepted and dropped until its + // layout is known: the first request's raw words are logged for that. + void SendVibrationValueInMode(HLERequestContext& ctx); // 220: whether the Switch 2 vibration device behind a handle is mounted, as 211 answers it for // Switch 1 devices. Result IsOunceVibrationDeviceMounted(Out out_is_mounted, Core::HID::VibrationDeviceHandle vibration_device_handle, ClientAppletResourceUserId aruid); - // 138/139 (Switch 2): a scene id the title stores and reads back. + // 138/139 (Switch 2): a scene id each application stores and reads back. Result SetSceneIdForPrivateApp(ClientAppletResourceUserId aruid, u64 scene_id); Result GetSceneIdForPrivateApp(Out out_scene_id, ClientAppletResourceUserId aruid); - // Switch 2 mouse mode (Nmouse). The pointer ranges (the screen size the pointer moves in) set by - // InitializeNmouse, InitializeNmouse2 and SetNmousePointerRanges are what + // Switch 2 mouse mode (Nmouse). The pointer ranges (the screen size the pointer moves in) an + // application sets with InitializeNmouse2 or SetNmousePointerRanges are what // GetNmousePointerRanges reads back. No mouse is ever attached, so the shared memory the title - // hands over stays as it is. - Result InitializeNmouse(u8 pointer_type, s32 width, s32 height, - ClientAppletResourceUserId aruid, u64 t_mem_size, - InCopyHandle t_mem); + // hands over stays as it is. InitializeNmouse (3130) stays a placeholder: no title has been + // seen calling it, so its layout is unconfirmed. Result InitializeNmouse2(u32 pointer_type, s32 width, s32 height, u32 unknown, ClientAppletResourceUserId aruid, u64 t_mem_size, InCopyHandle t_mem); @@ -297,9 +298,13 @@ class IHidServer final : public ServiceFramework { std::shared_ptr resource_manager; std::shared_ptr firmware_settings; - u64 scene_id_for_private_app{}; - s32 nmouse_pointer_width{}; - s32 nmouse_pointer_height{}; + struct NmousePointerRanges { + s32 width; + s32 height; + }; + // Keyed by applet resource user id, as the console keeps them per application. + std::unordered_map scene_ids_for_private_app; + std::unordered_map nmouse_pointer_ranges; }; } // namespace Service::HID From 36afaf9c48338d7168f536aa2049002282b3de1c Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Tue, 3 Nov 2026 20:09:37 -0500 Subject: [PATCH 14/14] chat: use StubReplyEvent for the events that never fire IChatSession 1-4 and IUserLayoutCustomizationShimScopedObject 1 each created their own never-signalled event. ServiceFrameworkBase already hands out one lazily (StubReplyEvent), so the event members, their cleanup and the per-command handlers go. --- src/core/hle/service/friend/friend.cpp | 77 ++++---------------------- 1 file changed, 12 insertions(+), 65 deletions(-) diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp index 56757fb7e2..423b49eca0 100644 --- a/src/core/hle/service/friend/friend.cpp +++ b/src/core/hle/service/friend/friend.cpp @@ -560,20 +560,19 @@ class IServiceForSystem final : public ServiceFramework { } }; -// Session returned by chat:u 0 and 1. Command 1 hands back a handle the title waits on, so it gets -// an event that is never signalled. 2-4 attach to the room info, local camera image processing -// mode and local camera zoom change events (Switchbrew: no input, an event out); none of them -// changes outside a GameChat room, so they never fire. Everything else falls through to the nx2 -// auto stub. +// Session returned by chat:u 0 and 1 (nn::chat::IUserShimScopedObject). Command 1 hands back a +// handle the title waits on; 2-4 attach to the room info, local camera image processing mode and +// local camera zoom change events (Switchbrew: no input, an event out). None of them fires outside +// a GameChat room, so each gets the never-signalled stub event. The other named commands answer 0; +// everything else falls through to the nx2 auto stub. class IChatSession final : public ServiceFramework { public: - explicit IChatSession(Core::System& system_) - : ServiceFramework{system_, "nx2:chat:u::0"}, service_context{system_, "IChatSession"} { + explicit IChatSession(Core::System& system_) : ServiceFramework{system_, "nx2:chat:u::0"} { static const FunctionInfo functions[] = { - {1, &IChatSession::GetEvent, "GetEvent (guessed)"}, - {2, &IChatSession::AttachToChangedEvent, "AttachToRoomInfoChangedEvent"}, - {3, &IChatSession::AttachToChangedEvent, "AttachToLocalCameraImageProcessingModeChangedEvent"}, - {4, &IChatSession::AttachToChangedEvent, "AttachToLocalCameraZoomMagnificationChangedEvent"}, + {1, &IChatSession::StubReplyEvent, "GetEvent (guessed)"}, + {2, &IChatSession::StubReplyEvent, "AttachToRoomInfoChangedEvent"}, + {3, &IChatSession::StubReplyEvent, "AttachToLocalCameraImageProcessingModeChangedEvent"}, + {4, &IChatSession::StubReplyEvent, "AttachToLocalCameraZoomMagnificationChangedEvent"}, {10, &IChatSession::StubReplyZeroed, "StartLocalCameraStreaming"}, {11, &IChatSession::StubReplyZeroed, "StopLocalCameraStreaming"}, {20, &IChatSession::StubReplyZeroed, "StartRemoteCameraStreaming"}, @@ -588,42 +587,7 @@ class IChatSession final : public ServiceFramework { {37, &IChatSession::StubReplyZeroed, "ReadAudioStream"}, }; RegisterHandlers(functions); - event = service_context.CreateEvent("IChatSession:Event"); - static constexpr std::array changed_event_names{ - "IChatSession:RoomInfoChanged", - "IChatSession:LocalCameraImageProcessingModeChanged", - "IChatSession:LocalCameraZoomMagnificationChanged", - }; - for (size_t i = 0; i < changed_events.size(); ++i) { - changed_events[i] = service_context.CreateEvent(changed_event_names[i]); - } - } - - ~IChatSession() override { - service_context.CloseEvent(event); - for (auto* changed_event : changed_events) { - service_context.CloseEvent(changed_event); - } - } - -private: - void GetEvent(HLERequestContext& ctx) { - LOG_WARNING(Service_Friend, "(STUBBED) chat:u::0 1 [5]={:#x}", ctx.CommandBuffer()[5]); - IPC::ResponseBuilder rb{ctx, 2, 1}; - rb.Push(ResultSuccess); - rb.PushCopyObjects(ctx, event->GetReadableEvent()); - } - - void AttachToChangedEvent(HLERequestContext& ctx) { - LOG_DEBUG(Service_Friend, "chat:u::0 {}", ctx.GetCommand()); - IPC::ResponseBuilder rb{ctx, 2, 1}; - rb.Push(ResultSuccess); - rb.PushCopyObjects(ctx, changed_events[ctx.GetCommand() - 2]->GetReadableEvent()); } - - KernelHelpers::ServiceContext service_context; - Kernel::KEvent* event; - std::array changed_events{}; }; // nn::chat::IUserLayoutCustomizationShimScopedObject, returned by chat:u 2. Per Switchbrew: @@ -635,31 +599,14 @@ class IUserLayoutCustomizationShimScopedObject final : public ServiceFramework { public: explicit IUserLayoutCustomizationShimScopedObject(Core::System& system_) - : ServiceFramework{system_, "nx2:chat:IUserLayoutCustomizationShimScopedObject"}, - service_context{system_, "IUserLayoutCustomizationShimScopedObject"} { + : ServiceFramework{system_, "nx2:chat:IUserLayoutCustomizationShimScopedObject"} { static const FunctionInfo functions[] = { {0, &IUserLayoutCustomizationShimScopedObject::StubReplyZeroed, "Initialize"}, - {1, &IUserLayoutCustomizationShimScopedObject::GetEvent, "GetEvent (guessed)"}, + {1, &IUserLayoutCustomizationShimScopedObject::StubReplyEvent, "GetEvent (guessed)"}, {12, &IUserLayoutCustomizationShimScopedObject::StubReplyZeroed, "EndFullScreenWithLocalCameraAvailableSection"}, }; RegisterHandlers(functions); - event = service_context.CreateEvent("IUserLayoutCustomizationShimScopedObject:Event"); - } - - ~IUserLayoutCustomizationShimScopedObject() override { - service_context.CloseEvent(event); - } - -private: - void GetEvent(HLERequestContext& ctx) { - LOG_DEBUG(Service_Friend, "called"); - IPC::ResponseBuilder rb{ctx, 2, 1}; - rb.Push(ResultSuccess); - rb.PushCopyObjects(ctx, event->GetReadableEvent()); } - - KernelHelpers::ServiceContext service_context; - Kernel::KEvent* event; }; // Switch 2 GameChat (chat:u, nn::chat::IUserService). 0 and 1 open the session the title keeps