Implement Rumble Pak support. (#2101)
This commit is contained in:
parent
5eadd67df6
commit
9b828c2cde
|
@ -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<CartCommon>&& cart) noexcept : NDS(nds), Cart(std::move(cart))
|
GBACartSlot::GBACartSlot(melonDS::NDS& nds, std::unique_ptr<CartCommon>&& 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)
|
switch (type)
|
||||||
{
|
{
|
||||||
case GBAAddon_RAMExpansion:
|
case GBAAddon_RAMExpansion:
|
||||||
Cart = std::make_unique<CartRAMExpansion>();
|
Cart = std::make_unique<CartRAMExpansion>();
|
||||||
break;
|
break;
|
||||||
|
case GBAAddon_RumblePak:
|
||||||
|
Cart = std::make_unique<CartRumblePak>(userdata);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Log(LogLevel::Warn, "GBACart: !! invalid addon type %d\n", type);
|
Log(LogLevel::Warn, "GBACart: !! invalid addon type %d\n", type);
|
||||||
|
|
|
@ -32,6 +32,7 @@ enum CartType
|
||||||
Game = 0x101,
|
Game = 0x101,
|
||||||
GameSolarSensor = 0x102,
|
GameSolarSensor = 0x102,
|
||||||
RAMExpansion = 0x201,
|
RAMExpansion = 0x201,
|
||||||
|
RumblePak = 0x202,
|
||||||
};
|
};
|
||||||
|
|
||||||
// CartCommon -- base code shared by all cart types
|
// CartCommon -- base code shared by all cart types
|
||||||
|
@ -189,6 +190,25 @@ private:
|
||||||
u16 RAMEnable = 0;
|
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
|
// possible inputs for GBA carts that might accept user input
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
|
@ -219,7 +239,7 @@ public:
|
||||||
[[nodiscard]] CartCommon* GetCart() noexcept { return Cart.get(); }
|
[[nodiscard]] CartCommon* GetCart() noexcept { return Cart.get(); }
|
||||||
[[nodiscard]] const CartCommon* GetCart() const 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,
|
/// @return The cart that was in the cart slot if any,
|
||||||
/// or \c nullptr if the cart slot was empty.
|
/// or \c nullptr if the cart slot was empty.
|
||||||
|
|
|
@ -751,7 +751,7 @@ void NDS::SetGBASave(const u8* savedata, u32 savelen)
|
||||||
|
|
||||||
void NDS::LoadGBAAddon(int type)
|
void NDS::LoadGBAAddon(int type)
|
||||||
{
|
{
|
||||||
GBACartSlot.LoadAddon(type);
|
GBACartSlot.LoadAddon(UserData, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NDS::LoadBIOS()
|
void NDS::LoadBIOS()
|
||||||
|
|
|
@ -210,6 +210,7 @@ enum
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
GBAAddon_RAMExpansion = 1,
|
GBAAddon_RAMExpansion = 1,
|
||||||
|
GBAAddon_RumblePak = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
class SPU;
|
class SPU;
|
||||||
|
|
|
@ -316,6 +316,17 @@ void Camera_Start(int num, void* userdata);
|
||||||
void Camera_Stop(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);
|
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;
|
struct DynamicLibrary;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -127,6 +127,8 @@ public:
|
||||||
void inputInit();
|
void inputInit();
|
||||||
void inputDeInit();
|
void inputDeInit();
|
||||||
void inputLoadConfig();
|
void inputLoadConfig();
|
||||||
|
void inputRumbleStart(melonDS::u32 len_ms);
|
||||||
|
void inputRumbleStop();
|
||||||
|
|
||||||
void setJoystick(int id);
|
void setJoystick(int id);
|
||||||
int getJoystickID() { return joystickID; }
|
int getJoystickID() { return joystickID; }
|
||||||
|
@ -291,6 +293,9 @@ private:
|
||||||
|
|
||||||
int joystickID;
|
int joystickID;
|
||||||
SDL_Joystick* joystick;
|
SDL_Joystick* joystick;
|
||||||
|
SDL_GameController* controller;
|
||||||
|
bool hasRumble = false;
|
||||||
|
bool isRumbling = false;
|
||||||
|
|
||||||
melonDS::u32 keyInputMask, joyInputMask;
|
melonDS::u32 keyInputMask, joyInputMask;
|
||||||
melonDS::u32 keyHotkeyMask, joyHotkeyMask;
|
melonDS::u32 keyHotkeyMask, joyHotkeyMask;
|
||||||
|
|
|
@ -72,6 +72,9 @@ void EmuInstance::inputInit()
|
||||||
lastHotkeyMask = 0;
|
lastHotkeyMask = 0;
|
||||||
|
|
||||||
joystick = nullptr;
|
joystick = nullptr;
|
||||||
|
controller = nullptr;
|
||||||
|
hasRumble = false;
|
||||||
|
isRumbling = false;
|
||||||
inputLoadConfig();
|
inputLoadConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,6 +103,24 @@ void EmuInstance::inputLoadConfig()
|
||||||
setJoystick(localCfg.GetInt("JoystickID"));
|
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)
|
void EmuInstance::setJoystick(int id)
|
||||||
{
|
{
|
||||||
|
@ -109,12 +130,16 @@ void EmuInstance::setJoystick(int id)
|
||||||
|
|
||||||
void EmuInstance::openJoystick()
|
void EmuInstance::openJoystick()
|
||||||
{
|
{
|
||||||
|
if (controller) SDL_GameControllerClose(controller);
|
||||||
|
|
||||||
if (joystick) SDL_JoystickClose(joystick);
|
if (joystick) SDL_JoystickClose(joystick);
|
||||||
|
|
||||||
int num = SDL_NumJoysticks();
|
int num = SDL_NumJoysticks();
|
||||||
if (num < 1)
|
if (num < 1)
|
||||||
{
|
{
|
||||||
|
controller = nullptr;
|
||||||
joystick = nullptr;
|
joystick = nullptr;
|
||||||
|
hasRumble = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,10 +147,30 @@ void EmuInstance::openJoystick()
|
||||||
joystickID = 0;
|
joystickID = 0;
|
||||||
|
|
||||||
joystick = SDL_JoystickOpen(joystickID);
|
joystick = SDL_JoystickOpen(joystickID);
|
||||||
|
|
||||||
|
if (SDL_IsGameController(joystickID))
|
||||||
|
{
|
||||||
|
controller = SDL_GameControllerOpen(joystickID);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controller)
|
||||||
|
{
|
||||||
|
if (SDL_GameControllerHasRumble(controller))
|
||||||
|
{
|
||||||
|
hasRumble = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuInstance::closeJoystick()
|
void EmuInstance::closeJoystick()
|
||||||
{
|
{
|
||||||
|
if (controller)
|
||||||
|
{
|
||||||
|
SDL_GameControllerClose(controller);
|
||||||
|
controller = nullptr;
|
||||||
|
hasRumble = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (joystick)
|
if (joystick)
|
||||||
{
|
{
|
||||||
SDL_JoystickClose(joystick);
|
SDL_JoystickClose(joystick);
|
||||||
|
|
|
@ -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);
|
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)
|
DynamicLibrary* DynamicLibrary_Load(const char* lib)
|
||||||
{
|
{
|
||||||
return (DynamicLibrary*) SDL_LoadObject(lib);
|
return (DynamicLibrary*) SDL_LoadObject(lib);
|
||||||
|
|
|
@ -312,6 +312,10 @@ MainWindow::MainWindow(int id, EmuInstance* inst, QWidget* parent) :
|
||||||
actInsertGBAAddon[0] = submenu->addAction("Memory expansion");
|
actInsertGBAAddon[0] = submenu->addAction("Memory expansion");
|
||||||
actInsertGBAAddon[0]->setData(QVariant(GBAAddon_RAMExpansion));
|
actInsertGBAAddon[0]->setData(QVariant(GBAAddon_RAMExpansion));
|
||||||
connect(actInsertGBAAddon[0], &QAction::triggered, this, &MainWindow::onInsertGBAAddon);
|
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");
|
actEjectGBACart = menu->addAction("Eject cart");
|
||||||
|
|
|
@ -258,7 +258,7 @@ public:
|
||||||
QAction* actEjectCart;
|
QAction* actEjectCart;
|
||||||
QAction* actCurrentGBACart;
|
QAction* actCurrentGBACart;
|
||||||
QAction* actInsertGBACart;
|
QAction* actInsertGBACart;
|
||||||
QAction* actInsertGBAAddon[1];
|
QAction* actInsertGBAAddon[2];
|
||||||
QAction* actEjectGBACart;
|
QAction* actEjectGBACart;
|
||||||
QAction* actImportSavefile;
|
QAction* actImportSavefile;
|
||||||
QAction* actSaveState[9];
|
QAction* actSaveState[9];
|
||||||
|
|
Loading…
Reference in New Issue