SNES: Added new controller type that supports rumble

This commit is contained in:
Sour 2025-06-23 10:26:31 +09:00
parent 13ee75aa05
commit 5350959230
28 changed files with 134 additions and 29 deletions

View File

@ -586,6 +586,7 @@
<ClInclude Include="Netplay\ClientConnectionData.h" />
<ClInclude Include="SNES\DSP\DspTypes.h" />
<ClInclude Include="SNES\DSP\DspVoice.h" />
<ClInclude Include="SNES\Input\SnesRumbleController.h" />
<ClInclude Include="SNES\SnesCpu.Shared.h" />
<ClInclude Include="SNES\Coprocessors\SA1\CpuBwRamHandler.h" />
<ClInclude Include="SNES\Debugger\SnesDebugger.h" />
@ -978,6 +979,7 @@
<ClCompile Include="SNES\Debugger\TraceLogger\St018TraceLogger.cpp" />
<ClCompile Include="SNES\DSP\Dsp.cpp" />
<ClCompile Include="SNES\DSP\DspVoice.cpp" />
<ClCompile Include="SNES\Input\SnesRumbleController.cpp" />
<ClCompile Include="SNES\SnesConsole.cpp" />
<ClCompile Include="Shared\EmulatorLock.cpp" />
<ClCompile Include="SNES\SnesControlManager.cpp" />

View File

@ -2982,6 +2982,9 @@
<ClInclude Include="GBA\GbaWaitStates.h">
<Filter>GBA</Filter>
</ClInclude>
<ClInclude Include="SNES\Input\SnesRumbleController.h">
<Filter>SNES\Input</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Shared\Video\RotateFilter.cpp">
@ -3344,6 +3347,9 @@
<ClCompile Include="SNES\Debugger\St018DisUtils.cpp">
<Filter>SNES\Debugger</Filter>
</ClCompile>
<ClCompile Include="SNES\Input\SnesRumbleController.cpp">
<Filter>SNES\Input</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Filter Include="PCE">

View File

@ -0,0 +1,52 @@
#include "pch.h"
#include "SNES/Input/SnesRumbleController.h"
#include "SNES/SnesConsole.h"
#include "SNES/InternalRegisters.h"
#include "Shared/Emulator.h"
#include "Shared/EmuSettings.h"
#include "Shared/KeyManager.h"
SnesRumbleController::SnesRumbleController(Emulator* emu, SnesConsole* console, uint8_t port, KeyMappingSet keyMappings) : SnesController(emu, port, keyMappings)
{
_console = console;
_type = ControllerType::SnesRumbleController;
}
SnesRumbleController::~SnesRumbleController()
{
KeyManager::SetForceFeedback(0, 0);
}
void SnesRumbleController::Serialize(Serializer& s)
{
SnesController::Serialize(s);
SV(_rumbleData);
}
uint8_t SnesRumbleController::ReadRam(uint16_t addr)
{
if(IsCurrentPort(addr)) {
uint8_t ioPort = _console->GetInternalRegisters()->GetIoPortOutput();
//Technically, when plugged into port 2, this uses bit 7, but activating the rumble
//in both ports at the same time could potentially draw too much current, so the
//UI current prevents picking a rumble controller for P2
uint8_t ioBit = (GetPort() == 0 ? (ioPort >> 6) : (ioPort >> 7)) & 0x01;
_rumbleData = (_rumbleData << 1) | ioBit;
if((_rumbleData & 0xFF00) == 0x7200) {
uint8_t rumble = _rumbleData & 0xFF;
//Multiply by 4369 to use all values from 0 to 65535
uint16_t rightRumble = (rumble >> 4) * 4369;
uint16_t leftRumble = (rumble & 0x0F) * 4369;
KeyManager::SetForceFeedback(rightRumble, leftRumble);
_rumbleData = 0;
}
}
return SnesController::ReadRam(addr);
}

View File

@ -0,0 +1,24 @@
#pragma once
#include "pch.h"
#include "Shared/BaseControlDevice.h"
#include "SNES/Input/SnesController.h"
#include "Utilities/Serializer.h"
class Emulator;
class SnesConsole;
class SnesRumbleController : public SnesController
{
private:
SnesConsole* _console = nullptr;
uint16_t _rumbleData = 0;
protected:
void Serialize(Serializer& s) override;
public:
SnesRumbleController(Emulator* emu, SnesConsole* console, uint8_t port, KeyMappingSet keyMappings);
virtual ~SnesRumbleController();
uint8_t ReadRam(uint16_t addr) override;
};

View File

