diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a4cb6f1e..cc0b479e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -30,6 +30,7 @@ add_library(core STATIC FATStorage.cpp FIFO.h GBACart.cpp + GBACartMotionPak.cpp GPU.cpp GPU2D.cpp GPU2D_Soft.cpp diff --git a/src/GBACart.cpp b/src/GBACart.cpp index a62aca6b..5e0a261a 100644 --- a/src/GBACart.cpp +++ b/src/GBACart.cpp @@ -724,6 +724,27 @@ void CartRumblePak::ROMWrite(u32 addr, u16 val) } } +CartGuitarGrip::CartGuitarGrip(void* userdata) : + CartCommon(GuitarGrip), + UserData(userdata) +{ +} + +CartGuitarGrip::~CartGuitarGrip() = default; + +u16 CartGuitarGrip::ROMRead(u32 addr) const +{ + return 0xF9FF; +} + +u8 CartGuitarGrip::SRAMRead(u32 addr) +{ + return ~((Platform::Addon_KeyDown(Platform::KeyGuitarGripGreen, UserData) ? 0x40 : 0) + | (Platform::Addon_KeyDown(Platform::KeyGuitarGripRed, UserData) ? 0x20 : 0) + | (Platform::Addon_KeyDown(Platform::KeyGuitarGripYellow, UserData) ? 0x10 : 0) + | (Platform::Addon_KeyDown(Platform::KeyGuitarGripBlue, UserData) ? 0x08 : 0)); +} + GBACartSlot::GBACartSlot(melonDS::NDS& nds, std::unique_ptr&& cart) noexcept : NDS(nds), Cart(std::move(cart)) { } @@ -874,6 +895,15 @@ void GBACartSlot::LoadAddon(void* userdata, int type) noexcept case GBAAddon_RumblePak: Cart = std::make_unique(userdata); break; + case GBAAddon_MotionPakHomebrew: + Cart = std::make_unique(userdata); + break; + case GBAAddon_MotionPakRetail: + Cart = std::make_unique(userdata); + break; + case GBAAddon_GuitarGrip: + 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 726a234d..9ab42f1a 100644 --- a/src/GBACart.h +++ b/src/GBACart.h @@ -33,6 +33,9 @@ enum CartType GameSolarSensor = 0x102, RAMExpansion = 0x201, RumblePak = 0x202, + MotionPakHomebrew = 0x203, + MotionPakRetail = 0x204, + GuitarGrip = 0x205, }; // CartCommon -- base code shared by all cart types @@ -211,11 +214,68 @@ private: u16 RumbleState = 0; }; +// CartGuitarGrip -- DS Guitar Grip (used in various NDS games) +class CartGuitarGrip : public CartCommon +{ +public: + CartGuitarGrip(void* userdata); + ~CartGuitarGrip() override; + + u16 ROMRead(u32 addr) const override; + u8 SRAMRead(u32 addr) override; + +private: + void* UserData; +}; + +// CartMotionPakHomebrew -- DS Motion Pak (Homebrew) +class CartMotionPakHomebrew : public CartCommon +{ +public: + CartMotionPakHomebrew(void* userdata); + ~CartMotionPakHomebrew() override; + + void Reset() override; + + void DoSavestate(Savestate* file) override; + + u16 ROMRead(u32 addr) const override; + u8 SRAMRead(u32 addr) override; + +private: + void* UserData; + u16 ShiftVal = 0; +}; + +// CartMotionPakRetail -- DS Motion Pack (Retail) +class CartMotionPakRetail : public CartCommon +{ +public: + CartMotionPakRetail(void* userdata); + ~CartMotionPakRetail() override; + + void Reset() override; + + void DoSavestate(Savestate* file) override; + + u16 ROMRead(u32 addr) const override; + u8 SRAMRead(u32 addr) override; + +private: + void* UserData; + u8 Value; + u8 Step = 16; +}; + // possible inputs for GBA carts that might accept user input enum { Input_SolarSensorDown = 0, Input_SolarSensorUp, + Input_GuitarGripGreen, + Input_GuitarGripRed, + Input_GuitarGripYellow, + Input_GuitarGripBlue, }; class GBACartSlot diff --git a/src/GBACartMotionPak.cpp b/src/GBACartMotionPak.cpp new file mode 100644 index 00000000..c213bb7f --- /dev/null +++ b/src/GBACartMotionPak.cpp @@ -0,0 +1,196 @@ +/* + Copyright 2016-2024 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include +#include "NDS.h" +#include "GBACart.h" +#include "Platform.h" +#include +#include "math.h" + +namespace melonDS +{ +using Platform::Log; +using Platform::LogLevel; + +namespace GBACart +{ + +CartMotionPakHomebrew::CartMotionPakHomebrew(void* userdata) : + CartCommon(MotionPakHomebrew), + UserData(userdata) +{ +} + +CartMotionPakHomebrew::~CartMotionPakHomebrew() = default; + +void CartMotionPakHomebrew::Reset() +{ + ShiftVal = 0; +} + +void CartMotionPakHomebrew::DoSavestate(Savestate* file) +{ + CartCommon::DoSavestate(file); + file->Var16(&ShiftVal); +} + +u16 CartMotionPakHomebrew::ROMRead(u32 addr) const +{ + // CHECKME: Does this apply to the homebrew cart as well? + return 0xFCFF; +} + +static int AccelerationToMotionPak(float accel) +{ + const float GRAVITY_M_S2 = 9.80665f; + + return std::clamp( + (int) ((accel / (5 * GRAVITY_M_S2) + 0.5) * 4096), + 0, 4095 + ); +} + +static int AccelerationToMotionPakRetail(float accel) +{ + const float GRAVITY_M_S2 = 9.80665f; + + return std::clamp( + (int) ((accel / (5 * GRAVITY_M_S2) + 0.5) * 256), + 0, 254 + ); +} + +static int RotationToMotionPak(float rot) +{ + const float DEGREES_PER_RAD = 180 / M_PI; + const float COUNTS_PER_DEG_PER_SEC = 0.825; + const int CENTER = 1680; + + return std::clamp( + (int) ((rot * DEGREES_PER_RAD * COUNTS_PER_DEG_PER_SEC) + CENTER + 0.5), + 0, 4095 + ); +} + +u8 CartMotionPakHomebrew::SRAMRead(u32 addr) +{ + // CHECKME: SRAM address mask + addr &= 0xFFFF; + + switch (addr) + { + case 0: + // Read next byte + break; + case 2: + // Read X acceleration + ShiftVal = AccelerationToMotionPak(Platform::Addon_MotionQuery(Platform::MotionAccelerationX, UserData)) << 4; + // CHECKME: First byte returned when reading acceleration/rotation + return 0; + case 4: + // Read Y acceleration + ShiftVal = AccelerationToMotionPak(Platform::Addon_MotionQuery(Platform::MotionAccelerationY, UserData)) << 4; + return 0; + case 6: + // Read Z acceleration + ShiftVal = AccelerationToMotionPak(Platform::Addon_MotionQuery(Platform::MotionAccelerationZ, UserData)) << 4; + return 0; + case 8: + // Read Z rotation + // CHECKME: This is a guess, compare with real hardware + ShiftVal = RotationToMotionPak(-Platform::Addon_MotionQuery(Platform::MotionRotationZ, UserData)) << 4; + return 0; + case 10: + // Identify cart + ShiftVal = 0xF00F; + return 0; + case 12: + case 14: + case 16: + case 18: + // Read/enable analog inputs + // + // These are not connected by defualt and require do-it-yourself cart + // modification, so there is no reason to emulate them. + ShiftVal = 0; + break; + } + + // Read high byte from the emulated shift register + u8 val = ShiftVal >> 8; + ShiftVal <<= 8; + return val; +} + +CartMotionPakRetail::CartMotionPakRetail(void* userdata) : + CartCommon(MotionPakRetail), + UserData(userdata) +{ +} + +CartMotionPakRetail::~CartMotionPakRetail() = default; + +void CartMotionPakRetail::Reset() +{ + Value = 0; + Step = 16; +} + +void CartMotionPakRetail::DoSavestate(Savestate* file) +{ + CartCommon::DoSavestate(file); + file->Var8(&Value); + file->Var8(&Step); +} + +u16 CartMotionPakRetail::ROMRead(u32 addr) const +{ + // A9-A8 is pulled low on a real Motion Pack. + return 0xFCFF; +} + +u8 CartMotionPakRetail::SRAMRead(u32 addr) +{ + switch (Step) + { + case 0: // Synchronization - read 0xFF + Value = 0xFF; + break; + case 4: // X acceleration + Value = AccelerationToMotionPakRetail(Platform::Addon_MotionQuery(Platform::MotionAccelerationX, UserData)); + break; + case 8: // Y acceleration + Value = AccelerationToMotionPakRetail(Platform::Addon_MotionQuery(Platform::MotionAccelerationY, UserData)); + break; + case 12: // Z acceleration + Value = AccelerationToMotionPakRetail(Platform::Addon_MotionQuery(Platform::MotionAccelerationZ, UserData)); + break; + case 16: // Synchronization - read 0b00 + Step = 0; + return 0; + } + + int shift = 6 - ((Step & 3) * 2); + Step++; + return (Value >> shift) & 0x03; +} + +} + +} diff --git a/src/NDS.h b/src/NDS.h index b2bfb385..3dfed01d 100644 --- a/src/NDS.h +++ b/src/NDS.h @@ -211,6 +211,9 @@ enum { GBAAddon_RAMExpansion = 1, GBAAddon_RumblePak = 2, + GBAAddon_MotionPakHomebrew = 3, + GBAAddon_MotionPakRetail = 4, + GBAAddon_GuitarGrip = 5, }; class SPU; diff --git a/src/Platform.h b/src/Platform.h index bef66593..d7108b20 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -322,6 +322,18 @@ void Camera_CaptureFrame(int num, u32* frame, int width, int height, bool yuv, v // interface for addon inputs +enum KeyType +{ + KeyGuitarGripGreen, + KeyGuitarGripRed, + KeyGuitarGripYellow, + KeyGuitarGripBlue, +}; + +// Check if a given key is being pressed. +// @param type The type of the key to check. +bool Addon_KeyDown(KeyType type, void* userdata); + // 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. @@ -331,6 +343,42 @@ void Addon_RumbleStart(u32 len, void* userdata); // rumble effects on the connected game controller, if available. void Addon_RumbleStop(void* userdata); +enum MotionQueryType +{ + /** + * @brief X axis acceleration, measured in SI meters per second squared. + * On a DS, the X axis refers to the top screen X-axis (left ... right). + */ + MotionAccelerationX, + /** + * @brief Y axis acceleration, measured in SI meters per second squared. + * On a DS, the Y axis refers to the top screen Y-axis (bottom ... top). + */ + MotionAccelerationY, + /** + * @brief Z axis acceleration, measured in SI meters per second squared. + * On a DS, the Z axis refers to the axis perpendicular to the top screen (farther ... closer). + */ + MotionAccelerationZ, + /** + * @brief X axis rotation, measured in radians per second. + */ + MotionRotationX, + /** + * @brief Y axis rotation, measured in radians per second. + */ + MotionRotationY, + /** + * @brief Z axis rotation, measured in radians per second. + */ + MotionRotationZ, +}; + +// Called by the DS Motion Pak emulation to query the game controller's +// aceelration and rotation, if available. +// @param type The value being queried. +float Addon_MotionQuery(MotionQueryType type, void* userdata); + struct DynamicLibrary; /** diff --git a/src/frontend/qt_sdl/Config.cpp b/src/frontend/qt_sdl/Config.cpp index 02be5b65..90eb6a6c 100644 --- a/src/frontend/qt_sdl/Config.cpp +++ b/src/frontend/qt_sdl/Config.cpp @@ -169,6 +169,10 @@ LegacyEntry LegacyFile[] = {"HKKey_PowerButton", 0, "Keyboard.HK_PowerButton", true}, {"HKKey_VolumeUp", 0, "Keyboard.HK_VolumeUp", true}, {"HKKey_VolumeDown", 0, "Keyboard.HK_VolumeDown", true}, + {"HKKey_GuitarGripGreen", 0, "Keyboard.HK_GuitarGripGreen", true}, + {"HKKey_GuitarGripRed", 0, "Keyboard.HK_GuitarGripRed", true}, + {"HKKey_GuitarGripYellow", 0, "Keyboard.HK_GuitarGripYellow", true}, + {"HKKey_GuitarGripBlue", 0, "Keyboard.HK_GuitarGripBlue", true}, {"HKJoy_Lid", 0, "Joystick.HK_Lid", true}, {"HKJoy_Mic", 0, "Joystick.HK_Mic", true}, @@ -185,6 +189,10 @@ LegacyEntry LegacyFile[] = {"HKJoy_PowerButton", 0, "Joystick.HK_PowerButton", true}, {"HKJoy_VolumeUp", 0, "Joystick.HK_VolumeUp", true}, {"HKJoy_VolumeDown", 0, "Joystick.HK_VolumeDown", true}, + {"HKJoy_GuitarGripGreen", 0, "Joystick.HK_GuitarGripGreen", true}, + {"HKJoy_GuitarGripRed", 0, "Joystick.HK_GuitarGripRed", true}, + {"HKJoy_GuitarGripYellow", 0, "Joystick.HK_GuitarGripYellow", true}, + {"HKJoy_GuitarGripBlue", 0, "Joystick.HK_GuitarGripBlue", true}, {"JoystickID", 0, "JoystickID", true}, diff --git a/src/frontend/qt_sdl/EmuInstance.cpp b/src/frontend/qt_sdl/EmuInstance.cpp index 2ac3f42d..07987be2 100644 --- a/src/frontend/qt_sdl/EmuInstance.cpp +++ b/src/frontend/qt_sdl/EmuInstance.cpp @@ -2088,6 +2088,12 @@ QString EmuInstance::gbaAddonName(int addon) return "Rumble Pak"; case GBAAddon_RAMExpansion: return "Memory expansion"; + case GBAAddon_MotionPakHomebrew: + return "Motion Pak (Homebrew)"; + case GBAAddon_MotionPakRetail: + return "Motion Pack (Retail)"; + case GBAAddon_GuitarGrip: + return "Guitar Grip"; } return "???"; diff --git a/src/frontend/qt_sdl/EmuInstance.h b/src/frontend/qt_sdl/EmuInstance.h index a135a52c..bb7d4d11 100644 --- a/src/frontend/qt_sdl/EmuInstance.h +++ b/src/frontend/qt_sdl/EmuInstance.h @@ -21,6 +21,7 @@ #include +#include "Platform.h" #include "main.h" #include "NDS.h" #include "EmuThread.h" @@ -50,6 +51,10 @@ enum HK_SlowMo, HK_FastForwardToggle, HK_SlowMoToggle, + HK_GuitarGripGreen, + HK_GuitarGripRed, + HK_GuitarGripYellow, + HK_GuitarGripBlue, HK_MAX }; @@ -143,6 +148,9 @@ public: void inputRumbleStart(melonDS::u32 len_ms); void inputRumbleStop(); + bool inputHotkeyDown(int id) { return hotkeyDown(id); } + float inputMotionQuery(melonDS::Platform::MotionQueryType type); + void setJoystick(int id); int getJoystickID() { return joystickID; } SDL_Joystick* getJoystick() { return joystick; } @@ -326,6 +334,8 @@ private: int joystickID; SDL_Joystick* joystick; SDL_GameController* controller; + bool hasAccelerometer = false; + bool hasGyroscope = false; bool hasRumble = false; bool isRumbling = false; diff --git a/src/frontend/qt_sdl/EmuInstanceInput.cpp b/src/frontend/qt_sdl/EmuInstanceInput.cpp index aa1c529f..9374cabe 100644 --- a/src/frontend/qt_sdl/EmuInstanceInput.cpp +++ b/src/frontend/qt_sdl/EmuInstanceInput.cpp @@ -19,6 +19,9 @@ #include #include +#include "Platform.h" +#include "SDL_gamecontroller.h" +#include "SDL_sensor.h" #include "main.h" #include "Config.h" @@ -59,7 +62,11 @@ const char* EmuInstance::hotkeyNames[HK_MAX] = "HK_VolumeDown", "HK_SlowMo", "HK_FastForwardToggle", - "HK_SlowMoToggle" + "HK_SlowMoToggle", + "HK_GuitarGripGreen", + "HK_GuitarGripRed", + "HK_GuitarGripYellow", + "HK_GuitarGripBlue" }; @@ -81,6 +88,8 @@ void EmuInstance::inputInit() joystick = nullptr; controller = nullptr; hasRumble = false; + hasAccelerometer = false; + hasGyroscope = false; isRumbling = false; inputLoadConfig(); } @@ -128,6 +137,48 @@ void EmuInstance::inputRumbleStop() } } +float EmuInstance::inputMotionQuery(melonDS::Platform::MotionQueryType type) +{ + float values[3]; + if (type <= melonDS::Platform::MotionAccelerationZ) + { + if (controller && hasAccelerometer) + if (SDL_GameControllerGetSensorData(controller, SDL_SENSOR_ACCEL, values, 3) == 0) + { + // Map values from DS console orientation to SDL controller orientation. + switch (type) + { + case melonDS::Platform::MotionAccelerationX: + return values[0]; + case melonDS::Platform::MotionAccelerationY: + return -values[2]; + case melonDS::Platform::MotionAccelerationZ: + return values[1]; + } + } + } + else if (type <= melonDS::Platform::MotionRotationZ) + { + if (controller && hasGyroscope) + if (SDL_GameControllerGetSensorData(controller, SDL_SENSOR_GYRO, values, 3) == 0) + { + // Map values from DS console orientation to SDL controller orientation. + switch (type) + { + case melonDS::Platform::MotionRotationX: + return values[0]; + case melonDS::Platform::MotionRotationY: + return -values[2]; + case melonDS::Platform::MotionRotationZ: + return values[1]; + } + } + } + if (type == melonDS::Platform::MotionAccelerationZ) + return SDL_STANDARD_GRAVITY; + return 0.0f; +} + void EmuInstance::setJoystick(int id) { @@ -147,6 +198,8 @@ void EmuInstance::openJoystick() controller = nullptr; joystick = nullptr; hasRumble = false; + hasAccelerometer = false; + hasGyroscope = false; return; } @@ -163,9 +216,17 @@ void EmuInstance::openJoystick() if (controller) { if (SDL_GameControllerHasRumble(controller)) - { + { hasRumble = true; - } + } + if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL)) + { + hasAccelerometer = SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE) == 0; + } + if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO)) + { + hasGyroscope = SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE) == 0; + } } } @@ -176,8 +237,9 @@ void EmuInstance::closeJoystick() SDL_GameControllerClose(controller); controller = nullptr; hasRumble = false; + hasAccelerometer = false; + hasGyroscope = false; } - if (joystick) { SDL_JoystickClose(joystick); diff --git a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h index 3337228f..5f016b5b 100644 --- a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h +++ b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h @@ -32,12 +32,20 @@ static constexpr std::initializer_list hk_addons = { HK_SolarSensorIncrease, HK_SolarSensorDecrease, + HK_GuitarGripGreen, + HK_GuitarGripRed, + HK_GuitarGripYellow, + HK_GuitarGripBlue, }; static constexpr std::initializer_list hk_addons_labels = { "[Boktai] Sunlight + ", "[Boktai] Sunlight - ", + "[Guitar Grip] Green", + "[Guitar Grip] Red", + "[Guitar Grip] Yellow", + "[Guitar Grip] Blue", }; static_assert(hk_addons.size() == hk_addons_labels.size()); diff --git a/src/frontend/qt_sdl/Platform.cpp b/src/frontend/qt_sdl/Platform.cpp index 541b51f2..f7f44d1e 100644 --- a/src/frontend/qt_sdl/Platform.cpp +++ b/src/frontend/qt_sdl/Platform.cpp @@ -35,6 +35,7 @@ #include "Platform.h" #include "Config.h" +#include "EmuInstance.h" #include "main.h" #include "CameraManager.h" #include "Net.h" @@ -549,6 +550,18 @@ void Camera_CaptureFrame(int num, u32* frame, int width, int height, bool yuv, v return camManager[num]->captureFrame(frame, width, height, yuv); } +static const int hotkeyMap[] = { + HK_GuitarGripGreen, + HK_GuitarGripRed, + HK_GuitarGripYellow, + HK_GuitarGripBlue, +}; + +bool Addon_KeyDown(KeyType type, void* userdata) +{ + return ((EmuInstance*)userdata)->inputHotkeyDown(hotkeyMap[type]); +} + void Addon_RumbleStart(u32 len, void* userdata) { ((EmuInstance*)userdata)->inputRumbleStart(len); @@ -559,6 +572,11 @@ void Addon_RumbleStop(void* userdata) ((EmuInstance*)userdata)->inputRumbleStop(); } +float Addon_MotionQuery(MotionQueryType type, void* userdata) +{ + return ((EmuInstance*)userdata)->inputMotionQuery(type); +} + 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 596a0f5c..3a669c46 100644 --- a/src/frontend/qt_sdl/Window.cpp +++ b/src/frontend/qt_sdl/Window.cpp @@ -16,6 +16,7 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ +#include "NDS.h" #include #include #include @@ -320,7 +321,14 @@ MainWindow::MainWindow(int id, EmuInstance* inst, QWidget* parent) : QMenu * submenu = menu->addMenu("Insert add-on cart"); QAction *act; - int addons[] = {GBAAddon_RAMExpansion, GBAAddon_RumblePak, -1}; + int addons[] = { + GBAAddon_RAMExpansion, + GBAAddon_RumblePak, + GBAAddon_MotionPakHomebrew, + GBAAddon_MotionPakRetail, + GBAAddon_GuitarGrip, + -1 + }; for (int i = 0; addons[i] != -1; i++) { int addon = addons[i]; diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp index d940340a..103dc5cf 100644 --- a/src/frontend/qt_sdl/main.cpp +++ b/src/frontend/qt_sdl/main.cpp @@ -307,6 +307,10 @@ int main(int argc, char** argv) { printf("SDL couldn't init joystick\n"); } + if (SDL_Init(SDL_INIT_SENSOR) < 0) + { + printf("SDL couldn't init motion sensors\n"); + } if (SDL_Init(SDL_INIT_AUDIO) < 0) { const char* err = SDL_GetError();