/* * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG * Copyright (C) 2020 Rosalie Wanders * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3. * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #define CORE_PLUGIN #define M64P_PLUGIN_PROTOTYPES 1 #define INPUT_PLUGIN_API_VERSION 0x020100 #include #include "Thread/SDLThread.hpp" #include "Thread/HotkeysThread.hpp" #include "Utilities/InputDevice.hpp" #include "common.hpp" #include "main.hpp" #ifdef VRU #include "VRU.hpp" #endif // VRU #include #include #include #include #include #include #include // // Local Defines // #define NUM_CONTROLLERS 4 #define N64_AXIS_PEAK 85 #define MAX_DIAGONAL_VALUE 69 #define RD_GETSTATUS 0x00 // get status #define RD_READKEYS 0x01 // read button values #define RD_READPAK 0x02 // read from controllerpack #define RD_WRITEPAK 0x03 // write to controllerpack #define RD_RESETCONTROLLER 0xff // reset controller #define RD_READEEPROM 0x04 // read eeprom #define RD_WRITEEPROM 0x05 // write eeprom #define PAK_IO_RUMBLE 0xC000 // the address where rumble-commands are sent to // // Local Structures // struct InputMapping { std::vector Name; std::vector Type; std::vector Data; std::vector ExtraData; int Count = 0; }; struct InputProfile { bool PluggedIn = false; int DeadzoneValue = 0; int SensitivityValue = 100; N64ControllerPak ControllerPak = N64ControllerPak::None; // input device information std::string DeviceName; int DeviceNum = -1; std::chrono::time_point LastDeviceCheckTime = std::chrono::high_resolution_clock::now(); // Gameboy information std::string GameboyRom; std::string GameboySave; // input device Utilities::InputDevice InputDevice; // buttons InputMapping Button_A; InputMapping Button_B; InputMapping Button_Start; InputMapping Button_DpadUp; InputMapping Button_DpadDown; InputMapping Button_DpadLeft; InputMapping Button_DpadRight; InputMapping Button_CButtonUp; InputMapping Button_CButtonDown; InputMapping Button_CButtonLeft; InputMapping Button_CButtonRight; InputMapping Button_LeftTrigger; InputMapping Button_RightTrigger; InputMapping Button_ZTrigger; // analog stick InputMapping AnalogStick_Up; InputMapping AnalogStick_Down; InputMapping AnalogStick_Left; InputMapping AnalogStick_Right; // hotkeys bool Hotkey_Shutdown_Pressed = false; InputMapping Hotkey_Shutdown; bool Hotkey_Exit_Pressed = false; InputMapping Hotkey_Exit; bool Hotkey_SoftReset_Pressed = false; InputMapping Hotkey_SoftReset; bool Hotkey_HardReset_Pressed = false; InputMapping Hotkey_HardReset; bool Hotkey_Resume_Pressed = false; InputMapping Hotkey_Resume; bool Hotkey_Screenshot_Pressed = false; InputMapping Hotkey_Screenshot; bool Hotkey_LimitFPS_Pressed = false; InputMapping Hotkey_LimitFPS; bool Hotkey_SpeedFactor_Pressed = false; InputMapping Hotkey_SpeedFactor25; InputMapping Hotkey_SpeedFactor50; InputMapping Hotkey_SpeedFactor75; InputMapping Hotkey_SpeedFactor100; InputMapping Hotkey_SpeedFactor125; InputMapping Hotkey_SpeedFactor150; InputMapping Hotkey_SpeedFactor175; InputMapping Hotkey_SpeedFactor200; InputMapping Hotkey_SpeedFactor225; InputMapping Hotkey_SpeedFactor250; InputMapping Hotkey_SpeedFactor275; InputMapping Hotkey_SpeedFactor300; bool Hotkey_SaveState_Pressed = false; InputMapping Hotkey_SaveState; bool Hotkey_LoadState_Pressed = false; InputMapping Hotkey_LoadState; bool Hotkey_GSButton_Pressed = false; InputMapping Hotkey_GSButton; bool Hotkey_SaveStateSlot_Pressed = false; InputMapping Hotkey_SaveStateSlot0; InputMapping Hotkey_SaveStateSlot1; InputMapping Hotkey_SaveStateSlot2; InputMapping Hotkey_SaveStateSlot3; InputMapping Hotkey_SaveStateSlot4; InputMapping Hotkey_SaveStateSlot5; InputMapping Hotkey_SaveStateSlot6; InputMapping Hotkey_SaveStateSlot7; InputMapping Hotkey_SaveStateSlot8; InputMapping Hotkey_SaveStateSlot9; bool Hotkey_IncreaseSaveStateSlot_Pressed = false; InputMapping Hotkey_IncreaseSaveStateSlot; bool Hotkey_DecreaseSaveStateSlot_Pressed = false; InputMapping Hotkey_DecreaseSaveStateSlot; bool Hotkey_MemoryPak_Pressed = false; InputMapping Hotkey_MemoryPak; bool Hotkey_RumblePak_Pressed = false; InputMapping Hotkey_RumblePak; bool Hotkey_NoPak_Pressed = false; InputMapping Hotkey_NoPak; bool Hotkey_Fullscreen_Pressed = false; InputMapping Hotkey_Fullscreen; }; // // Local variables // // SDL thread static Thread::SDLThread *l_SDLThread = nullptr; // Hotkeys thread (for when paused) static Thread::HotkeysThread *l_HotkeysThread = nullptr; // input profiles static InputProfile l_InputProfiles[NUM_CONTROLLERS]; // whether we have mupen64plus' control info static bool l_HasControlInfo = false; // mupen64plus control info static CONTROL_INFO l_ControlInfo; // mupen64plus debug callback static void (*l_DebugCallback)(void *, int, const char *) = nullptr; static void *l_DebugCallContext = nullptr; // keyboard state static bool l_KeyboardState[SDL_NUM_SCANCODES]; // // Local Functions // static void load_inputmapping_settings(InputMapping* mapping, std::string section, SettingsID inputNameSettingsId, SettingsID inputTypeSettingsId, SettingsID dataSettingsId, SettingsID extraDataSettingsId) { mapping->Name = CoreSettingsGetStringListValue(inputNameSettingsId, section); mapping->Type = CoreSettingsGetIntListValue(inputTypeSettingsId, section); mapping->Data = CoreSettingsGetIntListValue(dataSettingsId, section); mapping->ExtraData = CoreSettingsGetIntListValue(extraDataSettingsId, section); mapping->Count = std::min(mapping->Type.size(), std::min(mapping->Data.size(), mapping->ExtraData.size())); // check if mapping is old profile type, // it should be true when the minimum of the lengths // of all int lists is 0, and the name isn't empty or whitespace if (mapping->Count == 0 && !mapping->Name.empty() && !mapping->Name.at(0).empty() && mapping->Name.at(0).find_first_not_of(' ') != std::string::npos) { mapping->Type.push_back(CoreSettingsGetIntValue(inputTypeSettingsId, section)); mapping->Data.push_back(CoreSettingsGetIntValue(dataSettingsId, section)); mapping->ExtraData.push_back(CoreSettingsGetIntValue(extraDataSettingsId, section)); mapping->Count = 1; } } static void load_settings(void) { std::string gameId; CoreRomSettings romSettings; std::string userProfileSectionBase = "Rosalie's Mupen GUI - Input Plugin User Profile"; std::vector userProfiles; std::vector::iterator userProfilesIter; // try to retrieve current ROM settings if (CoreGetCurrentRomSettings(romSettings)) { gameId = romSettings.MD5; } // retrieve all user profiles userProfiles = CoreSettingsGetStringListValue(SettingsID::Input_Profiles); for (int i = 0; i < NUM_CONTROLLERS; i++) { InputProfile* profile = &l_InputProfiles[i]; std::string section = "Rosalie's Mupen GUI - Input Plugin Profile " + std::to_string(i); std::string gameSection = section + " Game " + gameId; std::string userProfileSection; std::string userProfileName; // when a user profile has been specified, // ensure it exists in the profile list and // in the settings, if it does, // use that user profile userProfileName = CoreSettingsGetStringValue(SettingsID::Input_UseProfile, section); if (!userProfileName.empty()) { userProfilesIter = std::find(userProfiles.begin(), userProfiles.end(), userProfileName); if (userProfilesIter != userProfiles.end()) { userProfileSection = userProfileSectionBase; userProfileSection += " \"" + userProfileName + "\""; if (CoreSettingsSectionExists(userProfileSection)) { section = userProfileSection; } } } // if game ID was retrieved, // check if game section exists, // if it does, check if the 'UseGameProfile' key exists, // if it doesn't then use the profile, else // check if the value is true if (!gameId.empty()) { if (CoreSettingsSectionExists(gameSection)) { if (!CoreSettingsKeyExists(gameSection, "UseGameProfile") || CoreSettingsGetBoolValue(SettingsID::Input_UseGameProfile, gameSection)) { section = gameSection; } } } // when the settings section doesn't exist, // disable profile if (!CoreSettingsSectionExists(section)) { profile->PluggedIn = false; continue; } profile->PluggedIn = CoreSettingsGetBoolValue(SettingsID::Input_PluggedIn, section); profile->DeadzoneValue = CoreSettingsGetIntValue(SettingsID::Input_Deadzone, section); profile->ControllerPak = (N64ControllerPak)CoreSettingsGetIntValue(SettingsID::Input_Pak, section); profile->DeviceName = CoreSettingsGetStringValue(SettingsID::Input_DeviceName, section); profile->DeviceNum = CoreSettingsGetIntValue(SettingsID::Input_DeviceNum, section); profile->GameboyRom = CoreSettingsGetStringValue(SettingsID::Input_GameboyRom, section); profile->GameboySave = CoreSettingsGetStringValue(SettingsID::Input_GameboySave, section); // keep compatibility with profiles before version v0.3.9 if (CoreSettingsKeyExists(section, "Sensitivity")) { profile->SensitivityValue = CoreSettingsGetIntValue(SettingsID::Input_Sensitivity, section); } else { profile->SensitivityValue = 100; } // load inputmapping settings load_inputmapping_settings(&profile->Button_A, section, SettingsID::Input_A_Name, SettingsID::Input_A_InputType, SettingsID::Input_A_Data, SettingsID::Input_A_ExtraData); load_inputmapping_settings(&profile->Button_B, section, SettingsID::Input_B_Name, SettingsID::Input_B_InputType, SettingsID::Input_B_Data, SettingsID::Input_B_ExtraData); load_inputmapping_settings(&profile->Button_Start, section, SettingsID::Input_Start_Name, SettingsID::Input_Start_InputType, SettingsID::Input_Start_Data, SettingsID::Input_Start_ExtraData); load_inputmapping_settings(&profile->Button_DpadUp, section, SettingsID::Input_DpadUp_Name, SettingsID::Input_DpadUp_InputType, SettingsID::Input_DpadUp_Data, SettingsID::Input_DpadUp_ExtraData); load_inputmapping_settings(&profile->Button_DpadDown, section, SettingsID::Input_DpadDown_Name, SettingsID::Input_DpadDown_InputType, SettingsID::Input_DpadDown_Data, SettingsID::Input_DpadDown_ExtraData); load_inputmapping_settings(&profile->Button_DpadLeft, section, SettingsID::Input_DpadLeft_Name, SettingsID::Input_DpadLeft_InputType, SettingsID::Input_DpadLeft_Data, SettingsID::Input_DpadLeft_ExtraData); load_inputmapping_settings(&profile->Button_DpadRight, section, SettingsID::Input_DpadRight_Name, SettingsID::Input_DpadRight_InputType, SettingsID::Input_DpadRight_Data, SettingsID::Input_DpadRight_ExtraData); load_inputmapping_settings(&profile->Button_CButtonUp, section, SettingsID::Input_CButtonUp_Name, SettingsID::Input_CButtonUp_InputType, SettingsID::Input_CButtonUp_Data, SettingsID::Input_CButtonUp_ExtraData); load_inputmapping_settings(&profile->Button_CButtonDown, section, SettingsID::Input_CButtonDown_Name, SettingsID::Input_CButtonDown_InputType, SettingsID::Input_CButtonDown_Data, SettingsID::Input_CButtonDown_ExtraData); load_inputmapping_settings(&profile->Button_CButtonLeft, section, SettingsID::Input_CButtonLeft_Name, SettingsID::Input_CButtonLeft_InputType, SettingsID::Input_CButtonLeft_Data, SettingsID::Input_CButtonLeft_ExtraData); load_inputmapping_settings(&profile->Button_CButtonRight, section, SettingsID::Input_CButtonRight_Name, SettingsID::Input_CButtonRight_InputType, SettingsID::Input_CButtonRight_Data, SettingsID::Input_CButtonRight_ExtraData); load_inputmapping_settings(&profile->Button_LeftTrigger, section, SettingsID::Input_LeftTrigger_Name, SettingsID::Input_LeftTrigger_InputType, SettingsID::Input_LeftTrigger_Data, SettingsID::Input_LeftTrigger_ExtraData); load_inputmapping_settings(&profile->Button_RightTrigger, section, SettingsID::Input_RightTrigger_Name, SettingsID::Input_RightTrigger_InputType, SettingsID::Input_RightTrigger_Data, SettingsID::Input_RightTrigger_ExtraData); load_inputmapping_settings(&profile->Button_ZTrigger, section, SettingsID::Input_ZTrigger_Name, SettingsID::Input_ZTrigger_InputType, SettingsID::Input_ZTrigger_Data, SettingsID::Input_ZTrigger_ExtraData); load_inputmapping_settings(&profile->AnalogStick_Up, section, SettingsID::Input_AnalogStickUp_Name, SettingsID::Input_AnalogStickUp_InputType, SettingsID::Input_AnalogStickUp_Data, SettingsID::Input_AnalogStickUp_ExtraData); load_inputmapping_settings(&profile->AnalogStick_Down, section, SettingsID::Input_AnalogStickDown_Name, SettingsID::Input_AnalogStickDown_InputType, SettingsID::Input_AnalogStickDown_Data, SettingsID::Input_AnalogStickDown_ExtraData); load_inputmapping_settings(&profile->AnalogStick_Left, section, SettingsID::Input_AnalogStickLeft_Name, SettingsID::Input_AnalogStickLeft_InputType, SettingsID::Input_AnalogStickLeft_Data, SettingsID::Input_AnalogStickLeft_ExtraData); load_inputmapping_settings(&profile->AnalogStick_Right, section, SettingsID::Input_AnalogStickRight_Name, SettingsID::Input_AnalogStickRight_InputType, SettingsID::Input_AnalogStickRight_Data, SettingsID::Input_AnalogStickRight_ExtraData); // load hotkeys settings load_inputmapping_settings(&profile->Hotkey_Shutdown, section, SettingsID::Input_Hotkey_Shutdown_Name, SettingsID::Input_Hotkey_Shutdown_InputType, SettingsID::Input_Hotkey_Shutdown_Data, SettingsID::Input_Hotkey_Shutdown_ExtraData); load_inputmapping_settings(&profile->Hotkey_Exit, section, SettingsID::Input_Hotkey_Exit_Name, SettingsID::Input_Hotkey_Exit_InputType, SettingsID::Input_Hotkey_Exit_Data, SettingsID::Input_Hotkey_Exit_ExtraData); load_inputmapping_settings(&profile->Hotkey_SoftReset, section, SettingsID::Input_Hotkey_SoftReset_Name, SettingsID::Input_Hotkey_SoftReset_InputType, SettingsID::Input_Hotkey_SoftReset_Data, SettingsID::Input_Hotkey_SoftReset_ExtraData); load_inputmapping_settings(&profile->Hotkey_HardReset, section, SettingsID::Input_Hotkey_HardReset_Name, SettingsID::Input_Hotkey_HardReset_InputType, SettingsID::Input_Hotkey_HardReset_Data, SettingsID::Input_Hotkey_HardReset_ExtraData); load_inputmapping_settings(&profile->Hotkey_Resume, section, SettingsID::Input_Hotkey_Resume_Name, SettingsID::Input_Hotkey_Resume_InputType, SettingsID::Input_Hotkey_Resume_Data, SettingsID::Input_Hotkey_Resume_ExtraData); load_inputmapping_settings(&profile->Hotkey_Screenshot, section, SettingsID::Input_Hotkey_Screenshot_Name, SettingsID::Input_Hotkey_Screenshot_InputType, SettingsID::Input_Hotkey_Screenshot_Data, SettingsID::Input_Hotkey_Screenshot_ExtraData); load_inputmapping_settings(&profile->Hotkey_LimitFPS, section, SettingsID::Input_Hotkey_LimitFPS_Name, SettingsID::Input_Hotkey_LimitFPS_InputType, SettingsID::Input_Hotkey_LimitFPS_Data, SettingsID::Input_Hotkey_LimitFPS_ExtraData); load_inputmapping_settings(&profile->Hotkey_SpeedFactor25, section, SettingsID::Input_Hotkey_SpeedFactor25_Name, SettingsID::Input_Hotkey_SpeedFactor25_InputType, SettingsID::Input_Hotkey_SpeedFactor25_Data, SettingsID::Input_Hotkey_SpeedFactor25_ExtraData); load_inputmapping_settings(&profile->Hotkey_SpeedFactor50, section, SettingsID::Input_Hotkey_SpeedFactor50_Name, SettingsID::Input_Hotkey_SpeedFactor50_InputType, SettingsID::Input_Hotkey_SpeedFactor50_Data, SettingsID::Input_Hotkey_SpeedFactor50_ExtraData); load_inputmapping_settings(&profile->Hotkey_SpeedFactor75, section, SettingsID::Input_Hotkey_SpeedFactor75_Name, SettingsID::Input_Hotkey_SpeedFactor75_InputType, SettingsID::Input_Hotkey_SpeedFactor75_Data, SettingsID::Input_Hotkey_SpeedFactor75_ExtraData); load_inputmapping_settings(&profile->Hotkey_SpeedFactor100, section, SettingsID::Input_Hotkey_SpeedFactor100_Name, SettingsID::Input_Hotkey_SpeedFactor100_InputType, SettingsID::Input_Hotkey_SpeedFactor100_Data, SettingsID::Input_Hotkey_SpeedFactor100_ExtraData); load_inputmapping_settings(&profile->Hotkey_SpeedFactor125, section, SettingsID::Input_Hotkey_SpeedFactor125_Name, SettingsID::Input_Hotkey_SpeedFactor125_InputType, SettingsID::Input_Hotkey_SpeedFactor125_Data, SettingsID::Input_Hotkey_SpeedFactor125_ExtraData); load_inputmapping_settings(&profile->Hotkey_SpeedFactor150, section, SettingsID::Input_Hotkey_SpeedFactor150_Name, SettingsID::Input_Hotkey_SpeedFactor150_InputType, SettingsID::Input_Hotkey_SpeedFactor150_Data, SettingsID::Input_Hotkey_SpeedFactor150_ExtraData); load_inputmapping_settings(&profile->Hotkey_SpeedFactor175, section, SettingsID::Input_Hotkey_SpeedFactor175_Name, SettingsID::Input_Hotkey_SpeedFactor175_InputType, SettingsID::Input_Hotkey_SpeedFactor175_Data, SettingsID::Input_Hotkey_SpeedFactor175_ExtraData); load_inputmapping_settings(&profile->Hotkey_SpeedFactor200, section, SettingsID::Input_Hotkey_SpeedFactor200_Name, SettingsID::Input_Hotkey_SpeedFactor200_InputType, SettingsID::Input_Hotkey_SpeedFactor200_Data, SettingsID::Input_Hotkey_SpeedFactor200_ExtraData); load_inputmapping_settings(&profile->Hotkey_SpeedFactor225, section, SettingsID::Input_Hotkey_SpeedFactor225_Name, SettingsID::Input_Hotkey_SpeedFactor225_InputType, SettingsID::Input_Hotkey_SpeedFactor225_Data, SettingsID::Input_Hotkey_SpeedFactor225_ExtraData); load_inputmapping_settings(&profile->Hotkey_SpeedFactor250, section, SettingsID::Input_Hotkey_SpeedFactor250_Name, SettingsID::Input_Hotkey_SpeedFactor250_InputType, SettingsID::Input_Hotkey_SpeedFactor250_Data, SettingsID::Input_Hotkey_SpeedFactor250_ExtraData); load_inputmapping_settings(&profile->Hotkey_SpeedFactor275, section, SettingsID::Input_Hotkey_SpeedFactor275_Name, SettingsID::Input_Hotkey_SpeedFactor275_InputType, SettingsID::Input_Hotkey_SpeedFactor275_Data, SettingsID::Input_Hotkey_SpeedFactor275_ExtraData); load_inputmapping_settings(&profile->Hotkey_SpeedFactor300, section, SettingsID::Input_Hotkey_SpeedFactor300_Name, SettingsID::Input_Hotkey_SpeedFactor300_InputType, SettingsID::Input_Hotkey_SpeedFactor300_Data, SettingsID::Input_Hotkey_SpeedFactor300_ExtraData); load_inputmapping_settings(&profile->Hotkey_SaveState, section, SettingsID::Input_Hotkey_SaveState_Name, SettingsID::Input_Hotkey_SaveState_InputType, SettingsID::Input_Hotkey_SaveState_Data, SettingsID::Input_Hotkey_SaveState_ExtraData); load_inputmapping_settings(&profile->Hotkey_LoadState, section, SettingsID::Input_Hotkey_LoadState_Name, SettingsID::Input_Hotkey_LoadState_InputType, SettingsID::Input_Hotkey_LoadState_Data, SettingsID::Input_Hotkey_LoadState_ExtraData); load_inputmapping_settings(&profile->Hotkey_GSButton, section, SettingsID::Input_Hotkey_GSButton_Name, SettingsID::Input_Hotkey_GSButton_InputType, SettingsID::Input_Hotkey_GSButton_Data, SettingsID::Input_Hotkey_GSButton_ExtraData); load_inputmapping_settings(&profile->Hotkey_SaveStateSlot0, section, SettingsID::Input_Hotkey_SaveStateSlot0_Name, SettingsID::Input_Hotkey_SaveStateSlot0_InputType, SettingsID::Input_Hotkey_SaveStateSlot0_Data, SettingsID::Input_Hotkey_SaveStateSlot0_ExtraData); load_inputmapping_settings(&profile->Hotkey_SaveStateSlot1, section, SettingsID::Input_Hotkey_SaveStateSlot1_Name, SettingsID::Input_Hotkey_SaveStateSlot1_InputType, SettingsID::Input_Hotkey_SaveStateSlot1_Data, SettingsID::Input_Hotkey_SaveStateSlot1_ExtraData); load_inputmapping_settings(&profile->Hotkey_SaveStateSlot2, section, SettingsID::Input_Hotkey_SaveStateSlot2_Name, SettingsID::Input_Hotkey_SaveStateSlot2_InputType, SettingsID::Input_Hotkey_SaveStateSlot2_Data, SettingsID::Input_Hotkey_SaveStateSlot2_ExtraData); load_inputmapping_settings(&profile->Hotkey_SaveStateSlot3, section, SettingsID::Input_Hotkey_SaveStateSlot3_Name, SettingsID::Input_Hotkey_SaveStateSlot3_InputType, SettingsID::Input_Hotkey_SaveStateSlot3_Data, SettingsID::Input_Hotkey_SaveStateSlot3_ExtraData); load_inputmapping_settings(&profile->Hotkey_SaveStateSlot4, section, SettingsID::Input_Hotkey_SaveStateSlot4_Name, SettingsID::Input_Hotkey_SaveStateSlot4_InputType, SettingsID::Input_Hotkey_SaveStateSlot4_Data, SettingsID::Input_Hotkey_SaveStateSlot4_ExtraData); load_inputmapping_settings(&profile->Hotkey_SaveStateSlot5, section, SettingsID::Input_Hotkey_SaveStateSlot5_Name, SettingsID::Input_Hotkey_SaveStateSlot5_InputType, SettingsID::Input_Hotkey_SaveStateSlot5_Data, SettingsID::Input_Hotkey_SaveStateSlot5_ExtraData); load_inputmapping_settings(&profile->Hotkey_SaveStateSlot6, section, SettingsID::Input_Hotkey_SaveStateSlot6_Name, SettingsID::Input_Hotkey_SaveStateSlot6_InputType, SettingsID::Input_Hotkey_SaveStateSlot6_Data, SettingsID::Input_Hotkey_SaveStateSlot6_ExtraData); load_inputmapping_settings(&profile->Hotkey_SaveStateSlot7, section, SettingsID::Input_Hotkey_SaveStateSlot7_Name, SettingsID::Input_Hotkey_SaveStateSlot7_InputType, SettingsID::Input_Hotkey_SaveStateSlot7_Data, SettingsID::Input_Hotkey_SaveStateSlot7_ExtraData); load_inputmapping_settings(&profile->Hotkey_SaveStateSlot8, section, SettingsID::Input_Hotkey_SaveStateSlot8_Name, SettingsID::Input_Hotkey_SaveStateSlot8_InputType, SettingsID::Input_Hotkey_SaveStateSlot8_Data, SettingsID::Input_Hotkey_SaveStateSlot8_ExtraData); load_inputmapping_settings(&profile->Hotkey_SaveStateSlot9, section, SettingsID::Input_Hotkey_SaveStateSlot9_Name, SettingsID::Input_Hotkey_SaveStateSlot9_InputType, SettingsID::Input_Hotkey_SaveStateSlot9_Data, SettingsID::Input_Hotkey_SaveStateSlot9_ExtraData); load_inputmapping_settings(&profile->Hotkey_IncreaseSaveStateSlot, section, SettingsID::Input_Hotkey_IncreaseSaveStateSlot_Name, SettingsID::Input_Hotkey_IncreaseSaveStateSlot_InputType, SettingsID::Input_Hotkey_IncreaseSaveStateSlot_Data, SettingsID::Input_Hotkey_IncreaseSaveStateSlot_ExtraData); load_inputmapping_settings(&profile->Hotkey_DecreaseSaveStateSlot, section, SettingsID::Input_Hotkey_DecreaseSaveStateSlot_Name, SettingsID::Input_Hotkey_DecreaseSaveStateSlot_InputType, SettingsID::Input_Hotkey_DecreaseSaveStateSlot_Data, SettingsID::Input_Hotkey_DecreaseSaveStateSlot_ExtraData); load_inputmapping_settings(&profile->Hotkey_MemoryPak, section, SettingsID::Input_Hotkey_MemoryPak_Name, SettingsID::Input_Hotkey_MemoryPak_InputType, SettingsID::Input_Hotkey_MemoryPak_Data, SettingsID::Input_Hotkey_MemoryPak_ExtraData); load_inputmapping_settings(&profile->Hotkey_RumblePak, section, SettingsID::Input_Hotkey_RumblePak_Name, SettingsID::Input_Hotkey_RumblePak_InputType, SettingsID::Input_Hotkey_RumblePak_Data, SettingsID::Input_Hotkey_RumblePak_ExtraData); load_inputmapping_settings(&profile->Hotkey_NoPak, section, SettingsID::Input_Hotkey_NoPak_Name, SettingsID::Input_Hotkey_NoPak_InputType, SettingsID::Input_Hotkey_NoPak_Data, SettingsID::Input_Hotkey_NoPak_ExtraData); load_inputmapping_settings(&profile->Hotkey_Fullscreen, section, SettingsID::Input_Hotkey_Fullscreen_Name, SettingsID::Input_Hotkey_Fullscreen_InputType, SettingsID::Input_Hotkey_Fullscreen_Data, SettingsID::Input_Hotkey_Fullscreen_ExtraData); } } static void apply_controller_profiles(void) { // only apply profile when // we have the control info if (!l_HasControlInfo) { return; } for (int i = 0; i < NUM_CONTROLLERS; i++) { InputProfile* profile = &l_InputProfiles[i]; int plugin = PLUGIN_NONE; bool emulateVRU = (profile->DeviceNum == (int)InputDeviceType::EmulateVRU); switch (profile->ControllerPak) { case N64ControllerPak::MemoryPak: plugin = PLUGIN_MEMPAK; break; case N64ControllerPak::RumblePak: plugin = PLUGIN_RAW; break; case N64ControllerPak::TransferPak: plugin = PLUGIN_TRANSFER_PAK; break; default: plugin = PLUGIN_NONE; break; } #ifdef VRU // attempt to try initializing VRU when needed, // if it fails, unplug the VRU if (emulateVRU && !IsVRUInit() && !InitVRU()) { profile->PluggedIn = false; } #else // always unplug VRU when RMG-Input was built // without VRU support if (emulateVRU) { profile->PluggedIn = false; } #endif // VRU l_ControlInfo.Controls[i].Present = profile->PluggedIn ? 1 : 0; l_ControlInfo.Controls[i].Plugin = emulateVRU ? PLUGIN_NONE : plugin; l_ControlInfo.Controls[i].RawData = 0; l_ControlInfo.Controls[i].Type = emulateVRU ? CONT_TYPE_VRU : CONT_TYPE_STANDARD; } } static void switch_controller_pak(InputProfile* profile, const int Control, const int pak) { const int currentPak = l_ControlInfo.Controls[Control].Plugin; // we don't need to do anything, // when the current pak matches if (currentPak == pak) { return; } l_ControlInfo.Controls[Control].Plugin = pak; // when switching from rumble pak to a different pak, // ensure that we stop the controller's rumble if (currentPak == PLUGIN_RAW) { profile->InputDevice.StopRumble(); } } static void apply_gameboy_settings(void) { SettingsID gameBoyRomSettings[] = { SettingsID::Core_Gameboy_P1_Rom, SettingsID::Core_Gameboy_P2_Rom, SettingsID::Core_Gameboy_P3_Rom, SettingsID::Core_Gameboy_P4_Rom, }; SettingsID gameBoySaveSettings[] = { SettingsID::Core_Gameboy_P1_Save, SettingsID::Core_Gameboy_P2_Save, SettingsID::Core_Gameboy_P3_Save, SettingsID::Core_Gameboy_P4_Save, }; for (int i = 0; i < NUM_CONTROLLERS; i++) { InputProfile* profile = &l_InputProfiles[i]; CoreSettingsSetValue(gameBoyRomSettings[i], profile->GameboyRom); CoreSettingsSetValue(gameBoySaveSettings[i], profile->GameboySave); } CoreSettingsSave(); } static void setup_device_automatic(int num, InputProfile* profile) { static int previousSdlDeviceNum = -1; // reset variable when needed if (num == 0) { previousSdlDeviceNum = -1; } bool foundSdlDevice = false; SDL_GameController* sdlGameController = nullptr; SDL_Joystick* sdlJoystick = nullptr; std::string sdlDeviceName; for (int i = (previousSdlDeviceNum + 1); i < SDL_NumJoysticks(); i++) { if (SDL_IsGameController(i)) { sdlGameController = SDL_GameControllerOpen(i); if (sdlGameController != nullptr) { sdlDeviceName = SDL_GameControllerName(sdlGameController); SDL_GameControllerClose(sdlGameController); profile->DeviceNum = i; profile->DeviceName = sdlDeviceName; previousSdlDeviceNum = i; foundSdlDevice = true; break; } } else { sdlJoystick = SDL_JoystickOpen(i); if (sdlJoystick != nullptr) { sdlDeviceName = SDL_JoystickName(sdlJoystick); SDL_JoystickClose(sdlJoystick); profile->DeviceNum = i; profile->DeviceName = sdlDeviceName; previousSdlDeviceNum = i; foundSdlDevice = true; break; } } } if (!foundSdlDevice) { // fallback to keyboard if (num == 0) { profile->DeviceNum = (int)InputDeviceType::Keyboard; } else { profile->PluggedIn = false; // only override present in core // when we have the control info if (l_HasControlInfo) { l_ControlInfo.Controls[num].Present = 0; } } } } static void open_controllers(void) { // force re-fresh joystick list SDL_JoystickUpdate(); for (int i = 0; i < NUM_CONTROLLERS; i++) { InputProfile* profile = &l_InputProfiles[i]; profile->InputDevice.CloseDevice(); if (profile->DeviceNum == (int)InputDeviceType::Automatic) { setup_device_automatic(i, profile); } if (profile->DeviceNum != (int)InputDeviceType::Keyboard) { profile->InputDevice.OpenDevice(profile->DeviceName, profile->DeviceNum); } } } static void close_controllers(void) { for (int i = 0; i < NUM_CONTROLLERS; i++) { InputProfile* profile = &l_InputProfiles[i]; profile->InputDevice.CloseDevice(); } } static int get_button_state(InputProfile* profile, const InputMapping* inputMapping, const bool allPressed = false) { int state = 0; for (int i = 0; i < inputMapping->Count; i++) { const int data = inputMapping->Data.at(i); const int extraData = inputMapping->ExtraData.at(i); switch ((InputType)inputMapping->Type[i]) { case InputType::GamepadButton: { if (allPressed && i > 0) { state &= SDL_GameControllerGetButton(profile->InputDevice.GetGameControllerHandle(), (SDL_GameControllerButton)data); } else { state |= SDL_GameControllerGetButton(profile->InputDevice.GetGameControllerHandle(), (SDL_GameControllerButton)data); } } break; case InputType::GamepadAxis: { int axis_value = SDL_GameControllerGetAxis(profile->InputDevice.GetGameControllerHandle(), (SDL_GameControllerAxis)data); if (allPressed && i > 0) { state &= (abs(axis_value) >= (SDL_AXIS_PEAK / 2) && (extraData ? axis_value > 0 : axis_value < 0)) ? 1 : 0; } else { state |= (abs(axis_value) >= (SDL_AXIS_PEAK / 2) && (extraData ? axis_value > 0 : axis_value < 0)) ? 1 : 0; } } break; case InputType::JoystickButton: { if (allPressed && i > 0) { state &= SDL_JoystickGetButton(profile->InputDevice.GetJoystickHandle(), data); } else { state |= SDL_JoystickGetButton(profile->InputDevice.GetJoystickHandle(), data); } } break; case InputType::JoystickHat: { if (allPressed && i > 0) { state &= (SDL_JoystickGetHat(profile->InputDevice.GetJoystickHandle(), data) & extraData) ? 1 : 0; } else { state |= (SDL_JoystickGetHat(profile->InputDevice.GetJoystickHandle(), data) & extraData) ? 1 : 0; } } break; case InputType::JoystickAxis: { int axis_value = SDL_JoystickGetAxis(profile->InputDevice.GetJoystickHandle(), data); if (allPressed && i > 0) { state &= (abs(axis_value) >= (SDL_AXIS_PEAK / 2) && (extraData ? axis_value > 0 : axis_value < 0)) ? 1 : 0; } else { state |= (abs(axis_value) >= (SDL_AXIS_PEAK / 2) && (extraData ? axis_value > 0 : axis_value < 0)) ? 1 : 0; } } break; case InputType::Keyboard: { if (allPressed && i > 0) { state &= l_KeyboardState[data] ? 1 : 0; } else { state |= l_KeyboardState[data] ? 1 : 0; } } break; default: break; } // early return when needed if (allPressed && !state) { return state; } } return state; } // returns axis input scaled to the range [-1, 1] static double get_axis_state(InputProfile* profile, const InputMapping* inputMapping, const int direction, const double value, bool& useButtonMapping) { double axis_state = value; bool button_state = false; for (int i = 0; i < inputMapping->Count; i++) { const int data = inputMapping->Data.at(i); const int extraData = inputMapping->ExtraData.at(i); switch ((InputType)inputMapping->Type[i]) { case InputType::GamepadButton: { button_state |= SDL_GameControllerGetButton(profile->InputDevice.GetGameControllerHandle(), (SDL_GameControllerButton)data);; } break; case InputType::GamepadAxis: { double axis_value = SDL_GameControllerGetAxis(profile->InputDevice.GetGameControllerHandle(), (SDL_GameControllerAxis)data); if (axis_value < -32767.0) axis_value = -32767.0; if (extraData ? axis_value > 0 : axis_value < 0) { axis_value = (axis_value / SDL_AXIS_PEAK); axis_value = abs(axis_value) * direction; axis_state = axis_value; } } break; case InputType::JoystickButton: { button_state |= SDL_JoystickGetButton(profile->InputDevice.GetJoystickHandle(), data);; } break; case InputType::JoystickHat: { button_state |= (SDL_JoystickGetHat(profile->InputDevice.GetJoystickHandle(), data) & extraData) ? 1 : 0; } break; case InputType::JoystickAxis: { double axis_value = SDL_JoystickGetAxis(profile->InputDevice.GetJoystickHandle(), data); if (axis_value < -32767.0) axis_value = -32767.0; if (extraData ? axis_value > 0 : axis_value < 0) { axis_value = (axis_value / SDL_AXIS_PEAK); axis_value = abs(axis_value) * direction; axis_state = axis_value; } } break; case InputType::Keyboard: { button_state |= l_KeyboardState[data]; } break; default: break; } } // when a button has been mapped // to an axis, we should prioritize // the button when it's been pressed if (button_state) { useButtonMapping = true; return direction; } else if (!useButtonMapping) { return axis_state; } else { return value; } } // maps a value in one range to a value in another static double map_range_to_range(const double value, const double fromLower, const double fromUpper, const double toLower, const double toUpper) { const double fromDelta = fromUpper - fromLower; const double toDelta = toUpper - toLower; const double toUnitsPerFromUnit = toDelta / fromDelta; const double fromUnits = value - fromLower; return toLower + fromUnits * toUnitsPerFromUnit; } // applies square deadzone, then scales result such that the edge of the deadzone is 0 static double apply_deadzone(const double input, const double deadzone) { const double inputAbsolute = std::abs(input); if (inputAbsolute <= deadzone) { return 0; } return std::copysign(map_range_to_range(inputAbsolute, deadzone, 1.0, 0.0, 1.0), input); } // Credit: MerryMage, fzurita & kev4cards static void simulate_octagon(const double deadzone, const double inputX, const double inputY, int& outputX, int& outputY) { const double maxAxis = N64_AXIS_PEAK; const double maxDiagonal = MAX_DIAGONAL_VALUE; const double maxInputRadius = sqrt(2) * (maxDiagonal + deadzone * (maxAxis - maxDiagonal)); // scale to [-maxInputRadius, maxInputRadius] double ax = inputX * maxInputRadius; double ay = inputY * maxInputRadius; // check whether (ax, ay) is within the circle of radius maxInputRadius double distance = std::hypot(ax, ay); if (distance > maxInputRadius) { // scale ax and ay to stay on the same line, but at the edge of the circle const double scale = maxInputRadius / distance; ax *= scale; ay *= scale; } // bound diagonals to an octagonal range [-maxDiagonal, maxDiagonal] if (ax != 0.0 && ay != 0.0) { const double slope = ay / ax; double edgex = std::copysign(maxAxis / (std::abs(slope) + (maxAxis - maxDiagonal) / maxDiagonal), ax); const double edgey = std::copysign(std::min(std::abs(edgex * slope), maxAxis / (1.0 / std::abs(slope) + (maxAxis - maxDiagonal) / maxDiagonal)), ay); edgex = edgey / slope; distance = std::hypot(ax, ay); const double distanceToOctagonalEdge = std::hypot(edgex, edgey); if (distance > distanceToOctagonalEdge) { ax = edgex; ay = edgey; } } // keep cardinal input within positive and negative bounds of maxAxis if (std::abs(ax) > maxAxis) ax = std::copysign(maxAxis, ax); if (std::abs(ay) > maxAxis) ay = std::copysign(maxAxis, ay); outputX = (int)ax; outputY = (int)ay; } static unsigned char data_crc(unsigned char *data, int length) { unsigned char remainder = data[0]; int byte = 1; unsigned char bit = 0; while (byte <= length) { int highBit = ((remainder & 0x80) != 0); remainder = remainder << 1; remainder += (byte < length && data[byte] & (0x80 >> bit )) ? 1 : 0; remainder ^= (highBit) ? 0x85 : 0; bit++; byte += bit/8; bit %= 8; } return remainder; } static bool check_hotkeys(int Control) { InputProfile* profile = &l_InputProfiles[Control]; int state = 0; // we only have to check for hotkeys // when there's a controller opened if (!profile->InputDevice.HasOpenDevice()) { return false; } #define DEFINE_HOTKEY(mapping, pressed, function, function2) \ state = get_button_state(profile, &profile->mapping, true); \ if (state) \ { \ if (!profile->pressed) \ { \ profile->pressed = true; \ function; \ } \ return true; \ } \ else if (!state && profile->pressed) \ { \ function2; \ profile->pressed = false; \ } DEFINE_HOTKEY(Hotkey_Shutdown, Hotkey_Shutdown_Pressed, CoreStopEmulation(), ); DEFINE_HOTKEY(Hotkey_Exit, Hotkey_Exit_Pressed, QGuiApplication::quit(), ); DEFINE_HOTKEY(Hotkey_SoftReset, Hotkey_SoftReset_Pressed, CoreResetEmulation(false), ); DEFINE_HOTKEY(Hotkey_Resume, Hotkey_Resume_Pressed, CoreIsEmulationPaused() ? CoreResumeEmulation() : CorePauseEmulation(), ); DEFINE_HOTKEY(Hotkey_Screenshot, Hotkey_Screenshot_Pressed, CoreTakeScreenshot(), ); DEFINE_HOTKEY(Hotkey_LimitFPS, Hotkey_LimitFPS_Pressed, CoreSetSpeedLimiterState(!CoreIsSpeedLimiterEnabled()), ); DEFINE_HOTKEY(Hotkey_SpeedFactor25, Hotkey_SpeedFactor_Pressed, CoreSetSpeedFactor(25), ); DEFINE_HOTKEY(Hotkey_SpeedFactor50, Hotkey_SpeedFactor_Pressed, CoreSetSpeedFactor(50), ); DEFINE_HOTKEY(Hotkey_SpeedFactor75, Hotkey_SpeedFactor_Pressed, CoreSetSpeedFactor(75), ); DEFINE_HOTKEY(Hotkey_SpeedFactor100, Hotkey_SpeedFactor_Pressed, CoreSetSpeedFactor(100), ); DEFINE_HOTKEY(Hotkey_SpeedFactor125, Hotkey_SpeedFactor_Pressed, CoreSetSpeedFactor(125), ); DEFINE_HOTKEY(Hotkey_SpeedFactor150, Hotkey_SpeedFactor_Pressed, CoreSetSpeedFactor(150), ); DEFINE_HOTKEY(Hotkey_SpeedFactor175, Hotkey_SpeedFactor_Pressed, CoreSetSpeedFactor(175), ); DEFINE_HOTKEY(Hotkey_SpeedFactor200, Hotkey_SpeedFactor_Pressed, CoreSetSpeedFactor(200), ); DEFINE_HOTKEY(Hotkey_SpeedFactor225, Hotkey_SpeedFactor_Pressed, CoreSetSpeedFactor(225), ); DEFINE_HOTKEY(Hotkey_SpeedFactor250, Hotkey_SpeedFactor_Pressed, CoreSetSpeedFactor(250), ); DEFINE_HOTKEY(Hotkey_SpeedFactor275, Hotkey_SpeedFactor_Pressed, CoreSetSpeedFactor(275), ); DEFINE_HOTKEY(Hotkey_SpeedFactor300, Hotkey_SpeedFactor_Pressed, CoreSetSpeedFactor(300), ); DEFINE_HOTKEY(Hotkey_SaveState, Hotkey_SaveState_Pressed, CoreSaveState(), ); DEFINE_HOTKEY(Hotkey_LoadState, Hotkey_LoadState_Pressed, CoreLoadSaveState(), ); DEFINE_HOTKEY(Hotkey_GSButton, Hotkey_GSButton_Pressed, CorePressGamesharkButton(true), CorePressGamesharkButton(false)); DEFINE_HOTKEY(Hotkey_SaveStateSlot0, Hotkey_SaveStateSlot_Pressed, CoreSetSaveStateSlot(0), ); DEFINE_HOTKEY(Hotkey_SaveStateSlot1, Hotkey_SaveStateSlot_Pressed, CoreSetSaveStateSlot(1), ); DEFINE_HOTKEY(Hotkey_SaveStateSlot2, Hotkey_SaveStateSlot_Pressed, CoreSetSaveStateSlot(2), ); DEFINE_HOTKEY(Hotkey_SaveStateSlot3, Hotkey_SaveStateSlot_Pressed, CoreSetSaveStateSlot(3), ); DEFINE_HOTKEY(Hotkey_SaveStateSlot4, Hotkey_SaveStateSlot_Pressed, CoreSetSaveStateSlot(4), ); DEFINE_HOTKEY(Hotkey_SaveStateSlot5, Hotkey_SaveStateSlot_Pressed, CoreSetSaveStateSlot(5), ); DEFINE_HOTKEY(Hotkey_SaveStateSlot6, Hotkey_SaveStateSlot_Pressed, CoreSetSaveStateSlot(6), ); DEFINE_HOTKEY(Hotkey_SaveStateSlot7, Hotkey_SaveStateSlot_Pressed, CoreSetSaveStateSlot(7), ); DEFINE_HOTKEY(Hotkey_SaveStateSlot8, Hotkey_SaveStateSlot_Pressed, CoreSetSaveStateSlot(8), ); DEFINE_HOTKEY(Hotkey_SaveStateSlot9, Hotkey_SaveStateSlot_Pressed, CoreSetSaveStateSlot(9), ); DEFINE_HOTKEY(Hotkey_IncreaseSaveStateSlot, Hotkey_IncreaseSaveStateSlot_Pressed, CoreIncreaseSaveStateSlot(), ); DEFINE_HOTKEY(Hotkey_DecreaseSaveStateSlot, Hotkey_DecreaseSaveStateSlot_Pressed, CoreDecreaseSaveStateSlot(), ); DEFINE_HOTKEY(Hotkey_MemoryPak, Hotkey_MemoryPak_Pressed, switch_controller_pak(profile, Control, PLUGIN_MEMPAK), ); DEFINE_HOTKEY(Hotkey_RumblePak, Hotkey_RumblePak_Pressed, switch_controller_pak(profile, Control, PLUGIN_RAW), ); DEFINE_HOTKEY(Hotkey_NoPak, Hotkey_NoPak_Pressed, switch_controller_pak(profile, Control, PLUGIN_NONE), ); DEFINE_HOTKEY(Hotkey_Fullscreen, Hotkey_Fullscreen_Pressed, CoreToggleFullscreen(), ); #undef DEFINE_HOTKEY return false; } static void sdl_init() { std::filesystem::path gameControllerDbPath; std::string debugMessage; int ret = -1; for (const int subsystem : {SDL_INIT_GAMECONTROLLER, SDL_INIT_AUDIO, SDL_INIT_VIDEO, SDL_INIT_HAPTIC}) { if (!SDL_WasInit(subsystem)) { SDL_InitSubSystem(subsystem); } } gameControllerDbPath = CoreGetSharedDataDirectory(); gameControllerDbPath += "/gamecontrollerdb.txt"; // try to load SDL_GameControllerDB when the file exists if (std::filesystem::is_regular_file(gameControllerDbPath)) { ret = SDL_GameControllerAddMappingsFromFile(gameControllerDbPath.string().c_str()); if (ret == -1) { debugMessage = "sdl_init(): SDL_GameControllerAddMappingsFromFile Failed: "; debugMessage += SDL_GetError(); PluginDebugMessage(M64MSG_WARNING, debugMessage); } } else { debugMessage = "sdl_init(): Cannot find SDL_GameControllerDB at \""; debugMessage += gameControllerDbPath.string().c_str(); debugMessage += "\"!"; PluginDebugMessage(M64MSG_WARNING, debugMessage); } } static void sdl_quit() { for (const int subsystem : {SDL_INIT_GAMECONTROLLER, SDL_INIT_HAPTIC}) { if (SDL_WasInit(subsystem)) { SDL_QuitSubSystem(subsystem); } } } // // Basic Plugin Functions // EXPORT m64p_error CALL PluginStartup(m64p_dynlib_handle CoreLibHandle, void *Context, void (*DebugCallback)(void *, int, const char *)) { if (l_SDLThread != nullptr) { return M64ERR_ALREADY_INIT; } if (!CoreInit(CoreLibHandle)) { return M64ERR_SYSTEM_FAIL; } // setup debug callback l_DebugCallback = DebugCallback; l_DebugCallContext = Context; sdl_init(); l_SDLThread = new Thread::SDLThread(nullptr); l_SDLThread->start(); for (int i = 0; i < NUM_CONTROLLERS; i++) { l_InputProfiles[i].InputDevice.SetSDLThread(l_SDLThread); } l_HotkeysThread = new Thread::HotkeysThread(check_hotkeys, nullptr); l_HotkeysThread->start(); load_settings(); return M64ERR_SUCCESS; } EXPORT m64p_error CALL PluginShutdown(void) { if (l_SDLThread == nullptr) { return M64ERR_NOT_INIT; } close_controllers(); l_SDLThread->StopLoop(); l_SDLThread->deleteLater(); l_SDLThread = nullptr; l_HotkeysThread->StopLoop(); l_HotkeysThread->deleteLater(); l_HotkeysThread = nullptr; sdl_quit(); // clear debug callback l_DebugCallback = nullptr; l_DebugCallContext = nullptr; return M64ERR_SUCCESS; } EXPORT m64p_error CALL PluginGetVersion(m64p_plugin_type *pluginType, int *pluginVersion, int *apiVersion, const char **pluginNamePtr, int *capabilities) { if (pluginType != nullptr) { *pluginType = M64PLUGIN_INPUT; } if (pluginVersion != nullptr) { *pluginVersion = 0x010000; } if (apiVersion != nullptr) { *apiVersion = INPUT_PLUGIN_API_VERSION; } if (pluginNamePtr != nullptr) { *pluginNamePtr = "Rosalie's Mupen GUI - Input Plugin"; } if (capabilities != nullptr) { *capabilities = 0; } return M64ERR_SUCCESS; } // // Custom Internal Plugin Functions // void PluginDebugMessage(int level, std::string message) { if (l_DebugCallback == nullptr) { return; } l_DebugCallback(l_DebugCallContext, level, message.c_str()); } // // Custom Plugin Functions // EXPORT m64p_error CALL PluginConfig2(int romConfig) { if (l_SDLThread == nullptr) { return M64ERR_NOT_INIT; } // close controllers close_controllers(); l_SDLThread->SetAction(SDLThreadAction::SDLPumpEvents); UserInterface::MainDialog dialog(nullptr, l_SDLThread, romConfig); dialog.exec(); // when PluginShutdown() is called during PluginConfig2(), // we shouldn't do anything here anymore because // l_SDLThread will be nullptr, causing a crash if (l_SDLThread == nullptr) { return M64ERR_SUCCESS; } l_SDLThread->SetAction(SDLThreadAction::None); // wait until it's done pumping events while (l_SDLThread->GetCurrentAction() == SDLThreadAction::SDLPumpEvents) { QThread::msleep(5); } // reload settings load_settings(); // apply profiles apply_controller_profiles(); // apply gameboy settings apply_gameboy_settings(); // open controllers open_controllers(); return M64ERR_SUCCESS; } EXPORT int CALL PluginConfig2HasRomConfig(void) { return 1; } // // Input Plugin Functions // EXPORT void CALL ControllerCommand(int Control, unsigned char* Command) { unsigned char* data = &Command[5]; if (Control == -1) { return; } InputProfile* profile = &l_InputProfiles[Control]; switch (Command[2]) { case RD_GETSTATUS: case RD_READKEYS: break; case RD_READPAK: if (l_ControlInfo.Controls[Control].Plugin == PLUGIN_RAW) { unsigned int dwAddress = (Command[3] << 8) + (Command[4] & 0xE0); if ((dwAddress >= 0x8000 ) && (dwAddress < 0x9000 )) { memset(data, 0x80, 32); } else { memset(data, 0x00, 32); } data[32] = data_crc(data, 32); } break; case RD_WRITEPAK: if (l_ControlInfo.Controls[Control].Plugin == PLUGIN_RAW) { unsigned int dwAddress = (Command[3] << 8) + (Command[4] & 0xE0); if (dwAddress == PAK_IO_RUMBLE) { if (*data) { profile->InputDevice.StartRumble(); } else { profile->InputDevice.StopRumble(); } } data[32] = data_crc( data, 32 ); } break; case RD_RESETCONTROLLER: case RD_READEEPROM: case RD_WRITEEPROM: break; } } EXPORT void CALL GetKeys(int Control, BUTTONS* Keys) { InputProfile* profile = &l_InputProfiles[Control]; if (!profile->PluggedIn) { return; } #ifdef VRU // when we're emulating the VRU, // we need to check the mic state if (profile->DeviceNum == (int)InputDeviceType::EmulateVRU) { if (GetVRUMicState()) { Keys->Value = 0x0020; } else { Keys->Value = 0x0000; } return; } #endif // VRU // check if device has been disconnected, // if it has, try to open it again, // only do this every 2 seconds to prevent lag const auto currentTime = std::chrono::high_resolution_clock::now(); const int secondsPassed = std::chrono::duration_cast(currentTime - profile->LastDeviceCheckTime).count(); if (secondsPassed >= 2) { profile->LastDeviceCheckTime = currentTime; if (profile->DeviceNum != (int)InputDeviceType::Keyboard) { if (profile->InputDevice.IsOpeningDevice()) { return; } if (!profile->InputDevice.HasOpenDevice() || !profile->InputDevice.IsAttached()) { profile->InputDevice.OpenDevice(profile->DeviceName, profile->DeviceNum); } } } // when we've matched a hotkey, // we don't need to check anything // else if (check_hotkeys(Control)) { return; } Keys->A_BUTTON = get_button_state(profile, &profile->Button_A); Keys->B_BUTTON = get_button_state(profile, &profile->Button_B); Keys->START_BUTTON = get_button_state(profile, &profile->Button_Start); Keys->U_DPAD = get_button_state(profile, &profile->Button_DpadUp); Keys->D_DPAD = get_button_state(profile, &profile->Button_DpadDown); Keys->L_DPAD = get_button_state(profile, &profile->Button_DpadLeft); Keys->R_DPAD = get_button_state(profile, &profile->Button_DpadRight); Keys->U_CBUTTON = get_button_state(profile, &profile->Button_CButtonUp); Keys->D_CBUTTON = get_button_state(profile, &profile->Button_CButtonDown); Keys->L_CBUTTON = get_button_state(profile, &profile->Button_CButtonLeft); Keys->R_CBUTTON = get_button_state(profile, &profile->Button_CButtonRight); Keys->L_TRIG = get_button_state(profile, &profile->Button_LeftTrigger); Keys->R_TRIG = get_button_state(profile, &profile->Button_RightTrigger); Keys->Z_TRIG = get_button_state(profile, &profile->Button_ZTrigger); double inputX = 0, inputY = 0; bool useButtonMapping = false; inputY = get_axis_state(profile, &profile->AnalogStick_Up, 1, inputY, useButtonMapping); inputY = get_axis_state(profile, &profile->AnalogStick_Down, -1, inputY, useButtonMapping); inputX = get_axis_state(profile, &profile->AnalogStick_Left, -1, inputX, useButtonMapping); inputX = get_axis_state(profile, &profile->AnalogStick_Right, 1, inputX, useButtonMapping); // take deadzone into account const double deadzone = profile->DeadzoneValue / 100.0; inputX = apply_deadzone(inputX, deadzone); inputY = apply_deadzone(inputY, deadzone); // take sensitivity into account const double sensitivityRatio = profile->SensitivityValue / 100.0; const double lowerInputLimit = std::max(-1.0, -sensitivityRatio); const double upperInputLimit = std::min(1.0, sensitivityRatio); inputX = std::clamp(inputX * sensitivityRatio, lowerInputLimit, upperInputLimit); inputY = std::clamp(inputY * sensitivityRatio, lowerInputLimit, upperInputLimit); int octagonX = 0, octagonY = 0; simulate_octagon( deadzone, // deadzone inputX, // inputX inputY, // inputY octagonX, // outputX octagonY // outputY ); Keys->X_AXIS = octagonX; Keys->Y_AXIS = octagonY; } EXPORT void CALL InitiateControllers(CONTROL_INFO ControlInfo) { for (int i = 0; i < SDL_NUM_SCANCODES; i++) { l_KeyboardState[i] = 0; } l_ControlInfo = ControlInfo; l_HasControlInfo = true; // load settings load_settings(); // apply profiles apply_controller_profiles(); // apply gameboy settings apply_gameboy_settings(); // open controllers open_controllers(); } EXPORT void CALL ReadController(int Control, unsigned char *Command) { } EXPORT int CALL RomOpen(void) { l_HotkeysThread->SetState(HotkeysThreadState::RomOpened); return 1; } EXPORT void CALL RomClosed(void) { l_HotkeysThread->SetState(HotkeysThreadState::RomClosed); l_HasControlInfo = false; close_controllers(); #ifdef VRU QuitVRU(); #endif // VRU } EXPORT void CALL SDL_KeyDown(int keymod, int keysym) { l_KeyboardState[keysym] = true; } EXPORT void CALL SDL_KeyUp(int keymod, int keysym) { l_KeyboardState[keysym] = false; }