@ -10,6 +10,7 @@
#include "Shared/Interfaces/IInputProvider.h"
#include "Shared/Interfaces/IInputRecorder.h"
#include "SNES/Input/SnesController.h"
#include "SNES/Input/SnesRumbleController.h"
#include "SNES/Input/SnesMouse.h"
#include "SNES/Input/Multitap.h"
#include "SNES/Input/SuperScope.h"
@ -62,6 +63,10 @@ shared_ptr<BaseControlDevice> SnesControlManager::CreateControllerDevice(Control
device.reset(new Multitap(_console, port, controllers));
break;
}
case ControllerType::SnesRumbleController:
device.reset(new SnesRumbleController(_emu, _console, port, port == 0 ? cfg.Port1.Keys : cfg.Port2.Keys));
break;
}
return device;

View File

@ -45,5 +45,5 @@ public:
virtual void ResetKeyState() = 0;
virtual void SetDisabled(bool disabled) = 0;
virtual void SetForceFeedback(uint16_t magnitude) {}
virtual void SetForceFeedback(uint16_t magnitudeRight, uint16_t magnitudeLeft) {}
};

View File

@ -135,9 +135,15 @@ MousePosition KeyManager::GetMousePosition()
return _mousePosition;
}
void KeyManager::SetForceFeedback(uint16_t magnitude)
void KeyManager::SetForceFeedback(uint16_t magnitudeRight, uint16_t magnitudeLeft)
{
if(_keyManager != nullptr) {
_keyManager->SetForceFeedback(magnitude * _settings->GetInputConfig().ForceFeedbackIntensity);
double intensity = _settings->GetInputConfig().ForceFeedbackIntensity;
_keyManager->SetForceFeedback(magnitudeRight * intensity, magnitudeLeft * intensity);
}
}
void KeyManager::SetForceFeedback(uint16_t magnitude)
{
SetForceFeedback(magnitude, magnitude);
}

View File

@ -37,4 +37,5 @@ public:
static MousePosition GetMousePosition();
static void SetForceFeedback(uint16_t magnitude);
static void SetForceFeedback(uint16_t magnitudeRight, uint16_t magnitudeLeft);
};

View File

@ -181,6 +181,7 @@ enum class ControllerType
SnesMouse,
SuperScope,
Multitap,
SnesRumbleController,
//NES controllers
NesController,

View File

@ -248,14 +248,14 @@ optional<int16_t> LinuxGameController::GetAxisPosition(int axis)
return axis & 0x01 ? axisValue : -axisValue;
}
void LinuxGameController::SetForceFeedback(uint16_t magnitude)
void LinuxGameController::SetForceFeedback(uint16_t magnitudeRight, uint16_t magnitudeLeft)
{
if(!_rumbleEffect || !_enableForceFeedback) {
return;
}
_rumbleEffect->u.rumble.strong_magnitude = magnitude;
_rumbleEffect->u.rumble.weak_magnitude = magnitude;
_rumbleEffect->u.rumble.strong_magnitude = magnitudeLeft;
_rumbleEffect->u.rumble.weak_magnitude = magnitudeRight;
int rc = ioctl(_fd, EVIOCSFF, _rumbleEffect.get());
if(rc < 0) {
//MessageManager::Log("Could not update force feedback effect.");

View File

@ -35,5 +35,5 @@ public:
bool IsButtonPressed(int buttonNumber);
optional<int16_t> GetAxisPosition(int axis);
void SetForceFeedback(uint16_t magnitude);
void SetForceFeedback(uint16_t rightMagnitude, uint16_t leftMagnitude);
};

View File

@ -214,9 +214,9 @@ void LinuxKeyManager::SetDisabled(bool disabled)
_disableAllKeys = disabled;
}
void LinuxKeyManager::SetForceFeedback(uint16_t magnitude)
void LinuxKeyManager::SetForceFeedback(uint16_t magnitudeRight, uint16_t magnitudeLeft)
{
for(auto& controller : _controllers) {
controller->SetForceFeedback(magnitude);
controller->SetForceFeedback(magnitudeRight, magnitudeLeft);
}
}

View File

@ -46,5 +46,5 @@ public:
void SetDisabled(bool disabled) override;
void SetForceFeedback(uint16_t magnitude) override;
void SetForceFeedback(uint16_t magnitudeRight, uint16_t magnitudeLeft) override;
};

View File

@ -34,5 +34,5 @@ public:
bool IsButtonPressed(int buttonNumber);
std::optional<int16_t> GetAxisPosition(int axis);
void SetForceFeedback(uint16_t magnitude);
void SetForceFeedback(uint16_t magnitudeRight, uint16_t magnitudeLeft);
};

