From ca7bf38186fd70d40c02110f766d87c939d15eac Mon Sep 17 00:00:00 2001 From: Marcus Rowe Date: Thu, 30 Apr 2026 20:14:31 +1000 Subject: [PATCH 1/2] SNES: Clear _rumbleData when the rumble controller is latched The rumble controller spec document states the 16-bit shift register should be cleared when the P/S Out line goes high and then low. This behaviour has been verified on a Limited Run Games rumble controller with a custom test ROM. https://forums.nesdev.org/viewtopic.php?t=26602 --- Core/SNES/Input/SnesRumbleController.cpp | 9 +++++++-- Core/SNES/Input/SnesRumbleController.h | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Core/SNES/Input/SnesRumbleController.cpp b/Core/SNES/Input/SnesRumbleController.cpp index f2ee18e05..25e1e57c0 100644 --- a/Core/SNES/Input/SnesRumbleController.cpp +++ b/Core/SNES/Input/SnesRumbleController.cpp @@ -23,6 +23,13 @@ void SnesRumbleController::Serialize(Serializer& s) SV(_rumbleData); } +void SnesRumbleController::RefreshStateBuffer() +{ + _rumbleData = 0; + + SnesController::RefreshStateBuffer(); +} + uint8_t SnesRumbleController::ReadRam(uint16_t addr) { if(IsCurrentPort(addr)) { @@ -43,8 +50,6 @@ uint8_t SnesRumbleController::ReadRam(uint16_t addr) uint16_t leftRumble = (rumble & 0x0F) * 4369; KeyManager::SetForceFeedback(rightRumble, leftRumble); - - _rumbleData = 0; } } diff --git a/Core/SNES/Input/SnesRumbleController.h b/Core/SNES/Input/SnesRumbleController.h index b9f2dcd5e..63e1da293 100644 --- a/Core/SNES/Input/SnesRumbleController.h +++ b/Core/SNES/Input/SnesRumbleController.h @@ -15,6 +15,7 @@ class SnesRumbleController : public SnesController protected: void Serialize(Serializer& s) override; + void RefreshStateBuffer() override; public: SnesRumbleController(Emulator* emu, SnesConsole* console, uint8_t port, KeyMappingSet keyMappings); From 3ebdd21a71c91f5138b1a1920d2a79b85cb0a8d2 Mon Sep 17 00:00:00 2001 From: Marcus Rowe Date: Fri, 1 May 2026 19:05:14 +1000 Subject: [PATCH 2/2] SNES: Stop rumble controller shortly after the last rumble command --- Core/SNES/Input/SnesRumbleController.cpp | 14 ++++++++++++++ Core/SNES/Input/SnesRumbleController.h | 2 ++ 2 files changed, 16 insertions(+) diff --git a/Core/SNES/Input/SnesRumbleController.cpp b/Core/SNES/Input/SnesRumbleController.cpp index 25e1e57c0..74d0d2a0b 100644 --- a/Core/SNES/Input/SnesRumbleController.cpp +++ b/Core/SNES/Input/SnesRumbleController.cpp @@ -21,12 +21,23 @@ void SnesRumbleController::Serialize(Serializer& s) { SnesController::Serialize(s); SV(_rumbleData); + SV(_rumbleActive); + SV(_lastRumbleFrame); } void SnesRumbleController::RefreshStateBuffer() { _rumbleData = 0; + //The LRG rumble controller stops rumbling shortly after the last rumble command. + //Not accurate, assumes the game regularly latches the controller. + if(_rumbleActive) { + if(_emu->GetFrameCount() - _lastRumbleFrame > 2) { + KeyManager::SetForceFeedback(0, 0); + _rumbleActive = false; + } + } + SnesController::RefreshStateBuffer(); } @@ -50,6 +61,9 @@ uint8_t SnesRumbleController::ReadRam(uint16_t addr) uint16_t leftRumble = (rumble & 0x0F) * 4369; KeyManager::SetForceFeedback(rightRumble, leftRumble); + + _rumbleActive = true; + _lastRumbleFrame = _emu->GetFrameCount(); } } diff --git a/Core/SNES/Input/SnesRumbleController.h b/Core/SNES/Input/SnesRumbleController.h index 63e1da293..19223a2f7 100644 --- a/Core/SNES/Input/SnesRumbleController.h +++ b/Core/SNES/Input/SnesRumbleController.h @@ -12,6 +12,8 @@ class SnesRumbleController : public SnesController private: SnesConsole* _console = nullptr; uint16_t _rumbleData = 0; + bool _rumbleActive = false; + uint32_t _lastRumbleFrame = 0; protected: void Serialize(Serializer& s) override;