Skip to content

Commit

Permalink
GB: Added support for MBC5 rumble feature
Browse files Browse the repository at this point in the history
  • Loading branch information
SourMesen committed Jul 13, 2024
1 parent 64db1fa commit 640a215
Show file tree
Hide file tree
Showing 20 changed files with 246 additions and 97 deletions.
2 changes: 1 addition & 1 deletion Core/Debugger/Debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ void Debugger::SleepUntilResume(CpuType sourceCpu, BreakSource source, MemoryOpe

bool notificationSent = false;
if(source != BreakSource::Unspecified || _breakRequestCount == 0) {
_emu->GetSoundMixer()->StopAudio();
_emu->OnBeforePause(false);

if(_settings->GetDebugConfig().SingleBreakpointPerInstruction) {
_debuggers[(int)sourceCpu].Debugger->IgnoreBreakpoints = true;
Expand Down
15 changes: 14 additions & 1 deletion Core/Gameboy/Carts/GbMbc5.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,23 @@
#include "Gameboy/Carts/GbCart.h"
#include "Gameboy/GbMemoryManager.h"
#include "Utilities/Serializer.h"
#include "Shared/KeyManager.h"

class GbMbc5 : public GbCart
{
private:
bool _hasRumble = false;

bool _ramEnabled = false;
uint16_t _prgBank = 1;
uint8_t _ramBank = 0;

public:
GbMbc5(bool hasRumble)
{
_hasRumble = hasRumble;
}

void InitCart() override
{
_memoryManager->MapRegisters(0x0000, 0x5FFF, RegisterAccess::Write);
Expand Down Expand Up @@ -58,7 +66,12 @@ class GbMbc5 : public GbCart

case 0x4000:
case 0x5000:
_ramBank = value & 0x0F;
if(_hasRumble) {
_ramBank = value & 0x07;
KeyManager::SetForceFeedback((value & 0x08) ? 0xFFFF : 0);
} else {
_ramBank = value & 0x0F;
}
break;
}
RefreshMappings();
Expand Down
11 changes: 6 additions & 5 deletions Core/Gameboy/GbCartFactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class GbCartFactory
bool isMbc30 = footer.GetRamSize() >= 0x10000;
return new GbMbc3(emu, footer.HasRtc(), isMbc30);
} else if(mapperId == "MBC5") {
return new GbMbc5();
return new GbMbc5(footer.HasRumble());
} else if(mapperId == "MBC6") {
return new GbMbc6();
} else if(mapperId == "MBC7") {
Expand All @@ -57,8 +57,7 @@ class GbCartFactory
case 0x00:
return new GbCart();

case 0x01: case 0x02: case 0x03:
{
case 0x01: case 0x02: case 0x03: {
//When the boot rom logo appears at both of these offsets, this is usually a multicart collection
//which uses a MBC1 chip with slightly different wiring.
bool isMbc1m = prgRom.size() > 0x40134 && memcmp(&prgRom[0x104], &prgRom[0x40104], 0x30) == 0;
Expand All @@ -80,8 +79,10 @@ class GbCartFactory
return new GbMbc3(emu, hasRtc, isMbc30);
}

case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E:
return new GbMbc5();
case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E: {
bool hasRumble = header.CartType >= 0x1C;
return new GbMbc5(hasRumble);
}

case 0x20:
return new GbMbc6();
Expand Down
14 changes: 11 additions & 3 deletions Core/Shared/Emulator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ void Emulator::Stop(bool sendNotification, bool preventRecentGameSave, bool save
_console.reset();
}

_soundMixer->StopAudio(true);
OnBeforePause(true);

if(sendNotification) {
_notificationManager->SendNotification(ConsoleNotificationType::EmulationStopped);
Expand Down Expand Up @@ -794,12 +794,20 @@ bool Emulator::IsPaused()
}
}

void Emulator::OnBeforePause(bool clearAudioBuffer)
{
//Prevent audio from looping endlessly while game is paused
_soundMixer->StopAudio(clearAudioBuffer);

//Stop force feedback
KeyManager::SetForceFeedback(0);
}

void Emulator::WaitForPauseEnd()
{
_notificationManager->SendNotification(ConsoleNotificationType::GamePaused);

//Prevent audio from looping endlessly while game is paused
_soundMixer->StopAudio();
OnBeforePause(false);
_runLock.Release();

PlatformUtilities::EnableScreensaver();
Expand Down
2 changes: 2 additions & 0 deletions Core/Shared/Emulator.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ class Emulator
void Resume();
bool IsPaused();

void OnBeforePause(bool clearAudioBuffer);

bool LoadRom(VirtualFile romFile, VirtualFile patchFile, bool stopRom = true, bool forPowerCycle = false);
RomInfo& GetRomInfo() { return _rom; }
string GetHash(HashType type);
Expand Down
2 changes: 2 additions & 0 deletions Core/Shared/Interfaces/IKeyManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,6 @@ class IKeyManager
virtual bool SetKeyState(uint16_t scanCode, bool state) = 0;
virtual void ResetKeyState() = 0;
virtual void SetDisabled(bool disabled) = 0;

virtual void SetForceFeedback(uint16_t magnitude) {}
};
9 changes: 8 additions & 1 deletion Core/Shared/KeyManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,11 @@ void KeyManager::SetMousePosition(Emulator* emu, double x, double y)
MousePosition KeyManager::GetMousePosition()
{
return _mousePosition;
}
}

void KeyManager::SetForceFeedback(uint16_t magnitude)
{
if(_keyManager != nullptr) {
_keyManager->SetForceFeedback(magnitude * _settings->GetInputConfig().ForceFeedbackIntensity);
}
}
2 changes: 2 additions & 0 deletions Core/Shared/KeyManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,6 @@ class KeyManager

static void SetMousePosition(Emulator* emu, double x, double y);
static MousePosition GetMousePosition();

static void SetForceFeedback(uint16_t magnitude);
};
2 changes: 2 additions & 0 deletions Core/Shared/SettingTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,8 @@ struct InputConfig
InputDisplayPosition DisplayInputPosition = InputDisplayPosition::TopLeft;
bool DisplayInputPort[8] = { };
bool DisplayInputHorizontally = true;

double ForceFeedbackIntensity = 1.0;
};

enum class RamState
Expand Down
163 changes: 103 additions & 60 deletions Linux/LinuxGameController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ std::shared_ptr<LinuxGameController> LinuxGameController::GetController(Emulator
std::string deviceName = "/dev/input/event" + std::to_string(deviceID);
struct stat buffer;
if(stat(deviceName.c_str(), &buffer) == 0) {
int fd = open(deviceName.c_str(), O_RDONLY | O_NONBLOCK);
int fd = open(deviceName.c_str(), O_RDWR | O_NONBLOCK);
if(fd < 0) {
if(logInformation) {
MessageManager::Log("[Input] " + deviceName + " error: " + std::to_string(errno) + " " + strerror(errno));
Expand Down Expand Up @@ -57,6 +57,21 @@ LinuxGameController::LinuxGameController(Emulator* emu, int deviceID, int fileDe
_fd = fileDescriptor;
memset(_axisDefaultValue, 0, sizeof(_axisDefaultValue));

_rumbleEffect.reset(new ff_effect());
memset(_rumbleEffect.get(), 0, sizeof(ff_effect));
_rumbleEffect->type = FF_RUMBLE;
_rumbleEffect->id = -1;
_rumbleEffect->u.rumble.strong_magnitude = 0x8000;
_rumbleEffect->u.rumble.weak_magnitude = 0x8000;
_rumbleEffect->replay.length = 2000;
_rumbleEffect->replay.delay = 0;

int rc = ioctl(_fd, EVIOCSFF, _rumbleEffect.get());
if(rc < 0) {
MessageManager::Log("Could not initialize force feedback effect");
_rumbleEffect.reset();
}

_eventThread = std::thread([=]() {
int rc;
bool calibrate = true;
Expand Down Expand Up @@ -136,68 +151,71 @@ bool LinuxGameController::CheckAxis(unsigned int code, bool forPositive)

bool LinuxGameController::IsButtonPressed(int buttonNumber)
{
bool pressed = false;
switch(buttonNumber) {
case 0: return libevdev_get_event_value(_device, EV_KEY, BTN_A) == 1;
case 1: return libevdev_get_event_value(_device, EV_KEY, BTN_B) == 1;
case 2: return libevdev_get_event_value(_device, EV_KEY, BTN_C) == 1;
case 3: return libevdev_get_event_value(_device, EV_KEY, BTN_X) == 1;
case 4: return libevdev_get_event_value(_device, EV_KEY, BTN_Y) == 1;
case 5: return libevdev_get_event_value(_device, EV_KEY, BTN_Z) == 1;
case 6: return libevdev_get_event_value(_device, EV_KEY, BTN_TL) == 1;
case 7: return libevdev_get_event_value(_device, EV_KEY, BTN_TR) == 1;
case 8: return libevdev_get_event_value(_device, EV_KEY, BTN_TL2) == 1;
case 9: return libevdev_get_event_value(_device, EV_KEY, BTN_TR2) == 1;
case 10: return libevdev_get_event_value(_device, EV_KEY, BTN_SELECT) == 1;
case 11: return libevdev_get_event_value(_device, EV_KEY, BTN_START) == 1;
case 12: return libevdev_get_event_value(_device, EV_KEY, BTN_THUMBL) == 1;
case 13: return libevdev_get_event_value(_device, EV_KEY, BTN_THUMBR) == 1;

case 14: return CheckAxis(ABS_X, true);
case 15: return CheckAxis(ABS_X, false);
case 16: return CheckAxis(ABS_Y, true);
case 17: return CheckAxis(ABS_Y, false);
case 18: return CheckAxis(ABS_Z, true);
case 19: return CheckAxis(ABS_Z, false);
case 20: return CheckAxis(ABS_RX, true);
case 21: return CheckAxis(ABS_RX, false);
case 22: return CheckAxis(ABS_RY, true);
case 23: return CheckAxis(ABS_RY, false);
case 24: return CheckAxis(ABS_RZ, true);
case 25: return CheckAxis(ABS_RZ, false);

case 26: return CheckAxis(ABS_HAT0X, true);
case 27: return CheckAxis(ABS_HAT0X, false);
case 28: return CheckAxis(ABS_HAT0Y, true);
case 29: return CheckAxis(ABS_HAT0Y, false);
case 30: return CheckAxis(ABS_HAT1X, true);
case 31: return CheckAxis(ABS_HAT1X, false);
case 32: return CheckAxis(ABS_HAT1Y, true);
case 33: return CheckAxis(ABS_HAT1Y, false);
case 34: return CheckAxis(ABS_HAT2X, true);
case 35: return CheckAxis(ABS_HAT2X, false);
case 36: return CheckAxis(ABS_HAT2Y, true);
case 37: return CheckAxis(ABS_HAT2Y, false);
case 38: return CheckAxis(ABS_HAT3X, true);
case 39: return CheckAxis(ABS_HAT3X, false);
case 40: return CheckAxis(ABS_HAT3Y, true);
case 41: return CheckAxis(ABS_HAT3Y, false);

case 42: return libevdev_get_event_value(_device, EV_KEY, BTN_TRIGGER) == 1;
case 43: return libevdev_get_event_value(_device, EV_KEY, BTN_THUMB) == 1;
case 44: return libevdev_get_event_value(_device, EV_KEY, BTN_THUMB2) == 1;
case 45: return libevdev_get_event_value(_device, EV_KEY, BTN_TOP) == 1;
case 46: return libevdev_get_event_value(_device, EV_KEY, BTN_TOP2) == 1;
case 47: return libevdev_get_event_value(_device, EV_KEY, BTN_PINKIE) == 1;
case 48: return libevdev_get_event_value(_device, EV_KEY, BTN_BASE) == 1;
case 49: return libevdev_get_event_value(_device, EV_KEY, BTN_BASE2) == 1;
case 50: return libevdev_get_event_value(_device, EV_KEY, BTN_BASE3) == 1;
case 51: return libevdev_get_event_value(_device, EV_KEY, BTN_BASE4) == 1;
case 52: return libevdev_get_event_value(_device, EV_KEY, BTN_BASE5) == 1;
case 53: return libevdev_get_event_value(_device, EV_KEY, BTN_BASE6) == 1;
case 54: return libevdev_get_event_value(_device, EV_KEY, BTN_DEAD) == 1;
case 0: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_A) == 1; break;
case 1: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_B) == 1; break;
case 2: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_C) == 1; break;
case 3: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_X) == 1; break;
case 4: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_Y) == 1; break;
case 5: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_Z) == 1; break;
case 6: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_TL) == 1; break;
case 7: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_TR) == 1; break;
case 8: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_TL2) == 1; break;
case 9: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_TR2) == 1; break;
case 10: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_SELECT) == 1; break;
case 11: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_START) == 1; break;
case 12: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_THUMBL) == 1; break;
case 13: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_THUMBR) == 1; break;