View File

@ -115,7 +115,7 @@ std::optional<int16_t> MacOSGameController::GetAxisPosition(int axis)
return _axisState[axis];
}
void MacOSGameController::SetForceFeedback(uint16_t magnitude)
void MacOSGameController::SetForceFeedback(uint16_t magnitudeRight, uint16_t magnitudeLeft)
{
NSError* error = nil;
@ -134,11 +134,11 @@ void MacOSGameController::SetForceFeedback(uint16_t magnitude)
}
//If magnitude is zero, only stop current effect
if(magnitude == 0) {
if(magnitudeRight == 0 && magnitudeLeft == 0) {
return;
}
double strength = magnitude / (double) UINT16_MAX;
double strength = (magnitudeRight < magnitudeLeft ? magnitudeLeft : magnitudeRight) / (double)UINT16_MAX;
CHHapticEventParameter* intensityPar = [[CHHapticEventParameter alloc] initWithParameterID:CHHapticEventParameterIDHapticIntensity value:strength];
CHHapticEventParameter* sharpnessPar = [[CHHapticEventParameter alloc] initWithParameterID:CHHapticEventParameterIDHapticSharpness value:0.6];
CHHapticEvent* event = [[CHHapticEvent alloc] initWithEventType:CHHapticEventTypeHapticContinuous parameters:@[intensityPar, sharpnessPar] relativeTime:0.0 duration:GCHapticDurationInfinite];

View File

@ -61,5 +61,5 @@ public:
void SetDisabled(bool disabled) override;
void SetForceFeedback(uint16_t magnitude) override;
void SetForceFeedback(uint16_t magnitudeRight, uint16_t magnitudeLeft) override;
};

View File

@ -215,9 +215,9 @@ void MacOSKeyManager::SetDisabled(bool disabled)
_disableAllKeys = disabled;
}
void MacOSKeyManager::SetForceFeedback(uint16_t magnitude)
void MacOSKeyManager::SetForceFeedback(uint16_t magnitudeRight, uint16_t magnitudeLeft)
{
for(auto& controller : _controllers) {
controller->SetForceFeedback(magnitude);
controller->SetForceFeedback(magnitudeRight, magnitudeLeft);
}
}

View File

