From 2b413a46a2820f0a646baaa8dbd683009f096f32 Mon Sep 17 00:00:00 2001 From: néo Luigi <85024393+neoluigi4123@users.noreply.github.com> Date: Sun, 1 Nov 2026 03:04:13 +0100 Subject: Vibration fix for ns2 games | Assisted by claude (code) --- src/core/hle/service/hid/hid_server.cpp | 39 +++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) (limited to 'src/core/hle/service/hid/hid_server.cpp') diff --git a/src/core/hle/service/hid/hid_server.cpp b/src/core/hle/service/hid/hid_server.cpp index 04b4663bc2..9d5f5f91d2 100644 --- a/src/core/hle/service/hid/hid_server.cpp +++ b/src/core/hle/service/hid/hid_server.cpp @@ -132,7 +132,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"}, - {214, C<&IHidServer::SendNx2VibrationValues>, "SendNx2VibrationValues (guessed)"}, + {214, &IHidServer::SendNx2VibrationValues, "SendNx2VibrationValues (guessed)"}, {300, C<&IHidServer::ActivateConsoleSixAxisSensor>, "ActivateConsoleSixAxisSensor"}, {301, C<&IHidServer::StartConsoleSixAxisSensor>, "StartConsoleSixAxisSensor"}, {302, C<&IHidServer::StopConsoleSixAxisSensor>, "StopConsoleSixAxisSensor"}, @@ -1070,9 +1070,40 @@ Result IHidServer::IsVibrationDeviceMounted( R_SUCCEED(); } -Result IHidServer::SendNx2VibrationValues() { - LOG_DEBUG(Service_HID, "(STUBBED) called"); - R_SUCCEED(); +// TEMPORARY local diagnostic for #103: dumps the request layout of 214 when it changes. +void IHidServer::SendNx2VibrationValues(HLERequestContext& ctx) { + static std::string last_dump; + static u64 call_count = 0; + ++call_count; + + std::string dump = fmt::format("words(payload@{}):", ctx.GetDataPayloadOffset()); + const u32* cmd_buf = ctx.CommandBuffer(); + for (u32 i = 0; i < 16; ++i) { + dump += fmt::format(" {:08x}", cmd_buf[i]); + } + dump += fmt::format(" | X={} A={} B={} C={}", ctx.BufferDescriptorX().size(), + ctx.BufferDescriptorA().size(), ctx.BufferDescriptorB().size(), + ctx.BufferDescriptorC().size()); + const auto dump_buffer = [&dump](char kind, std::size_t index, std::span data) { + dump += fmt::format(" | {}{} size={}:", kind, index, data.size()); + for (std::size_t i = 0; i < std::min(data.size(), 96); ++i) { + dump += fmt::format("{}{:02x}", i % 4 == 0 ? " " : "", data[i]); + } + }; + for (std::size_t i = 0; i < ctx.BufferDescriptorX().size(); ++i) { + dump_buffer('X', i, ctx.ReadBufferX(i)); + } + for (std::size_t i = 0; i < ctx.BufferDescriptorA().size(); ++i) { + dump_buffer('A', i, ctx.ReadBufferA(i)); + } + + if (dump != last_dump) { + LOG_INFO(Service_HID, "214 #{}: {}", call_count, dump); + last_dump = std::move(dump); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); } Result IHidServer::SendVibrationValueInBool( -- cgit v1.2.3 From 465aa33077f44d3f8664c076f8580e78446c0dee Mon Sep 17 00:00:00 2001 From: néo Luigi <85024393+neoluigi4123@users.noreply.github.com> Date: Tue, 3 Nov 2026 02:15:22 +0100 Subject: Vibration support for ns2 games This branch supports vibration by achieving proper rumble reading from games and uses regular eglantine/eden logic from ns1 to send them over. Tested on windows 10 on Mario Kart World Assisted-by: Claude Opus 5.5 (code cleanup) --- src/core/hle/service/hid/hid_server.cpp | 50 ++++++++++++--------------------- src/core/hle/service/hid/hid_server.h | 11 +++++--- 2 files changed, 25 insertions(+), 36 deletions(-) (limited to 'src/core/hle/service/hid/hid_server.cpp') diff --git a/src/core/hle/service/hid/hid_server.cpp b/src/core/hle/service/hid/hid_server.cpp index eb6a29c158..f2a7b7d04a 100644 --- a/src/core/hle/service/hid/hid_server.cpp +++ b/src/core/hle/service/hid/hid_server.cpp @@ -4,6 +4,7 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later +#include #include #include "common/common_types.h" @@ -140,7 +141,7 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr r {211, C<&IHidServer::IsVibrationDeviceMounted>, "IsVibrationDeviceMounted"}, {212, C<&IHidServer::SendVibrationValueInBool>, "SendVibrationValueInBool"}, {213, C<&IHidServer::Unknown213>, "Unknown213"}, - {214, &IHidServer::SendNx2VibrationValues, "SendNx2VibrationValues (guessed)"}, + {214, C<&IHidServer::SendNx2VibrationValues>, "SendNx2VibrationValues (guessed)"}, {220, C<&IHidServer::Unknown220>, "Unknown220"}, {300, C<&IHidServer::ActivateConsoleSixAxisSensor>, "ActivateConsoleSixAxisSensor"}, {301, C<&IHidServer::StartConsoleSixAxisSensor>, "StartConsoleSixAxisSensor"}, @@ -1112,40 +1113,25 @@ Result IHidServer::IsVibrationDeviceMounted( R_SUCCEED(); } -// TEMPORARY local diagnostic for #103: dumps the request layout of 214 when it changes. -void IHidServer::SendNx2VibrationValues(HLERequestContext& ctx) { - static std::string last_dump; - static u64 call_count = 0; - ++call_count; - - std::string dump = fmt::format("words(payload@{}):", ctx.GetDataPayloadOffset()); - const u32* cmd_buf = ctx.CommandBuffer(); - for (u32 i = 0; i < 16; ++i) { - dump += fmt::format(" {:08x}", cmd_buf[i]); - } - dump += fmt::format(" | X={} A={} B={} C={}", ctx.BufferDescriptorX().size(), - ctx.BufferDescriptorA().size(), ctx.BufferDescriptorB().size(), - ctx.BufferDescriptorC().size()); - const auto dump_buffer = [&dump](char kind, std::size_t index, std::span data) { - dump += fmt::format(" | {}{} size={}:", kind, index, data.size()); - for (std::size_t i = 0; i < std::min(data.size(), 96); ++i) { - dump += fmt::format("{}{:02x}", i % 4 == 0 ? " " : "", data[i]); +Result IHidServer::SendNx2VibrationValues( + u64 unknown, AppletResourceUserId aruid, + InArray vibration_handles, + InArray vibration_values) { + LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", unknown, aruid.pid); + + // Unlike 206 this never fails: rumble is best effort and the command is only known from + // captures, so an unexpected handle or layout shouldn't return an error a game may treat as + // fatal. + const auto count = (std::min)(vibration_handles.size(), vibration_values.size()); + for (std::size_t i = 0; i < count; i++) { + const auto result = GetResourceManager()->SendVibrationValue( + aruid.pid, vibration_handles[i], vibration_values[i]); + if (result.IsError()) { + LOG_DEBUG(Service_HID, "failed to send vibration {}, result={:#x}", i, result.raw); } - }; - for (std::size_t i = 0; i < ctx.BufferDescriptorX().size(); ++i) { - dump_buffer('X', i, ctx.ReadBufferX(i)); - } - for (std::size_t i = 0; i < ctx.BufferDescriptorA().size(); ++i) { - dump_buffer('A', i, ctx.ReadBufferA(i)); - } - - if (dump != last_dump) { - LOG_INFO(Service_HID, "214 #{}: {}", call_count, dump); - last_dump = std::move(dump); } - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + R_SUCCEED(); } Result IHidServer::Unknown213() { diff --git a/src/core/hle/service/hid/hid_server.h b/src/core/hle/service/hid/hid_server.h index 5738e1798f..9bb62fabc4 100644 --- a/src/core/hle/service/hid/hid_server.h +++ b/src/core/hle/service/hid/hid_server.h @@ -189,11 +189,14 @@ private: Result SendVibrationValueInBool(bool is_vibrating, Core::HID::VibrationDeviceHandle vibration_device_handle, ClientAppletResourceUserId aruid); - // Switch 2 sends vibration every frame with 214 (pointer buffers of device handles and - // values, like 206); Eden doesn't emulate vibration, so accept and drop it. - void SendNx2VibrationValues(HLERequestContext& ctx); + // 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( + 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 like 214. + // 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. -- cgit v1.2.3