case 14: pressed = CheckAxis(ABS_X, true); break;
case 15: pressed = CheckAxis(ABS_X, false); break;
case 16: pressed = CheckAxis(ABS_Y, true); break;
case 17: pressed = CheckAxis(ABS_Y, false); break;
case 18: pressed = CheckAxis(ABS_Z, true); break;
case 19: pressed = CheckAxis(ABS_Z, false); break;
case 20: pressed = CheckAxis(ABS_RX, true); break;
case 21: pressed = CheckAxis(ABS_RX, false); break;
case 22: pressed = CheckAxis(ABS_RY, true); break;
case 23: pressed = CheckAxis(ABS_RY, false); break;
case 24: pressed = CheckAxis(ABS_RZ, true); break;
case 25: pressed = CheckAxis(ABS_RZ, false); break;

case 26: pressed = CheckAxis(ABS_HAT0X, true); break;
case 27: pressed = CheckAxis(ABS_HAT0X, false); break;
case 28: pressed = CheckAxis(ABS_HAT0Y, true); break;
case 29: pressed = CheckAxis(ABS_HAT0Y, false); break;
case 30: pressed = CheckAxis(ABS_HAT1X, true); break;
case 31: pressed = CheckAxis(ABS_HAT1X, false); break;
case 32: pressed = CheckAxis(ABS_HAT1Y, true); break;
case 33: pressed = CheckAxis(ABS_HAT1Y, false); break;
case 34: pressed = CheckAxis(ABS_HAT2X, true); break;
case 35: pressed = CheckAxis(ABS_HAT2X, false); break;
case 36: pressed = CheckAxis(ABS_HAT2Y, true); break;
case 37: pressed = CheckAxis(ABS_HAT2Y, false); break;
case 38: pressed = CheckAxis(ABS_HAT3X, true); break;
case 39: pressed = CheckAxis(ABS_HAT3X, false); break;
case 40: pressed = CheckAxis(ABS_HAT3Y, true); break;
case 41: pressed = CheckAxis(ABS_HAT3Y, false); break;