@ -351,6 +351,7 @@ namespace Mesen.Config
SnesMouse,
SuperScope,
Multitap,
SnesRumbleController,
//NES controllers
NesController,
@ -417,6 +418,7 @@ namespace Mesen.Config
{
switch(type) {
case ControllerType.SnesController:
case ControllerType.SnesRumbleController:
case ControllerType.NesController:
case ControllerType.FamicomController:
case ControllerType.FamicomControllerP2:
@ -440,6 +442,7 @@ namespace Mesen.Config
{
switch(type) {
case ControllerType.SnesController:
case ControllerType.SnesRumbleController:
case ControllerType.NesController:
case ControllerType.FamicomController:
case ControllerType.FamicomControllerP2:
@ -461,6 +464,7 @@ namespace Mesen.Config
{
switch(type) {
case ControllerType.SnesController:
case ControllerType.SnesRumbleController:
case ControllerType.NesController:
case ControllerType.FamicomController:
case ControllerType.FamicomControllerP2:

View File

@ -17,7 +17,7 @@ namespace Mesen.Config
m.Left = InputApi.GetKeyCode("A");
m.Right = InputApi.GetKeyCode("D");
if(type == ControllerType.SnesController) {
if(type == ControllerType.SnesController || type == ControllerType.SnesRumbleController) {
m.X = InputApi.GetKeyCode(";");
m.Y = InputApi.GetKeyCode("M");
m.L = InputApi.GetKeyCode("U");
@ -79,7 +79,7 @@ namespace Mesen.Config
m.Left = InputApi.GetKeyCode("Left Arrow");
m.Right = InputApi.GetKeyCode("Right Arrow");
if(type == ControllerType.SnesController) {
if(type == ControllerType.SnesController || type == ControllerType.SnesRumbleController) {
m.X = InputApi.GetKeyCode("X");
m.Y = InputApi.GetKeyCode("Z");
m.L = InputApi.GetKeyCode("Q");
@ -145,7 +145,7 @@ namespace Mesen.Config
m.Left = InputApi.GetKeyCode(prefix + "Left");
m.Right = InputApi.GetKeyCode(prefix + "Right");
if(type == ControllerType.SnesController || type == ControllerType.PceAvenuePad6 || type == ControllerType.GbaController) {
if(type == ControllerType.SnesController || type == ControllerType.PceAvenuePad6 || type == ControllerType.GbaController || type == ControllerType.SnesRumbleController) {
m.X = InputApi.GetKeyCode(prefix + "Y");
m.Y = InputApi.GetKeyCode(prefix + "X");
m.L = InputApi.GetKeyCode(prefix + "L1");
@ -190,7 +190,7 @@ namespace Mesen.Config
m.Down = InputApi.GetKeyCode(prefix + "DPad Down");
m.Left = InputApi.GetKeyCode(prefix + "DPad Left");
m.Right = InputApi.GetKeyCode(prefix + "DPad Right");
if(type == ControllerType.SnesController || type == ControllerType.PceAvenuePad6 || type == ControllerType.GbaController) {
if(type == ControllerType.SnesController || type == ControllerType.PceAvenuePad6 || type == ControllerType.GbaController || type == ControllerType.SnesRumbleController) {
m.X = InputApi.GetKeyCode(prefix + "But4");
m.Y = InputApi.GetKeyCode(prefix + "But1");
m.L = InputApi.GetKeyCode(prefix + "But5");

View File

@ -186,6 +186,7 @@ namespace Mesen.Config
break;
case ControllerType.SnesController:
case ControllerType.SnesRumbleController:
case ControllerType.NesController:
case ControllerType.FamicomController:
case ControllerType.FamicomControllerP2:

View File

@ -78,6 +78,7 @@ namespace Mesen.Config
break;
case ControllerType.SnesController:
case ControllerType.SnesRumbleController:
base.ClearKeys(type);
break;
}

View File

@ -2002,6 +2002,7 @@ E
<Enum ID="ControllerType">
<Value ID="None">None</Value>
<Value ID="SnesController">SNES Controller</Value>
<Value ID="SnesRumbleController">SNES Rumble</Value>
<Value ID="SnesMouse">SNES Mouse</Value>
<Value ID="SuperScope">Super Scope</Value>
<Value ID="Multitap">Super Multitap</Value>

View File

@ -20,6 +20,7 @@ namespace Mesen.ViewModels
ControllerType.SnesMouse,
ControllerType.SuperScope,
ControllerType.Multitap,
ControllerType.SnesRumbleController,
};
public Enum[] AvailableControllerTypesMultitap => new Enum[] {

View File

@ -18,7 +18,7 @@ namespace Mesen.Views
if(mappings != null) {
return mappings.Type switch {
ControllerType.SnesController => new SnesControllerView(),
ControllerType.SnesController or ControllerType.SnesRumbleController => new SnesControllerView(),
ControllerType.NesController => new NesControllerView(),
ControllerType.FamicomController => new NesControllerView(),
ControllerType.FamicomControllerP2 => new NesControllerView(true),

View File

@ -229,9 +229,9 @@ void WindowsKeyManager::SetDisabled(bool disabled)
_disableAllKeys = disabled;
}
void WindowsKeyManager::SetForceFeedback(uint16_t magnitude)
void WindowsKeyManager::SetForceFeedback(uint16_t magnitudeRight, uint16_t magnitudeLeft)
{
_xInput->SetForceFeedback(magnitude);
_xInput->SetForceFeedback(magnitudeRight, magnitudeLeft);
}
void WindowsKeyManager::ResetKeyState()

View File

@ -50,7 +50,7 @@ public:
void ResetKeyState() override;
void SetDisabled(bool disabled) override;
void SetForceFeedback(uint16_t magnitude) override;
void SetForceFeedback(uint16_t magnitudeRight, uint16_t magnitudeLeft) override;
void UpdateDevices() override;
};

View File

@ -95,11 +95,11 @@ optional<int16_t> XInputManager::GetAxisPosition(uint8_t port, int axis)
return std::nullopt;
}
void XInputManager::SetForceFeedback(uint16_t magnitude)
void XInputManager::SetForceFeedback(uint16_t magnitudeRight, uint16_t magnitudeLeft)
{
XINPUT_VIBRATION settings = {};
settings.wLeftMotorSpeed = magnitude;
settings.wRightMotorSpeed = magnitude;
settings.wRightMotorSpeed = magnitudeRight;
settings.wLeftMotorSpeed = magnitudeLeft;
for(int i = 0; i < XUSER_MAX_COUNT; i++) {
if(_enableForceFeedback[i]) {

View File

@ -23,5 +23,5 @@ class XInputManager
bool IsPressed(uint8_t gamepadPort, uint8_t button);
optional<int16_t> GetAxisPosition(uint8_t gamepadPort, int axis);
void SetForceFeedback(uint16_t magnitude);
void SetForceFeedback(uint16_t magnitudeRight, uint16_t magnitudeLeft);
};