From 9b828c2cde4b83a9c76a2484fc86b16d5b5dfea0 Mon Sep 17 00:00:00 2001 From: BueniaDev Date: Sun, 21 Jul 2024 10:01:30 -0500 Subject: [PATCH] Implement Rumble Pak support. (#2101) --- src/GBACart.cpp | 43 +++++++++++++++++++++- src/GBACart.h | 22 +++++++++++- src/NDS.cpp | 2 +- src/NDS.h | 1 + src/Platform.h | 11 ++++++ src/frontend/qt_sdl/EmuInstance.h | 5 +++ src/frontend/qt_sdl/EmuInstanceInput.cpp | 45 ++++++++++++++++++++++++ src/frontend/qt_sdl/Platform.cpp | 10 ++++++ src/frontend/qt_sdl/Window.cpp | 4 +++ src/frontend/qt_sdl/Window.h | 2 +- 10 files changed, 141 insertions(+), 4 deletions(-) diff --git a/src/GBACart.cpp b/src/GBACart.cpp index 40e90436..1518b6cf 100644 --- a/src/GBACart.cpp +++ b/src/GBACart.cpp @@ -681,6 +681,44 @@ void CartRAMExpansion::ROMWrite(u32 addr, u16 val) } } +CartRumblePak::CartRumblePak(void* userdata) : + CartCommon(RumblePak), + UserData(userdata) +{ +} + +CartRumblePak::~CartRumblePak() = default; + +void CartRumblePak::Reset() +{ + RumbleState = 0; +} + +void CartRumblePak::DoSavestate(Savestate* file) +{ + CartCommon::DoSavestate(file); + file->Var16(&RumbleState); +} + +u16 CartRumblePak::ROMRead(u32 addr) const +{ + // A1 is pulled low on a real Rumble Pak, so return the + // necessary detection value here, + // and let the existing open bus implementation take care of the rest + return 0xFFFD; +} + +void CartRumblePak::ROMWrite(u32 addr, u16 val) +{ + addr &= 0x01FFFFFF; + if (RumbleState != val) + { + Platform::Addon_RumbleStop(UserData); + RumbleState = val; + Platform::Addon_RumbleStart(16, UserData); + } +} + GBACartSlot::GBACartSlot(melonDS::NDS& nds, std::unique_ptr&& cart) noexcept : NDS(nds), Cart(std::move(cart)) { } @@ -821,13 +859,16 @@ void GBACartSlot::SetSaveMemory(const u8* savedata, u32 savelen) noexcept } } -void GBACartSlot::LoadAddon(int type) noexcept +void GBACartSlot::LoadAddon(void* userdata, int type) noexcept { switch (type) { case GBAAddon_RAMExpansion: Cart = std::make_unique(); break; + case GBAAddon_RumblePak: + Cart = std::make_unique(userdata); + break; default: Log(LogLevel::Warn, "GBACart: !! invalid addon type %d\n", type); diff --git a/src/GBACart.h b/src/GBACart.h index 4eb0faad..f6fb95dd 100644 --- a/src/GBACart.h +++ b/src/GBACart.h @@ -32,6 +32,7 @@ enum CartType Game = 0x101, GameSolarSensor = 0x102, RAMExpansion = 0x201, + RumblePak = 0x202, }; // CartCommon -- base code shared by all cart types @@ -189,6 +190,25 @@ private: u16 RAMEnable = 0; }; +// CartRumblePak -- DS Rumble Pak (used in various NDS games) +class CartRumblePak : public CartCommon +{ +public: + CartRumblePak(void* userdata); + ~CartRumblePak() override; + + void Reset() override; + + void DoSavestate(Savestate* file) override; + + u16 ROMRead(u32 addr) const override; + void ROMWrite(u32 addr, u16 val) override; + +private: + void* UserData; + u16 RumbleState = 0; +}; + // possible inputs for GBA carts that might accept user input enum { @@ -219,7 +239,7 @@ public: [[nodiscard]] CartCommon* GetCart() noexcept { return Cart.get(); } [[nodiscard]] const CartCommon* GetCart() const noexcept { return Cart.get(); } - void LoadAddon(int type) noexcept; + void LoadAddon(void* userdata, int type) noexcept; /// @return The cart that was in the cart slot if any, /// or \c nullptr if the cart slot was empty. diff --git a/src/NDS.cpp b/src/NDS.cpp index 7ef6602c..e6c82557 100644 --- a/src/NDS.cpp +++ b/src/NDS.cpp @@ -751,7 +751,7 @@ void NDS::SetGBASave(const u8* savedata, u32 savelen) void NDS::LoadGBAAddon(int type) { - GBACartSlot.LoadAddon(type); + GBACartSlot.LoadAddon(UserData, type); } void NDS::LoadBIOS() diff --git a/src/NDS.h b/src/NDS.h index c965cde0..415fdebd 100644 --- a/src/NDS.h +++ b/src/NDS.h @@ -210,6 +210,7 @@ enum enum { GBAAddon_RAMExpansion = 1, + GBAAddon_RumblePak = 2, }; class SPU; diff --git a/src/Platform.h b/src/Platform.h index 0dfc04f6..425f4f4e 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -316,6 +316,17 @@ void Camera_Start(int num, void* userdata); void Camera_Stop(int num, void* userdata); void Camera_CaptureFrame(int num, u32* frame, int width, int height, bool yuv, void* userdata); +// interface for addon inputs + +// Called by the DS Rumble Pak emulation to start the necessary +// rumble effects on the connected game controller, if available. +// @param len The duration of the controller rumble effect in milliseconds. +void Addon_RumbleStart(u32 len, void* userdata); + +// Called by the DS Rumble Pak emulation to stop any necessary +// rumble effects on the connected game controller, if available. +void Addon_RumbleStop(void* userdata); + struct DynamicLibrary; /** diff --git a/src/frontend/qt_sdl/EmuInstance.h b/src/frontend/qt_sdl/EmuInstance.h index 44bd75e1..43b9d9ff 100644 --- a/src/frontend/qt_sdl/EmuInstance.h +++ b/src/frontend/qt_sdl/EmuInstance.h @@ -127,6 +127,8 @@ public: void inputInit(); void inputDeInit(); void inputLoadConfig(); + void inputRumbleStart(melonDS::u32 len_ms); + void inputRumbleStop(); void setJoystick(int id); int getJoystickID() { return joystickID; } @@ -291,6 +293,9 @@ private: int joystickID; SDL_Joystick* joystick; + SDL_GameController* controller; + bool hasRumble = false; + bool isRumbling = false; melonDS::u32 keyInputMask, joyInputMask; melonDS::u32 keyHotkeyMask, joyHotkeyMask; diff --git a/src/frontend/qt_sdl/EmuInstanceInput.cpp b/src/frontend/qt_sdl/EmuInstanceInput.cpp index ccc9fe79..ddaca8f0 100644 --- a/src/frontend/qt_sdl/EmuInstanceInput.cpp +++ b/src/frontend/qt_sdl/EmuInstanceInput.cpp @@ -72,6 +72,9 @@ void EmuInstance::inputInit() lastHotkeyMask = 0; joystick = nullptr; + controller = nullptr; + hasRumble = false; + isRumbling = false; inputLoadConfig(); } @@ -100,6 +103,24 @@ void EmuInstance::inputLoadConfig() setJoystick(localCfg.GetInt("JoystickID")); } +void EmuInstance::inputRumbleStart(melonDS::u32 len_ms) +{ + if (controller && hasRumble && !isRumbling) + { + SDL_GameControllerRumble(controller, 0xFFFF, 0xFFFF, len_ms); + isRumbling = true; + } +} + +void EmuInstance::inputRumbleStop() +{ + if (controller && hasRumble && isRumbling) + { + SDL_GameControllerRumble(controller, 0, 0, 0); + isRumbling = false; + } +} + void EmuInstance::setJoystick(int id) { @@ -109,12 +130,16 @@ void EmuInstance::setJoystick(int id) void EmuInstance::openJoystick() { + if (controller) SDL_GameControllerClose(controller); + if (joystick) SDL_JoystickClose(joystick); int num = SDL_NumJoysticks(); if (num < 1) { + controller = nullptr; joystick = nullptr; + hasRumble = false; return; } @@ -122,10 +147,30 @@ void EmuInstance::openJoystick() joystickID = 0; joystick = SDL_JoystickOpen(joystickID); + + if (SDL_IsGameController(joystickID)) + { + controller = SDL_GameControllerOpen(joystickID); + } + + if (controller) + { + if (SDL_GameControllerHasRumble(controller)) + { + hasRumble = true; + } + } } void EmuInstance::closeJoystick() { + if (controller) + { + SDL_GameControllerClose(controller); + controller = nullptr; + hasRumble = false; + } + if (joystick) { SDL_JoystickClose(joystick); diff --git a/src/frontend/qt_sdl/Platform.cpp b/src/frontend/qt_sdl/Platform.cpp index 605d80f9..9455cf51 100644 --- a/src/frontend/qt_sdl/Platform.cpp +++ b/src/frontend/qt_sdl/Platform.cpp @@ -538,6 +538,16 @@ void Camera_CaptureFrame(int num, u32* frame, int width, int height, bool yuv, v return camManager[num]->captureFrame(frame, width, height, yuv); } +void Addon_RumbleStart(u32 len, void* userdata) +{ + ((EmuInstance*)userdata)->inputRumbleStart(len); +} + +void Addon_RumbleStop(void* userdata) +{ + ((EmuInstance*)userdata)->inputRumbleStop(); +} + DynamicLibrary* DynamicLibrary_Load(const char* lib) { return (DynamicLibrary*) SDL_LoadObject(lib); diff --git a/src/frontend/qt_sdl/Window.cpp b/src/frontend/qt_sdl/Window.cpp index 9f575d74..e4e23755 100644 --- a/src/frontend/qt_sdl/Window.cpp +++ b/src/frontend/qt_sdl/Window.cpp @@ -312,6 +312,10 @@ MainWindow::MainWindow(int id, EmuInstance* inst, QWidget* parent) : actInsertGBAAddon[0] = submenu->addAction("Memory expansion"); actInsertGBAAddon[0]->setData(QVariant(GBAAddon_RAMExpansion)); connect(actInsertGBAAddon[0], &QAction::triggered, this, &MainWindow::onInsertGBAAddon); + + actInsertGBAAddon[1] = submenu->addAction("Rumble Pak"); + actInsertGBAAddon[1]->setData(QVariant(GBAAddon_RumblePak)); + connect(actInsertGBAAddon[1], &QAction::triggered, this, &MainWindow::onInsertGBAAddon); } actEjectGBACart = menu->addAction("Eject cart"); diff --git a/src/frontend/qt_sdl/Window.h b/src/frontend/qt_sdl/Window.h index d9455fb8..aff8507f 100644 --- a/src/frontend/qt_sdl/Window.h +++ b/src/frontend/qt_sdl/Window.h @@ -258,7 +258,7 @@ public: QAction* actEjectCart; QAction* actCurrentGBACart; QAction* actInsertGBACart; - QAction* actInsertGBAAddon[1]; + QAction* actInsertGBAAddon[2]; QAction* actEjectGBACart; QAction* actImportSavefile; QAction* actSaveState[9];