case 42: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_TRIGGER) == 1; break;
case 43: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_THUMB) == 1; break;
case 44: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_THUMB2) == 1; break;
case 45: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_TOP) == 1; break;
case 46: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_TOP2) == 1; break;
case 47: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_PINKIE) == 1; break;
case 48: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_BASE) == 1; break;
case 49: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_BASE2) == 1; break;
case 50: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_BASE3) == 1; break;
case 51: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_BASE4) == 1; break;
case 52: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_BASE5) == 1; break;
case 53: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_BASE6) == 1; break;
case 54: pressed = libevdev_get_event_value(_device, EV_KEY, BTN_DEAD) == 1; break;
}

return false;
_enableForceFeedback |= pressed;

return pressed;
}

optional<int16_t> LinuxGameController::GetAxisPosition(int axis)
Expand Down Expand Up @@ -228,6 +246,31 @@ optional<int16_t> LinuxGameController::GetAxisPosition(int axis)
return axis & 0x01 ? axisValue : -axisValue;
}

void LinuxGameController::SetForceFeedback(uint16_t magnitude)
{
if(!_rumbleEffect || !_enableForceFeedback) {
return;
}

_rumbleEffect->u.rumble.strong_magnitude = magnitude;
_rumbleEffect->u.rumble.weak_magnitude = magnitude;
int rc = ioctl(_fd, EVIOCSFF, _rumbleEffect.get());
if(rc < 0) {
//MessageManager::Log("Could not update force feedback effect.");
return;
}

struct input_event play = {};
play.type = EV_FF;
play.code = _rumbleEffect->id;
play.value = 1;

rc = write(_fd, (const void*)&play, sizeof(play));
if(rc < 0) {
//MessageManager::Log("Could not play force feedback effect.");
}
}

bool LinuxGameController::IsDisconnected()
{
return _disconnected;
Expand Down
10 changes: 8 additions & 2 deletions Linux/LinuxGameController.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <atomic>

struct libevdev;
struct ff_effect;
class Emulator;

class LinuxGameController
Expand All @@ -14,8 +15,11 @@ class LinuxGameController
bool _disconnected = false;
std::thread _eventThread;
std::atomic<bool> _stopFlag;
Emulator* _emu;
int _axisDefaultValue[0x100];
Emulator* _emu = nullptr;

unique_ptr<ff_effect> _rumbleEffect;
bool _enableForceFeedback = false;
int _axisDefaultValue[0x100] = {};

LinuxGameController(Emulator* emu, int deviceID, int fileDescriptor, libevdev *device);
bool CheckAxis(unsigned int code, bool forPositive);
Expand All @@ -30,4 +34,6 @@ class LinuxGameController
int GetDeviceID();
bool IsButtonPressed(int buttonNumber);
optional<int16_t> GetAxisPosition(int axis);

void SetForceFeedback(uint16_t magnitude);
};
7 changes: 7 additions & 0 deletions Linux/LinuxKeyManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -213,3 +213,10 @@ void LinuxKeyManager::SetDisabled(bool disabled)
{
_disableAllKeys = disabled;
}

void LinuxKeyManager::SetForceFeedback(uint16_t magnitude)
{
for(auto& controller : _controllers) {
controller->SetForceFeedback(magnitude);
}
}
Loading

0 comments on commit 640a215

Please sign in to comment.