From 44aaf108d132e7b961db8600c16d19d6d3f37eb4 Mon Sep 17 00:00:00 2001 From: Bonta <40473493+Bonta0@users.noreply.github.com> Date: Sun, 4 Jul 2021 12:41:46 +0200 Subject: [PATCH 01/18] Externals: Integrate mGBA as a submodule --- .gitignore | 1 + .gitmodules | 5 + CMakeLists.txt | 9 + Externals/ExternalsReferenceAll.props | 3 + Externals/licenses.md | 2 + Externals/mGBA/CMakeLists.txt | 13 + Externals/mGBA/make_version.c.js | 140 +++++++++ Externals/mGBA/mgba | 1 + Externals/mGBA/mgba.vcxproj | 241 +++++++++++++++ Externals/mGBA/mgba.vcxproj.filters | 427 ++++++++++++++++++++++++++ Source/Core/Common/SCMRevGen.vcxproj | 4 - Source/Core/Core/CMakeLists.txt | 5 + Source/VSProps/Base.Macros.props | 2 + Source/VSProps/Base.props | 2 + Source/dolphin-emu.sln | 11 + 15 files changed, 862 insertions(+), 4 deletions(-) create mode 100644 Externals/mGBA/CMakeLists.txt create mode 100644 Externals/mGBA/make_version.c.js create mode 160000 Externals/mGBA/mgba create mode 100644 Externals/mGBA/mgba.vcxproj create mode 100644 Externals/mGBA/mgba.vcxproj.filters diff --git a/.gitignore b/.gitignore index deef6c5b0a..cebabd2c39 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ Thumbs.db # Ignore Finder view option files created by OS X .DS_Store # Ignore autogenerated source files +Externals/mGBA/version.c Source/Core/Common/scmrev.h # Ignore files output by build /[Bb]uild*/ diff --git a/.gitmodules b/.gitmodules index 7082458311..3459dd91fb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,3 +3,8 @@ url = https://github.com/dolphin-emu/ext-win-qt.git branch = master shallow = true +[submodule "Externals/mGBA/mgba"] + path = Externals/mGBA/mgba + url = https://github.com/mgba-emu/mgba.git + branch = master + shallow = true diff --git a/CMakeLists.txt b/CMakeLists.txt index a480467b3e..d446e0b465 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,7 @@ option(ENABLE_LLVM "Enables LLVM support, for disassembly" ON) option(ENABLE_TESTS "Enables building the unit tests" ON) option(ENABLE_VULKAN "Enables vulkan video backend" ON) option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence, show the current game on Discord" ON) +option(USE_MGBA "Enables GBA controllers emulation using libmgba" ON) # Maintainers: if you consider blanket disabling this for your users, please # consider the following points: @@ -826,6 +827,14 @@ if(USE_DISCORD_PRESENCE) include_directories(Externals/discord-rpc/include) endif() +if(NOT ENABLE_QT) + set(USE_MGBA 0) +endif() +if(USE_MGBA) + message(STATUS "Using static libmgba from Externals") + add_subdirectory(Externals/mGBA) +endif() + find_package(SYSTEMD) if(SYSTEMD_FOUND) message(STATUS "libsystemd found, enabling traversal server watchdog support") diff --git a/Externals/ExternalsReferenceAll.props b/Externals/ExternalsReferenceAll.props index 0d7de6fecd..38ec0bd827 100644 --- a/Externals/ExternalsReferenceAll.props +++ b/Externals/ExternalsReferenceAll.props @@ -58,6 +58,9 @@ {bdb6578b-0691-4e80-a46c-df21639fd3b8} + + {864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6} + {31643fdb-1bb8-4965-9de7-000fc88d35ae} diff --git a/Externals/licenses.md b/Externals/licenses.md index f7b20041b9..716b794fb0 100644 --- a/Externals/licenses.md +++ b/Externals/licenses.md @@ -38,6 +38,8 @@ Dolphin includes or links code of the following third-party software projects: [University of Illinois/NCSA Open Source license](http://llvm.org/docs/DeveloperPolicy.html#license) - [LZO](http://www.oberhumer.com/opensource/lzo/): [GPLv2+](http://www.oberhumer.com/opensource/gpl.html) +- [mGBA](http://mgba.io) + [MPL 2.0](https://github.com/mgba-emu/mgba/blob/master/LICENSE) - [MiniUPnPc](http://miniupnp.free.fr/): [3-clause BSD](https://github.com/miniupnp/miniupnp/blob/master/miniupnpc/LICENSE) - [Microsoft Visual C++ Runtime Library](http://www.microsoft.com/en-us/download/details.aspx?id=40784): diff --git a/Externals/mGBA/CMakeLists.txt b/Externals/mGBA/CMakeLists.txt new file mode 100644 index 0000000000..4a87c7b58c --- /dev/null +++ b/Externals/mGBA/CMakeLists.txt @@ -0,0 +1,13 @@ +set(LIBMGBA_ONLY ON) +set(USE_LZMA ON) +add_subdirectory(mgba EXCLUDE_FROM_ALL) + +if(NOT MSVC) + target_compile_options(mgba PRIVATE -Wno-unused-parameter -Wno-unused-result -Wno-unused-variable) +endif() + +if(ANDROID) + target_compile_definitions(mgba PRIVATE -Dfutimes=futimens) +endif() + +add_library(mGBA::mgba ALIAS mgba) diff --git a/Externals/mGBA/make_version.c.js b/Externals/mGBA/make_version.c.js new file mode 100644 index 0000000000..d52d9e9a05 --- /dev/null +++ b/Externals/mGBA/make_version.c.js @@ -0,0 +1,140 @@ +var wshShell = new ActiveXObject("WScript.Shell") +var oFS = new ActiveXObject("Scripting.FileSystemObject"); + +wshShell.CurrentDirectory += "\\mgba"; +var outfile = "../version.c"; +var cmd_commit = " describe --always --abbrev=40 --dirty"; +var cmd_commit_short = " describe --always --dirty"; +var cmd_branch = " symbolic-ref --short HEAD"; +var cmd_rev = " rev-list HEAD --count"; +var cmd_tag = " describe --tag --exact-match"; + +function GetGitExe() +{ + try + { + gitexe = wshShell.RegRead("HKCU\\Software\\GitExtensions\\gitcommand"); + wshShell.Exec(gitexe); + return gitexe; + } + catch (e) + {} + + for (var gitexe in {"git.cmd":1, "git":1, "git.bat":1}) + { + try + { + wshShell.Exec(gitexe); + return gitexe; + } + catch (e) + {} + } + + // last try - msysgit not in path (vs2015 default) + msyspath = "\\Git\\cmd\\git.exe"; + gitexe = wshShell.ExpandEnvironmentStrings("%PROGRAMFILES(x86)%") + msyspath; + if (oFS.FileExists(gitexe)) { + return gitexe; + } + gitexe = wshShell.ExpandEnvironmentStrings("%PROGRAMFILES%") + msyspath; + if (oFS.FileExists(gitexe)) { + return gitexe; + } + + WScript.Echo("Cannot find git or git.cmd, check your PATH:\n" + + wshShell.ExpandEnvironmentStrings("%PATH%")); + WScript.Quit(1); +} + +function GetFirstStdOutLine(cmd) +{ + try + { + return wshShell.Exec(cmd).StdOut.ReadLine(); + } + catch (e) + { + // catch "the system cannot find the file specified" error + WScript.Echo("Failed to exec " + cmd + " this should never happen"); + WScript.Quit(1); + } +} + +function GetFileContents(f) +{ + try + { + return oFS.OpenTextFile(f).ReadAll(); + } + catch (e) + { + // file doesn't exist + return ""; + } +} + +// get version from version.cmake +var version_cmake = GetFileContents("version.cmake"); +var version_major = version_cmake.match(/set\(LIB_VERSION_MAJOR (.*)\)/)[1]; +var version_minor = version_cmake.match(/set\(LIB_VERSION_MINOR (.*)\)/)[1]; +var version_patch = version_cmake.match(/set\(LIB_VERSION_PATCH (.*)\)/)[1]; +var version_abi = version_cmake.match(/set\(LIB_VERSION_ABI (.*)\)/)[1]; +var version_string = version_major + "." + version_minor + "." + version_patch; + +// get info from git +var gitexe = GetGitExe(); +var commit = GetFirstStdOutLine(gitexe + cmd_commit); +var commit_short = GetFirstStdOutLine(gitexe + cmd_commit_short); +var branch = GetFirstStdOutLine(gitexe + cmd_branch); +var rev = GetFirstStdOutLine(gitexe + cmd_rev); +var tag = GetFirstStdOutLine(gitexe + cmd_tag); +var binary_name = "mgba"; +var project_name = "mGBA"; + +if (!rev) + rev = -1; + +if (tag) +{ + version_string = tag; +} +else if (branch) +{ + if (branch == "master") + version_string = rev + "-" + commit_short; + else + version_string = branch + "-" + rev + "-" + commit_short; + + if (branch != version_abi) + version_string = version_abi + "-" + version_string; +} + +if (!commit) + commit = "(unknown)"; +if (!commit_short) + commit_short = "(unknown)"; +if (!branch) + branch = "(unknown)"; + +var out_contents = + "#include \n" + + "MGBA_EXPORT const char* const gitCommit = \"" + commit + "\";\n" + + "MGBA_EXPORT const char* const gitCommitShort = \"" + commit_short + "\";\n" + + "MGBA_EXPORT const char* const gitBranch = \"" + branch + "\";\n" + + "MGBA_EXPORT const int gitRevision = " + rev + ";\n" + + "MGBA_EXPORT const char* const binaryName = \"" + binary_name + "\";\n" + + "MGBA_EXPORT const char* const projectName = \"" + project_name + "\";\n" + + "MGBA_EXPORT const char* const projectVersion = \"" + version_string + "\";\n"; + +// check if file needs updating +if (out_contents == GetFileContents(outfile)) +{ + WScript.Echo(project_name + ": " + outfile + " current at " + version_string); +} +else +{ + // needs updating - writeout current info + oFS.CreateTextFile(outfile, true).Write(out_contents); + WScript.Echo(project_name + ": " + outfile + " updated to " + version_string); +} diff --git a/Externals/mGBA/mgba b/Externals/mGBA/mgba new file mode 160000 index 0000000000..9cccc5197e --- /dev/null +++ b/Externals/mGBA/mgba @@ -0,0 +1 @@ +Subproject commit 9cccc5197ed73ba0a54f584d3121c27dc97405f5 diff --git a/Externals/mGBA/mgba.vcxproj b/Externals/mGBA/mgba.vcxproj new file mode 100644 index 0000000000..2338faa0d8 --- /dev/null +++ b/Externals/mGBA/mgba.vcxproj @@ -0,0 +1,241 @@ + + + + + + {864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6} + + + + + + + + + + + + + + mgba\include;mgba\src;mgba\src\third-party\lzma;%(AdditionalIncludeDirectories) + BUILD_STATIC;M_CORE_GB;M_CORE_GBA;USE_LZMA;_7ZIP_PPMD_SUPPPORT;HAVE_STRDUP;HAVE_SETLOCALE;HAVE_CHMOD;HAVE_UMASK;%(PreprocessorDefinitions) + + + "$(CScript)" /nologo /E:JScript "make_version.c.js" + + + + + + $(IntDir)/src/core/cache-set.c.obj + + + $(IntDir)/src/core/cheats.c.obj + + + + $(IntDir)/src/core/core.c.obj + + + + $(IntDir)/src/core/input.c.obj + + + + + $(IntDir)/src/core/lockstep.c.obj + + + + + + + + $(IntDir)/src/core/serialize.c.obj + + + + + + + $(IntDir)/src/sm83/decoder.c.obj + + + + + $(IntDir)/src/gb/audio.c.obj + + + $(IntDir)/src/gb/cheats.c.obj + + + $(IntDir)/src/gb/core.c.obj + + + + $(IntDir)/src/gb/input.c.obj + + + $(IntDir)/src/gb/io.c.obj + + + + $(IntDir)/src/gb/memory.c.obj + + + $(IntDir)/src/gb/overrides.c.obj + + + $(IntDir)/src/gb/serialize.c.obj + + + $(IntDir)/src/gb/renderers/cache-set.c.obj + + + + $(IntDir)/src/gb/sio.c.obj + + + $(IntDir)/src/gb/timer.c.obj + + + $(IntDir)/src/gb/video.c.obj + + + + + $(IntDir)/src/arm/decoder.c.obj + + + + + + $(IntDir)/src/gba/audio.c.obj + + + + + + + + $(IntDir)/src/gba/cheats.c.obj + + + + + + $(IntDir)/src/gba/core.c.obj + + + + + + $(IntDir)/src/gba/input.c.obj + + + $(IntDir)/src/gba/io.c.obj + + + $(IntDir)/src/gba/memory.c.obj + + + $(IntDir)/src/gba/overrides.c.obj + + + $(IntDir)/src/gba/renderers/cache-set.c.obj + + + + + + + + + + $(IntDir)/src/gba/serialize.c.obj + + + + $(IntDir)/src/gba/sio.c.obj + + + + + $(IntDir)/src/gba/timer.c.obj + + + $(IntDir)/src/gba/video.c.obj + + + + + + + + + + + + + + + + + + + + + + + + + + + $(IntDir)/src/platform/windows/memory.c.obj + + + + + + + + + + + + + + + + + + + + + + + + + + $(IntDir)/src/gba/sio/lockstep.c.obj + + + $(IntDir)/src/gb/sio/lockstep.c.obj + + + + + + $(IntDir)/src/gba/extra/proxy.c.obj + + + $(IntDir)/src/gb/extra/proxy.c.obj + + + + + + + + + \ No newline at end of file diff --git a/Externals/mGBA/mgba.vcxproj.filters b/Externals/mGBA/mgba.vcxproj.filters new file mode 100644 index 0000000000..b5e7b0ed6a --- /dev/null +++ b/Externals/mGBA/mgba.vcxproj.filters @@ -0,0 +1,427 @@ + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Generated sources + + + Virtual files + + + Virtual files + + + Virtual files + + + Virtual files + + + Windows-specific code + + + Third-party code + + + Third-party code + + + Virtual files + + + Virtual files + + + Virtual files + + + Virtual files + + + Virtual files + + + Virtual files + + + Virtual files + + + Virtual files + + + Virtual files + + + Virtual files + + + Virtual files + + + Virtual files + + + Virtual files + + + Virtual files + + + Virtual files + + + Virtual files + + + Virtual files + + + Virtual files + + + Virtual files + + + Virtual files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + {57438DCC-46E8-3FBA-90F2-185F80CEBE2C} + + + {C0CFD641-7357-3B1D-B2A3-B2477AEF3147} + + + {6C07F537-79D5-3651-A634-9E523B9936B2} + + + {AFF59D0C-C624-393F-8703-2FB3784928C8} + + + {37E5D4D5-B263-3B94-8968-21228F26DF67} + + + diff --git a/Source/Core/Common/SCMRevGen.vcxproj b/Source/Core/Common/SCMRevGen.vcxproj index f8c9829144..938df1c6f7 100644 --- a/Source/Core/Common/SCMRevGen.vcxproj +++ b/Source/Core/Common/SCMRevGen.vcxproj @@ -35,10 +35,6 @@ - - %windir%\System32\cscript - %windir%\Sysnative\cscript - diff --git a/Source/dolphin-emu.sln b/Source/dolphin-emu.sln index 0f31f47801..1fdc7499b3 100644 --- a/Source/dolphin-emu.sln +++ b/Source/dolphin-emu.sln @@ -73,6 +73,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "liblzma", "..\Externals\lib EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zstd", "..\Externals\zstd\zstd.vcxproj", "{1BEA10F3-80CE-4BC4-9331-5769372CDF99}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mgba", "..\Externals\mGBA\mgba.vcxproj", "{864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -349,6 +351,14 @@ Global {1BEA10F3-80CE-4BC4-9331-5769372CDF99}.Release|ARM64.Build.0 = Release|ARM64 {1BEA10F3-80CE-4BC4-9331-5769372CDF99}.Release|x64.ActiveCfg = Release|x64 {1BEA10F3-80CE-4BC4-9331-5769372CDF99}.Release|x64.Build.0 = Release|x64 + {864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6}.Debug|ARM64.Build.0 = Debug|ARM64 + {864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6}.Debug|x64.ActiveCfg = Debug|x64 + {864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6}.Debug|x64.Build.0 = Debug|x64 + {864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6}.Release|ARM64.ActiveCfg = Release|ARM64 + {864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6}.Release|ARM64.Build.0 = Release|ARM64 + {864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6}.Release|x64.ActiveCfg = Release|x64 + {864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -379,6 +389,7 @@ Global {1D8C51D2-FFA4-418E-B183-9F42B6A6717E} = {87ADDFF9-5768-4DA2-A33B-2477593D6677} {055A775F-B4F5-4970-9240-F6CF7661F37B} = {87ADDFF9-5768-4DA2-A33B-2477593D6677} {1BEA10F3-80CE-4BC4-9331-5769372CDF99} = {87ADDFF9-5768-4DA2-A33B-2477593D6677} + {864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6} = {87ADDFF9-5768-4DA2-A33B-2477593D6677} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {64B0A343-3B94-4522-9C24-6937FE5EFB22} From 1b27f22cbcba62958d54daa3ec5ca3931a800e79 Mon Sep 17 00:00:00 2001 From: Bonta <40473493+Bonta0@users.noreply.github.com> Date: Sun, 4 Jul 2021 12:44:02 +0200 Subject: [PATCH 02/18] SI: Allow devices to schedule events --- Source/Core/Core/HW/SI/SI.cpp | 40 +++++++++++++++++++++++++--- Source/Core/Core/HW/SI/SI.h | 3 +++ Source/Core/Core/HW/SI/SI_Device.cpp | 4 +++ Source/Core/Core/HW/SI/SI_Device.h | 3 +++ 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/Source/Core/Core/HW/SI/SI.cpp b/Source/Core/Core/HW/SI/SI.cpp index 12d0790cb4..14e586fc05 100644 --- a/Source/Core/Core/HW/SI/SI.cpp +++ b/Source/Core/Core/HW/SI/SI.cpp @@ -209,6 +209,7 @@ union USIEXIClockCount static CoreTiming::EventType* s_change_device_event; static CoreTiming::EventType* s_tranfer_pending_event; +static std::array s_device_events; // User-configured device type. possibly overridden by TAS/Netplay static std::array, MAX_SI_CHANNELS> s_desired_device_types; @@ -369,8 +370,44 @@ void DoState(PointerWrap& p) p.Do(s_si_buffer); } +template +static void DeviceEventCallback(u64 userdata, s64 cyclesLate) +{ + s_channel[device_number].device->OnEvent(userdata, cyclesLate); +} + +static void RegisterEvents() +{ + s_change_device_event = CoreTiming::RegisterEvent("ChangeSIDevice", ChangeDeviceCallback); + s_tranfer_pending_event = CoreTiming::RegisterEvent("SITransferPending", RunSIBuffer); + + constexpr std::array event_callbacks = { + DeviceEventCallback<0>, + DeviceEventCallback<1>, + DeviceEventCallback<2>, + DeviceEventCallback<3>, + }; + for (int i = 0; i < MAX_SI_CHANNELS; ++i) + { + s_device_events[i] = + CoreTiming::RegisterEvent(fmt::format("SIEventChannel{}", i), event_callbacks[i]); + } +} + +void ScheduleEvent(int device_number, s64 cycles_into_future, u64 userdata) +{ + CoreTiming::ScheduleEvent(cycles_into_future, s_device_events[device_number], userdata); +} + +void RemoveEvent(int device_number) +{ + CoreTiming::RemoveEvent(s_device_events[device_number]); +} + void Init() { + RegisterEvents(); + for (int i = 0; i < MAX_SI_CHANNELS; i++) { s_channel[i].out.hex = 0; @@ -415,9 +452,6 @@ void Init() // s_exi_clock_count.LOCK = 1; s_si_buffer = {}; - - s_change_device_event = CoreTiming::RegisterEvent("ChangeSIDevice", ChangeDeviceCallback); - s_tranfer_pending_event = CoreTiming::RegisterEvent("SITransferPending", RunSIBuffer); } void Shutdown() diff --git a/Source/Core/Core/HW/SI/SI.h b/Source/Core/Core/HW/SI/SI.h index 89671fe04d..07ece208ce 100644 --- a/Source/Core/Core/HW/SI/SI.h +++ b/Source/Core/Core/HW/SI/SI.h @@ -30,6 +30,9 @@ void DoState(PointerWrap& p); void RegisterMMIO(MMIO::Mapping* mmio, u32 base); +void ScheduleEvent(int device_number, s64 cycles_into_future, u64 userdata = 0); +void RemoveEvent(int device_number); + void UpdateDevices(); void RemoveDevice(int device_number); diff --git a/Source/Core/Core/HW/SI/SI_Device.cpp b/Source/Core/Core/HW/SI/SI_Device.cpp index d13e64f602..5717fe10a9 100644 --- a/Source/Core/Core/HW/SI/SI_Device.cpp +++ b/Source/Core/Core/HW/SI/SI_Device.cpp @@ -97,6 +97,10 @@ void ISIDevice::DoState(PointerWrap& p) { } +void ISIDevice::OnEvent(u64 userdata, s64 cycles_late) +{ +} + // Check if a device class is inheriting from CSIDevice_GCController // The goal of this function is to avoid special casing a long list of // device types when there is no "real" input device, e.g. when playing diff --git a/Source/Core/Core/HW/SI/SI_Device.h b/Source/Core/Core/HW/SI/SI_Device.h index 9225195689..323f6c602a 100644 --- a/Source/Core/Core/HW/SI/SI_Device.h +++ b/Source/Core/Core/HW/SI/SI_Device.h @@ -88,6 +88,9 @@ public: // Savestate support virtual void DoState(PointerWrap& p); + // Schedulable event + virtual void OnEvent(u64 userdata, s64 cycles_late); + protected: int m_device_number; SIDevices m_device_type; From 502def7f71efb78170bd0d5fed7e4cae1112120a Mon Sep 17 00:00:00 2001 From: Bonta <40473493+Bonta0@users.noreply.github.com> Date: Sun, 4 Jul 2021 12:44:56 +0200 Subject: [PATCH 03/18] Mixer: Support GBA samples mixing --- Source/Core/AudioCommon/Mixer.cpp | 41 +++++++++++++++++++++++++------ Source/Core/AudioCommon/Mixer.h | 18 +++++++++++--- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index ae8ba47d00..e50d90b32b 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -47,6 +47,8 @@ void Mixer::DoState(PointerWrap& p) m_dma_mixer.DoState(p); m_streaming_mixer.DoState(p); m_wiimote_speaker_mixer.DoState(p); + for (auto& mixer : m_gba_mixers) + mixer.DoState(p); } // Executed from sound stream thread @@ -93,20 +95,24 @@ unsigned int Mixer::MixerFifo::Mix(short* samples, unsigned int numSamples, s32 lvolume = m_LVolume.load(); s32 rvolume = m_RVolume.load(); + const auto read_buffer = [this](auto index) { + return m_little_endian ? m_buffer[index] : Common::swap16(m_buffer[index]); + }; + // TODO: consider a higher-quality resampling algorithm. for (; currentSample < numSamples * 2 && ((indexW - indexR) & INDEX_MASK) > 2; currentSample += 2) { u32 indexR2 = indexR + 2; // next sample - s16 l1 = Common::swap16(m_buffer[indexR & INDEX_MASK]); // current - s16 l2 = Common::swap16(m_buffer[indexR2 & INDEX_MASK]); // next + s16 l1 = read_buffer(indexR & INDEX_MASK); // current + s16 l2 = read_buffer(indexR2 & INDEX_MASK); // next int sampleL = ((l1 << 16) + (l2 - l1) * (u16)m_frac) >> 16; sampleL = (sampleL * lvolume) >> 8; sampleL += samples[currentSample + 1]; samples[currentSample + 1] = std::clamp(sampleL, -32767, 32767); - s16 r1 = Common::swap16(m_buffer[(indexR + 1) & INDEX_MASK]); // current - s16 r2 = Common::swap16(m_buffer[(indexR2 + 1) & INDEX_MASK]); // next + s16 r1 = read_buffer((indexR + 1) & INDEX_MASK); // current + s16 r2 = read_buffer((indexR2 + 1) & INDEX_MASK); // next int sampleR = ((r1 << 16) + (r2 - r1) * (u16)m_frac) >> 16; sampleR = (sampleR * rvolume) >> 8; sampleR += samples[currentSample]; @@ -122,8 +128,8 @@ unsigned int Mixer::MixerFifo::Mix(short* samples, unsigned int numSamples, // Padding short s[2]; - s[0] = Common::swap16(m_buffer[(indexR - 1) & INDEX_MASK]); - s[1] = Common::swap16(m_buffer[(indexR - 2) & INDEX_MASK]); + s[0] = read_buffer((indexR - 1) & INDEX_MASK); + s[1] = read_buffer((indexR - 2) & INDEX_MASK); s[0] = (s[0] * rvolume) >> 8; s[1] = (s[1] * lvolume) >> 8; for (; currentSample < numSamples * 2; currentSample += 2) @@ -158,6 +164,8 @@ unsigned int Mixer::Mix(short* samples, unsigned int num_samples) m_dma_mixer.Mix(m_scratch_buffer.data(), available_samples, false); m_streaming_mixer.Mix(m_scratch_buffer.data(), available_samples, false); m_wiimote_speaker_mixer.Mix(m_scratch_buffer.data(), available_samples, false); + for (auto& mixer : m_gba_mixers) + mixer.Mix(m_scratch_buffer.data(), available_samples, false); if (!m_is_stretching) { @@ -172,6 +180,8 @@ unsigned int Mixer::Mix(short* samples, unsigned int num_samples) m_dma_mixer.Mix(samples, num_samples, true); m_streaming_mixer.Mix(samples, num_samples, true); m_wiimote_speaker_mixer.Mix(samples, num_samples, true); + for (auto& mixer : m_gba_mixers) + mixer.Mix(samples, num_samples, true); m_is_stretching = false; } @@ -258,14 +268,19 @@ void Mixer::PushWiimoteSpeakerSamples(const short* samples, unsigned int num_sam for (unsigned int i = 0; i < num_samples; ++i) { - samples_stereo[i * 2] = Common::swap16(samples[i]); - samples_stereo[i * 2 + 1] = Common::swap16(samples[i]); + samples_stereo[i * 2] = samples[i]; + samples_stereo[i * 2 + 1] = samples[i]; } m_wiimote_speaker_mixer.PushSamples(samples_stereo, num_samples); } } +void Mixer::PushGBASamples(int device_number, const short* samples, unsigned int num_samples) +{ + m_gba_mixers[device_number].PushSamples(samples, num_samples); +} + void Mixer::SetDMAInputSampleRate(unsigned int rate) { m_dma_mixer.SetInputSampleRate(rate); @@ -276,6 +291,11 @@ void Mixer::SetStreamInputSampleRate(unsigned int rate) m_streaming_mixer.SetInputSampleRate(rate); } +void Mixer::SetGBAInputSampleRates(int device_number, unsigned int rate) +{ + m_gba_mixers[device_number].SetInputSampleRate(rate); +} + void Mixer::SetStreamingVolume(unsigned int lvolume, unsigned int rvolume) { m_streaming_mixer.SetVolume(lvolume, rvolume); @@ -286,6 +306,11 @@ void Mixer::SetWiimoteSpeakerVolume(unsigned int lvolume, unsigned int rvolume) m_wiimote_speaker_mixer.SetVolume(lvolume, rvolume); } +void Mixer::SetGBAVolume(int device_number, unsigned int lvolume, unsigned int rvolume) +{ + m_gba_mixers[device_number].SetVolume(lvolume, rvolume); +} + void Mixer::StartLogDTKAudio(const std::string& filename) { if (!m_log_dtk_audio) diff --git a/Source/Core/AudioCommon/Mixer.h b/Source/Core/AudioCommon/Mixer.h index 36630c7013..2a1a62ba4f 100644 --- a/Source/Core/AudioCommon/Mixer.h +++ b/Source/Core/AudioCommon/Mixer.h @@ -30,11 +30,17 @@ public: void PushStreamingSamples(const short* samples, unsigned int num_samples); void PushWiimoteSpeakerSamples(const short* samples, unsigned int num_samples, unsigned int sample_rate); + void PushGBASamples(int device_number, const short* samples, unsigned int num_samples); + unsigned int GetSampleRate() const { return m_sampleRate; } + void SetDMAInputSampleRate(unsigned int rate); void SetStreamInputSampleRate(unsigned int rate); + void SetGBAInputSampleRates(int device_number, unsigned int rate); + void SetStreamingVolume(unsigned int lvolume, unsigned int rvolume); void SetWiimoteSpeakerVolume(unsigned int lvolume, unsigned int rvolume); + void SetGBAVolume(int device_number, unsigned int lvolume, unsigned int rvolume); void StartLogDTKAudio(const std::string& filename); void StopLogDTKAudio(); @@ -57,7 +63,8 @@ private: class MixerFifo final { public: - MixerFifo(Mixer* mixer, unsigned sample_rate) : m_mixer(mixer), m_input_sample_rate(sample_rate) + MixerFifo(Mixer* mixer, unsigned sample_rate, bool little_endian) + : m_mixer(mixer), m_input_sample_rate(sample_rate), m_little_endian(little_endian) { } void DoState(PointerWrap& p); @@ -71,6 +78,7 @@ private: private: Mixer* m_mixer; unsigned m_input_sample_rate; + bool m_little_endian; std::array m_buffer{}; std::atomic m_indexW{0}; std::atomic m_indexR{0}; @@ -81,9 +89,11 @@ private: u32 m_frac = 0; }; - MixerFifo m_dma_mixer{this, 32000}; - MixerFifo m_streaming_mixer{this, 48000}; - MixerFifo m_wiimote_speaker_mixer{this, 3000}; + MixerFifo m_dma_mixer{this, 32000, false}; + MixerFifo m_streaming_mixer{this, 48000, false}; + MixerFifo m_wiimote_speaker_mixer{this, 3000, true}; + std::array m_gba_mixers{MixerFifo{this, 48000, true}, MixerFifo{this, 48000, true}, + MixerFifo{this, 48000, true}, MixerFifo{this, 48000, true}}; unsigned int m_sampleRate; bool m_is_stretching = false; From 110887435c990c48548c802dff95f099d7eda921 Mon Sep 17 00:00:00 2001 From: Bonta <40473493+Bonta0@users.noreply.github.com> Date: Sun, 4 Jul 2021 12:51:36 +0200 Subject: [PATCH 04/18] Config: GBA settings --- Source/Core/Common/CommonPaths.h | 4 ++++ Source/Core/Common/FileUtil.cpp | 4 ++++ Source/Core/Common/FileUtil.h | 3 +++ Source/Core/Core/Config/MainSettings.cpp | 12 ++++++++++++ Source/Core/Core/Config/MainSettings.h | 9 +++++++++ Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp | 2 +- Source/Core/UICommon/UICommon.cpp | 7 ++++--- 7 files changed, 37 insertions(+), 4 deletions(-) diff --git a/Source/Core/Common/CommonPaths.h b/Source/Core/Common/CommonPaths.h index 4afeb698a3..e4b4a67d86 100644 --- a/Source/Core/Common/CommonPaths.h +++ b/Source/Core/Common/CommonPaths.h @@ -34,6 +34,7 @@ // Subdirs in the User dir returned by GetUserPath(D_USER_IDX) #define GC_USER_DIR "GC" +#define GBA_USER_DIR "GBA" #define WII_USER_DIR "Wii" #define CONFIG_DIR "Config" #define GAMESETTINGS_DIR "GameSettings" @@ -61,6 +62,7 @@ #define RESOURCES_DIR "Resources" #define THEMES_DIR "Themes" #define STYLES_DIR "Styles" +#define GBASAVES_DIR "Saves" #define ANAGLYPH_DIR "Anaglyph" #define PASSIVE_DIR "Passive" #define PIPES_DIR "Pipes" @@ -119,6 +121,8 @@ #define GC_MEMCARDB "MemoryCardB" #define GC_MEMCARD_NETPLAY "NetPlayTemp" +#define GBA_BIOS "gba_bios.bin" + #define WII_STATE "state.dat" #define WII_SDCARD "sd.raw" diff --git a/Source/Core/Common/FileUtil.cpp b/Source/Core/Common/FileUtil.cpp index a65f58f793..d04fdc60a1 100644 --- a/Source/Core/Common/FileUtil.cpp +++ b/Source/Core/Common/FileUtil.cpp @@ -983,6 +983,10 @@ static void RebuildUserDirectories(unsigned int dir_index) s_user_paths[F_MEMORYWATCHERSOCKET_IDX] = s_user_paths[D_MEMORYWATCHER_IDX] + MEMORYWATCHER_SOCKET; + s_user_paths[D_GBAUSER_IDX] = s_user_paths[D_USER_IDX] + GBA_USER_DIR DIR_SEP; + s_user_paths[D_GBASAVES_IDX] = s_user_paths[D_GBAUSER_IDX] + GBASAVES_DIR DIR_SEP; + s_user_paths[F_GBABIOS_IDX] = s_user_paths[D_GBAUSER_IDX] + GBA_BIOS; + // The shader cache has moved to the cache directory, so remove the old one. // TODO: remove that someday. File::DeleteDirRecursively(s_user_paths[D_USER_IDX] + SHADERCACHE_LEGACY_DIR DIR_SEP); diff --git a/Source/Core/Common/FileUtil.h b/Source/Core/Common/FileUtil.h index c4419fb317..1a487bbe4e 100644 --- a/Source/Core/Common/FileUtil.h +++ b/Source/Core/Common/FileUtil.h @@ -59,6 +59,8 @@ enum D_BACKUP_IDX, D_RESOURCEPACK_IDX, D_DYNAMICINPUT_IDX, + D_GBAUSER_IDX, + D_GBASAVES_IDX, F_DOLPHINCONFIG_IDX, F_GCPADCONFIG_IDX, F_WIIPADCONFIG_IDX, @@ -77,6 +79,7 @@ enum F_WIISDCARD_IDX, F_DUALSHOCKUDPCLIENTCONFIG_IDX, F_FREELOOKCONFIG_IDX, + F_GBABIOS_IDX, NUM_PATH_INDICES }; diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index dd3e633991..b3fa308160 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -145,6 +145,18 @@ const Info MAIN_RESOURCEPACK_PATH{{System::Main, "General", "Resour const Info MAIN_FS_PATH{{System::Main, "General", "NANDRootPath"}, ""}; const Info MAIN_SD_PATH{{System::Main, "General", "WiiSDCardPath"}, ""}; +// Main.GBA + +const Info MAIN_GBA_BIOS_PATH{{System::Main, "GBA", "BIOS"}, ""}; +const std::array, 4> MAIN_GBA_ROM_PATHS{ + Info{{System::Main, "GBA", "Rom1"}, ""}, + Info{{System::Main, "GBA", "Rom2"}, ""}, + Info{{System::Main, "GBA", "Rom3"}, ""}, + Info{{System::Main, "GBA", "Rom4"}, ""}}; +const Info MAIN_GBA_SAVES_PATH{{System::Main, "GBA", "SavesPath"}, ""}; +const Info MAIN_GBA_SAVES_IN_ROM_PATH{{System::Main, "GBA", "SavesInRomPath"}, false}; +const Info MAIN_GBA_THREADS{{System::Main, "GBA", "Threads"}, true}; + // Main.Network const Info MAIN_NETWORK_SSL_DUMP_READ{{System::Main, "Network", "SSLDumpRead"}, false}; diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index 981b6c663f..4fad910606 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include "Common/Config/Config.h" @@ -119,6 +120,14 @@ extern const Info MAIN_RESOURCEPACK_PATH; extern const Info MAIN_FS_PATH; extern const Info MAIN_SD_PATH; +// Main.GBA + +extern const Info MAIN_GBA_BIOS_PATH; +extern const std::array, 4> MAIN_GBA_ROM_PATHS; +extern const Info MAIN_GBA_SAVES_PATH; +extern const Info MAIN_GBA_SAVES_IN_ROM_PATH; +extern const Info MAIN_GBA_THREADS; + // Main.Network extern const Info MAIN_NETWORK_SSL_DUMP_READ; diff --git a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp index f2eb0eb930..1f8074518d 100644 --- a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp +++ b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp @@ -26,7 +26,7 @@ bool IsSettingSaveable(const Config::Location& config_location) if (config_location.system == Config::System::Main) { for (const std::string_view section : - {"NetPlay", "General", "Display", "Network", "Analytics", "AndroidOverlayButtons"}) + {"NetPlay", "General", "GBA", "Display", "Network", "Analytics", "AndroidOverlayButtons"}) { if (config_location.section == section) return true; diff --git a/Source/Core/UICommon/UICommon.cpp b/Source/Core/UICommon/UICommon.cpp index 40815f049f..a5e7450b85 100644 --- a/Source/Core/UICommon/UICommon.cpp +++ b/Source/Core/UICommon/UICommon.cpp @@ -81,9 +81,10 @@ static void InitCustomPaths() CreateLoadPath(Config::Get(Config::MAIN_LOAD_PATH)); CreateDumpPath(Config::Get(Config::MAIN_DUMP_PATH)); CreateResourcePackPath(Config::Get(Config::MAIN_RESOURCEPACK_PATH)); - const std::string sd_path = Config::Get(Config::MAIN_SD_PATH); - if (!sd_path.empty()) - File::SetUserPath(F_WIISDCARD_IDX, sd_path); + File::SetUserPath(F_WIISDCARD_IDX, Config::Get(Config::MAIN_SD_PATH)); + File::SetUserPath(F_GBABIOS_IDX, Config::Get(Config::MAIN_GBA_BIOS_PATH)); + File::SetUserPath(D_GBASAVES_IDX, Config::Get(Config::MAIN_GBA_SAVES_PATH)); + File::CreateFullPath(File::GetUserPath(D_GBASAVES_IDX)); } void Init() From ec5d557895e1fd2c9bab4e50a0d6529815b88640 Mon Sep 17 00:00:00 2001 From: Bonta <40473493+Bonta0@users.noreply.github.com> Date: Sun, 4 Jul 2021 12:53:53 +0200 Subject: [PATCH 05/18] Qt: GBA Config dialog --- .../Core/DolphinQt/Settings/GameCubePane.cpp | 169 ++++++++++++++++++ Source/Core/DolphinQt/Settings/GameCubePane.h | 23 +++ 2 files changed, 192 insertions(+) diff --git a/Source/Core/DolphinQt/Settings/GameCubePane.cpp b/Source/Core/DolphinQt/Settings/GameCubePane.cpp index cc9dd14fe0..fcaa4af794 100644 --- a/Source/Core/DolphinQt/Settings/GameCubePane.cpp +++ b/Source/Core/DolphinQt/Settings/GameCubePane.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -19,16 +20,19 @@ #include "Common/CommonPaths.h" #include "Common/Config/Config.h" #include "Common/FileUtil.h" +#include "Common/MsgHandler.h" #include "Core/Config/MainSettings.h" #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/HW/EXI/EXI.h" #include "Core/HW/GCMemcard/GCMemcard.h" +#include "Core/NetPlayServer.h" #include "DolphinQt/Config/Mapping/MappingWindow.h" #include "DolphinQt/GCMemcardManager.h" #include "DolphinQt/QtUtils/ModalMessageBox.h" +#include "DolphinQt/Settings.h" enum { @@ -123,8 +127,51 @@ void GameCubePane::CreateWidgets() device_layout->addWidget(m_slot_combos[2], 2, 1); device_layout->addWidget(m_slot_buttons[2], 2, 2); +#ifdef HAS_LIBMGBA + // GBA Settings + auto* gba_box = new QGroupBox(tr("GBA Settings"), this); + auto* gba_layout = new QGridLayout(gba_box); + gba_box->setLayout(gba_layout); + int gba_row = 0; + + m_gba_threads = new QCheckBox(tr("Run GBA Cores in Dedicated Threads")); + gba_layout->addWidget(m_gba_threads, gba_row, 0, 1, -1); + gba_row++; + + m_gba_bios_edit = new QLineEdit(); + m_gba_browse_bios = new QPushButton(QStringLiteral("...")); + gba_layout->addWidget(new QLabel(tr("BIOS:")), gba_row, 0); + gba_layout->addWidget(m_gba_bios_edit, gba_row, 1); + gba_layout->addWidget(m_gba_browse_bios, gba_row, 2); + gba_row++; + + for (size_t i = 0; i < m_gba_rom_edits.size(); ++i) + { + m_gba_rom_edits[i] = new QLineEdit(); + m_gba_browse_roms[i] = new QPushButton(QStringLiteral("...")); + gba_layout->addWidget(new QLabel(tr("Port %1 ROM:").arg(i + 1)), gba_row, 0); + gba_layout->addWidget(m_gba_rom_edits[i], gba_row, 1); + gba_layout->addWidget(m_gba_browse_roms[i], gba_row, 2); + gba_row++; + } + + m_gba_save_rom_path = new QCheckBox(tr("Save in Same Directory as the ROM")); + gba_layout->addWidget(m_gba_save_rom_path, gba_row, 0, 1, -1); + gba_row++; + + m_gba_saves_edit = new QLineEdit(); + m_gba_browse_saves = new QPushButton(QStringLiteral("...")); + gba_layout->addWidget(new QLabel(tr("Saves:")), gba_row, 0); + gba_layout->addWidget(m_gba_saves_edit, gba_row, 1); + gba_layout->addWidget(m_gba_browse_saves, gba_row, 2); + gba_row++; +#endif + layout->addWidget(ipl_box); layout->addWidget(device_box); +#ifdef HAS_LIBMGBA + layout->addWidget(gba_box); +#endif layout->addStretch(); @@ -147,6 +194,44 @@ void GameCubePane::ConnectWidgets() &GameCubePane::SaveSettings); connect(m_slot_buttons[i], &QPushButton::clicked, [this, i] { OnConfigPressed(i); }); } + +#ifdef HAS_LIBMGBA + // GBA Settings + connect(m_gba_threads, &QCheckBox::stateChanged, this, &GameCubePane::SaveSettings); + connect(m_gba_bios_edit, &QLineEdit::editingFinished, this, &GameCubePane::SaveSettings); + connect(m_gba_browse_bios, &QPushButton::clicked, this, &GameCubePane::BrowseGBABios); + connect(m_gba_save_rom_path, &QCheckBox::stateChanged, this, &GameCubePane::SaveRomPathChanged); + connect(m_gba_saves_edit, &QLineEdit::editingFinished, this, &GameCubePane::SaveSettings); + connect(m_gba_browse_saves, &QPushButton::clicked, this, &GameCubePane::BrowseGBASaves); + for (size_t i = 0; i < m_gba_browse_roms.size(); ++i) + { + connect(m_gba_rom_edits[i], &QLineEdit::editingFinished, this, &GameCubePane::SaveSettings); + connect(m_gba_browse_roms[i], &QPushButton::clicked, this, [this, i] { BrowseGBARom(i); }); + } +#endif + + // Emulation State + connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, + &GameCubePane::OnEmulationStateChanged); + OnEmulationStateChanged(); +} + +void GameCubePane::OnEmulationStateChanged() +{ +#ifdef HAS_LIBMGBA + bool gba_enabled = !NetPlay::IsNetPlayRunning(); + m_gba_threads->setEnabled(gba_enabled); + m_gba_bios_edit->setEnabled(gba_enabled); + m_gba_browse_bios->setEnabled(gba_enabled); + m_gba_save_rom_path->setEnabled(gba_enabled); + m_gba_saves_edit->setEnabled(gba_enabled); + m_gba_browse_saves->setEnabled(gba_enabled); + for (size_t i = 0; i < m_gba_browse_roms.size(); ++i) + { + m_gba_rom_edits[i]->setEnabled(gba_enabled); + m_gba_browse_roms[i]->setEnabled(gba_enabled); + } +#endif } void GameCubePane::UpdateButton(int slot) @@ -312,6 +397,47 @@ void GameCubePane::OnConfigPressed(int slot) } } +void GameCubePane::BrowseGBABios() +{ + QString file = QDir::toNativeSeparators(QFileDialog::getOpenFileName( + this, tr("Select GBA BIOS"), QString::fromStdString(File::GetUserPath(F_GBABIOS_IDX)), + tr("All Files (*)"))); + if (!file.isEmpty()) + { + m_gba_bios_edit->setText(file); + SaveSettings(); + } +} + +void GameCubePane::BrowseGBARom(size_t index) +{ + QString file = QString::fromStdString(GetOpenGBARom({})); + if (!file.isEmpty()) + { + m_gba_rom_edits[index]->setText(file); + SaveSettings(); + } +} + +void GameCubePane::SaveRomPathChanged() +{ + m_gba_saves_edit->setEnabled(!m_gba_save_rom_path->isChecked()); + m_gba_browse_saves->setEnabled(!m_gba_save_rom_path->isChecked()); + SaveSettings(); +} + +void GameCubePane::BrowseGBASaves() +{ + QString dir = QDir::toNativeSeparators( + QFileDialog::getExistingDirectory(this, tr("Select GBA Saves Path"), + QString::fromStdString(File::GetUserPath(D_GBASAVES_IDX)))); + if (!dir.isEmpty()) + { + m_gba_saves_edit->setText(dir); + SaveSettings(); + } +} + void GameCubePane::LoadSettings() { const SConfig& params = SConfig::GetInstance(); @@ -346,6 +472,16 @@ void GameCubePane::LoadSettings() m_slot_combos[i]->findData(SConfig::GetInstance().m_EXIDevice[i])); UpdateButton(i); } + +#ifdef HAS_LIBMGBA + // GBA Settings + m_gba_threads->setChecked(Config::Get(Config::MAIN_GBA_THREADS)); + m_gba_bios_edit->setText(QString::fromStdString(File::GetUserPath(F_GBABIOS_IDX))); + m_gba_save_rom_path->setChecked(Config::Get(Config::MAIN_GBA_SAVES_IN_ROM_PATH)); + m_gba_saves_edit->setText(QString::fromStdString(File::GetUserPath(D_GBASAVES_IDX))); + for (size_t i = 0; i < m_gba_rom_edits.size(); ++i) + m_gba_rom_edits[i]->setText(QString::fromStdString(Config::Get(Config::MAIN_GBA_ROM_PATHS[i]))); +#endif } void GameCubePane::SaveSettings() @@ -360,6 +496,7 @@ void GameCubePane::SaveSettings() params.SelectedLanguage = m_language_combo->currentData().toInt(); Config::SetBaseOrCurrent(Config::MAIN_GC_LANGUAGE, m_language_combo->currentData().toInt()); + // Device Settings for (int i = 0; i < SLOT_COUNT; i++) { const auto dev = ExpansionInterface::TEXIDevices(m_slot_combos[i]->currentData().toInt()); @@ -389,5 +526,37 @@ void GameCubePane::SaveSettings() break; } } + +#ifdef HAS_LIBMGBA + // GBA Settings + if (!NetPlay::IsNetPlayRunning()) + { + Config::SetBaseOrCurrent(Config::MAIN_GBA_THREADS, m_gba_threads->isChecked()); + Config::SetBaseOrCurrent(Config::MAIN_GBA_BIOS_PATH, m_gba_bios_edit->text().toStdString()); + Config::SetBaseOrCurrent(Config::MAIN_GBA_SAVES_IN_ROM_PATH, m_gba_save_rom_path->isChecked()); + Config::SetBaseOrCurrent(Config::MAIN_GBA_SAVES_PATH, m_gba_saves_edit->text().toStdString()); + File::SetUserPath(F_GBABIOS_IDX, Config::Get(Config::MAIN_GBA_BIOS_PATH)); + File::SetUserPath(D_GBASAVES_IDX, Config::Get(Config::MAIN_GBA_SAVES_PATH)); + for (size_t i = 0; i < m_gba_rom_edits.size(); ++i) + { + Config::SetBaseOrCurrent(Config::MAIN_GBA_ROM_PATHS[i], + m_gba_rom_edits[i]->text().toStdString()); + } + } +#endif + LoadSettings(); } + +std::string GameCubePane::GetOpenGBARom(std::string_view title) +{ + QString caption = tr("Select GBA ROM"); + if (!title.empty()) + caption += QStringLiteral(": %1").arg(QString::fromStdString(std::string(title))); + return QDir::toNativeSeparators( + QFileDialog::getOpenFileName( + nullptr, caption, QString(), + tr("Game Boy Advance ROMs (*.gba *.gbc *.gb *.7z *.zip *.agb *.mb *.rom *.bin);;" + "All Files (*)"))) + .toStdString(); +} diff --git a/Source/Core/DolphinQt/Settings/GameCubePane.h b/Source/Core/DolphinQt/Settings/GameCubePane.h index 0b21a26558..ea945cfc1d 100644 --- a/Source/Core/DolphinQt/Settings/GameCubePane.h +++ b/Source/Core/DolphinQt/Settings/GameCubePane.h @@ -3,10 +3,15 @@ #pragma once +#include +#include +#include + #include class QCheckBox; class QComboBox; +class QLineEdit; class QPushButton; class GameCubePane : public QWidget @@ -15,6 +20,8 @@ class GameCubePane : public QWidget public: explicit GameCubePane(); + static std::string GetOpenGBARom(std::string_view title); + private: void CreateWidgets(); void ConnectWidgets(); @@ -22,12 +29,28 @@ private: void LoadSettings(); void SaveSettings(); + void OnEmulationStateChanged(); + void UpdateButton(int slot); void OnConfigPressed(int slot); + void BrowseGBABios(); + void BrowseGBARom(size_t index); + void SaveRomPathChanged(); + void BrowseGBASaves(); + QCheckBox* m_skip_main_menu; QComboBox* m_language_combo; QPushButton* m_slot_buttons[3]; QComboBox* m_slot_combos[3]; + + QCheckBox* m_gba_threads; + QCheckBox* m_gba_save_rom_path; + QPushButton* m_gba_browse_bios; + QLineEdit* m_gba_bios_edit; + std::array m_gba_browse_roms; + std::array m_gba_rom_edits; + QPushButton* m_gba_browse_saves; + QLineEdit* m_gba_saves_edit; }; From 27eab609dcc0619b8b00cdb544b903550f577099 Mon Sep 17 00:00:00 2001 From: Bonta <40473493+Bonta0@users.noreply.github.com> Date: Sun, 4 Jul 2021 12:55:37 +0200 Subject: [PATCH 06/18] GCController: Make HandleMoviePadStatus static --- Source/Core/Core/HW/SI/SI_DeviceGCAdapter.cpp | 2 +- Source/Core/Core/HW/SI/SI_DeviceGCController.cpp | 14 +++++++------- Source/Core/Core/HW/SI/SI_DeviceGCController.h | 3 ++- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Source/Core/Core/HW/SI/SI_DeviceGCAdapter.cpp b/Source/Core/Core/HW/SI/SI_DeviceGCAdapter.cpp index 3f60b44cf7..a3f24824fb 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceGCAdapter.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceGCAdapter.cpp @@ -38,7 +38,7 @@ GCPadStatus CSIDevice_GCAdapter::GetPadStatus() pad_status = GCAdapter::Input(m_device_number); } - HandleMoviePadStatus(&pad_status); + HandleMoviePadStatus(m_device_number, &pad_status); // Our GCAdapter code sets PAD_GET_ORIGIN when a new device has been connected. // Watch for this to calibrate real controllers on connection. diff --git a/Source/Core/Core/HW/SI/SI_DeviceGCController.cpp b/Source/Core/Core/HW/SI/SI_DeviceGCController.cpp index 7a2b317f08..8c485f1a9f 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceGCController.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceGCController.cpp @@ -108,27 +108,27 @@ int CSIDevice_GCController::RunBuffer(u8* buffer, int request_length) return 0; } -void CSIDevice_GCController::HandleMoviePadStatus(GCPadStatus* pad_status) +void CSIDevice_GCController::HandleMoviePadStatus(int device_number, GCPadStatus* pad_status) { - Movie::CallGCInputManip(pad_status, m_device_number); + Movie::CallGCInputManip(pad_status, device_number); Movie::SetPolledDevice(); - if (NetPlay_GetInput(m_device_number, pad_status)) + if (NetPlay_GetInput(device_number, pad_status)) { } else if (Movie::IsPlayingInput()) { - Movie::PlayController(pad_status, m_device_number); + Movie::PlayController(pad_status, device_number); Movie::InputUpdate(); } else if (Movie::IsRecordingInput()) { - Movie::RecordInput(pad_status, m_device_number); + Movie::RecordInput(pad_status, device_number); Movie::InputUpdate(); } else { - Movie::CheckPadStatus(pad_status, m_device_number); + Movie::CheckPadStatus(pad_status, device_number); } } @@ -143,7 +143,7 @@ GCPadStatus CSIDevice_GCController::GetPadStatus() pad_status = Pad::GetStatus(m_device_number); } - HandleMoviePadStatus(&pad_status); + HandleMoviePadStatus(m_device_number, &pad_status); // Our GCAdapter code sets PAD_GET_ORIGIN when a new device has been connected. // Watch for this to calibrate real controllers on connection. diff --git a/Source/Core/Core/HW/SI/SI_DeviceGCController.h b/Source/Core/Core/HW/SI/SI_DeviceGCController.h index fa336d3357..9904e855bb 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceGCController.h +++ b/Source/Core/Core/HW/SI/SI_DeviceGCController.h @@ -105,8 +105,9 @@ public: // Direct rumble to the right GC Controller static void Rumble(int pad_num, ControlState strength); + static void HandleMoviePadStatus(int device_number, GCPadStatus* pad_status); + protected: - void HandleMoviePadStatus(GCPadStatus* pad_status); void SetOrigin(const GCPadStatus& pad_status); }; From d2353c79eabeec0cbcec531c94148527816a3d1a Mon Sep 17 00:00:00 2001 From: Bonta <40473493+Bonta0@users.noreply.github.com> Date: Sun, 4 Jul 2021 12:56:07 +0200 Subject: [PATCH 07/18] MappingWidget: Support for boxes with multiple columns --- .../Config/Mapping/MappingWidget.cpp | 48 +++++++++++++++---- .../DolphinQt/Config/Mapping/MappingWidget.h | 4 ++ 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp index da04403137..1206ad75ff 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -118,16 +119,7 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con } for (auto& control : group->controls) - { - auto* button = new MappingButton(this, control->control_ref.get(), !indicator); - - button->setMinimumWidth(100); - button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - const bool translate = control->translate == ControllerEmu::Translate; - const QString translated_name = - translate ? tr(control->ui_name.c_str()) : QString::fromStdString(control->ui_name); - form_layout->addRow(translated_name, button); - } + CreateControl(control.get(), form_layout, !indicator); for (auto& setting : group->numeric_settings) { @@ -186,6 +178,42 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con return group_box; } +QGroupBox* MappingWidget::CreateControlsBox(const QString& name, ControllerEmu::ControlGroup* group, + int columns) +{ + auto* group_box = new QGroupBox(name); + auto* hbox_layout = new QHBoxLayout(); + + group_box->setLayout(hbox_layout); + + std::vector form_layouts; + for (int i = 0; i < columns; ++i) + { + form_layouts.push_back(new QFormLayout()); + hbox_layout->addLayout(form_layouts[i]); + } + + for (size_t i = 0; i < group->controls.size(); ++i) + { + CreateControl(group->controls[i].get(), form_layouts[i % columns], true); + } + + return group_box; +} + +void MappingWidget::CreateControl(const ControllerEmu::Control* control, QFormLayout* layout, + bool indicator) +{ + auto* button = new MappingButton(this, control->control_ref.get(), indicator); + + button->setMinimumWidth(100); + button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + const bool translate = control->translate == ControllerEmu::Translate; + const QString translated_name = + translate ? tr(control->ui_name.c_str()) : QString::fromStdString(control->ui_name); + layout->addRow(translated_name, button); +} + ControllerEmu::EmulatedController* MappingWidget::GetController() const { return m_parent->GetController(); diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h index ed092c8c5e..ad55172f4a 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h @@ -16,6 +16,7 @@ class InputConfig; class MappingButton; class MappingNumeric; class MappingWindow; +class QFormLayout; class QPushButton; class QGroupBox; @@ -52,6 +53,9 @@ protected: QGroupBox* CreateGroupBox(ControllerEmu::ControlGroup* group); QGroupBox* CreateGroupBox(const QString& name, ControllerEmu::ControlGroup* group); + QGroupBox* CreateControlsBox(const QString& name, ControllerEmu::ControlGroup* group, + int columns); + void CreateControl(const ControllerEmu::Control* control, QFormLayout* layout, bool indicator); QPushButton* CreateSettingAdvancedMappingButton(ControllerEmu::NumericSettingBase& setting); private: From 8ee21acf3460e00d55002fb3387be4dd763b01e9 Mon Sep 17 00:00:00 2001 From: Bonta <40473493+Bonta0@users.noreply.github.com> Date: Sun, 4 Jul 2021 13:02:03 +0200 Subject: [PATCH 08/18] Pad: GBA config --- Source/Core/Core/CMakeLists.txt | 4 + Source/Core/Core/Core.cpp | 4 + Source/Core/Core/FreeLookManager.cpp | 4 +- Source/Core/Core/HW/GBAPad.cpp | 64 ++++++++++++++ Source/Core/Core/HW/GBAPad.h | 28 +++++++ Source/Core/Core/HW/GBAPadEmu.cpp | 107 ++++++++++++++++++++++++ Source/Core/Core/HW/GBAPadEmu.h | 40 +++++++++ Source/Core/Core/HW/GCKeyboard.cpp | 4 +- Source/Core/Core/HW/GCPad.cpp | 4 +- Source/Core/Core/HW/Wiimote.cpp | 2 +- Source/Core/Core/HotkeyManager.cpp | 2 +- Source/Core/DolphinLib.props | 6 +- Source/Core/DolphinQt/MainWindow.cpp | 6 ++ Source/Core/InputCommon/InputConfig.cpp | 28 ++++--- Source/Core/InputCommon/InputConfig.h | 9 +- 15 files changed, 291 insertions(+), 21 deletions(-) create mode 100644 Source/Core/Core/HW/GBAPad.cpp create mode 100644 Source/Core/Core/HW/GBAPad.h create mode 100644 Source/Core/Core/HW/GBAPadEmu.cpp create mode 100644 Source/Core/Core/HW/GBAPadEmu.h diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index 6f3ef7a623..6de7e6dbe2 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -197,6 +197,10 @@ add_library(core HW/EXI/EXI_DeviceMic.h HW/EXI/EXI.cpp HW/EXI/EXI.h + HW/GBAPad.cpp + HW/GBAPad.h + HW/GBAPadEmu.cpp + HW/GBAPadEmu.h HW/GCKeyboard.cpp HW/GCKeyboard.h HW/GCKeyboardEmu.cpp diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index 2592a6a674..c88b16fc9d 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -48,6 +48,7 @@ #include "Core/HW/CPU.h" #include "Core/HW/DSP.h" #include "Core/HW/EXI/EXI.h" +#include "Core/HW/GBAPad.h" #include "Core/HW/GCKeyboard.h" #include "Core/HW/GCPad.h" #include "Core/HW/HW.h" @@ -460,6 +461,7 @@ static void EmuThread(std::unique_ptr boot, WindowSystemInfo wsi { g_controller_interface.Initialize(wsi); Pad::Initialize(); + Pad::InitializeGBA(); Keyboard::Initialize(); init_controllers = true; } @@ -467,6 +469,7 @@ static void EmuThread(std::unique_ptr boot, WindowSystemInfo wsi { g_controller_interface.ChangeWindow(wsi.render_window); Pad::LoadConfig(); + Pad::LoadGBAConfig(); Keyboard::LoadConfig(); } @@ -517,6 +520,7 @@ static void EmuThread(std::unique_ptr boot, WindowSystemInfo wsi Keyboard::Shutdown(); Pad::Shutdown(); + Pad::ShutdownGBA(); g_controller_interface.Shutdown(); }}; diff --git a/Source/Core/Core/FreeLookManager.cpp b/Source/Core/Core/FreeLookManager.cpp index 90f2667890..39d1f47278 100644 --- a/Source/Core/Core/FreeLookManager.cpp +++ b/Source/Core/Core/FreeLookManager.cpp @@ -309,12 +309,12 @@ void Initialize() FreeLook::GetConfig().Refresh(); - s_config.LoadConfig(true); + s_config.LoadConfig(InputConfig::InputClass::GC); } void LoadInputConfig() { - s_config.LoadConfig(true); + s_config.LoadConfig(InputConfig::InputClass::GC); } bool IsInitialized() diff --git a/Source/Core/Core/HW/GBAPad.cpp b/Source/Core/Core/HW/GBAPad.cpp new file mode 100644 index 0000000000..7f25b56fd1 --- /dev/null +++ b/Source/Core/Core/HW/GBAPad.cpp @@ -0,0 +1,64 @@ +// Copyright 2021 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/HW/GBAPad.h" + +#include "Core/HW/GBAPadEmu.h" +#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h" +#include "InputCommon/GCPadStatus.h" +#include "InputCommon/InputConfig.h" + +namespace Pad +{ +static InputConfig s_config("GBA", _trans("Pad"), "GBA"); +InputConfig* GetGBAConfig() +{ + return &s_config; +} + +void ShutdownGBA() +{ + s_config.UnregisterHotplugCallback(); + + s_config.ClearControllers(); +} + +void InitializeGBA() +{ + if (s_config.ControllersNeedToBeCreated()) + { + for (unsigned int i = 0; i < 4; ++i) + s_config.CreateController(i); + } + + s_config.RegisterHotplugCallback(); + + // Load the saved controller config + s_config.LoadConfig(InputConfig::InputClass::GBA); +} + +void LoadGBAConfig() +{ + s_config.LoadConfig(InputConfig::InputClass::GBA); +} + +bool IsGBAInitialized() +{ + return !s_config.ControllersNeedToBeCreated(); +} + +GCPadStatus GetGBAStatus(int pad_num) +{ + return static_cast(s_config.GetController(pad_num))->GetInput(); +} + +void SetGBAReset(int pad_num, bool reset) +{ + static_cast(s_config.GetController(pad_num))->SetReset(reset); +} + +ControllerEmu::ControlGroup* GetGBAGroup(int pad_num, GBAPadGroup group) +{ + return static_cast(s_config.GetController(pad_num))->GetGroup(group); +} +} // namespace Pad diff --git a/Source/Core/Core/HW/GBAPad.h b/Source/Core/Core/HW/GBAPad.h new file mode 100644 index 0000000000..76d68f26df --- /dev/null +++ b/Source/Core/Core/HW/GBAPad.h @@ -0,0 +1,28 @@ +// Copyright 2021 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +class InputConfig; +enum class GBAPadGroup; +struct GCPadStatus; + +namespace ControllerEmu +{ +class ControlGroup; +} // namespace ControllerEmu + +namespace Pad +{ +void ShutdownGBA(); +void InitializeGBA(); +void LoadGBAConfig(); +bool IsGBAInitialized(); + +InputConfig* GetGBAConfig(); + +GCPadStatus GetGBAStatus(int pad_num); +void SetGBAReset(int pad_num, bool reset); + +ControllerEmu::ControlGroup* GetGBAGroup(int pad_num, GBAPadGroup group); +} // namespace Pad diff --git a/Source/Core/Core/HW/GBAPadEmu.cpp b/Source/Core/Core/HW/GBAPadEmu.cpp new file mode 100644 index 0000000000..c25763b6d8 --- /dev/null +++ b/Source/Core/Core/HW/GBAPadEmu.cpp @@ -0,0 +1,107 @@ +// Copyright 2021 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/HW/GBAPadEmu.h" + +#include + +#include "InputCommon/ControllerEmu/Control/Input.h" +#include "InputCommon/ControllerEmu/ControlGroup/Buttons.h" +#include "InputCommon/GCPadStatus.h" + +static const u16 dpad_bitmasks[] = {PAD_BUTTON_UP, PAD_BUTTON_DOWN, PAD_BUTTON_LEFT, + PAD_BUTTON_RIGHT}; + +static const u16 button_bitmasks[] = {PAD_BUTTON_B, PAD_BUTTON_A, PAD_TRIGGER_L, + PAD_TRIGGER_R, PAD_TRIGGER_Z, PAD_BUTTON_START}; + +static const char* const named_buttons[] = {"B", "A", "L", "R", _trans("SELECT"), _trans("START")}; + +GBAPad::GBAPad(const unsigned int index) : m_reset_pending(false), m_index(index) +{ + // Buttons + groups.emplace_back(m_buttons = new ControllerEmu::Buttons(_trans("Buttons"))); + for (const char* named_button : named_buttons) + { + const ControllerEmu::Translatability translate = + (named_button == std::string(_trans("SELECT")) || + named_button == std::string(_trans("START"))) ? + ControllerEmu::Translate : + ControllerEmu::DoNotTranslate; + m_buttons->AddInput(translate, named_button); + } + + // DPad + groups.emplace_back(m_dpad = new ControllerEmu::Buttons(_trans("D-Pad"))); + for (const char* named_direction : named_directions) + { + m_dpad->AddInput(ControllerEmu::Translate, named_direction); + } +} + +std::string GBAPad::GetName() const +{ + return fmt::format("GBA{}", m_index + 1); +} + +ControllerEmu::ControlGroup* GBAPad::GetGroup(GBAPadGroup group) const +{ + switch (group) + { + case GBAPadGroup::Buttons: + return m_buttons; + case GBAPadGroup::DPad: + return m_dpad; + default: + return nullptr; + } +} + +GCPadStatus GBAPad::GetInput() +{ + const auto lock = GetStateLock(); + GCPadStatus pad = {}; + + // Buttons + m_buttons->GetState(&pad.button, button_bitmasks); + + // DPad + m_dpad->GetState(&pad.button, dpad_bitmasks); + + // Use X button as a reset signal + if (m_reset_pending) + pad.button |= PAD_BUTTON_X; + m_reset_pending = false; + + return pad; +} + +void GBAPad::SetReset(bool reset) +{ + const auto lock = GetStateLock(); + m_reset_pending = reset; +} + +void GBAPad::LoadDefaults(const ControllerInterface& ciface) +{ + EmulatedController::LoadDefaults(ciface); + + // Buttons + m_buttons->SetControlExpression(0, "`Z`"); // B + m_buttons->SetControlExpression(1, "`X`"); // A + m_buttons->SetControlExpression(2, "`Q`"); // L + m_buttons->SetControlExpression(3, "`W`"); // R +#ifdef _WIN32 + m_buttons->SetControlExpression(4, "`BACK`"); // Select + m_buttons->SetControlExpression(5, "`RETURN`"); // Start +#else + m_buttons->SetControlExpression(4, "`Backspace`"); // Select + m_buttons->SetControlExpression(5, "`Return`"); // Start +#endif + + // D-Pad + m_dpad->SetControlExpression(0, "`T`"); // Up + m_dpad->SetControlExpression(1, "`G`"); // Down + m_dpad->SetControlExpression(2, "`F`"); // Left + m_dpad->SetControlExpression(3, "`H`"); // Right +} diff --git a/Source/Core/Core/HW/GBAPadEmu.h b/Source/Core/Core/HW/GBAPadEmu.h new file mode 100644 index 0000000000..8dc8f24457 --- /dev/null +++ b/Source/Core/Core/HW/GBAPadEmu.h @@ -0,0 +1,40 @@ +// Copyright 2021 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "InputCommon/ControllerEmu/ControllerEmu.h" + +struct GCPadStatus; + +namespace ControllerEmu +{ +class Buttons; +} // namespace ControllerEmu + +enum class GBAPadGroup +{ + DPad, + Buttons +}; + +class GBAPad : public ControllerEmu::EmulatedController +{ +public: + explicit GBAPad(unsigned int index); + GCPadStatus GetInput(); + void SetReset(bool reset); + + std::string GetName() const override; + + ControllerEmu::ControlGroup* GetGroup(GBAPadGroup group) const; + + void LoadDefaults(const ControllerInterface& ciface) override; + +private: + ControllerEmu::Buttons* m_buttons; + ControllerEmu::Buttons* m_dpad; + bool m_reset_pending; + + const unsigned int m_index; +}; diff --git a/Source/Core/Core/HW/GCKeyboard.cpp b/Source/Core/Core/HW/GCKeyboard.cpp index de03ec32b4..f99193f4d4 100644 --- a/Source/Core/Core/HW/GCKeyboard.cpp +++ b/Source/Core/Core/HW/GCKeyboard.cpp @@ -41,12 +41,12 @@ void Initialize() s_config.RegisterHotplugCallback(); // Load the saved controller config - s_config.LoadConfig(true); + s_config.LoadConfig(InputConfig::InputClass::GC); } void LoadConfig() { - s_config.LoadConfig(true); + s_config.LoadConfig(InputConfig::InputClass::GC); } ControllerEmu::ControlGroup* GetGroup(int port, KeyboardGroup group) diff --git a/Source/Core/Core/HW/GCPad.cpp b/Source/Core/Core/HW/GCPad.cpp index a34c83638d..faf50f3c02 100644 --- a/Source/Core/Core/HW/GCPad.cpp +++ b/Source/Core/Core/HW/GCPad.cpp @@ -38,12 +38,12 @@ void Initialize() s_config.RegisterHotplugCallback(); // Load the saved controller config - s_config.LoadConfig(true); + s_config.LoadConfig(InputConfig::InputClass::GC); } void LoadConfig() { - s_config.LoadConfig(true); + s_config.LoadConfig(InputConfig::InputClass::GC); } bool IsInitialized() diff --git a/Source/Core/Core/HW/Wiimote.cpp b/Source/Core/Core/HW/Wiimote.cpp index eaed4c91eb..30d522b564 100644 --- a/Source/Core/Core/HW/Wiimote.cpp +++ b/Source/Core/Core/HW/Wiimote.cpp @@ -173,7 +173,7 @@ void ResetAllWiimotes() void LoadConfig() { - s_config.LoadConfig(false); + s_config.LoadConfig(InputConfig::InputClass::Wii); s_last_connect_request_counter.fill(0); } diff --git a/Source/Core/Core/HotkeyManager.cpp b/Source/Core/Core/HotkeyManager.cpp index 77960cc381..01ddadb6be 100644 --- a/Source/Core/Core/HotkeyManager.cpp +++ b/Source/Core/Core/HotkeyManager.cpp @@ -285,7 +285,7 @@ void Initialize() void LoadConfig() { - s_config.LoadConfig(true); + s_config.LoadConfig(InputConfig::InputClass::GC); LoadLegacyConfig(s_config.GetController(0)); } diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 67c1a2eace..45cc52e6e3 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -1,4 +1,4 @@ - + @@ -264,6 +264,8 @@ + + @@ -844,6 +846,8 @@ + + diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index c4970c335d..317150812b 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -42,6 +42,7 @@ #include "Core/Core.h" #include "Core/FreeLookManager.h" #include "Core/HW/DVD/DVDInterface.h" +#include "Core/HW/GBAPad.h" #include "Core/HW/GCKeyboard.h" #include "Core/HW/GCPad.h" #include "Core/HW/ProcessorInterface.h" @@ -306,6 +307,7 @@ void MainWindow::InitControllers() g_controller_interface.Initialize(GetWindowSystemInfo(windowHandle())); Pad::Initialize(); + Pad::InitializeGBA(); Keyboard::Initialize(); Wiimote::Initialize(Wiimote::InitializeMode::DO_NOT_WAIT_FOR_WIIMOTES); FreeLook::Initialize(); @@ -320,6 +322,9 @@ void MainWindow::InitControllers() Pad::LoadConfig(); Pad::GetConfig()->SaveConfig(); + Pad::LoadGBAConfig(); + Pad::GetGBAConfig()->SaveConfig(); + Keyboard::LoadConfig(); Keyboard::GetConfig()->SaveConfig(); @@ -332,6 +337,7 @@ void MainWindow::ShutdownControllers() m_hotkey_scheduler->Stop(); Pad::Shutdown(); + Pad::ShutdownGBA(); Keyboard::Shutdown(); Wiimote::Shutdown(); HotkeyManagerEmu::Shutdown(); diff --git a/Source/Core/InputCommon/InputConfig.cpp b/Source/Core/InputCommon/InputConfig.cpp index 07249b0ed8..bace091ca5 100644 --- a/Source/Core/InputCommon/InputConfig.cpp +++ b/Source/Core/InputCommon/InputConfig.cpp @@ -26,7 +26,7 @@ InputConfig::InputConfig(const std::string& ini_name, const std::string& gui_nam InputConfig::~InputConfig() = default; -bool InputConfig::LoadConfig(bool isGC) +bool InputConfig::LoadConfig(InputClass type) { IniFile inifile; bool useProfile[MAX_BBMOTES] = {false, false, false, false, false}; @@ -43,16 +43,22 @@ bool InputConfig::LoadConfig(bool isGC) if (SConfig::GetInstance().GetGameID() != "00000000") { - std::string type; - if (isGC) + std::string type_str; + switch (type) { - type = "Pad"; - path = "Profiles/GCPad/"; - } - else - { - type = "Wiimote"; + case InputClass::GBA: + type_str = "GBA"; + path = "Profiles/GBA/"; + break; + case InputClass::Wii: + type_str = "Wiimote"; path = "Profiles/Wiimote/"; + break; + case InputClass::GC: + default: + type_str = "Pad"; + path = "Profiles/GCPad/"; + break; } IniFile game_ini = SConfig::GetInstance().LoadGameIni(); @@ -60,7 +66,7 @@ bool InputConfig::LoadConfig(bool isGC) for (int i = 0; i < 4; i++) { - const auto profile_name = fmt::format("{}Profile{}", type, num[i]); + const auto profile_name = fmt::format("{}Profile{}", type_str, num[i]); if (control_section->Exists(profile_name)) { @@ -124,7 +130,7 @@ bool InputConfig::LoadConfig(bool isGC) } #if defined(ANDROID) // Only set for wii pads - if (!isGC && use_ir_config) + if (type == InputClass::Wii && use_ir_config) { config.Set("IR/Total Yaw", ir_values[0]); config.Set("IR/Total Pitch", ir_values[1]); diff --git a/Source/Core/InputCommon/InputConfig.h b/Source/Core/InputCommon/InputConfig.h index 62eb30b4de..2c3f290576 100644 --- a/Source/Core/InputCommon/InputConfig.h +++ b/Source/Core/InputCommon/InputConfig.h @@ -24,7 +24,14 @@ public: ~InputConfig(); - bool LoadConfig(bool isGC); + enum class InputClass + { + GC, + Wii, + GBA, + }; + + bool LoadConfig(InputClass type); void SaveConfig(); template From fdcee30a3d191946ebbd93f6ecc5be644e493de9 Mon Sep 17 00:00:00 2001 From: Bonta <40473493+Bonta0@users.noreply.github.com> Date: Sun, 4 Jul 2021 13:02:50 +0200 Subject: [PATCH 09/18] SI: Expose Commands constants and switch to enum class --- Source/Core/Core/HW/SI/SI_Device.h | 34 ++++++++++++++++++ Source/Core/Core/HW/SI/SI_DeviceDanceMat.cpp | 4 +-- Source/Core/Core/HW/SI/SI_DeviceGBA.cpp | 35 ++++++++----------- Source/Core/Core/HW/SI/SI_DeviceGBA.h | 2 +- .../Core/Core/HW/SI/SI_DeviceGCController.cpp | 27 +++++--------- .../Core/Core/HW/SI/SI_DeviceGCController.h | 29 --------------- .../Core/HW/SI/SI_DeviceGCSteeringWheel.cpp | 8 ++--- .../Core/HW/SI/SI_DeviceGCSteeringWheel.h | 15 -------- Source/Core/Core/HW/SI/SI_DeviceKeyboard.cpp | 18 +++------- Source/Core/Core/HW/SI/SI_DeviceKeyboard.h | 28 --------------- 10 files changed, 69 insertions(+), 131 deletions(-) diff --git a/Source/Core/Core/HW/SI/SI_Device.h b/Source/Core/Core/HW/SI/SI_Device.h index 323f6c602a..14bd83201c 100644 --- a/Source/Core/Core/HW/SI/SI_Device.h +++ b/Source/Core/Core/HW/SI/SI_Device.h @@ -41,6 +41,40 @@ enum TSIDevices : u32 SI_AM_BASEBOARD = 0x10110800 // gets ORd with dipswitch state }; +// Commands +enum class EBufferCommands : u8 +{ + CMD_STATUS = 0x00, + CMD_READ_GBA = 0x14, + CMD_WRITE_GBA = 0x15, + CMD_DIRECT = 0x40, + CMD_ORIGIN = 0x41, + CMD_RECALIBRATE = 0x42, + CMD_DIRECT_KB = 0x54, + CMD_RESET = 0xFF +}; + +enum class EDirectCommands : u8 +{ + CMD_FORCE = 0x30, + CMD_WRITE = 0x40, + CMD_POLL = 0x54 +}; + +union UCommand +{ + u32 hex = 0; + struct + { + u32 parameter1 : 8; + u32 parameter2 : 8; + u32 command : 8; + u32 : 8; + }; + UCommand() = default; + UCommand(u32 value) : hex{value} {} +}; + // For configuration use, since some devices can have the same SI Device ID enum SIDevices : int { diff --git a/Source/Core/Core/HW/SI/SI_DeviceDanceMat.cpp b/Source/Core/Core/HW/SI/SI_DeviceDanceMat.cpp index ae5ae477ab..f2f6545776 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceDanceMat.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceDanceMat.cpp @@ -19,8 +19,8 @@ CSIDevice_DanceMat::CSIDevice_DanceMat(SIDevices device, int device_number) int CSIDevice_DanceMat::RunBuffer(u8* buffer, int request_length) { // Read the command - EBufferCommands command = static_cast(buffer[0]); - if (command == CMD_RESET) + const auto command = static_cast(buffer[0]); + if (command == EBufferCommands::CMD_STATUS) { ISIDevice::RunBuffer(buffer, request_length); diff --git a/Source/Core/Core/HW/SI/SI_DeviceGBA.cpp b/Source/Core/Core/HW/SI/SI_DeviceGBA.cpp index b1d2704a47..57679bbe50 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceGBA.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceGBA.cpp @@ -34,14 +34,6 @@ int s_num_connected; Common::Flag s_server_running; } // namespace -enum EJoybusCmds -{ - CMD_RESET = 0xff, - CMD_STATUS = 0x00, - CMD_READ = 0x14, - CMD_WRITE = 0x15 -}; - constexpr auto GC_BITS_PER_SECOND = 200000; constexpr auto GBA_BITS_PER_SECOND = 250000; constexpr auto GC_STOP_BIT_NS = 6500; @@ -50,7 +42,7 @@ constexpr auto SEND_MAX_SIZE = 5, RECV_MAX_SIZE = 5; // --- GameBoy Advance "Link Cable" --- -static int GetTransferTime(u8 cmd) +static int GetTransferTime(EBufferCommands cmd) { u64 gc_bytes_transferred = 1; u64 gba_bytes_transferred = 1; @@ -58,18 +50,18 @@ static int GetTransferTime(u8 cmd) switch (cmd) { - case CMD_RESET: - case CMD_STATUS: + case EBufferCommands::CMD_RESET: + case EBufferCommands::CMD_STATUS: { gba_bytes_transferred = 3; break; } - case CMD_READ: + case EBufferCommands::CMD_READ_GBA: { gba_bytes_transferred = 5; break; } - case CMD_WRITE: + case EBufferCommands::CMD_WRITE_GBA: { gc_bytes_transferred = 5; break; @@ -249,10 +241,10 @@ void GBASockServer::Send(const u8* si_buffer) for (size_t i = 0; i < send_data.size(); i++) send_data[i] = si_buffer[i]; - u8 cmd = send_data[0]; + const auto cmd = static_cast(send_data[0]); sf::Socket::Status status; - if (cmd == CMD_WRITE) + if (cmd == EBufferCommands::CMD_WRITE_GBA) status = m_client->send(send_data.data(), send_data.size()); else status = m_client->send(send_data.data(), 1); @@ -334,7 +326,7 @@ int CSIDevice_GBA::RunBuffer(u8* buffer, int request_length) return -1; } - m_last_cmd = buffer[0]; + m_last_cmd = static_cast(buffer[0]); m_timestamp_sent = CoreTiming::GetTicks(); m_next_action = NextAction::WaitTransferTime; return 0; @@ -355,11 +347,11 @@ int CSIDevice_GBA::RunBuffer(u8* buffer, int request_length) u8 bytes = 1; switch (m_last_cmd) { - case CMD_RESET: - case CMD_STATUS: + case EBufferCommands::CMD_RESET: + case EBufferCommands::CMD_STATUS: bytes = 3; break; - case CMD_READ: + case EBufferCommands::CMD_READ_GBA: bytes = 5; break; default: @@ -372,8 +364,9 @@ int CSIDevice_GBA::RunBuffer(u8* buffer, int request_length) return -1; #ifdef _DEBUG const Common::Log::LOG_LEVELS log_level = - (m_last_cmd == CMD_STATUS || m_last_cmd == CMD_RESET) ? Common::Log::LERROR : - Common::Log::LWARNING; + (m_last_cmd == EBufferCommands::CMD_STATUS || m_last_cmd == EBufferCommands::CMD_RESET) ? + Common::Log::LERROR : + Common::Log::LWARNING; GENERIC_LOG_FMT(Common::Log::SERIALINTERFACE, log_level, "{} [< {:02x}{:02x}{:02x}{:02x}{:02x}] ({})", m_device_number, buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], diff --git a/Source/Core/Core/HW/SI/SI_DeviceGBA.h b/Source/Core/Core/HW/SI/SI_DeviceGBA.h index dccbf2235e..663c4c9b1f 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceGBA.h +++ b/Source/Core/Core/HW/SI/SI_DeviceGBA.h @@ -60,7 +60,7 @@ private: GBASockServer m_sock_server; NextAction m_next_action = NextAction::SendCommand; - u8 m_last_cmd; + EBufferCommands m_last_cmd; u64 m_timestamp_sent = 0; }; } // namespace SerialInterface diff --git a/Source/Core/Core/HW/SI/SI_DeviceGCController.cpp b/Source/Core/Core/HW/SI/SI_DeviceGCController.cpp index 8c485f1a9f..985033f97b 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceGCController.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceGCController.cpp @@ -45,20 +45,20 @@ int CSIDevice_GCController::RunBuffer(u8* buffer, int request_length) return -1; // Read the command - EBufferCommands command = static_cast(buffer[0]); + const auto command = static_cast(buffer[0]); // Handle it switch (command) { - case CMD_RESET: - case CMD_ID: + case EBufferCommands::CMD_STATUS: + case EBufferCommands::CMD_RESET: { u32 id = Common::swap32(SI_GC_CONTROLLER); std::memcpy(buffer, &id, sizeof(id)); return sizeof(id); } - case CMD_DIRECT: + case EBufferCommands::CMD_DIRECT: { INFO_LOG_FMT(SERIALINTERFACE, "PAD - Direct (Request length: {})", request_length); u32 high, low; @@ -71,7 +71,7 @@ int CSIDevice_GCController::RunBuffer(u8* buffer, int request_length) return sizeof(high) + sizeof(low); } - case CMD_ORIGIN: + case EBufferCommands::CMD_ORIGIN: { INFO_LOG_FMT(SERIALINTERFACE, "PAD - Get Origin"); @@ -84,7 +84,7 @@ int CSIDevice_GCController::RunBuffer(u8* buffer, int request_length) } // Recalibrate (FiRES: i am not 100 percent sure about this) - case CMD_RECALIBRATE: + case EBufferCommands::CMD_RECALIBRATE: { INFO_LOG_FMT(SERIALINTERFACE, "PAD - Recalibrate"); @@ -290,13 +290,7 @@ void CSIDevice_GCController::SendCommand(u32 command, u8 poll) { UCommand controller_command(command); - switch (controller_command.command) - { - // Costis sent it in some demos :) - case 0x00: - break; - - case CMD_WRITE: + if (static_cast(controller_command.command) == EDirectCommands::CMD_WRITE) { const u32 type = controller_command.parameter1; // 0 = stop, 1 = rumble, 2 = stop hard @@ -317,15 +311,12 @@ void CSIDevice_GCController::SendCommand(u32 command, u8 poll) INFO_LOG_FMT(SERIALINTERFACE, "PAD {} set to mode {}", m_device_number, m_mode); } } - break; - - default: + else if (controller_command.command != 0x00) { + // Costis sent 0x00 in some demos :) ERROR_LOG_FMT(SERIALINTERFACE, "Unknown direct command ({:#x})", command); PanicAlertFmt("SI: Unknown direct command"); } - break; - } } // Savestate support diff --git a/Source/Core/Core/HW/SI/SI_DeviceGCController.h b/Source/Core/Core/HW/SI/SI_DeviceGCController.h index 9904e855bb..6d7f4722af 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceGCController.h +++ b/Source/Core/Core/HW/SI/SI_DeviceGCController.h @@ -12,16 +12,6 @@ namespace SerialInterface class CSIDevice_GCController : public ISIDevice { protected: - // Commands - enum EBufferCommands - { - CMD_RESET = 0x00, - CMD_DIRECT = 0x40, - CMD_ORIGIN = 0x41, - CMD_RECALIBRATE = 0x42, - CMD_ID = 0xff, - }; - struct SOrigin { u16 button; @@ -35,25 +25,6 @@ protected: u8 unk_5; }; - enum EDirectCommands - { - CMD_WRITE = 0x40 - }; - - union UCommand - { - u32 hex = 0; - struct - { - u32 parameter1 : 8; - u32 parameter2 : 8; - u32 command : 8; - u32 : 8; - }; - UCommand() = default; - UCommand(u32 value) : hex{value} {} - }; - enum EButtonCombo { COMBO_NONE = 0, diff --git a/Source/Core/Core/HW/SI/SI_DeviceGCSteeringWheel.cpp b/Source/Core/Core/HW/SI/SI_DeviceGCSteeringWheel.cpp index a801296267..212c08d52b 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceGCSteeringWheel.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceGCSteeringWheel.cpp @@ -25,13 +25,13 @@ int CSIDevice_GCSteeringWheel::RunBuffer(u8* buffer, int request_length) ISIDevice::RunBuffer(buffer, request_length); // Read the command - EBufferCommands command = static_cast(buffer[0]); + const auto command = static_cast(buffer[0]); // Handle it switch (command) { - case CMD_RESET: - case CMD_ID: + case EBufferCommands::CMD_STATUS: + case EBufferCommands::CMD_RESET: { u32 id = Common::swap32(SI_GC_STEERING); std::memcpy(buffer, &id, sizeof(id)); @@ -101,7 +101,7 @@ void CSIDevice_GCSteeringWheel::SendCommand(u32 command, u8 poll) { UCommand wheel_command(command); - if (wheel_command.command == CMD_FORCE) + if (static_cast(wheel_command.command) == EDirectCommands::CMD_FORCE) { // get the correct pad number that should rumble locally when using netplay const int pad_num = NetPlay_InGamePadToLocalPad(m_device_number); diff --git a/Source/Core/Core/HW/SI/SI_DeviceGCSteeringWheel.h b/Source/Core/Core/HW/SI/SI_DeviceGCSteeringWheel.h index 37b0cfff3a..4de13d142d 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceGCSteeringWheel.h +++ b/Source/Core/Core/HW/SI/SI_DeviceGCSteeringWheel.h @@ -17,21 +17,6 @@ public: void SendCommand(u32 command, u8 poll) override; private: - // Commands - enum EBufferCommands - { - CMD_RESET = 0x00, - CMD_ORIGIN = 0x41, - CMD_RECALIBRATE = 0x42, - CMD_ID = 0xff, - }; - - enum EDirectCommands - { - CMD_FORCE = 0x30, - CMD_WRITE = 0x40 - }; - enum class ForceCommandType : u8 { MotorOn = 0x03, diff --git a/Source/Core/Core/HW/SI/SI_DeviceKeyboard.cpp b/Source/Core/Core/HW/SI/SI_DeviceKeyboard.cpp index 8c5092adc7..13f067dc59 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceKeyboard.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceKeyboard.cpp @@ -31,15 +31,15 @@ int CSIDevice_Keyboard::RunBuffer(u8* buffer, int request_length) // Handle it switch (command) { - case CMD_RESET: - case CMD_ID: + case EBufferCommands::CMD_STATUS: + case EBufferCommands::CMD_RESET: { u32 id = Common::swap32(SI_GC_KEYBOARD); std::memcpy(buffer, &id, sizeof(id)); return sizeof(id); } - case CMD_DIRECT: + case EBufferCommands::CMD_DIRECT_KB: { INFO_LOG_FMT(SERIALINTERFACE, "Keyboard - Direct (Request Length: {})", request_length); u32 high, low; @@ -84,23 +84,15 @@ void CSIDevice_Keyboard::SendCommand(u32 command, u8 poll) { UCommand keyboard_command(command); - switch (keyboard_command.command) - { - case 0x00: - break; - - case CMD_POLL: + if (static_cast(keyboard_command.command) == EDirectCommands::CMD_POLL) { m_counter++; m_counter &= 15; } - break; - default: + else if (keyboard_command.command != 0x00) { ERROR_LOG_FMT(SERIALINTERFACE, "Unknown direct command ({:#x})", command); } - break; - } } void CSIDevice_Keyboard::DoState(PointerWrap& p) diff --git a/Source/Core/Core/HW/SI/SI_DeviceKeyboard.h b/Source/Core/Core/HW/SI/SI_DeviceKeyboard.h index beb34f0960..aebc63afa0 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceKeyboard.h +++ b/Source/Core/Core/HW/SI/SI_DeviceKeyboard.h @@ -32,34 +32,6 @@ public: void DoState(PointerWrap& p) override; protected: - // Commands - enum EBufferCommands - { - CMD_RESET = 0x00, - CMD_DIRECT = 0x54, - CMD_ID = 0xff, - }; - - enum EDirectCommands - { - CMD_WRITE = 0x40, - CMD_POLL = 0x54 - }; - - union UCommand - { - u32 hex = 0; - struct - { - u32 parameter1 : 8; - u32 parameter2 : 8; - u32 command : 8; - u32 : 8; - }; - UCommand() = default; - UCommand(u32 value) : hex{value} {} - }; - // PADAnalogMode u8 m_mode = 0; From d849d5669509eacae4a1d9fd995af77b53159df7 Mon Sep 17 00:00:00 2001 From: Bonta <40473493+Bonta0@users.noreply.github.com> Date: Sun, 4 Jul 2021 13:03:56 +0200 Subject: [PATCH 10/18] SI/DeviceGBA: Expose GetTransferTime --- Source/Core/Core/HW/SI/SI_Device.cpp | 44 +++++++++++++++++++++++ Source/Core/Core/HW/SI/SI_Device.h | 1 + Source/Core/Core/HW/SI/SI_DeviceGBA.cpp | 46 ++----------------------- 3 files changed, 47 insertions(+), 44 deletions(-) diff --git a/Source/Core/Core/HW/SI/SI_Device.cpp b/Source/Core/Core/HW/SI/SI_Device.cpp index 5717fe10a9..401b1178a7 100644 --- a/Source/Core/Core/HW/SI/SI_Device.cpp +++ b/Source/Core/Core/HW/SI/SI_Device.cpp @@ -20,9 +20,15 @@ #include "Core/HW/SI/SI_DeviceGCSteeringWheel.h" #include "Core/HW/SI/SI_DeviceKeyboard.h" #include "Core/HW/SI/SI_DeviceNull.h" +#include "Core/HW/SystemTimers.h" namespace SerialInterface { +constexpr u64 GC_BITS_PER_SECOND = 200000; +constexpr u64 GBA_BITS_PER_SECOND = 250000; +constexpr u64 GC_STOP_BIT_NS = 6500; +constexpr u64 GBA_STOP_BIT_NS = 14000; + std::ostream& operator<<(std::ostream& stream, SIDevices device) { stream << static_cast>(device); @@ -101,6 +107,44 @@ void ISIDevice::OnEvent(u64 userdata, s64 cycles_late) { } +int SIDevice_GetGBATransferTime(EBufferCommands cmd) +{ + u64 gc_bytes_transferred = 1; + u64 gba_bytes_transferred = 1; + u64 stop_bits_ns = GC_STOP_BIT_NS + GBA_STOP_BIT_NS; + + switch (cmd) + { + case EBufferCommands::CMD_RESET: + case EBufferCommands::CMD_STATUS: + { + gba_bytes_transferred = 3; + break; + } + case EBufferCommands::CMD_READ_GBA: + { + gba_bytes_transferred = 5; + break; + } + case EBufferCommands::CMD_WRITE_GBA: + { + gc_bytes_transferred = 5; + break; + } + default: + { + gba_bytes_transferred = 0; + break; + } + } + + u64 cycles = + (gba_bytes_transferred * 8 * SystemTimers::GetTicksPerSecond() / GBA_BITS_PER_SECOND) + + (gc_bytes_transferred * 8 * SystemTimers::GetTicksPerSecond() / GC_BITS_PER_SECOND) + + (stop_bits_ns * SystemTimers::GetTicksPerSecond() / 1000000000LL); + return static_cast(cycles); +} + // Check if a device class is inheriting from CSIDevice_GCController // The goal of this function is to avoid special casing a long list of // device types when there is no "real" input device, e.g. when playing diff --git a/Source/Core/Core/HW/SI/SI_Device.h b/Source/Core/Core/HW/SI/SI_Device.h index 14bd83201c..c2ae9bef87 100644 --- a/Source/Core/Core/HW/SI/SI_Device.h +++ b/Source/Core/Core/HW/SI/SI_Device.h @@ -130,6 +130,7 @@ protected: SIDevices m_device_type; }; +int SIDevice_GetGBATransferTime(EBufferCommands cmd); bool SIDevice_IsGCController(SIDevices type); std::unique_ptr SIDevice_Create(SIDevices device, int port_number); diff --git a/Source/Core/Core/HW/SI/SI_DeviceGBA.cpp b/Source/Core/Core/HW/SI/SI_DeviceGBA.cpp index 57679bbe50..95ab599a8f 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceGBA.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceGBA.cpp @@ -34,52 +34,10 @@ int s_num_connected; Common::Flag s_server_running; } // namespace -constexpr auto GC_BITS_PER_SECOND = 200000; -constexpr auto GBA_BITS_PER_SECOND = 250000; -constexpr auto GC_STOP_BIT_NS = 6500; -constexpr auto GBA_STOP_BIT_NS = 14000; constexpr auto SEND_MAX_SIZE = 5, RECV_MAX_SIZE = 5; // --- GameBoy Advance "Link Cable" --- -static int GetTransferTime(EBufferCommands cmd) -{ - u64 gc_bytes_transferred = 1; - u64 gba_bytes_transferred = 1; - u64 stop_bits_ns = GC_STOP_BIT_NS + GBA_STOP_BIT_NS; - - switch (cmd) - { - case EBufferCommands::CMD_RESET: - case EBufferCommands::CMD_STATUS: - { - gba_bytes_transferred = 3; - break; - } - case EBufferCommands::CMD_READ_GBA: - { - gba_bytes_transferred = 5; - break; - } - case EBufferCommands::CMD_WRITE_GBA: - { - gc_bytes_transferred = 5; - break; - } - default: - { - gba_bytes_transferred = 0; - break; - } - } - - u64 cycles = - (gba_bytes_transferred * 8 * SystemTimers::GetTicksPerSecond() / GBA_BITS_PER_SECOND) + - (gc_bytes_transferred * 8 * SystemTimers::GetTicksPerSecond() / GC_BITS_PER_SECOND) + - (stop_bits_ns * SystemTimers::GetTicksPerSecond() / 1000000000LL); - return static_cast(cycles); -} - static void GBAConnectionWaiter() { s_server_running.Set(); @@ -336,7 +294,7 @@ int CSIDevice_GBA::RunBuffer(u8* buffer, int request_length) { int elapsed_time = static_cast(CoreTiming::GetTicks() - m_timestamp_sent); // Tell SI to ask again after TransferInterval() cycles - if (GetTransferTime(m_last_cmd) > elapsed_time) + if (SIDevice_GetGBATransferTime(m_last_cmd) > elapsed_time) return 0; m_next_action = NextAction::ReceiveResponse; [[fallthrough]]; @@ -383,7 +341,7 @@ int CSIDevice_GBA::RunBuffer(u8* buffer, int request_length) int CSIDevice_GBA::TransferInterval() { - return GetTransferTime(m_last_cmd); + return SIDevice_GetGBATransferTime(m_last_cmd); } bool CSIDevice_GBA::GetData(u32& hi, u32& low) From 2d744da68c31814ee3d5d3696aaa4d6bf5955a21 Mon Sep 17 00:00:00 2001 From: Bonta <40473493+Bonta0@users.noreply.github.com> Date: Sun, 4 Jul 2021 13:09:46 +0200 Subject: [PATCH 11/18] Core: Add GBA host interface --- Source/Android/jni/MainAndroid.cpp | 5 +++++ Source/Core/Core/Host.h | 18 ++++++++++++++++++ Source/Core/DolphinNoGUI/MainNoGUI.cpp | 5 +++++ Source/Core/DolphinQt/Host.cpp | 5 +++++ Source/DSPTool/StubHost.cpp | 4 ++++ Source/UnitTests/StubHost.cpp | 4 ++++ 6 files changed, 41 insertions(+) diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index e1066f1584..765bab1bcb 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -173,6 +173,11 @@ void Host_TitleChanged() env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnTitleChanged()); } +std::unique_ptr Host_CreateGBAHost(std::weak_ptr core) +{ + return nullptr; +} + static bool MsgAlert(const char* caption, const char* text, bool yes_no, Common::MsgType style) { // If a panic alert happens very early in the execution of a game, we can crash here with diff --git a/Source/Core/Core/Host.h b/Source/Core/Core/Host.h index a66e36b360..dad850a5b2 100644 --- a/Source/Core/Core/Host.h +++ b/Source/Core/Core/Host.h @@ -3,9 +3,12 @@ #pragma once +#include #include #include +#include "Common/CommonTypes.h" + // Host - defines an interface for the emulator core to communicate back to the // OS-specific layer // @@ -23,6 +26,19 @@ // The host can be just a command line app that opens a window, or a full blown debugger // interface. +namespace HW::GBA +{ +class Core; +} // namespace HW::GBA + +class GBAHostInterface +{ +public: + virtual ~GBAHostInterface() = default; + virtual void GameChanged() = 0; + virtual void FrameEnded(const std::vector& video_buffer) = 0; +}; + enum class HostMessageID { // Begin at 10 in case there is already messages with wParam = 0, 1, 2 and so on @@ -47,3 +63,5 @@ void Host_UpdateMainFrame(); void Host_UpdateTitle(const std::string& title); void Host_YieldToUI(); void Host_TitleChanged(); + +std::unique_ptr Host_CreateGBAHost(std::weak_ptr core); diff --git a/Source/Core/DolphinNoGUI/MainNoGUI.cpp b/Source/Core/DolphinNoGUI/MainNoGUI.cpp index 31ddb591bb..26b0e69a22 100644 --- a/Source/Core/DolphinNoGUI/MainNoGUI.cpp +++ b/Source/Core/DolphinNoGUI/MainNoGUI.cpp @@ -119,6 +119,11 @@ void Host_TitleChanged() #endif } +std::unique_ptr Host_CreateGBAHost(std::weak_ptr core) +{ + return nullptr; +} + static std::unique_ptr GetPlatform(const optparse::Values& options) { std::string platform_name = static_cast(options.get("platform")); diff --git a/Source/Core/DolphinQt/Host.cpp b/Source/Core/DolphinQt/Host.cpp index 29215f5bbe..61cbc7036d 100644 --- a/Source/Core/DolphinQt/Host.cpp +++ b/Source/Core/DolphinQt/Host.cpp @@ -229,3 +229,8 @@ void Host_TitleChanged() Discord::UpdateDiscordPresence(); #endif } + +std::unique_ptr Host_CreateGBAHost(std::weak_ptr core) +{ + return nullptr; +} diff --git a/Source/DSPTool/StubHost.cpp b/Source/DSPTool/StubHost.cpp index c782978fc7..de20be198f 100644 --- a/Source/DSPTool/StubHost.cpp +++ b/Source/DSPTool/StubHost.cpp @@ -52,6 +52,10 @@ void Host_YieldToUI() void Host_TitleChanged() { } +std::unique_ptr Host_CreateGBAHost(std::weak_ptr core) +{ + return nullptr; +} bool Host_UIBlocksControllerState() { return false; diff --git a/Source/UnitTests/StubHost.cpp b/Source/UnitTests/StubHost.cpp index ebb0915f31..233af18f79 100644 --- a/Source/UnitTests/StubHost.cpp +++ b/Source/UnitTests/StubHost.cpp @@ -56,3 +56,7 @@ void Host_YieldToUI() void Host_TitleChanged() { } +std::unique_ptr Host_CreateGBAHost(std::weak_ptr core) +{ + return nullptr; +} From 9a22ff653f089057f6990ea32dfd33d96a158698 Mon Sep 17 00:00:00 2001 From: Bonta <40473493+Bonta0@users.noreply.github.com> Date: Sun, 4 Jul 2021 13:13:21 +0200 Subject: [PATCH 12/18] Core: Implement GBA Core using libmgba --- Source/Core/Core/CMakeLists.txt | 8 +- Source/Core/Core/HW/GBACore.cpp | 714 ++++++++++++++++++++++++++++++++ Source/Core/Core/HW/GBACore.h | 129 ++++++ Source/Core/DolphinLib.props | 2 + 4 files changed, 851 insertions(+), 2 deletions(-) create mode 100644 Source/Core/Core/HW/GBACore.cpp create mode 100644 Source/Core/Core/HW/GBACore.h diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index 6de7e6dbe2..65eb857982 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -440,8 +440,8 @@ add_library(core PowerPC/Interpreter/Interpreter_Tables.cpp PowerPC/Interpreter/Interpreter.cpp PowerPC/Interpreter/Interpreter.h - PowerPC/JitCommon/DivUtils.cpp - PowerPC/JitCommon/DivUtils.h + PowerPC/JitCommon/DivUtils.cpp + PowerPC/JitCommon/DivUtils.h PowerPC/JitCommon/JitAsmCommon.cpp PowerPC/JitCommon/JitAsmCommon.h PowerPC/JitCommon/JitBase.cpp @@ -621,6 +621,10 @@ if(ENABLE_VULKAN) endif() if(USE_MGBA) + target_sources(core PRIVATE + HW/GBACore.cpp + HW/GBACore.h + ) target_link_libraries(core PUBLIC mGBA::mgba) target_compile_definitions(core PUBLIC -DHAS_LIBMGBA) endif() diff --git a/Source/Core/Core/HW/GBACore.cpp b/Source/Core/Core/HW/GBACore.cpp new file mode 100644 index 0000000000..38ef42f1ba --- /dev/null +++ b/Source/Core/Core/HW/GBACore.cpp @@ -0,0 +1,714 @@ +// Copyright 2021 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/HW/GBACore.h" + +#include + +#define PYCPARSE // Remove static functions from the header +#include +#undef PYCPARSE +#include +#include +#include +#include +#include +#include + +#include "AudioCommon/AudioCommon.h" +#include "Common/ChunkFile.h" +#include "Common/CommonPaths.h" +#include "Common/CommonTypes.h" +#include "Common/Config/Config.h" +#include "Common/FileUtil.h" +#include "Common/IOFile.h" +#include "Common/MinizipUtil.h" +#include "Common/ScopeGuard.h" +#include "Common/Thread.h" +#include "Core/Config/MainSettings.h" +#include "Core/ConfigManager.h" +#include "Core/Core.h" +#include "Core/HW/SystemTimers.h" +#include "Core/Host.h" +#include "Core/NetPlayProto.h" + +namespace HW::GBA +{ +namespace +{ +mLogger s_stub_logger = { + [](mLogger*, int category, mLogLevel level, const char* format, va_list args) {}, nullptr}; +} // namespace + +constexpr auto SAMPLES = 512; +constexpr auto SAMPLE_RATE = 48000; + +// libmGBA does not return the correct frequency for some GB models +static u32 GetCoreFrequency(mCore* core) +{ + if (core->platform(core) != mPLATFORM_GB) + return static_cast(core->frequency(core)); + + switch (static_cast<::GB*>(core->board)->model) + { + case GB_MODEL_CGB: + case GB_MODEL_SCGB: + case GB_MODEL_AGB: + return CGB_SM83_FREQUENCY; + case GB_MODEL_SGB: + return SGB_SM83_FREQUENCY; + default: + return DMG_SM83_FREQUENCY; + } +} + +static VFile* OpenROM_Archive(const char* path) +{ + VFile* vf{}; + VDir* archive = VDirOpenArchive(path); + if (!archive) + return nullptr; + VFile* vf_archive = + VDirFindFirst(archive, [](VFile* vf_) { return mCoreIsCompatible(vf_) != mPLATFORM_NONE; }); + if (vf_archive) + { + size_t size = static_cast(vf_archive->size(vf_archive)); + + std::vector buffer(size); + vf_archive->seek(vf_archive, 0, SEEK_SET); + vf_archive->read(vf_archive, buffer.data(), size); + vf_archive->close(vf_archive); + + vf = VFileMemChunk(buffer.data(), size); + } + archive->close(archive); + return vf; +} + +static VFile* OpenROM_Zip(const char* path) +{ + VFile* vf{}; + unzFile zip = unzOpen(path); + if (!zip) + return nullptr; + do + { + unz_file_info info{}; + if (unzGetCurrentFileInfo(zip, &info, nullptr, 0, nullptr, 0, nullptr, 0) != UNZ_OK || + !info.uncompressed_size) + continue; + + std::vector buffer(info.uncompressed_size); + if (!Common::ReadFileFromZip(zip, &buffer)) + continue; + + vf = VFileMemChunk(buffer.data(), info.uncompressed_size); + if (mCoreIsCompatible(vf) == mPLATFORM_GBA) + { + vf->seek(vf, 0, SEEK_SET); + break; + } + + vf->close(vf); + vf = nullptr; + } while (unzGoToNextFile(zip) == UNZ_OK); + unzClose(zip); + return vf; +} + +static VFile* OpenROM(const char* rom_path) +{ + VFile* vf{}; + + vf = OpenROM_Archive(rom_path); + if (!vf) + vf = OpenROM_Zip(rom_path); + if (!vf) + vf = VFileOpen(rom_path, O_RDONLY); + if (!vf) + return nullptr; + + if (mCoreIsCompatible(vf) == mPLATFORM_NONE) + { + vf->close(vf); + return nullptr; + } + vf->seek(vf, 0, SEEK_SET); + + return vf; +} + +static std::array GetROMHash(VFile* rom) +{ + size_t size = rom->size(rom); + u8* buffer = static_cast(rom->map(rom, size, MAP_READ)); + + std::array hash; + mbedtls_sha1_ret(buffer, size, hash.data()); + rom->unmap(rom, buffer, size); + + return hash; +} + +Core::Core(int device_number) : m_device_number(device_number) +{ + mLogSetDefaultLogger(&s_stub_logger); +} + +Core::~Core() +{ + Stop(); +} + +bool Core::Start(u64 gc_ticks) +{ + if (IsStarted()) + return false; + + Common::ScopeGuard start_guard{[&] { Stop(); }}; + + VFile* rom{}; + Common::ScopeGuard rom_guard{[&] { + if (rom) + rom->close(rom); + }}; + + m_rom_path = Config::Get(Config::MAIN_GBA_ROM_PATHS[m_device_number]); + if (!m_rom_path.empty()) + { + rom = OpenROM(m_rom_path.c_str()); + if (!rom) + { + PanicAlertFmtT("Error: GBA{0} failed to open the ROM in {1}", m_device_number + 1, + m_rom_path); + return false; + } + m_rom_hash = GetROMHash(rom); + } + + m_core = rom ? mCoreFindVF(rom) : mCoreCreate(mPLATFORM_GBA); + if (!m_core) + { + PanicAlertFmtT("Error: GBA{0} failed to create core", m_device_number + 1); + return false; + } + m_core->init(m_core); + + mCoreInitConfig(m_core, "dolphin"); + mCoreConfigSetValue(&m_core->config, "idleOptimization", "detect"); + mCoreConfigSetIntValue(&m_core->config, "useBios", 0); + mCoreConfigSetIntValue(&m_core->config, "skipBios", 0); + + if (m_core->platform(m_core) == mPLATFORM_GBA && + !LoadBIOS(File::GetUserPath(F_GBABIOS_IDX).c_str())) + { + return false; + } + + if (rom) + { + if (!m_core->loadROM(m_core, rom)) + { + PanicAlertFmtT("Error: GBA{0} failed to load the ROM in {1}", m_device_number + 1, + m_rom_path); + return false; + } + rom_guard.Dismiss(); + + std::array game_title{}; + m_core->getGameTitle(m_core, game_title.data()); + m_game_title = game_title.data(); + + m_save_path = GetSavePath(m_rom_path, m_device_number); + if (!m_save_path.empty() && !LoadSave(m_save_path.c_str())) + return false; + } + + m_last_gc_ticks = gc_ticks; + m_gc_ticks_remainder = 0; + m_keys = 0; + + SetSIODriver(); + SetVideoBuffer(); + SetSampleRates(); + AddCallbacks(); + SetAVStream(); + SetupEvent(); + + m_core->reset(m_core); + m_started = true; + start_guard.Dismiss(); + // Notify the host and handle a dimension change if that happened after reset() + SetVideoBuffer(); + + if (Config::Get(Config::MAIN_GBA_THREADS)) + { + m_idle = true; + m_exit_loop = false; + m_thread = std::make_unique([this] { ThreadLoop(); }); + } + + return true; +} + +void Core::Stop() +{ + if (m_thread) + { + Flush(); + m_exit_loop = true; + { + std::lock_guard lock(m_queue_mutex); + m_command_cv.notify_one(); + } + m_thread->join(); + m_thread.reset(); + } + if (m_core) + { + mCoreConfigDeinit(&m_core->config); + m_core->deinit(m_core); + m_core = nullptr; + } + m_started = false; + m_rom_path = {}; + m_save_path = {}; + m_rom_hash = {}; + m_game_title = {}; +} + +void Core::Reset() +{ + Flush(); + if (!IsStarted()) + return; + + m_core->reset(m_core); +} + +bool Core::IsStarted() const +{ + return m_started; +} + +void Core::SetHost(std::weak_ptr host) +{ + m_host = std::move(host); +} + +void Core::SetForceDisconnect(bool force_disconnect) +{ + m_force_disconnect = force_disconnect; +} + +void Core::EReaderQueueCard(std::string_view card_path) +{ + Flush(); + if (!IsStarted() || m_core->platform(m_core) != mPlatform::mPLATFORM_GBA) + return; + + File::IOFile file(std::string(card_path), "rb"); + std::vector core_state(file.GetSize()); + file.ReadBytes(core_state.data(), core_state.size()); + GBACartEReaderQueueCard(static_cast<::GBA*>(m_core->board), core_state.data(), core_state.size()); +} + +bool Core::LoadBIOS(const char* bios_path) +{ + VFile* vf = VFileOpen(bios_path, O_RDONLY); + if (!vf) + { + PanicAlertFmtT("Error: GBA{0} failed to open the BIOS in {1}", m_device_number + 1, bios_path); + return false; + } + + if (!m_core->loadBIOS(m_core, vf, 0)) + { + PanicAlertFmtT("Error: GBA{0} failed to load the BIOS in {1}", m_device_number + 1, bios_path); + vf->close(vf); + return false; + } + + return true; +} + +bool Core::LoadSave(const char* save_path) +{ + VFile* vf = VFileOpen(save_path, O_CREAT | O_RDWR); + if (!vf) + { + PanicAlertFmtT("Error: GBA{0} failed to open the save in {1}", m_device_number + 1, save_path); + return false; + } + + if (!m_core->loadSave(m_core, vf)) + { + PanicAlertFmtT("Error: GBA{0} failed to load the save in {1}", m_device_number + 1, save_path); + vf->close(vf); + return false; + } + + return true; +} + +void Core::SetSIODriver() +{ + if (m_core->platform(m_core) != mPLATFORM_GBA) + return; + + GBASIOJOYCreate(&m_sio_driver); + GBASIOSetDriver(&static_cast<::GBA*>(m_core->board)->sio, &m_sio_driver, SIO_JOYBUS); + + m_sio_driver.core = this; + m_sio_driver.load = [](GBASIODriver* driver) { + static_cast(driver)->core->m_link_enabled = true; + return true; + }; + m_sio_driver.unload = [](GBASIODriver* driver) { + static_cast(driver)->core->m_link_enabled = false; + return true; + }; +} + +void Core::SetVideoBuffer() +{ + u32 width, height; + m_core->desiredVideoDimensions(m_core, &width, &height); + m_video_buffer.resize(width * height); + m_core->setVideoBuffer(m_core, m_video_buffer.data(), width); + if (auto host = m_host.lock()) + host->GameChanged(); +} + +void Core::SetSampleRates() +{ + m_core->setAudioBufferSize(m_core, SAMPLES); + blip_set_rates(m_core->getAudioChannel(m_core, 0), m_core->frequency(m_core), SAMPLE_RATE); + blip_set_rates(m_core->getAudioChannel(m_core, 1), m_core->frequency(m_core), SAMPLE_RATE); + g_sound_stream->GetMixer()->SetGBAInputSampleRates(m_device_number, SAMPLE_RATE); +} + +void Core::AddCallbacks() +{ + mCoreCallbacks callbacks{}; + callbacks.context = this; + callbacks.keysRead = [](void* context) { + auto core = static_cast(context); + core->m_core->setKeys(core->m_core, core->m_keys); + }; + callbacks.videoFrameEnded = [](void* context) { + auto core = static_cast(context); + if (auto host = core->m_host.lock()) + host->FrameEnded(core->m_video_buffer); + }; + m_core->addCoreCallbacks(m_core, &callbacks); +} + +void Core::SetAVStream() +{ + m_stream = {}; + m_stream.core = this; + m_stream.videoDimensionsChanged = [](mAVStream* stream, unsigned width, unsigned height) { + auto core = static_cast(stream)->core; + core->SetVideoBuffer(); + }; + m_stream.postAudioBuffer = [](mAVStream* stream, blip_t* left, blip_t* right) { + auto core = static_cast(stream)->core; + std::vector buffer(SAMPLES * 2); + blip_read_samples(left, &buffer[0], SAMPLES, 1); + blip_read_samples(right, &buffer[1], SAMPLES, 1); + g_sound_stream->GetMixer()->PushGBASamples(core->m_device_number, &buffer[0], SAMPLES); + }; + m_core->setAVStream(m_core, &m_stream); +} + +void Core::SetupEvent() +{ + m_event.context = this; + m_event.name = "Dolphin Sync"; + m_event.callback = [](mTiming* timing, void* context, u32 cycles_late) { + Core* core = static_cast(context); + if (core->m_core->platform(core->m_core) == mPLATFORM_GBA) + static_cast<::GBA*>(core->m_core->board)->earlyExit = true; + else if (core->m_core->platform(core->m_core) == mPLATFORM_GB) + static_cast<::GB*>(core->m_core->board)->earlyExit = true; + core->m_waiting_for_event = false; + }; + m_event.priority = 0x80; +} + +int Core::GetDeviceNumber() const +{ + return m_device_number; +} + +void Core::GetVideoDimensions(u32* width, u32* height) const +{ + if (!IsStarted()) + { + *width = GBA_VIDEO_HORIZONTAL_PIXELS; + *height = GBA_VIDEO_VERTICAL_PIXELS; + return; + } + + m_core->desiredVideoDimensions(m_core, width, height); +} + +std::string Core::GetGameTitle() const +{ + return m_game_title; +} + +void Core::SendJoybusCommand(u64 gc_ticks, int transfer_time, u8* buffer, u16 keys) +{ + if (!IsStarted()) + return; + + Command command{}; + command.ticks = gc_ticks; + command.transfer_time = transfer_time; + command.sync_only = buffer == nullptr; + if (buffer) + std::copy_n(buffer, command.buffer.size(), command.buffer.begin()); + command.keys = keys; + + if (m_thread) + { + std::lock_guard lock(m_queue_mutex); + m_command_queue.push(command); + m_idle = false; + m_command_cv.notify_one(); + } + else + { + RunCommand(command); + } +} + +std::vector Core::GetJoybusResponse() +{ + if (!IsStarted()) + return {}; + + if (m_thread) + { + std::unique_lock lock(m_response_mutex); + m_response_cv.wait(lock, [&] { return m_response_ready; }); + } + m_response_ready = false; + return m_response; +} + +void Core::Flush() +{ + if (!IsStarted() || !m_thread) + return; + std::unique_lock lock(m_queue_mutex); + m_response_cv.wait(lock, [&] { return m_idle; }); +} + +void Core::ThreadLoop() +{ + Common::SetCurrentThreadName(fmt::format("GBA{}", m_device_number + 1).c_str()); + std::unique_lock queue_lock(m_queue_mutex); + while (true) + { + m_command_cv.wait(queue_lock, [&] { return !m_command_queue.empty() || m_exit_loop; }); + if (m_exit_loop) + break; + Command command{m_command_queue.front()}; + m_command_queue.pop(); + queue_lock.unlock(); + + RunCommand(command); + + queue_lock.lock(); + if (m_command_queue.empty()) + m_idle = true; + m_response_cv.notify_one(); + } +} + +void Core::RunCommand(Command& command) +{ + m_keys = command.keys; + RunUntil(command.ticks); + if (!command.sync_only) + { + m_response.clear(); + if (m_link_enabled && !m_force_disconnect) + { + int recvd = GBASIOJOYSendCommand( + &m_sio_driver, static_cast(command.buffer[0]), &command.buffer[1]); + std::copy(command.buffer.begin() + 1, command.buffer.begin() + 1 + recvd, + std::back_inserter(m_response)); + } + + if (m_thread && !m_response_ready) + { + std::lock_guard response_lock(m_response_mutex); + m_response_ready = true; + m_response_cv.notify_one(); + } + else + { + m_response_ready = true; + } + } + if (command.transfer_time) + RunFor(command.transfer_time); +} + +void Core::RunUntil(u64 gc_ticks) +{ + if (static_cast(gc_ticks - m_last_gc_ticks) <= 0) + return; + + const u64 gc_frequency = SystemTimers::GetTicksPerSecond(); + const u32 core_frequency = GetCoreFrequency(m_core); + + mTimingSchedule(m_core->timing, &m_event, + static_cast((gc_ticks - m_last_gc_ticks) * core_frequency / gc_frequency)); + m_waiting_for_event = true; + + s32 begin_time = mTimingCurrentTime(m_core->timing); + while (m_waiting_for_event) + m_core->runLoop(m_core); + s32 end_time = mTimingCurrentTime(m_core->timing); + + u64 d = (static_cast(end_time - begin_time) * gc_frequency) + m_gc_ticks_remainder; + m_last_gc_ticks += d / core_frequency; + m_gc_ticks_remainder = d % core_frequency; +} + +void Core::RunFor(u64 gc_ticks) +{ + RunUntil(m_last_gc_ticks + gc_ticks); +} + +void Core::ImportState(std::string_view state_path) +{ + Flush(); + if (!IsStarted()) + return; + + std::vector core_state(m_core->stateSize(m_core)); + File::IOFile file(std::string(state_path), "rb"); + if (core_state.size() != file.GetSize()) + return; + + file.ReadBytes(core_state.data(), core_state.size()); + m_core->loadState(m_core, core_state.data()); +} + +void Core::ExportState(std::string_view state_path) +{ + Flush(); + if (!IsStarted()) + return; + + std::vector core_state(m_core->stateSize(m_core)); + m_core->saveState(m_core, core_state.data()); + + File::IOFile file(std::string(state_path), "wb"); + file.WriteBytes(core_state.data(), core_state.size()); +} + +void Core::DoState(PointerWrap& p) +{ + Flush(); + if (!IsStarted()) + { + ::Core::DisplayMessage(fmt::format("GBA{} core not started. Aborting.", m_device_number + 1), + 3000); + p.SetMode(PointerWrap::MODE_VERIFY); + return; + } + + bool has_rom = !m_rom_path.empty(); + p.Do(has_rom); + auto old_hash = m_rom_hash; + p.Do(m_rom_hash); + auto old_title = m_game_title; + p.Do(m_game_title); + + if (p.GetMode() == PointerWrap::MODE_READ && + (has_rom != !m_rom_path.empty() || + (has_rom && (old_hash != m_rom_hash || old_title != m_game_title)))) + { + ::Core::DisplayMessage( + fmt::format("Incompatible ROM state in GBA{}. Aborting load state.", m_device_number + 1), + 3000); + p.SetMode(PointerWrap::MODE_VERIFY); + return; + } + + p.Do(m_video_buffer); + p.Do(m_last_gc_ticks); + p.Do(m_gc_ticks_remainder); + p.Do(m_keys); + p.Do(m_link_enabled); + p.Do(m_response_ready); + p.Do(m_response); + + std::vector core_state; + core_state.resize(m_core->stateSize(m_core)); + + if (p.GetMode() == PointerWrap::MODE_WRITE || p.GetMode() == PointerWrap::MODE_VERIFY) + { + m_core->saveState(m_core, core_state.data()); + } + + p.Do(core_state); + + if (p.GetMode() == PointerWrap::MODE_READ && m_core->stateSize(m_core) == core_state.size()) + { + m_core->loadState(m_core, core_state.data()); + if (auto host = m_host.lock()) + host->FrameEnded(m_video_buffer); + } +} + +bool Core::GetRomInfo(const char* rom_path, std::array& hash, std::string& title) +{ + VFile* rom = OpenROM(rom_path); + if (!rom) + return false; + + hash = GetROMHash(rom); + + mCore* core = mCoreFindVF(rom); + if (!core) + { + rom->close(rom); + return false; + } + core->init(core); + if (!core->loadROM(core, rom)) + { + rom->close(rom); + return false; + } + + std::array game_title{}; + core->getGameTitle(core, game_title.data()); + title = game_title.data(); + + core->deinit(core); + return true; +} + +std::string Core::GetSavePath(std::string_view rom_path, int device_number) +{ + std::string save_path = + fmt::format("{}-{}.sav", rom_path.substr(0, rom_path.find_last_of('.')), device_number + 1); + + if (!Config::Get(Config::MAIN_GBA_SAVES_IN_ROM_PATH)) + { + save_path = + File::GetUserPath(D_GBASAVES_IDX) + save_path.substr(save_path.find_last_of("\\/") + 1); + } + + return save_path; +} +} // namespace HW::GBA diff --git a/Source/Core/Core/HW/GBACore.h b/Source/Core/Core/HW/GBACore.h new file mode 100644 index 0000000000..1a9f8c4222 --- /dev/null +++ b/Source/Core/Core/HW/GBACore.h @@ -0,0 +1,129 @@ +// Copyright 2021 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PYCPARSE // Remove static functions from the header +#include +#undef PYCPARSE +#include +#include + +#include "Common/CommonTypes.h" + +class GBAHostInterface; +class PointerWrap; + +namespace HW::GBA +{ +class Core; +struct SIODriver : GBASIODriver +{ + Core* core; +}; +struct AVStream : mAVStream +{ + Core* core; +}; + +class Core final +{ +public: + explicit Core(int device_number); + ~Core(); + + bool Start(u64 gc_ticks); + void Stop(); + void Reset(); + bool IsStarted() const; + + void SetHost(std::weak_ptr host); + void SetForceDisconnect(bool force_disconnect); + void EReaderQueueCard(std::string_view card_path); + + int GetDeviceNumber() const; + void GetVideoDimensions(u32* width, u32* height) const; + std::string GetGameTitle() const; + + void SendJoybusCommand(u64 gc_ticks, int transfer_time, u8* buffer, u16 keys); + std::vector GetJoybusResponse(); + + void ImportState(std::string_view state_path); + void ExportState(std::string_view state_path); + void DoState(PointerWrap& p); + + static bool GetRomInfo(const char* rom_path, std::array& hash, std::string& title); + static std::string GetSavePath(std::string_view rom_path, int device_number); + +private: + void ThreadLoop(); + void RunUntil(u64 gc_ticks); + void RunFor(u64 gc_ticks); + void Flush(); + + struct Command + { + u64 ticks; + int transfer_time; + bool sync_only; + std::array buffer; + u16 keys; + }; + void RunCommand(Command& command); + + bool LoadBIOS(const char* bios_path); + bool LoadSave(const char* save_path); + + void SetSIODriver(); + void SetVideoBuffer(); + void SetSampleRates(); + void AddCallbacks(); + void SetAVStream(); + void SetupEvent(); + + const int m_device_number; + + bool m_started = false; + std::string m_rom_path; + std::string m_save_path; + std::array m_rom_hash{}; + std::string m_game_title; + + mCore* m_core{}; + mTimingEvent m_event{}; + bool m_waiting_for_event = false; + SIODriver m_sio_driver{}; + AVStream m_stream{}; + std::vector m_video_buffer; + + u64 m_last_gc_ticks = 0; + u64 m_gc_ticks_remainder = 0; + u16 m_keys = 0; + bool m_link_enabled = false; + bool m_force_disconnect = false; + + std::weak_ptr m_host; + + std::unique_ptr m_thread; + bool m_exit_loop = false; + bool m_idle = false; + std::mutex m_queue_mutex; + std::condition_variable m_command_cv; + std::queue m_command_queue; + + std::mutex m_response_mutex; + std::condition_variable m_response_cv; + bool m_response_ready = false; + std::vector m_response; +}; +} // namespace HW::GBA diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 45cc52e6e3..7835689d55 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -264,6 +264,7 @@ + @@ -846,6 +847,7 @@ + From d0f0b4c0e054b2177e524fba274dbefa98dfa7b7 Mon Sep 17 00:00:00 2001 From: Bonta <40473493+Bonta0@users.noreply.github.com> Date: Sun, 4 Jul 2021 13:15:37 +0200 Subject: [PATCH 13/18] SI: Implement GBAEmu device --- Source/Core/AudioCommon/AudioCommon.cpp | 3 +- Source/Core/Core/CMakeLists.txt | 2 + Source/Core/Core/HW/SI/SI.cpp | 2 +- Source/Core/Core/HW/SI/SI_Device.cpp | 12 ++ Source/Core/Core/HW/SI/SI_Device.h | 1 + Source/Core/Core/HW/SI/SI_DeviceGBAEmu.cpp | 167 +++++++++++++++++++++ Source/Core/Core/HW/SI/SI_DeviceGBAEmu.h | 49 ++++++ Source/Core/Core/State.cpp | 2 +- Source/Core/DolphinLib.props | 2 + 9 files changed, 237 insertions(+), 3 deletions(-) create mode 100644 Source/Core/Core/HW/SI/SI_DeviceGBAEmu.cpp create mode 100644 Source/Core/Core/HW/SI/SI_DeviceGBAEmu.h diff --git a/Source/Core/AudioCommon/AudioCommon.cpp b/Source/Core/AudioCommon/AudioCommon.cpp index cfa5383c7c..6623b5ad8b 100644 --- a/Source/Core/AudioCommon/AudioCommon.cpp +++ b/Source/Core/AudioCommon/AudioCommon.cpp @@ -68,7 +68,8 @@ void InitSoundStream() void PostInitSoundStream() { - // This needs to be called after AudioInterface::Init where input sample rates are set + // This needs to be called after AudioInterface::Init and SerialInterface::Init (for GBA devices) + // where input sample rates are set UpdateSoundStream(); SetSoundStreamRunning(true); diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index 65eb857982..0ae84e2bdc 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -624,6 +624,8 @@ if(USE_MGBA) target_sources(core PRIVATE HW/GBACore.cpp HW/GBACore.h + HW/SI/SI_DeviceGBAEmu.cpp + HW/SI/SI_DeviceGBAEmu.h ) target_link_libraries(core PUBLIC mGBA::mgba) target_compile_definitions(core PUBLIC -DHAS_LIBMGBA) diff --git a/Source/Core/Core/HW/SI/SI.cpp b/Source/Core/Core/HW/SI/SI.cpp index 14e586fc05..caaa95cbae 100644 --- a/Source/Core/Core/HW/SI/SI.cpp +++ b/Source/Core/Core/HW/SI/SI.cpp @@ -707,7 +707,7 @@ void UpdateDevices() SIDevices GetDeviceType(int channel) { - if (channel < 0 || channel > 3) + if (channel < 0 || channel >= MAX_SI_CHANNELS || !s_channel[channel].device) return SIDEVICE_NONE; return s_channel[channel].device->GetDeviceType(); diff --git a/Source/Core/Core/HW/SI/SI_Device.cpp b/Source/Core/Core/HW/SI/SI_Device.cpp index 401b1178a7..fa823ff8ad 100644 --- a/Source/Core/Core/HW/SI/SI_Device.cpp +++ b/Source/Core/Core/HW/SI/SI_Device.cpp @@ -13,8 +13,12 @@ #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" +#include "Common/MsgHandler.h" #include "Core/HW/SI/SI_DeviceDanceMat.h" #include "Core/HW/SI/SI_DeviceGBA.h" +#ifdef HAS_LIBMGBA +#include "Core/HW/SI/SI_DeviceGBAEmu.h" +#endif #include "Core/HW/SI/SI_DeviceGCAdapter.h" #include "Core/HW/SI/SI_DeviceGCController.h" #include "Core/HW/SI/SI_DeviceGCSteeringWheel.h" @@ -187,6 +191,14 @@ std::unique_ptr SIDevice_Create(const SIDevices device, const int por case SIDEVICE_GC_GBA: return std::make_unique(device, port_number); + case SIDEVICE_GC_GBA_EMULATED: +#ifdef HAS_LIBMGBA + return std::make_unique(device, port_number); +#else + PanicAlertT("Error: This build does not support emulated GBA controllers"); + return std::make_unique(device, port_number); +#endif + case SIDEVICE_GC_KEYBOARD: return std::make_unique(device, port_number); diff --git a/Source/Core/Core/HW/SI/SI_Device.h b/Source/Core/Core/HW/SI/SI_Device.h index c2ae9bef87..d109432b90 100644 --- a/Source/Core/Core/HW/SI/SI_Device.h +++ b/Source/Core/Core/HW/SI/SI_Device.h @@ -93,6 +93,7 @@ enum SIDevices : int // It's kept here so that values below will stay constant. SIDEVICE_AM_BASEBOARD, SIDEVICE_WIIU_ADAPTER, + SIDEVICE_GC_GBA_EMULATED, // Not a valid device. Used for checking whether enum values are valid. SIDEVICE_COUNT, }; diff --git a/Source/Core/Core/HW/SI/SI_DeviceGBAEmu.cpp b/Source/Core/Core/HW/SI/SI_DeviceGBAEmu.cpp new file mode 100644 index 0000000000..aaf971b45e --- /dev/null +++ b/Source/Core/Core/HW/SI/SI_DeviceGBAEmu.cpp @@ -0,0 +1,167 @@ +// Copyright 2021 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "Common/ChunkFile.h" +#include "Common/CommonTypes.h" +#include "Common/Logging/Log.h" +#include "Common/Swap.h" +#include "Core/Core.h" +#include "Core/CoreTiming.h" +#include "Core/HW/GBACore.h" +#include "Core/HW/GBAPad.h" +#include "Core/HW/SI/SI.h" +#include "Core/HW/SI/SI_DeviceGBAEmu.h" +#include "Core/HW/SI/SI_DeviceGCController.h" +#include "Core/HW/SystemTimers.h" +#include "Core/Host.h" +#include "Core/NetPlayProto.h" + +namespace SerialInterface +{ +static s64 GetSyncInterval() +{ + return SystemTimers::GetTicksPerSecond() / 1000; +} + +CSIDevice_GBAEmu::CSIDevice_GBAEmu(SIDevices device, int device_number) + : ISIDevice(device, device_number) +{ + m_core = std::make_shared(m_device_number); + m_core->Start(CoreTiming::GetTicks()); + m_gbahost = Host_CreateGBAHost(m_core); + m_core->SetHost(m_gbahost); + ScheduleEvent(m_device_number, GetSyncInterval()); +} + +CSIDevice_GBAEmu::~CSIDevice_GBAEmu() +{ + RemoveEvent(m_device_number); + m_core->Stop(); + m_gbahost.reset(); + m_core.reset(); +} + +int CSIDevice_GBAEmu::RunBuffer(u8* buffer, int request_length) +{ + switch (m_next_action) + { + case NextAction::SendCommand: + { +#ifdef _DEBUG + NOTICE_LOG_FMT(SERIALINTERFACE, "{} cmd {:02x} [> {:02x}{:02x}{:02x}{:02x}]", m_device_number, + buffer[0], buffer[1], buffer[2], buffer[3], buffer[4]); +#endif + m_last_cmd = static_cast(buffer[0]); + m_timestamp_sent = CoreTiming::GetTicks(); + m_core->SendJoybusCommand(m_timestamp_sent, TransferInterval(), buffer, m_keys); + + RemoveEvent(m_device_number); + ScheduleEvent(m_device_number, TransferInterval() + GetSyncInterval()); + for (int i = 0; i < MAX_SI_CHANNELS; ++i) + { + if (i == m_device_number || SerialInterface::GetDeviceType(i) != GetDeviceType()) + continue; + RemoveEvent(i); + ScheduleEvent(i, 0, static_cast(TransferInterval())); + } + + m_next_action = NextAction::WaitTransferTime; + [[fallthrough]]; + } + + case NextAction::WaitTransferTime: + { + int elapsed_time = static_cast(CoreTiming::GetTicks() - m_timestamp_sent); + // Tell SI to ask again after TransferInterval() cycles + if (TransferInterval() > elapsed_time) + return 0; + m_next_action = NextAction::ReceiveResponse; + [[fallthrough]]; + } + + case NextAction::ReceiveResponse: + { + m_next_action = NextAction::SendCommand; + + std::vector response = m_core->GetJoybusResponse(); + if (response.empty()) + return -1; + std::copy(response.begin(), response.end(), buffer); + +#ifdef _DEBUG + const Common::Log::LOG_LEVELS log_level = + (m_last_cmd == EBufferCommands::CMD_STATUS || m_last_cmd == EBufferCommands::CMD_RESET) ? + Common::Log::LERROR : + Common::Log::LWARNING; + GENERIC_LOG_FMT(Common::Log::SERIALINTERFACE, log_level, + "{} [< {:02x}{:02x}{:02x}{:02x}{:02x}] ({})", + m_device_number, buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], + response.size()); +#endif + + return static_cast(response.size()); + } + } + + // This should never happen, but appease MSVC which thinks it might. + ERROR_LOG_FMT(SERIALINTERFACE, "Unknown state {}\n", m_next_action); + return -1; +} + +int CSIDevice_GBAEmu::TransferInterval() +{ + return SIDevice_GetGBATransferTime(m_last_cmd); +} + +bool CSIDevice_GBAEmu::GetData(u32& hi, u32& low) +{ + GCPadStatus pad_status{}; + if (!NetPlay::IsNetPlayRunning()) + pad_status = Pad::GetGBAStatus(m_device_number); + SerialInterface::CSIDevice_GCController::HandleMoviePadStatus(m_device_number, &pad_status); + + static constexpr std::array buttons_map = { + PadButton::PAD_BUTTON_A, // A + PadButton::PAD_BUTTON_B, // B + PadButton::PAD_TRIGGER_Z, // Select + PadButton::PAD_BUTTON_START, // Start + PadButton::PAD_BUTTON_RIGHT, // Right + PadButton::PAD_BUTTON_LEFT, // Left + PadButton::PAD_BUTTON_UP, // Up + PadButton::PAD_BUTTON_DOWN, // Down + PadButton::PAD_TRIGGER_R, // R + PadButton::PAD_TRIGGER_L, // L + }; + + m_keys = 0; + for (size_t i = 0; i < buttons_map.size(); ++i) + m_keys |= static_cast(static_cast((pad_status.button & buttons_map[i]))) << i; + + // Use X button as a reset signal for NetPlay/Movies + if (pad_status.button & PadButton::PAD_BUTTON_X) + m_core->Reset(); + + return false; +} + +void CSIDevice_GBAEmu::SendCommand(u32 command, u8 poll) +{ +} + +void CSIDevice_GBAEmu::DoState(PointerWrap& p) +{ + p.Do(m_next_action); + p.Do(m_last_cmd); + p.Do(m_timestamp_sent); + p.Do(m_keys); + m_core->DoState(p); +} + +void CSIDevice_GBAEmu::OnEvent(u64 userdata, s64 cycles_late) +{ + m_core->SendJoybusCommand(CoreTiming::GetTicks() + userdata, 0, nullptr, m_keys); + ScheduleEvent(m_device_number, userdata + GetSyncInterval()); +} +} // namespace SerialInterface diff --git a/Source/Core/Core/HW/SI/SI_DeviceGBAEmu.h b/Source/Core/Core/HW/SI/SI_DeviceGBAEmu.h new file mode 100644 index 0000000000..d19c43e27b --- /dev/null +++ b/Source/Core/Core/HW/SI/SI_DeviceGBAEmu.h @@ -0,0 +1,49 @@ +// Copyright 2021 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "Common/CommonTypes.h" +#include "Core/HW/SI/SI_Device.h" + +namespace HW::GBA +{ +class Core; +} // namespace HW::GBA + +class GBAHostInterface; + +namespace SerialInterface +{ +class CSIDevice_GBAEmu : public ISIDevice +{ +public: + CSIDevice_GBAEmu(SIDevices device, int device_number); + ~CSIDevice_GBAEmu(); + + int RunBuffer(u8* buffer, int request_length) override; + int TransferInterval() override; + bool GetData(u32& hi, u32& low) override; + void SendCommand(u32 command, u8 poll) override; + void DoState(PointerWrap& p) override; + void OnEvent(u64 userdata, s64 cycles_late) override; + +private: + enum class NextAction + { + SendCommand, + WaitTransferTime, + ReceiveResponse + }; + + NextAction m_next_action = NextAction::SendCommand; + EBufferCommands m_last_cmd{}; + u64 m_timestamp_sent = 0; + u16 m_keys = 0; + + std::shared_ptr m_core; + std::shared_ptr m_gbahost; +}; +} // namespace SerialInterface diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index 5d5f6e9d33..77e31e1ac0 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -73,7 +73,7 @@ static Common::Event g_compressAndDumpStateSyncEvent; static std::thread g_save_thread; // Don't forget to increase this after doing changes on the savestate system -constexpr u32 STATE_VERSION = 132; // Last changed in PR 9532 +constexpr u32 STATE_VERSION = 133; // Last changed in PR 9600 // Maps savestate versions to Dolphin versions. // Versions after 42 don't need to be added to this list, diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 7835689d55..0f0dfe227b 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -287,6 +287,7 @@ + @@ -868,6 +869,7 @@ + From 9b80fb7deb676485fb780eadb56ea389aa4ec9d0 Mon Sep 17 00:00:00 2001 From: Bonta <40473493+Bonta0@users.noreply.github.com> Date: Sun, 4 Jul 2021 13:17:32 +0200 Subject: [PATCH 14/18] Qt: GBA Pad config --- Source/Core/DolphinQt/CMakeLists.txt | 2 + .../Config/GamecubeControllersWidget.cpp | 88 +++++++++++-------- .../DolphinQt/Config/Mapping/GBAPadEmu.cpp | 45 ++++++++++ .../Core/DolphinQt/Config/Mapping/GBAPadEmu.h | 20 +++++ .../Config/Mapping/MappingWindow.cpp | 8 +- .../DolphinQt/Config/Mapping/MappingWindow.h | 1 + Source/Core/DolphinQt/DolphinQt.vcxproj | 2 + 7 files changed, 128 insertions(+), 38 deletions(-) create mode 100644 Source/Core/DolphinQt/Config/Mapping/GBAPadEmu.cpp create mode 100644 Source/Core/DolphinQt/Config/Mapping/GBAPadEmu.h diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index 9ae4714317..004eb2b86f 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -124,6 +124,8 @@ add_executable(dolphin-emu Config/Mapping/FreeLookGeneral.h Config/Mapping/FreeLookRotation.cpp Config/Mapping/FreeLookRotation.h + Config/Mapping/GBAPadEmu.cpp + Config/Mapping/GBAPadEmu.h Config/Mapping/GCKeyboardEmu.cpp Config/Mapping/GCKeyboardEmu.h Config/Mapping/GCMicrophone.cpp diff --git a/Source/Core/DolphinQt/Config/GamecubeControllersWidget.cpp b/Source/Core/DolphinQt/Config/GamecubeControllersWidget.cpp index 9a9fdd2be0..d940f46461 100644 --- a/Source/Core/DolphinQt/Config/GamecubeControllersWidget.cpp +++ b/Source/Core/DolphinQt/Config/GamecubeControllersWidget.cpp @@ -10,8 +10,9 @@ #include #include -#include #include +#include +#include #include "Core/ConfigManager.h" #include "Core/Core.h" @@ -24,23 +25,37 @@ #include "InputCommon/GCAdapter.h" -static const std::map s_gc_types = { - {SerialInterface::SIDEVICE_NONE, 0}, {SerialInterface::SIDEVICE_GC_CONTROLLER, 1}, - {SerialInterface::SIDEVICE_WIIU_ADAPTER, 2}, {SerialInterface::SIDEVICE_GC_STEERING, 3}, - {SerialInterface::SIDEVICE_DANCEMAT, 4}, {SerialInterface::SIDEVICE_GC_TARUKONGA, 5}, - {SerialInterface::SIDEVICE_GC_GBA, 6}, {SerialInterface::SIDEVICE_GC_KEYBOARD, 7}}; +static const std::vector> s_gc_types = { + {SerialInterface::SIDEVICE_NONE, _trans("None")}, + {SerialInterface::SIDEVICE_GC_CONTROLLER, _trans("Standard Controller")}, + {SerialInterface::SIDEVICE_WIIU_ADAPTER, _trans("GameCube Adapter for Wii U")}, + {SerialInterface::SIDEVICE_GC_STEERING, _trans("Steering Wheel")}, + {SerialInterface::SIDEVICE_DANCEMAT, _trans("Dance Mat")}, + {SerialInterface::SIDEVICE_GC_TARUKONGA, _trans("DK Bongos")}, +#ifdef HAS_LIBMGBA + {SerialInterface::SIDEVICE_GC_GBA_EMULATED, _trans("GBA (Integrated)")}, +#endif + {SerialInterface::SIDEVICE_GC_GBA, _trans("GBA (TCP)")}, + {SerialInterface::SIDEVICE_GC_KEYBOARD, _trans("Keyboard")}}; static std::optional ToGCMenuIndex(const SerialInterface::SIDevices sidevice) { - auto it = s_gc_types.find(sidevice); - return it != s_gc_types.end() ? it->second : std::optional(); + for (size_t i = 0; i < s_gc_types.size(); ++i) + { + if (s_gc_types[i].first == sidevice) + return static_cast(i); + } + return {}; } -static std::optional FromGCMenuIndex(const int menudevice) +static SerialInterface::SIDevices FromGCMenuIndex(const int menudevice) { - auto it = std::find_if(s_gc_types.begin(), s_gc_types.end(), - [=](auto pair) { return pair.second == menudevice; }); - return it != s_gc_types.end() ? it->first : std::optional(); + return s_gc_types[menudevice].first; +} + +static bool IsConfigurable(SerialInterface::SIDevices sidevice) +{ + return sidevice != SerialInterface::SIDEVICE_NONE && sidevice != SerialInterface::SIDEVICE_GC_GBA; } GamecubeControllersWidget::GamecubeControllersWidget(QWidget* parent) : QWidget(parent) @@ -63,11 +78,9 @@ void GamecubeControllersWidget::CreateLayout() auto* gc_box = m_gc_controller_boxes[i] = new QComboBox(); auto* gc_button = m_gc_buttons[i] = new QPushButton(tr("Configure")); - for (const auto& item : - {tr("None"), tr("Standard Controller"), tr("GameCube Adapter for Wii U"), - tr("Steering Wheel"), tr("Dance Mat"), tr("DK Bongos"), tr("GBA"), tr("Keyboard")}) + for (const auto& item : s_gc_types) { - gc_box->addItem(item); + gc_box->addItem(tr(item.second)); } int controller_row = m_gc_layout->rowCount(); @@ -105,8 +118,8 @@ void GamecubeControllersWidget::OnGCTypeChanged(int type) { if (m_gc_controller_boxes[i] == box) { - const int index = box->currentIndex(); - m_gc_buttons[i]->setEnabled(index != 0 && index != 6); + const SerialInterface::SIDevices si_device = FromGCMenuIndex(box->currentIndex()); + m_gc_buttons[i]->setEnabled(IsConfigurable(si_device)); return; } } @@ -125,27 +138,30 @@ void GamecubeControllersWidget::OnGCPadConfigure() MappingWindow::Type type; - switch (m_gc_controller_boxes[index]->currentIndex()) + switch (FromGCMenuIndex(m_gc_controller_boxes[index]->currentIndex())) { - case 0: // None - case 6: // GBA + case SerialInterface::SIDEVICE_NONE: + case SerialInterface::SIDEVICE_GC_GBA: return; - case 1: // Standard Controller + case SerialInterface::SIDEVICE_GC_CONTROLLER: type = MappingWindow::Type::MAPPING_GCPAD; break; - case 2: // GameCube Adapter for Wii U + case SerialInterface::SIDEVICE_WIIU_ADAPTER: GCPadWiiUConfigDialog(static_cast(index), this).exec(); return; - case 3: // Steering Wheel + case SerialInterface::SIDEVICE_GC_STEERING: type = MappingWindow::Type::MAPPING_GC_STEERINGWHEEL; break; - case 4: // Dance Mat + case SerialInterface::SIDEVICE_DANCEMAT: type = MappingWindow::Type::MAPPING_GC_DANCEMAT; break; - case 5: // DK Bongos + case SerialInterface::SIDEVICE_GC_TARUKONGA: type = MappingWindow::Type::MAPPING_GC_BONGOS; break; - case 7: // Keyboard + case SerialInterface::SIDEVICE_GC_GBA_EMULATED: + type = MappingWindow::Type::MAPPING_GC_GBA; + break; + case SerialInterface::SIDEVICE_GC_KEYBOARD: type = MappingWindow::Type::MAPPING_GC_KEYBOARD; break; default: @@ -162,11 +178,12 @@ void GamecubeControllersWidget::LoadSettings() { for (size_t i = 0; i < m_gc_groups.size(); i++) { - const std::optional gc_index = ToGCMenuIndex(SConfig::GetInstance().m_SIDevice[i]); + const SerialInterface::SIDevices si_device = SConfig::GetInstance().m_SIDevice[i]; + const std::optional gc_index = ToGCMenuIndex(si_device); if (gc_index) { m_gc_controller_boxes[i]->setCurrentIndex(*gc_index); - m_gc_buttons[i]->setEnabled(*gc_index != 0 && *gc_index != 6); + m_gc_buttons[i]->setEnabled(IsConfigurable(si_device)); } } } @@ -176,16 +193,13 @@ void GamecubeControllersWidget::SaveSettings() for (size_t i = 0; i < m_gc_groups.size(); i++) { const int index = m_gc_controller_boxes[i]->currentIndex(); - const std::optional si_device = FromGCMenuIndex(index); - if (si_device) - { - SConfig::GetInstance().m_SIDevice[i] = *si_device; + const SerialInterface::SIDevices si_device = FromGCMenuIndex(index); + SConfig::GetInstance().m_SIDevice[i] = si_device; - if (Core::IsRunning()) - SerialInterface::ChangeDevice(*si_device, static_cast(i)); - } + if (Core::IsRunning()) + SerialInterface::ChangeDevice(si_device, static_cast(i)); - m_gc_buttons[i]->setEnabled(index != 0 && index != 6); + m_gc_buttons[i]->setEnabled(IsConfigurable(si_device)); } if (GCAdapter::UseAdapter()) diff --git a/Source/Core/DolphinQt/Config/Mapping/GBAPadEmu.cpp b/Source/Core/DolphinQt/Config/Mapping/GBAPadEmu.cpp new file mode 100644 index 0000000000..648d5aec64 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Mapping/GBAPadEmu.cpp @@ -0,0 +1,45 @@ +// Copyright 2021 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "DolphinQt/Config/Mapping/GBAPadEmu.h" + +#include +#include + +#include "Core/HW/GBAPad.h" +#include "Core/HW/GBAPadEmu.h" +#include "InputCommon/InputConfig.h" + +GBAPadEmu::GBAPadEmu(MappingWindow* window) : MappingWidget(window) +{ + CreateMainLayout(); +} + +void GBAPadEmu::CreateMainLayout() +{ + auto* layout = new QGridLayout; + + layout->addWidget( + CreateControlsBox(tr("D-Pad"), Pad::GetGBAGroup(GetPort(), GBAPadGroup::DPad), 2), 0, 0, -1, + 1); + layout->addWidget( + CreateControlsBox(tr("Buttons"), Pad::GetGBAGroup(GetPort(), GBAPadGroup::Buttons), 2), 0, 1, + -1, 1); + + setLayout(layout); +} + +void GBAPadEmu::LoadSettings() +{ + Pad::LoadGBAConfig(); +} + +void GBAPadEmu::SaveSettings() +{ + Pad::GetGBAConfig()->SaveConfig(); +} + +InputConfig* GBAPadEmu::GetConfig() +{ + return Pad::GetGBAConfig(); +} diff --git a/Source/Core/DolphinQt/Config/Mapping/GBAPadEmu.h b/Source/Core/DolphinQt/Config/Mapping/GBAPadEmu.h new file mode 100644 index 0000000000..5e06121c70 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Mapping/GBAPadEmu.h @@ -0,0 +1,20 @@ +// Copyright 2021 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "DolphinQt/Config/Mapping/MappingWidget.h" + +class GBAPadEmu final : public MappingWidget +{ + Q_OBJECT +public: + explicit GBAPadEmu(MappingWindow* window); + + InputConfig* GetConfig() override; + +private: + void LoadSettings() override; + void SaveSettings() override; + void CreateMainLayout(); +}; diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp index 10bf604268..3191f42fd9 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp @@ -23,6 +23,7 @@ #include "DolphinQt/Config/Mapping/FreeLookGeneral.h" #include "DolphinQt/Config/Mapping/FreeLookRotation.h" +#include "DolphinQt/Config/Mapping/GBAPadEmu.h" #include "DolphinQt/Config/Mapping/GCKeyboardEmu.h" #include "DolphinQt/Config/Mapping/GCMicrophone.h" #include "DolphinQt/Config/Mapping/GCPadEmu.h" @@ -374,10 +375,15 @@ void MappingWindow::SetMappingType(MappingWindow::Type type) switch (type) { + case Type::MAPPING_GC_GBA: + widget = new GBAPadEmu(this); + setWindowTitle(tr("GameBoy Advance at Port %1").arg(GetPort() + 1)); + AddWidget(tr("GameBoy Advance"), widget); + break; case Type::MAPPING_GC_KEYBOARD: widget = new GCKeyboardEmu(this); - AddWidget(tr("GameCube Keyboard"), widget); setWindowTitle(tr("GameCube Keyboard at Port %1").arg(GetPort() + 1)); + AddWidget(tr("GameCube Keyboard"), widget); break; case Type::MAPPING_GC_BONGOS: case Type::MAPPING_GC_STEERINGWHEEL: diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h index 2724170397..493c07f72d 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h @@ -34,6 +34,7 @@ public: // GameCube MAPPING_GC_BONGOS, MAPPING_GC_DANCEMAT, + MAPPING_GC_GBA, MAPPING_GC_KEYBOARD, MAPPING_GCPAD, MAPPING_GC_STEERINGWHEEL, diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index 18a838896e..e8e3822cd1 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -81,6 +81,7 @@ + @@ -256,6 +257,7 @@ + From d6f86e1754730d1e4670f4064c312a73a30679b5 Mon Sep 17 00:00:00 2001 From: Bonta <40473493+Bonta0@users.noreply.github.com> Date: Sun, 4 Jul 2021 13:23:30 +0200 Subject: [PATCH 15/18] Qt: Implement GBA host and widget --- Source/Core/DolphinQt/CMakeLists.txt | 9 + Source/Core/DolphinQt/DolphinQt.vcxproj | 4 + Source/Core/DolphinQt/GBAHost.cpp | 61 ++++ Source/Core/DolphinQt/GBAHost.h | 28 ++ Source/Core/DolphinQt/GBAWidget.cpp | 421 ++++++++++++++++++++++++ Source/Core/DolphinQt/GBAWidget.h | 96 ++++++ Source/Core/DolphinQt/Host.cpp | 16 +- Source/Core/DolphinQt/Host.h | 1 + 8 files changed, 635 insertions(+), 1 deletion(-) create mode 100644 Source/Core/DolphinQt/GBAHost.cpp create mode 100644 Source/Core/DolphinQt/GBAHost.h create mode 100644 Source/Core/DolphinQt/GBAWidget.cpp create mode 100644 Source/Core/DolphinQt/GBAWidget.h diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index 004eb2b86f..3d3bf12ed4 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -540,6 +540,15 @@ else() install(TARGETS dolphin-emu RUNTIME DESTINATION ${bindir}) endif() +if(USE_MGBA) + target_sources(dolphin-emu PRIVATE + GBAHost.cpp + GBAHost.h + GBAWidget.cpp + GBAWidget.h + ) +endif() + if(USE_DISCORD_PRESENCE) target_compile_definitions(dolphin-emu PRIVATE -DUSE_DISCORD_PRESENCE) endif() diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index e8e3822cd1..3647f255df 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -142,6 +142,8 @@ + + @@ -208,6 +210,7 @@ + @@ -313,6 +316,7 @@ + diff --git a/Source/Core/DolphinQt/GBAHost.cpp b/Source/Core/DolphinQt/GBAHost.cpp new file mode 100644 index 0000000000..918426e320 --- /dev/null +++ b/Source/Core/DolphinQt/GBAHost.cpp @@ -0,0 +1,61 @@ +// Copyright 2021 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "DolphinQt/GBAHost.h" + +#include + +#include "Core/HW/GBACore.h" +#include "DolphinQt/GBAWidget.h" +#include "DolphinQt/QtUtils/QueueOnObject.h" + +GBAHost::GBAHost(std::weak_ptr core) +{ + m_widget_controller = new GBAWidgetController(); + m_widget_controller->moveToThread(qApp->thread()); + m_core = std::move(core); + auto core_ptr = m_core.lock(); + + int device_number = core_ptr->GetDeviceNumber(); + std::string game_title = core_ptr->GetGameTitle(); + u32 width, height; + core_ptr->GetVideoDimensions(&width, &height); + + QueueOnObject(m_widget_controller, [widget_controller = m_widget_controller, core = m_core, + device_number, game_title, width, height] { + widget_controller->Create(core, device_number, game_title, width, height); + }); +} + +GBAHost::~GBAHost() +{ + m_widget_controller->deleteLater(); +} + +void GBAHost::GameChanged() +{ + auto core_ptr = m_core.lock(); + if (!core_ptr || !core_ptr->IsStarted()) + return; + + std::string game_title = core_ptr->GetGameTitle(); + u32 width, height; + core_ptr->GetVideoDimensions(&width, &height); + + QueueOnObject(m_widget_controller, + [widget_controller = m_widget_controller, game_title, width, height] { + widget_controller->GameChanged(game_title, width, height); + }); +} + +void GBAHost::FrameEnded(const std::vector& video_buffer) +{ + QueueOnObject(m_widget_controller, [widget_controller = m_widget_controller, video_buffer] { + widget_controller->FrameEnded(video_buffer); + }); +} + +std::unique_ptr Host_CreateGBAHost(std::weak_ptr core) +{ + return std::make_unique(core); +} diff --git a/Source/Core/DolphinQt/GBAHost.h b/Source/Core/DolphinQt/GBAHost.h new file mode 100644 index 0000000000..99e28bd2af --- /dev/null +++ b/Source/Core/DolphinQt/GBAHost.h @@ -0,0 +1,28 @@ +// Copyright 2021 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "Core/Host.h" + +namespace HW::GBA +{ +class Core; +} // namespace HW::GBA + +class GBAWidgetController; + +class GBAHost : public GBAHostInterface +{ +public: + explicit GBAHost(std::weak_ptr core); + ~GBAHost(); + void GameChanged() override; + void FrameEnded(const std::vector& video_buffer) override; + +private: + GBAWidgetController* m_widget_controller{}; + std::weak_ptr m_core; +}; diff --git a/Source/Core/DolphinQt/GBAWidget.cpp b/Source/Core/DolphinQt/GBAWidget.cpp new file mode 100644 index 0000000000..df9423a67f --- /dev/null +++ b/Source/Core/DolphinQt/GBAWidget.cpp @@ -0,0 +1,421 @@ +// Copyright 2021 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "DolphinQt/GBAWidget.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AudioCommon/AudioCommon.h" +#include "Core/Config/MainSettings.h" +#include "Core/Core.h" +#include "Core/CoreTiming.h" +#include "Core/HW/GBACore.h" +#include "Core/HW/GBAPad.h" +#include "Core/HW/SI/SI.h" +#include "Core/HW/SI/SI_Device.h" +#include "Core/Movie.h" +#include "Core/NetPlayProto.h" +#include "DolphinQt/QtUtils/ModalMessageBox.h" +#include "DolphinQt/Resources.h" +#include "DolphinQt/Settings.h" +#include "DolphinQt/Settings/GameCubePane.h" + +static void RestartCore(const std::weak_ptr& core, std::string_view rom_path = {}) +{ + Core::RunOnCPUThread( + [core, rom_path = std::string(rom_path)] { + if (auto core_ptr = core.lock()) + { + auto& info = Config::MAIN_GBA_ROM_PATHS[core_ptr->GetDeviceNumber()]; + core_ptr->Stop(); + Config::SetCurrent(info, rom_path); + if (core_ptr->Start(CoreTiming::GetTicks())) + return; + Config::SetCurrent(info, Config::GetBase(info)); + core_ptr->Start(CoreTiming::GetTicks()); + } + }, + false); +} + +GBAWidget::GBAWidget(std::weak_ptr core, int device_number, + std::string_view game_title, int width, int height, QWidget* parent, + Qt::WindowFlags flags) + : QWidget(parent, flags), m_core(std::move(core)), m_device_number(device_number), + m_local_pad(device_number), m_game_title(game_title), m_width(width), m_height(height), + m_is_local_pad(true), m_volume(0), m_muted(false), m_force_disconnect(false) +{ + bool visible = true; + + setWindowIcon(Resources::GetAppIcon()); + setAcceptDrops(true); + resize(m_width, m_height); + setVisible(visible); + + SetVolume(100); + if (!visible) + ToggleMute(); + + LoadGeometry(); + UpdateTitle(); +} + +GBAWidget::~GBAWidget() +{ + SaveGeometry(); +} + +void GBAWidget::GameChanged(std::string_view game_title, int width, int height) +{ + m_game_title = game_title; + m_width = width; + m_height = height; + UpdateTitle(); + update(); +} + +void GBAWidget::SetVideoBuffer(std::vector video_buffer) +{ + m_video_buffer = std::move(video_buffer); + update(); +} + +void GBAWidget::SetVolume(int volume) +{ + m_muted = false; + m_volume = std::clamp(volume, 0, 100); + UpdateVolume(); +} + +void GBAWidget::VolumeDown() +{ + SetVolume(m_volume - 10); +} + +void GBAWidget::VolumeUp() +{ + SetVolume(m_volume + 10); +} + +bool GBAWidget::IsMuted() +{ + return m_muted; +} + +void GBAWidget::ToggleMute() +{ + m_muted = !m_muted; + UpdateVolume(); +} + +void GBAWidget::ToggleDisconnect() +{ + if (!CanControlCore()) + return; + + m_force_disconnect = !m_force_disconnect; + + Core::RunOnCPUThread( + [core = m_core, force_disconnect = m_force_disconnect] { + if (auto core_ptr = core.lock()) + core_ptr->SetForceDisconnect(force_disconnect); + }, + false); +} + +void GBAWidget::LoadROM() +{ + if (!CanControlCore()) + return; + + std::string rom_path = GameCubePane::GetOpenGBARom(""); + if (rom_path.empty()) + return; + + RestartCore(m_core, rom_path); +} + +void GBAWidget::UnloadROM() +{ + if (!CanControlCore() || m_game_title.empty()) + return; + + RestartCore(m_core); +} + +void GBAWidget::ResetCore() +{ + if (!CanResetCore()) + return; + + Pad::SetGBAReset(m_local_pad, true); +} + +void GBAWidget::DoState(bool export_state) +{ + if (!CanControlCore() && !export_state) + return; + + QString state_path = QDir::toNativeSeparators( + (export_state ? QFileDialog::getSaveFileName : QFileDialog::getOpenFileName)( + this, tr("Select a File"), QString(), + tr("mGBA Save States (*.ss0 *.ss1 *.ss2 *.ss3 *.ss4 *.ss5 *.ss6 *.ss7 *.ss8 *.ss9);;" + "All Files (*)"), + nullptr, QFileDialog::Options())); + + if (state_path.isEmpty()) + return; + + Core::RunOnCPUThread( + [export_state, core = m_core, state_path = state_path.toStdString()] { + if (auto core_ptr = core.lock()) + { + if (export_state) + core_ptr->ExportState(state_path); + else + core_ptr->ImportState(state_path); + } + }, + false); +} + +void GBAWidget::Resize(int scale) +{ + resize(m_width * scale, m_height * scale); +} + +void GBAWidget::UpdateTitle() +{ + std::string title = fmt::format("GBA{}", m_device_number + 1); + if (!m_netplayer_name.empty()) + title += " " + m_netplayer_name; + + if (!m_game_title.empty()) + title += " | " + m_game_title; + + if (m_muted) + title += " | Muted"; + else + title += fmt::format(" | Volume {}%", m_volume); + + setWindowTitle(QString::fromStdString(title)); +} + +void GBAWidget::UpdateVolume() +{ + int volume = m_muted ? 0 : m_volume * 256 / 100; + g_sound_stream->GetMixer()->SetGBAVolume(m_device_number, volume, volume); + UpdateTitle(); +} + +void GBAWidget::LoadGeometry() +{ + const QSettings& settings = Settings::GetQSettings(); + const QString key = QStringLiteral("gbawidget/geometry%1").arg(m_local_pad + 1); + if (settings.contains(key)) + restoreGeometry(settings.value(key).toByteArray()); +} + +void GBAWidget::SaveGeometry() +{ + QSettings& settings = Settings::GetQSettings(); + const QString key = QStringLiteral("gbawidget/geometry%1").arg(m_local_pad + 1); + settings.setValue(key, saveGeometry()); +} + +bool GBAWidget::CanControlCore() +{ + return !Movie::IsMovieActive() && !NetPlay::IsNetPlayRunning(); +} + +bool GBAWidget::CanResetCore() +{ + return m_is_local_pad; +} + +void GBAWidget::closeEvent(QCloseEvent* event) +{ + event->ignore(); +} + +void GBAWidget::contextMenuEvent(QContextMenuEvent* event) +{ + auto* menu = new QMenu(this); + connect(menu, &QMenu::triggered, menu, &QMenu::deleteLater); + + auto* disconnect_action = + new QAction(m_force_disconnect ? tr("Dis&connected") : tr("&Connected"), menu); + disconnect_action->setEnabled(CanControlCore()); + disconnect_action->setCheckable(true); + disconnect_action->setChecked(!m_force_disconnect); + connect(disconnect_action, &QAction::triggered, this, &GBAWidget::ToggleDisconnect); + + auto* load_action = new QAction(tr("L&oad ROM"), menu); + load_action->setEnabled(CanControlCore()); + connect(load_action, &QAction::triggered, this, &GBAWidget::LoadROM); + + auto* unload_action = new QAction(tr("&Unload ROM"), menu); + unload_action->setEnabled(CanControlCore() && !m_game_title.empty()); + connect(unload_action, &QAction::triggered, this, &GBAWidget::UnloadROM); + + auto* reset_action = new QAction(tr("&Reset"), menu); + reset_action->setEnabled(CanResetCore()); + connect(reset_action, &QAction::triggered, this, &GBAWidget::ResetCore); + + auto* mute_action = new QAction(tr("&Mute"), menu); + mute_action->setCheckable(true); + mute_action->setChecked(m_muted); + connect(mute_action, &QAction::triggered, this, &GBAWidget::ToggleMute); + + auto* size_menu = new QMenu(tr("Window Size"), menu); + + auto* x1_action = new QAction(tr("&1x"), size_menu); + connect(x1_action, &QAction::triggered, this, [this] { Resize(1); }); + auto* x2_action = new QAction(tr("&2x"), size_menu); + connect(x2_action, &QAction::triggered, this, [this] { Resize(2); }); + auto* x3_action = new QAction(tr("&3x"), size_menu); + connect(x3_action, &QAction::triggered, this, [this] { Resize(3); }); + auto* x4_action = new QAction(tr("&4x"), size_menu); + connect(x4_action, &QAction::triggered, this, [this] { Resize(4); }); + + size_menu->addAction(x1_action); + size_menu->addAction(x2_action); + size_menu->addAction(x3_action); + size_menu->addAction(x4_action); + + auto* state_menu = new QMenu(tr("Save State"), menu); + + auto* import_action = new QAction(tr("&Import State"), state_menu); + import_action->setEnabled(CanControlCore()); + connect(import_action, &QAction::triggered, this, [this] { DoState(false); }); + + auto* export_state = new QAction(tr("&Export State"), state_menu); + connect(export_state, &QAction::triggered, this, [this] { DoState(true); }); + + state_menu->addAction(import_action); + state_menu->addAction(export_state); + + menu->addAction(disconnect_action); + menu->addSeparator(); + menu->addAction(load_action); + menu->addAction(unload_action); + menu->addAction(reset_action); + menu->addSeparator(); + menu->addMenu(state_menu); + menu->addSeparator(); + menu->addAction(mute_action); + menu->addSeparator(); + menu->addMenu(size_menu); + + menu->move(event->globalPos()); + menu->show(); +} + +void GBAWidget::paintEvent(QPaintEvent* event) +{ + QPainter painter(this); + painter.fillRect(QRect(QPoint(), size()), Qt::black); + + if (m_video_buffer.size() == static_cast(m_width * m_height)) + { + QImage image(reinterpret_cast(m_video_buffer.data()), m_width, m_height, + QImage::Format_ARGB32); + image = image.convertToFormat(QImage::Format_RGB32); + image = image.rgbSwapped(); + + QSize widget_size = size(); + if (widget_size == QSize(m_width, m_height)) + { + painter.drawImage(QPoint(), image, QRect(0, 0, m_width, m_height)); + } + else if (static_cast(m_width) / m_height > + static_cast(widget_size.width()) / widget_size.height()) + { + int new_height = widget_size.width() * m_height / m_width; + painter.drawImage( + QRect(0, (widget_size.height() - new_height) / 2, widget_size.width(), new_height), image, + QRect(0, 0, m_width, m_height)); + } + else + { + int new_width = widget_size.height() * m_width / m_height; + painter.drawImage( + QRect((widget_size.width() - new_width) / 2, 0, new_width, widget_size.height()), image, + QRect(0, 0, m_width, m_height)); + } + } +} + +void GBAWidget::dragEnterEvent(QDragEnterEvent* event) +{ + if (CanControlCore() && event->mimeData()->hasUrls()) + event->acceptProposedAction(); +} + +void GBAWidget::dropEvent(QDropEvent* event) +{ + if (!CanControlCore()) + return; + + for (const QUrl& url : event->mimeData()->urls()) + { + QFileInfo file_info(url.toLocalFile()); + QString path = file_info.filePath(); + + if (!file_info.isFile()) + continue; + + if (!file_info.exists() || !file_info.isReadable()) + { + ModalMessageBox::critical(this, tr("Error"), tr("Failed to open '%1'").arg(path)); + continue; + } + + if (file_info.suffix() == QStringLiteral("raw")) + { + Core::RunOnCPUThread( + [core = m_core, card_path = path.toStdString()] { + if (auto core_ptr = core.lock()) + core_ptr->EReaderQueueCard(card_path); + }, + false); + } + else + { + RestartCore(m_core, path.toStdString()); + } + } +} + +GBAWidgetController::~GBAWidgetController() +{ + m_widget->deleteLater(); +} + +void GBAWidgetController::Create(std::weak_ptr core, int device_number, + std::string_view game_title, int width, int height) +{ + m_widget = new GBAWidget(std::move(core), device_number, game_title, width, height); +} + +void GBAWidgetController::GameChanged(std::string_view game_title, int width, int height) +{ + m_widget->GameChanged(game_title, width, height); +} + +void GBAWidgetController::FrameEnded(std::vector video_buffer) +{ + m_widget->SetVideoBuffer(std::move(video_buffer)); +} diff --git a/Source/Core/DolphinQt/GBAWidget.h b/Source/Core/DolphinQt/GBAWidget.h new file mode 100644 index 0000000000..24b2147cd1 --- /dev/null +++ b/Source/Core/DolphinQt/GBAWidget.h @@ -0,0 +1,96 @@ +// Copyright 2021 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include + +#include "Common/CommonTypes.h" + +namespace HW::GBA +{ +class Core; +} // namespace HW::GBA + +class QCloseEvent; +class QContextMenuEvent; +class QDragEnterEvent; +class QDropEvent; +class QPaintEvent; + +class GBAWidget : public QWidget +{ + Q_OBJECT +public: + explicit GBAWidget(std::weak_ptr core, int device_number, + std::string_view game_title, int width, int height, QWidget* parent = nullptr, + Qt::WindowFlags flags = {}); + ~GBAWidget(); + + void GameChanged(std::string_view game_title, int width, int height); + void SetVideoBuffer(std::vector video_buffer); + + void SetVolume(int volume); + void VolumeDown(); + void VolumeUp(); + bool IsMuted(); + void ToggleMute(); + void ToggleDisconnect(); + + void LoadROM(); + void UnloadROM(); + void ResetCore(); + void DoState(bool export_state); + void Resize(int scale); + +private: + void UpdateTitle(); + void UpdateVolume(); + + void LoadGeometry(); + void SaveGeometry(); + + bool CanControlCore(); + bool CanResetCore(); + + void closeEvent(QCloseEvent* event) override; + void contextMenuEvent(QContextMenuEvent* event) override; + void paintEvent(QPaintEvent* event) override; + + void dragEnterEvent(QDragEnterEvent* event) override; + void dropEvent(QDropEvent* event) override; + + std::weak_ptr m_core; + std::vector m_video_buffer; + int m_device_number; + int m_local_pad; + std::string m_game_title; + int m_width; + int m_height; + std::string m_netplayer_name; + bool m_is_local_pad; + int m_volume; + bool m_muted; + bool m_force_disconnect; +}; + +class GBAWidgetController : public QObject +{ + Q_OBJECT +public: + explicit GBAWidgetController() = default; + ~GBAWidgetController(); + + void Create(std::weak_ptr core, int device_number, std::string_view game_title, + int width, int height); + void GameChanged(std::string_view game_title, int width, int height); + void FrameEnded(std::vector video_buffer); + +private: + GBAWidget* m_widget{}; +}; diff --git a/Source/Core/DolphinQt/Host.cpp b/Source/Core/DolphinQt/Host.cpp index 61cbc7036d..bc9d6103b3 100644 --- a/Source/Core/DolphinQt/Host.cpp +++ b/Source/Core/DolphinQt/Host.cpp @@ -24,6 +24,9 @@ #include "Core/PowerPC/PowerPC.h" #include "Core/State.h" +#ifdef HAS_LIBMGBA +#include "DolphinQt/GBAWidget.h" +#endif #include "DolphinQt/QtUtils/QueueOnObject.h" #include "DolphinQt/Settings.h" @@ -115,6 +118,15 @@ void Host::SetRenderFullFocus(bool focus) m_render_full_focus = focus; } +bool Host::GetGBAFocus() +{ +#ifdef HAS_LIBMGBA + return qobject_cast(QApplication::activeWindow()) != nullptr; +#else + return false; +#endif +} + bool Host::GetRenderFullscreen() { return m_render_fullscreen; @@ -167,7 +179,7 @@ void Host_UpdateTitle(const std::string& title) bool Host_RendererHasFocus() { - return Host::GetInstance()->GetRenderFocus(); + return Host::GetInstance()->GetRenderFocus() || Host::GetInstance()->GetGBAFocus(); } bool Host_RendererHasFullFocus() @@ -230,7 +242,9 @@ void Host_TitleChanged() #endif } +#ifndef HAS_LIBMGBA std::unique_ptr Host_CreateGBAHost(std::weak_ptr core) { return nullptr; } +#endif diff --git a/Source/Core/DolphinQt/Host.h b/Source/Core/DolphinQt/Host.h index 287333ac04..6ddc4cb3e4 100644 --- a/Source/Core/DolphinQt/Host.h +++ b/Source/Core/DolphinQt/Host.h @@ -27,6 +27,7 @@ public: bool GetRenderFocus(); bool GetRenderFullFocus(); bool GetRenderFullscreen(); + bool GetGBAFocus(); void SetMainWindowHandle(void* handle); void SetRenderHandle(void* handle); From b73d16a71a6a0e16ee7708aa659d8a61242f2781 Mon Sep 17 00:00:00 2001 From: Bonta <40473493+Bonta0@users.noreply.github.com> Date: Sun, 4 Jul 2021 13:26:47 +0200 Subject: [PATCH 16/18] Qt/Core: Implement GBA Hotkeys --- Source/Core/Core/HotkeyManager.cpp | 56 +++++++++++++++++-- Source/Core/Core/HotkeyManager.h | 20 ++++++- Source/Core/DolphinQt/CMakeLists.txt | 2 + .../DolphinQt/Config/Mapping/HotkeyGBA.cpp | 43 ++++++++++++++ .../Core/DolphinQt/Config/Mapping/HotkeyGBA.h | 25 +++++++++ .../Config/Mapping/MappingWindow.cpp | 2 + Source/Core/DolphinQt/DolphinQt.vcxproj | 2 + Source/Core/DolphinQt/HotkeyScheduler.cpp | 50 ++++++++++++++++- Source/Core/DolphinQt/HotkeyScheduler.h | 1 + 9 files changed, 193 insertions(+), 8 deletions(-) create mode 100644 Source/Core/DolphinQt/Config/Mapping/HotkeyGBA.cpp create mode 100644 Source/Core/DolphinQt/Config/Mapping/HotkeyGBA.h diff --git a/Source/Core/Core/HotkeyManager.cpp b/Source/Core/Core/HotkeyManager.cpp index 01ddadb6be..25b59d3943 100644 --- a/Source/Core/Core/HotkeyManager.cpp +++ b/Source/Core/Core/HotkeyManager.cpp @@ -23,7 +23,7 @@ #include "InputCommon/GCPadStatus.h" // clang-format off -constexpr std::array s_hotkey_labels{{ +constexpr std::array s_hotkey_labels{{ _trans("Open"), _trans("Change Disc"), _trans("Eject Disc"), @@ -178,6 +178,19 @@ constexpr std::array s_hotkey_labels{{ _trans("Undo Save State"), _trans("Save State"), _trans("Load State"), + + _trans("Load ROM"), + _trans("Unload ROM"), + _trans("Reset"), + + _trans("Volume Down"), + _trans("Volume Up"), + _trans("Volume Toggle Mute"), + + _trans("1x"), + _trans("2x"), + _trans("3x"), + _trans("4x"), }}; // clang-format on static_assert(NUM_HOTKEYS == s_hotkey_labels.size(), "Wrong count of hotkey_labels"); @@ -195,10 +208,10 @@ InputConfig* GetConfig() return &s_config; } -void GetStatus() +void GetStatus(bool ignore_focus) { // Get input - static_cast(s_config.GetController(0))->GetInput(&s_hotkey); + static_cast(s_config.GetController(0))->GetInput(&s_hotkey, ignore_focus); } bool IsEnabled() @@ -307,6 +320,7 @@ struct HotkeyGroupInfo const char* name; Hotkey first; Hotkey last; + bool ignore_focus = false; }; constexpr std::array s_groups_info = { @@ -334,7 +348,10 @@ constexpr std::array s_groups_info = { {_trans("Save State"), HK_SAVE_STATE_SLOT_1, HK_SAVE_STATE_SLOT_SELECTED}, {_trans("Select State"), HK_SELECT_STATE_SLOT_1, HK_SELECT_STATE_SLOT_10}, {_trans("Load Last State"), HK_LOAD_LAST_STATE_1, HK_LOAD_LAST_STATE_10}, - {_trans("Other State Hotkeys"), HK_SAVE_FIRST_STATE, HK_LOAD_STATE_FILE}}}; + {_trans("Other State Hotkeys"), HK_SAVE_FIRST_STATE, HK_LOAD_STATE_FILE}, + {_trans("GBA Core"), HK_GBA_LOAD, HK_GBA_RESET, true}, + {_trans("GBA Volume"), HK_GBA_VOLUME_DOWN, HK_GBA_TOGGLE_MUTE, true}, + {_trans("GBA Window Size"), HK_GBA_1X, HK_GBA_4X, true}}}; HotkeyManager::HotkeyManager() { @@ -359,11 +376,14 @@ std::string HotkeyManager::GetName() const return "Hotkeys"; } -void HotkeyManager::GetInput(HotkeyStatus* const kb) +void HotkeyManager::GetInput(HotkeyStatus* kb, bool ignore_focus) { const auto lock = GetStateLock(); for (std::size_t group = 0; group < s_groups_info.size(); group++) { + if (s_groups_info[group].ignore_focus != ignore_focus) + continue; + const int group_count = (s_groups_info[group].last - s_groups_info[group].first) + 1; std::vector bitmasks(group_count); for (size_t key = 0; key < bitmasks.size(); key++) @@ -441,4 +461,30 @@ void HotkeyManager::LoadDefaults(const ControllerInterface& ciface) } set_key_expression(HK_UNDO_LOAD_STATE, "F12"); set_key_expression(HK_UNDO_SAVE_STATE, hotkey_string({"Shift", "F12"})); + + // GBA + set_key_expression(HK_GBA_LOAD, hotkey_string({"`Shift`", "`O`"})); + set_key_expression(HK_GBA_UNLOAD, hotkey_string({"`Shift`", "`W`"})); + set_key_expression(HK_GBA_RESET, hotkey_string({"`Shift`", "`R`"})); + +#ifdef _WIN32 + set_key_expression(HK_GBA_VOLUME_DOWN, "`SUBTRACT`"); + set_key_expression(HK_GBA_VOLUME_UP, "`ADD`"); +#else + set_key_expression(HK_GBA_VOLUME_DOWN, "`KP_Subtract`"); + set_key_expression(HK_GBA_VOLUME_UP, "`KP_Add`"); +#endif + set_key_expression(HK_GBA_TOGGLE_MUTE, "`M`"); + +#ifdef _WIN32 + set_key_expression(HK_GBA_1X, "`NUMPAD1`"); + set_key_expression(HK_GBA_2X, "`NUMPAD2`"); + set_key_expression(HK_GBA_3X, "`NUMPAD3`"); + set_key_expression(HK_GBA_4X, "`NUMPAD4`"); +#else + set_key_expression(HK_GBA_1X, "`KP_1`"); + set_key_expression(HK_GBA_2X, "`KP_2`"); + set_key_expression(HK_GBA_3X, "`KP_3`"); + set_key_expression(HK_GBA_4X, "`KP_4`"); +#endif } diff --git a/Source/Core/Core/HotkeyManager.h b/Source/Core/Core/HotkeyManager.h index 9fec4f16a0..8f1f37dd10 100644 --- a/Source/Core/Core/HotkeyManager.h +++ b/Source/Core/Core/HotkeyManager.h @@ -164,6 +164,19 @@ enum Hotkey HK_SAVE_STATE_FILE, HK_LOAD_STATE_FILE, + HK_GBA_LOAD, + HK_GBA_UNLOAD, + HK_GBA_RESET, + + HK_GBA_VOLUME_DOWN, + HK_GBA_VOLUME_UP, + HK_GBA_TOGGLE_MUTE, + + HK_GBA_1X, + HK_GBA_2X, + HK_GBA_3X, + HK_GBA_4X, + NUM_HOTKEYS, }; @@ -192,6 +205,9 @@ enum HotkeyGroup : int HKGP_SELECT_STATE, HKGP_LOAD_LAST_STATE, HKGP_STATE_MISC, + HKGP_GBA_CORE, + HKGP_GBA_VOLUME, + HKGP_GBA_SIZE, NUM_HOTKEY_GROUPS, }; @@ -208,7 +224,7 @@ public: HotkeyManager(); ~HotkeyManager(); - void GetInput(HotkeyStatus* const hk); + void GetInput(HotkeyStatus* hk, bool ignore_focus); std::string GetName() const override; ControllerEmu::ControlGroup* GetHotkeyGroup(HotkeyGroup group) const; int FindGroupByID(int id) const; @@ -228,7 +244,7 @@ void LoadConfig(); InputConfig* GetConfig(); ControllerEmu::ControlGroup* GetHotkeyGroup(HotkeyGroup group); -void GetStatus(); +void GetStatus(bool ignore_focus); bool IsEnabled(); void Enable(bool enable_toggle); bool IsPressed(int Id, bool held); diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index 3d3bf12ed4..2c6ad5c0ef 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -140,6 +140,8 @@ add_executable(dolphin-emu Config/Mapping/HotkeyControllerProfile.h Config/Mapping/HotkeyDebugging.cpp Config/Mapping/HotkeyDebugging.h + Config/Mapping/HotkeyGBA.cpp + Config/Mapping/HotkeyGBA.h Config/Mapping/HotkeyGeneral.cpp Config/Mapping/HotkeyGeneral.h Config/Mapping/HotkeyGraphics.cpp diff --git a/Source/Core/DolphinQt/Config/Mapping/HotkeyGBA.cpp b/Source/Core/DolphinQt/Config/Mapping/HotkeyGBA.cpp new file mode 100644 index 0000000000..39922a37cd --- /dev/null +++ b/Source/Core/DolphinQt/Config/Mapping/HotkeyGBA.cpp @@ -0,0 +1,43 @@ +// Copyright 2021 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "DolphinQt/Config/Mapping/HotkeyGBA.h" + +#include +#include + +#include "Core/HotkeyManager.h" + +HotkeyGBA::HotkeyGBA(MappingWindow* window) : MappingWidget(window) +{ + CreateMainLayout(); +} + +void HotkeyGBA::CreateMainLayout() +{ + m_main_layout = new QHBoxLayout(); + + m_main_layout->addWidget( + CreateGroupBox(tr("Core"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_GBA_CORE))); + m_main_layout->addWidget( + CreateGroupBox(tr("Volume"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_GBA_VOLUME))); + m_main_layout->addWidget( + CreateGroupBox(tr("Window Size"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_GBA_SIZE))); + + setLayout(m_main_layout); +} + +InputConfig* HotkeyGBA::GetConfig() +{ + return HotkeyManagerEmu::GetConfig(); +} + +void HotkeyGBA::LoadSettings() +{ + HotkeyManagerEmu::LoadConfig(); +} + +void HotkeyGBA::SaveSettings() +{ + HotkeyManagerEmu::GetConfig()->SaveConfig(); +} diff --git a/Source/Core/DolphinQt/Config/Mapping/HotkeyGBA.h b/Source/Core/DolphinQt/Config/Mapping/HotkeyGBA.h new file mode 100644 index 0000000000..a71cef41a3 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Mapping/HotkeyGBA.h @@ -0,0 +1,25 @@ +// Copyright 2021 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "DolphinQt/Config/Mapping/MappingWidget.h" + +class QHBoxLayout; + +class HotkeyGBA final : public MappingWidget +{ + Q_OBJECT +public: + explicit HotkeyGBA(MappingWindow* window); + + InputConfig* GetConfig() override; + +private: + void LoadSettings() override; + void SaveSettings() override; + void CreateMainLayout(); + + // Main + QHBoxLayout* m_main_layout; +}; diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp index 3191f42fd9..32ef52c89e 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp @@ -30,6 +30,7 @@ #include "DolphinQt/Config/Mapping/Hotkey3D.h" #include "DolphinQt/Config/Mapping/HotkeyControllerProfile.h" #include "DolphinQt/Config/Mapping/HotkeyDebugging.h" +#include "DolphinQt/Config/Mapping/HotkeyGBA.h" #include "DolphinQt/Config/Mapping/HotkeyGeneral.h" #include "DolphinQt/Config/Mapping/HotkeyGraphics.h" #include "DolphinQt/Config/Mapping/HotkeyStates.h" @@ -435,6 +436,7 @@ void MappingWindow::SetMappingType(MappingWindow::Type type) AddWidget(tr("3D"), new Hotkey3D(this)); AddWidget(tr("Save and Load State"), new HotkeyStates(this)); AddWidget(tr("Other State Management"), new HotkeyStatesOther(this)); + AddWidget(tr("GameBoy Advance"), new HotkeyGBA(this)); setWindowTitle(tr("Hotkey Settings")); break; } diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index 3647f255df..63c2bfdca8 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -90,6 +90,7 @@ + @@ -268,6 +269,7 @@ + diff --git a/Source/Core/DolphinQt/HotkeyScheduler.cpp b/Source/Core/DolphinQt/HotkeyScheduler.cpp index 2748e219a8..33eab853e4 100644 --- a/Source/Core/DolphinQt/HotkeyScheduler.cpp +++ b/Source/Core/DolphinQt/HotkeyScheduler.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include "AudioCommon/AudioCommon.h" @@ -28,6 +29,10 @@ #include "Core/State.h" #include "Core/WiiUtils.h" +#ifdef HAS_LIBMGBA +#include "DolphinQt/GBAWidget.h" +#endif +#include "DolphinQt/QtUtils/QueueOnObject.h" #include "DolphinQt/Settings.h" #include "InputCommon/ControlReference/ControlReference.h" @@ -157,11 +162,13 @@ void HotkeyScheduler::Run() // Obey window focus (config permitting) before checking hotkeys. Core::UpdateInputGate(Config::Get(Config::MAIN_FOCUSED_HOTKEYS)); - HotkeyManagerEmu::GetStatus(); + HotkeyManagerEmu::GetStatus(false); // Everything else on the host thread (controller config dialog) should always get input. ControlReference::SetInputGate(true); + HotkeyManagerEmu::GetStatus(true); + if (!Core::IsRunningAndStarted()) continue; @@ -520,6 +527,8 @@ void HotkeyScheduler::Run() Config::SetCurrent(Config::GFX_ENHANCE_POST_SHADER, ""); } } + + CheckGBAHotkeys(); } const auto stereo_depth = Config::Get(Config::GFX_STEREO_DEPTH); @@ -607,3 +616,42 @@ void HotkeyScheduler::CheckDebuggingHotkeys() if (IsHotkey(HK_BP_ADD)) emit AddBreakpoint(); } + +void HotkeyScheduler::CheckGBAHotkeys() +{ +#ifdef HAS_LIBMGBA + GBAWidget* gba_widget = qobject_cast(QApplication::activeWindow()); + if (!gba_widget) + return; + + if (IsHotkey(HK_GBA_LOAD)) + QueueOnObject(gba_widget, [gba_widget] { gba_widget->LoadROM(); }); + + if (IsHotkey(HK_GBA_UNLOAD)) + QueueOnObject(gba_widget, [gba_widget] { gba_widget->UnloadROM(); }); + + if (IsHotkey(HK_GBA_RESET)) + QueueOnObject(gba_widget, [gba_widget] { gba_widget->ResetCore(); }); + + if (IsHotkey(HK_GBA_VOLUME_DOWN)) + QueueOnObject(gba_widget, [gba_widget] { gba_widget->VolumeDown(); }); + + if (IsHotkey(HK_GBA_VOLUME_UP)) + QueueOnObject(gba_widget, [gba_widget] { gba_widget->VolumeUp(); }); + + if (IsHotkey(HK_GBA_TOGGLE_MUTE)) + QueueOnObject(gba_widget, [gba_widget] { gba_widget->ToggleMute(); }); + + if (IsHotkey(HK_GBA_1X)) + QueueOnObject(gba_widget, [gba_widget] { gba_widget->Resize(1); }); + + if (IsHotkey(HK_GBA_2X)) + QueueOnObject(gba_widget, [gba_widget] { gba_widget->Resize(2); }); + + if (IsHotkey(HK_GBA_3X)) + QueueOnObject(gba_widget, [gba_widget] { gba_widget->Resize(3); }); + + if (IsHotkey(HK_GBA_4X)) + QueueOnObject(gba_widget, [gba_widget] { gba_widget->Resize(4); }); +#endif +} diff --git a/Source/Core/DolphinQt/HotkeyScheduler.h b/Source/Core/DolphinQt/HotkeyScheduler.h index f77c80168f..9f8426a854 100644 --- a/Source/Core/DolphinQt/HotkeyScheduler.h +++ b/Source/Core/DolphinQt/HotkeyScheduler.h @@ -65,6 +65,7 @@ signals: private: void Run(); void CheckDebuggingHotkeys(); + void CheckGBAHotkeys(); Common::Flag m_stop_requested; std::thread m_thread; From 45f2461a530150d0b7b7c0899a927666c80fa203 Mon Sep 17 00:00:00 2001 From: Bonta <40473493+Bonta0@users.noreply.github.com> Date: Sun, 4 Jul 2021 13:33:58 +0200 Subject: [PATCH 17/18] NetPlay: GBA Support --- Source/Core/Common/CommonPaths.h | 1 + Source/Core/Core/Config/NetplaySettings.cpp | 1 + Source/Core/Core/Config/NetplaySettings.h | 1 + .../ConfigLoaders/NetPlayConfigLoader.cpp | 5 + Source/Core/Core/HW/GBACore.cpp | 3 +- Source/Core/Core/NetPlayClient.cpp | 255 ++++++++++++++---- Source/Core/Core/NetPlayClient.h | 10 +- Source/Core/Core/NetPlayProto.h | 28 +- Source/Core/Core/NetPlayServer.cpp | 118 +++++++- Source/Core/Core/NetPlayServer.h | 5 + Source/Core/DolphinQt/GBAWidget.cpp | 11 + .../Core/DolphinQt/NetPlay/NetPlayDialog.cpp | 96 +++++-- Source/Core/DolphinQt/NetPlay/NetPlayDialog.h | 4 + .../DolphinQt/NetPlay/PadMappingDialog.cpp | 30 ++- .../Core/DolphinQt/NetPlay/PadMappingDialog.h | 4 + .../Core/DolphinQt/Settings/GameCubePane.cpp | 4 + 16 files changed, 491 insertions(+), 85 deletions(-) diff --git a/Source/Core/Common/CommonPaths.h b/Source/Core/Common/CommonPaths.h index e4b4a67d86..55ed606517 100644 --- a/Source/Core/Common/CommonPaths.h +++ b/Source/Core/Common/CommonPaths.h @@ -122,6 +122,7 @@ #define GC_MEMCARD_NETPLAY "NetPlayTemp" #define GBA_BIOS "gba_bios.bin" +#define GBA_SAVE_NETPLAY "NetPlayTemp" #define WII_STATE "state.dat" diff --git a/Source/Core/Core/Config/NetplaySettings.cpp b/Source/Core/Core/Config/NetplaySettings.cpp index fb7bec18c7..a584117e9d 100644 --- a/Source/Core/Core/Config/NetplaySettings.cpp +++ b/Source/Core/Core/Config/NetplaySettings.cpp @@ -57,5 +57,6 @@ const Info NETPLAY_NETWORK_MODE{{System::Main, "NetPlay", "NetworkM "fixeddelay"}; const Info NETPLAY_SYNC_ALL_WII_SAVES{{System::Main, "NetPlay", "SyncAllWiiSaves"}, false}; const Info NETPLAY_GOLF_MODE_OVERLAY{{System::Main, "NetPlay", "GolfModeOverlay"}, true}; +const Info NETPLAY_HIDE_REMOTE_GBAS{{System::Main, "NetPlay", "HideRemoteGBAs"}, false}; } // namespace Config diff --git a/Source/Core/Core/Config/NetplaySettings.h b/Source/Core/Core/Config/NetplaySettings.h index 1704840cbc..f55e7fd090 100644 --- a/Source/Core/Core/Config/NetplaySettings.h +++ b/Source/Core/Core/Config/NetplaySettings.h @@ -50,5 +50,6 @@ extern const Info NETPLAY_STRICT_SETTINGS_SYNC; extern const Info NETPLAY_NETWORK_MODE; extern const Info NETPLAY_SYNC_ALL_WII_SAVES; extern const Info NETPLAY_GOLF_MODE_OVERLAY; +extern const Info NETPLAY_HIDE_REMOTE_GBAS; } // namespace Config diff --git a/Source/Core/Core/ConfigLoaders/NetPlayConfigLoader.cpp b/Source/Core/Core/ConfigLoaders/NetPlayConfigLoader.cpp index 6ba4702bac..1ea56af04c 100644 --- a/Source/Core/Core/ConfigLoaders/NetPlayConfigLoader.cpp +++ b/Source/Core/Core/ConfigLoaders/NetPlayConfigLoader.cpp @@ -133,6 +133,11 @@ public: layer->Set(Config::SESSION_GCI_FOLDER_CURRENT_GAME_ONLY, true); } + for (size_t i = 0; i < m_settings.m_GBARomPaths.size(); ++i) + { + layer->Set(Config::MAIN_GBA_ROM_PATHS[i], m_settings.m_GBARomPaths[i]); + } + // Check To Override Client's Cheat Codes if (m_settings.m_SyncCodes && !m_settings.m_IsHosting) { diff --git a/Source/Core/Core/HW/GBACore.cpp b/Source/Core/Core/HW/GBACore.cpp index 38ef42f1ba..7916cbfdc0 100644 --- a/Source/Core/Core/HW/GBACore.cpp +++ b/Source/Core/Core/HW/GBACore.cpp @@ -219,7 +219,8 @@ bool Core::Start(u64 gc_ticks) m_core->getGameTitle(m_core, game_title.data()); m_game_title = game_title.data(); - m_save_path = GetSavePath(m_rom_path, m_device_number); + m_save_path = NetPlay::IsNetPlayRunning() ? NetPlay::GetGBASavePath(m_device_number) : + GetSavePath(m_rom_path, m_device_number); if (!m_save_path.empty() && !LoadSave(m_save_path.c_str())) return false; } diff --git a/Source/Core/Core/NetPlayClient.cpp b/Source/Core/Core/NetPlayClient.cpp index dd66fe6366..4212d3031e 100644 --- a/Source/Core/Core/NetPlayClient.cpp +++ b/Source/Core/Core/NetPlayClient.cpp @@ -33,12 +33,18 @@ #include "Common/Version.h" #include "Core/ActionReplay.h" +#include "Core/Config/MainSettings.h" #include "Core/Config/NetplaySettings.h" #include "Core/Config/SessionSettings.h" #include "Core/ConfigManager.h" #include "Core/GeckoCode.h" #include "Core/HW/EXI/EXI_DeviceIPL.h" +#ifdef HAS_LIBMGBA +#include "Core/HW/GBACore.h" +#endif +#include "Core/HW/GBAPad.h" #include "Core/HW/GCMemcard/GCMemcard.h" +#include "Core/HW/GCPad.h" #include "Core/HW/SI/SI.h" #include "Core/HW/SI/SI_Device.h" #include "Core/HW/SI/SI_DeviceGCController.h" @@ -71,7 +77,7 @@ static std::mutex crit_netplay_client; static NetPlayClient* netplay_client = nullptr; static std::unique_ptr s_wii_sync_fs; static std::vector s_wii_sync_titles; -static bool s_si_poll_batching; +static bool s_si_poll_batching = false; // called from ---GUI--- thread NetPlayClient::~NetPlayClient() @@ -463,6 +469,35 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) } break; + case NP_MSG_GBA_CONFIG: + { + for (size_t i = 0; i < m_gba_config.size(); ++i) + { + auto& config = m_gba_config[i]; + const auto old_config = config; + + packet >> config.enabled >> config.has_rom >> config.title; + for (auto& data : config.hash) + packet >> data; + + if (std::tie(config.has_rom, config.title, config.hash) != + std::tie(old_config.has_rom, old_config.title, old_config.hash)) + { + m_dialog->OnMsgChangeGBARom(static_cast(i), config); + m_net_settings.m_GBARomPaths[i] = + config.has_rom ? + m_dialog->FindGBARomPath(config.hash, config.title, static_cast(i)) : + ""; + } + } + + SendGameStatus(); + UpdateDevices(); + + m_dialog->Update(); + } + break; + case NP_MSG_WIIMOTE_MAPPING: { for (PlayerId& mapping : m_wiimote_map) @@ -482,8 +517,12 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) packet >> map; GCPadStatus pad; - packet >> pad.button >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> - pad.substickX >> pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected; + packet >> pad.button; + if (!m_gba_config.at(map).enabled) + { + packet >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> pad.substickX >> + pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected; + } // Trusting server for good map value (>=0 && <4) // add to pad buffer @@ -501,8 +540,12 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) packet >> map; GCPadStatus pad; - packet >> pad.button >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> - pad.substickX >> pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected; + packet >> pad.button; + if (!m_gba_config.at(map).enabled) + { + packet >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> pad.substickX >> + pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected; + } // Trusting server for good map value (>=0 && <4) // write to last status @@ -602,14 +645,7 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) // update gui m_dialog->OnMsgChangeGame(m_selected_game, netplay_name); - sf::Packet game_status_packet; - game_status_packet << static_cast(NP_MSG_GAME_STATUS); - - SyncIdentifierComparison result; - m_dialog->FindGameFile(m_selected_game, &result); - - game_status_packet << static_cast(result); - Send(game_status_packet); + SendGameStatus(); sf::Packet client_capabilities_packet; client_capabilities_packet << static_cast(NP_MSG_CLIENT_CAPABILITIES); @@ -739,6 +775,7 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) packet >> m_net_settings.m_GolfMode; packet >> m_net_settings.m_UseFMA; + packet >> m_net_settings.m_HideRemoteGBAs; m_net_settings.m_IsHosting = m_local_player->IsHost(); m_net_settings.m_HostInputAuthority = m_host_input_authority; @@ -1055,6 +1092,29 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) } break; + case SYNC_SAVE_DATA_GBA: + { + if (m_local_player->IsHost()) + return 0; + + u8 slot; + packet >> slot; + + const std::string path = + fmt::format("{}{}{}.sav", File::GetUserPath(D_GBAUSER_IDX), GBA_SAVE_NETPLAY, slot + 1); + if (File::Exists(path) && !File::Delete(path)) + { + PanicAlertFmtT("Failed to delete NetPlay GBA{0} save file. Verify your write permissions.", + slot + 1); + SyncSaveDataResponse(false); + return 0; + } + + const bool success = DecompressPacketIntoFile(packet, path); + SyncSaveDataResponse(success); + } + break; + default: PanicAlertFmtT("Unknown SYNC_SAVE_DATA message received with id: {0}", sub_id); break; @@ -1411,26 +1471,13 @@ void NetPlayClient::GetPlayerList(std::string& list, std::vector& pid_list) std::ostringstream ss; - const auto enumerate_player_controller_mappings = [&ss](const PadMappingArray& mappings, - const Player& player) { - for (size_t i = 0; i < mappings.size(); i++) - { - if (mappings[i] == player.pid) - ss << i + 1; - else - ss << '-'; - } - }; - for (const auto& entry : m_players) { const Player& player = entry.second; - ss << player.name << "[" << static_cast(player.pid) << "] : " << player.revision << " | "; + ss << player.name << "[" << static_cast(player.pid) << "] : " << player.revision << " | " + << GetPlayerMappingString(player.pid, m_pad_map, m_gba_config, m_wiimote_map) << " |\n"; - enumerate_player_controller_mappings(m_pad_map, player); - enumerate_player_controller_mappings(m_wiimote_map, player); - - ss << " |\nPing: " << player.ping << "ms\n"; + ss << "Ping: " << player.ping << "ms\n"; ss << "Status: "; switch (player.game_status) @@ -1492,8 +1539,12 @@ void NetPlayClient::AddPadStateToPacket(const int in_game_pad, const GCPadStatus sf::Packet& packet) { packet << static_cast(in_game_pad); - packet << pad.button << pad.analogA << pad.analogB << pad.stickX << pad.stickY << pad.substickX - << pad.substickY << pad.triggerLeft << pad.triggerRight << pad.isConnected; + packet << pad.button; + if (!m_gba_config[in_game_pad].enabled) + { + packet << pad.analogA << pad.analogB << pad.stickX << pad.stickY << pad.substickX + << pad.substickY << pad.triggerLeft << pad.triggerRight << pad.isConnected; + } } // called from ---CPU--- thread @@ -1648,11 +1699,13 @@ void NetPlayClient::UpdateDevices() for (auto player_id : m_pad_map) { - // Use local controller types for local controllers if they are compatible - // Only GCController-like controllers are supported, GBA and similar - // exotic devices are not supported on netplay. - if (player_id == m_local_player->pid) + if (m_gba_config[pad].enabled && player_id > 0) { + SerialInterface::ChangeDevice(SerialInterface::SIDEVICE_GC_GBA_EMULATED, pad); + } + else if (player_id == m_local_player->pid) + { + // Use local controller types for local controllers if they are compatible if (SerialInterface::SIDevice_IsGCController(SConfig::GetInstance().m_SIDevice[local_pad])) { SerialInterface::ChangeDevice(SConfig::GetInstance().m_SIDevice[local_pad], pad); @@ -1968,21 +2021,22 @@ bool NetPlayClient::WiimoteUpdate(int _number, u8* data, const std::size_t size, bool NetPlayClient::PollLocalPad(const int local_pad, sf::Packet& packet) { - GCPadStatus pad_status; - - switch (SConfig::GetInstance().m_SIDevice[local_pad]) - { - case SerialInterface::SIDEVICE_WIIU_ADAPTER: - pad_status = GCAdapter::Input(local_pad); - break; - case SerialInterface::SIDEVICE_GC_CONTROLLER: - default: - pad_status = Pad::GetStatus(local_pad); - break; - } - const int ingame_pad = LocalPadToInGamePad(local_pad); bool data_added = false; + GCPadStatus pad_status; + + if (m_gba_config[ingame_pad].enabled) + { + pad_status = Pad::GetGBAStatus(local_pad); + } + else if (SConfig::GetInstance().m_SIDevice[local_pad] == SerialInterface::SIDEVICE_WIIU_ADAPTER) + { + pad_status = GCAdapter::Input(local_pad); + } + else + { + pad_status = Pad::GetStatus(local_pad); + } if (m_host_input_authority) { @@ -2234,6 +2288,26 @@ bool NetPlayClient::IsLocalPlayer(const PlayerId pid) const return pid == m_local_player->pid; } +void NetPlayClient::SendGameStatus() +{ + sf::Packet packet; + packet << static_cast(NP_MSG_GAME_STATUS); + + SyncIdentifierComparison result; + m_dialog->FindGameFile(m_selected_game, &result); + for (size_t i = 0; i < 4; ++i) + { + if (m_gba_config[i].enabled && m_gba_config[i].has_rom && + m_net_settings.m_GBARomPaths[i].empty()) + { + result = SyncIdentifierComparison::DifferentGame; + } + } + + packet << static_cast(result); + Send(packet); +} + void NetPlayClient::SendTimeBase() { std::lock_guard lk(crit_netplay_client); @@ -2309,6 +2383,11 @@ const PadMappingArray& NetPlayClient::GetPadMapping() const return m_pad_map; } +const GBAConfigArray& NetPlayClient::GetGBAConfig() const +{ + return m_gba_config; +} + const PadMappingArray& NetPlayClient::GetWiimoteMapping() const { return m_wiimote_map; @@ -2325,6 +2404,32 @@ SyncIdentifier NetPlayClient::GetSDCardIdentifier() return SyncIdentifier{{}, "sd", {}, {}, {}, {}}; } +std::string GetPlayerMappingString(PlayerId pid, const PadMappingArray& pad_map, + const GBAConfigArray& gba_config, + const PadMappingArray& wiimote_map) +{ + std::vector gc_slots, gba_slots, wiimote_slots; + for (size_t i = 0; i < pad_map.size(); ++i) + { + if (pad_map[i] == pid && !gba_config[i].enabled) + gc_slots.push_back(i + 1); + if (pad_map[i] == pid && gba_config[i].enabled) + gba_slots.push_back(i + 1); + if (wiimote_map[i] == pid) + wiimote_slots.push_back(i + 1); + } + std::vector groups; + for (const auto& [group_name, slots] : + {std::make_pair("GC", &gc_slots), std::make_pair("GBA", &gba_slots), + std::make_pair("Wii", &wiimote_slots)}) + { + if (!slots->empty()) + groups.emplace_back(fmt::format("{}{}", group_name, fmt::join(*slots, ","))); + } + std::string res = fmt::format("{}", fmt::join(groups, "|")); + return res.empty() ? "None" : res; +} + bool IsNetPlayRunning() { return netplay_client != nullptr; @@ -2401,6 +2506,60 @@ void SetupWiimotes() } } +std::string GetGBASavePath(int pad_num) +{ + std::lock_guard lk(crit_netplay_client); + + if (!netplay_client || NetPlay::GetNetSettings().m_IsHosting) + { +#ifdef HAS_LIBMGBA + std::string rom_path = Config::Get(Config::MAIN_GBA_ROM_PATHS[pad_num]); + return HW::GBA::Core::GetSavePath(rom_path, pad_num); +#else + return {}; +#endif + } + + if (!NetPlay::GetNetSettings().m_SyncSaveData) + return {}; + + return fmt::format("{}{}{}.sav", File::GetUserPath(D_GBAUSER_IDX), GBA_SAVE_NETPLAY, pad_num + 1); +} + +PadDetails GetPadDetails(int pad_num) +{ + std::lock_guard lk(crit_netplay_client); + + PadDetails res{.local_pad = 4}; + if (!netplay_client) + return res; + + auto pad_map = netplay_client->GetPadMapping(); + if (pad_map[pad_num] <= 0) + return res; + + for (auto player : netplay_client->GetPlayers()) + { + if (player->pid == pad_map[pad_num]) + res.player_name = player->name; + } + + int local_pad = 0; + int non_local_pad = 0; + for (int i = 0; i < pad_num; ++i) + { + if (netplay_client->IsLocalPlayer(pad_map[i])) + ++local_pad; + else + ++non_local_pad; + } + res.is_local = netplay_client->IsLocalPlayer(pad_map[pad_num]); + res.local_pad = res.is_local ? local_pad : netplay_client->NumLocalPads() + non_local_pad; + res.hide_gba = !res.is_local && netplay_client->GetNetSettings().m_HideRemoteGBAs && + netplay_client->LocalPlayerHasControllerMapped(); + return res; +} + void NetPlay_Enable(NetPlayClient* const np) { std::lock_guard lk(crit_netplay_client); diff --git a/Source/Core/Core/NetPlayClient.h b/Source/Core/Core/NetPlayClient.h index 8cb2d7953e..1be8702b20 100644 --- a/Source/Core/Core/NetPlayClient.h +++ b/Source/Core/Core/NetPlayClient.h @@ -43,6 +43,7 @@ public: virtual void OnMsgChangeGame(const SyncIdentifier& sync_identifier, const std::string& netplay_name) = 0; + virtual void OnMsgChangeGBARom(int pad, const NetPlay::GBAConfig& config) = 0; virtual void OnMsgStartGame() = 0; virtual void OnMsgStopGame() = 0; virtual void OnMsgPowerButton() = 0; @@ -62,6 +63,8 @@ public: virtual std::shared_ptr FindGameFile(const SyncIdentifier& sync_identifier, SyncIdentifierComparison* found = nullptr) = 0; + virtual std::string FindGBARomPath(const std::array& hash, std::string_view title, + int device_number) = 0; virtual void ShowMD5Dialog(const std::string& title) = 0; virtual void SetMD5Progress(int pid, int progress) = 0; virtual void SetMD5Result(int pid, const std::string& result) = 0; @@ -139,6 +142,7 @@ public: bool DoAllPlayersHaveGame(); const PadMappingArray& GetPadMapping() const; + const GBAConfigArray& GetGBAConfig() const; const PadMappingArray& GetWiimoteMapping() const; void AdjustPadBufferSize(unsigned int size); @@ -199,8 +203,9 @@ protected: u32 m_current_game = 0; - PadMappingArray m_pad_map; - PadMappingArray m_wiimote_map; + PadMappingArray m_pad_map{}; + GBAConfigArray m_gba_config{}; + PadMappingArray m_wiimote_map{}; bool m_is_recording = false; @@ -231,6 +236,7 @@ private: void Send(const sf::Packet& packet, u8 channel_id = DEFAULT_CHANNEL); void Disconnect(); bool Connect(); + void SendGameStatus(); void ComputeMD5(const SyncIdentifier& sync_identifier); void DisplayPlayersPing(); u32 GetPlayersMaxPing() const; diff --git a/Source/Core/Core/NetPlayProto.h b/Source/Core/Core/NetPlayProto.h index 1b547eda80..4b7da05d80 100644 --- a/Source/Core/Core/NetPlayProto.h +++ b/Source/Core/Core/NetPlayProto.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include "Common/CommonTypes.h" @@ -97,10 +98,12 @@ struct NetSettings std::array m_WiimoteExtension; bool m_GolfMode; bool m_UseFMA; + bool m_HideRemoteGBAs; // These aren't sent over the network directly bool m_IsHosting; bool m_HostInputAuthority; + std::array m_GBARomPaths; }; struct NetTraversalConfig @@ -136,6 +139,7 @@ enum NP_MSG_PAD_MAPPING = 0x61, NP_MSG_PAD_BUFFER = 0x62, NP_MSG_PAD_HOST_DATA = 0x63, + NP_MSG_GBA_CONFIG = 0x64, NP_MSG_WIIMOTE_DATA = 0x70, NP_MSG_WIIMOTE_MAPPING = 0x71, @@ -191,7 +195,8 @@ enum SYNC_SAVE_DATA_FAILURE = 2, SYNC_SAVE_DATA_RAW = 3, SYNC_SAVE_DATA_GCI = 4, - SYNC_SAVE_DATA_WII = 5 + SYNC_SAVE_DATA_WII = 5, + SYNC_SAVE_DATA_GBA = 6 }; enum @@ -225,7 +230,26 @@ using PlayerId = u8; using FrameNum = u32; using PadIndex = s8; using PadMappingArray = std::array; +struct GBAConfig +{ + bool enabled; + bool has_rom; + std::string title; + std::array hash; +}; +using GBAConfigArray = std::array; +struct PadDetails +{ + std::string player_name; + bool is_local; + int local_pad; + bool hide_gba; +}; + +std::string GetPlayerMappingString(PlayerId pid, const PadMappingArray& pad_map, + const GBAConfigArray& gba_config, + const PadMappingArray& wiimote_map); bool IsNetPlayRunning(); // Precondition: A netplay client instance must be present. In other words, // IsNetPlayRunning() must be true before calling this. @@ -238,4 +262,6 @@ void SetSIPollBatching(bool state); void SendPowerButtonEvent(); bool IsSyncingAllWiiSaves(); void SetupWiimotes(); +std::string GetGBASavePath(int pad_num); +PadDetails GetPadDetails(int pad_num); } // namespace NetPlay diff --git a/Source/Core/Core/NetPlayServer.cpp b/Source/Core/Core/NetPlayServer.cpp index f0dae94923..efc46683e9 100644 --- a/Source/Core/Core/NetPlayServer.cpp +++ b/Source/Core/Core/NetPlayServer.cpp @@ -40,6 +40,9 @@ #include "Core/ConfigManager.h" #include "Core/GeckoCode.h" #include "Core/GeckoCodeConfig.h" +#ifdef HAS_LIBMGBA +#include "Core/HW/GBACore.h" +#endif #include "Core/HW/GCMemcard/GCMemcard.h" #include "Core/HW/GCMemcard/GCMemcardDirectory.h" #include "Core/HW/GCMemcard/GCMemcardRaw.h" @@ -119,6 +122,7 @@ NetPlayServer::NetPlayServer(const u16 port, const bool forward_port, NetPlayUI* } m_pad_map.fill(0); + m_gba_config.fill({}); m_wiimote_map.fill(0); if (traversal_config.use_traversal) @@ -480,6 +484,7 @@ unsigned int NetPlayServer::OnConnect(ENetPeer* socket, sf::Packet& rpac) std::lock_guard lkp(m_crit.players); m_players.emplace(*PeerPlayerId(player.socket), std::move(player)); UpdatePadMapping(); // sync pad mappings with everyone + UpdateGBAConfig(); UpdateWiimoteMapping(); } @@ -530,12 +535,14 @@ unsigned int NetPlayServer::OnDisconnect(const Client& player) // alert other players of disconnect SendToClients(spac); - for (PlayerId& mapping : m_pad_map) + for (size_t i = 0; i < m_pad_map.size(); ++i) { - if (mapping == pid) + if (m_pad_map[i] == pid) { - mapping = 0; + m_pad_map[i] = 0; + m_gba_config[i].enabled = false; UpdatePadMapping(); + UpdateGBAConfig(); } } @@ -557,6 +564,11 @@ PadMappingArray NetPlayServer::GetPadMapping() const return m_pad_map; } +GBAConfigArray NetPlayServer::GetGBAConfig() const +{ + return m_gba_config; +} + PadMappingArray NetPlayServer::GetWiimoteMapping() const { return m_wiimote_map; @@ -569,6 +581,26 @@ void NetPlayServer::SetPadMapping(const PadMappingArray& mappings) UpdatePadMapping(); } +// called from ---GUI--- thread +void NetPlayServer::SetGBAConfig(const GBAConfigArray& mappings, bool update_rom) +{ +#ifdef HAS_LIBMGBA + m_gba_config = mappings; + if (update_rom) + { + for (size_t i = 0; i < m_gba_config.size(); ++i) + { + auto& config = m_gba_config[i]; + if (!config.enabled) + continue; + std::string rom_path = Config::Get(Config::MAIN_GBA_ROM_PATHS[i]); + config.has_rom = HW::GBA::Core::GetRomInfo(rom_path.c_str(), config.hash, config.title); + } + } +#endif + UpdateGBAConfig(); +} + // called from ---GUI--- thread void NetPlayServer::SetWiimoteMapping(const PadMappingArray& mappings) { @@ -588,6 +620,20 @@ void NetPlayServer::UpdatePadMapping() SendToClients(spac); } +// called from ---GUI--- thread and ---NETPLAY--- thread +void NetPlayServer::UpdateGBAConfig() +{ + sf::Packet spac; + spac << static_cast(NP_MSG_GBA_CONFIG); + for (const auto& config : m_gba_config) + { + spac << config.enabled << config.has_rom << config.title; + for (auto& data : config.hash) + spac << data; + } + SendToClients(spac); +} + // called from ---NETPLAY--- thread void NetPlayServer::UpdateWiimoteMapping() { @@ -751,12 +797,16 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player) } GCPadStatus pad; - packet >> pad.button >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> - pad.substickX >> pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected; + packet >> pad.button; + spac << map << pad.button; + if (!m_gba_config.at(map).enabled) + { + packet >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> pad.substickX >> + pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected; - spac << map << pad.button << pad.analogA << pad.analogB << pad.stickX << pad.stickY - << pad.substickX << pad.substickY << pad.triggerLeft << pad.triggerRight - << pad.isConnected; + spac << pad.analogA << pad.analogB << pad.stickX << pad.stickY << pad.substickX + << pad.substickY << pad.triggerLeft << pad.triggerRight << pad.isConnected; + } } if (m_host_input_authority) @@ -787,12 +837,16 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player) packet >> map; GCPadStatus pad; - packet >> pad.button >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> - pad.substickX >> pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected; + packet >> pad.button; + spac << map << pad.button; + if (!m_gba_config.at(map).enabled) + { + packet >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> pad.substickX >> + pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected; - spac << map << pad.button << pad.analogA << pad.analogB << pad.stickX << pad.stickY - << pad.substickX << pad.substickY << pad.triggerLeft << pad.triggerRight - << pad.isConnected; + spac << pad.analogA << pad.analogB << pad.stickX << pad.stickY << pad.substickX + << pad.substickY << pad.triggerLeft << pad.triggerRight << pad.isConnected; + } } SendToClients(spac, player.pid); @@ -1316,6 +1370,7 @@ bool NetPlayServer::SetupNetSettings() Config::Get(Config::NETPLAY_SYNC_ALL_WII_SAVES) && Config::Get(Config::NETPLAY_SYNC_SAVES); settings.m_GolfMode = Config::Get(Config::NETPLAY_NETWORK_MODE) == "golf"; settings.m_UseFMA = DoAllPlayersHaveHardwareFMA(); + settings.m_HideRemoteGBAs = Config::Get(Config::NETPLAY_HIDE_REMOTE_GBAS); // Unload GameINI to restore things to normal Config::RemoveLayer(Config::LayerType::GlobalGame); @@ -1501,6 +1556,7 @@ bool NetPlayServer::StartGame() spac << m_settings.m_GolfMode; spac << m_settings.m_UseFMA; + spac << m_settings.m_HideRemoteGBAs; SendAsyncToClients(std::move(spac)); @@ -1556,6 +1612,12 @@ bool NetPlayServer::SyncSaveData() save_count++; } + for (const auto& config : m_gba_config) + { + if (config.enabled && config.has_rom) + save_count++; + } + { sf::Packet pac; pac << static_cast(NP_MSG_SYNC_SAVE_DATA); @@ -1758,6 +1820,36 @@ bool NetPlayServer::SyncSaveData() SendChunkedToClients(std::move(pac), 1, "Wii Save Synchronization"); } + for (size_t i = 0; i < m_gba_config.size(); ++i) + { + if (m_gba_config[i].enabled && m_gba_config[i].has_rom) + { + sf::Packet pac; + pac << static_cast(NP_MSG_SYNC_SAVE_DATA); + pac << static_cast(SYNC_SAVE_DATA_GBA); + pac << static_cast(i); + + std::string path; +#ifdef HAS_LIBMGBA + path = HW::GBA::Core::GetSavePath(Config::Get(Config::MAIN_GBA_ROM_PATHS[i]), + static_cast(i)); +#endif + if (File::Exists(path)) + { + if (!CompressFileIntoPacket(path, pac)) + return false; + } + else + { + // No file, so we'll say the size is 0 + pac << sf::Uint64{0}; + } + + SendChunkedToClients(std::move(pac), 1, + fmt::format("GBA{} Save File Synchronization", i + 1)); + } + } + return true; } diff --git a/Source/Core/Core/NetPlayServer.h b/Source/Core/Core/NetPlayServer.h index 26c7c6e20f..87c1d46f63 100644 --- a/Source/Core/Core/NetPlayServer.h +++ b/Source/Core/Core/NetPlayServer.h @@ -58,6 +58,9 @@ public: PadMappingArray GetPadMapping() const; void SetPadMapping(const PadMappingArray& mappings); + GBAConfigArray GetGBAConfig() const; + void SetGBAConfig(const GBAConfigArray& configs, bool update_rom); + PadMappingArray GetWiimoteMapping() const; void SetWiimoteMapping(const PadMappingArray& mappings); @@ -134,6 +137,7 @@ private: void OnConnectReady(ENetAddress) override {} void OnConnectFailed(TraversalConnectFailedReason) override {} void UpdatePadMapping(); + void UpdateGBAConfig(); void UpdateWiimoteMapping(); std::vector> GetInterfaceListInternal() const; void ChunkedDataThreadFunc(); @@ -153,6 +157,7 @@ private: u32 m_current_game = 0; unsigned int m_target_buffer_size = 0; PadMappingArray m_pad_map; + GBAConfigArray m_gba_config; PadMappingArray m_wiimote_map; unsigned int m_save_data_synced_players = 0; unsigned int m_codes_synced_players = 0; diff --git a/Source/Core/DolphinQt/GBAWidget.cpp b/Source/Core/DolphinQt/GBAWidget.cpp index df9423a67f..291720e99e 100644 --- a/Source/Core/DolphinQt/GBAWidget.cpp +++ b/Source/Core/DolphinQt/GBAWidget.cpp @@ -58,6 +58,17 @@ GBAWidget::GBAWidget(std::weak_ptr core, int device_number, m_is_local_pad(true), m_volume(0), m_muted(false), m_force_disconnect(false) { bool visible = true; + if (NetPlay::IsNetPlayRunning()) + { + NetPlay::PadDetails details = NetPlay::GetPadDetails(m_device_number); + if (details.local_pad < 4) + { + m_netplayer_name = details.player_name; + m_is_local_pad = details.is_local; + m_local_pad = details.local_pad; + visible = !details.hide_gba; + } + } setWindowIcon(Resources::GetAppIcon()); setAcceptDrops(true); diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp index d60eedc819..518eca5c23 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +36,9 @@ #include "Core/Config/NetplaySettings.h" #include "Core/ConfigManager.h" #include "Core/Core.h" +#ifdef HAS_LIBMGBA +#include "Core/HW/GBACore.h" +#endif #include "Core/NetPlayServer.h" #include "Core/SyncIdentifier.h" @@ -47,6 +51,7 @@ #include "DolphinQt/QtUtils/RunOnObject.h" #include "DolphinQt/Resources.h" #include "DolphinQt/Settings.h" +#include "DolphinQt/Settings/GameCubePane.h" #include "UICommon/DiscordPresence.h" #include "UICommon/GameFile.h" @@ -171,6 +176,8 @@ void NetPlayDialog::CreateMainLayout() m_record_input_action->setCheckable(true); m_golf_mode_overlay_action = m_other_menu->addAction(tr("Show Golf Mode Overlay")); m_golf_mode_overlay_action->setCheckable(true); + m_hide_remote_gbas_action = m_other_menu->addAction(tr("Hide Remote GBAs")); + m_hide_remote_gbas_action->setCheckable(true); m_game_button->setDefault(false); m_game_button->setAutoDefault(false); @@ -279,6 +286,7 @@ void NetPlayDialog::ConnectWidgets() m_pad_mapping->exec(); Settings::Instance().GetNetPlayServer()->SetPadMapping(m_pad_mapping->GetGCPadArray()); + Settings::Instance().GetNetPlayServer()->SetGBAConfig(m_pad_mapping->GetGBAArray(), true); Settings::Instance().GetNetPlayServer()->SetWiimoteMapping(m_pad_mapping->GetWiimoteArray()); }); @@ -365,6 +373,7 @@ void NetPlayDialog::ConnectWidgets() connect(m_golf_mode_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings); connect(m_golf_mode_overlay_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings); connect(m_fixed_delay_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings); + connect(m_hide_remote_gbas_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings); } void NetPlayDialog::SendMessage(const std::string& msg) @@ -471,6 +480,11 @@ void NetPlayDialog::show(std::string nickname, bool use_traversal) m_data_menu->menuAction()->setVisible(is_hosting); m_network_menu->menuAction()->setVisible(is_hosting); m_md5_menu->menuAction()->setVisible(is_hosting); +#ifdef HAS_LIBMGBA + m_hide_remote_gbas_action->setVisible(is_hosting); +#else + m_hide_remote_gbas_action->setVisible(false); +#endif m_start_button->setHidden(!is_hosting); m_kick_button->setHidden(!is_hosting); m_assign_ports_button->setHidden(!is_hosting); @@ -570,20 +584,6 @@ void NetPlayDialog::UpdateGUI() {tr("Player"), tr("Game Status"), tr("Ping"), tr("Mapping"), tr("Revision")}); m_players_list->setRowCount(m_player_count); - const auto get_mapping_string = [](const NetPlay::Player* player, - const NetPlay::PadMappingArray& array) { - std::string str; - for (size_t i = 0; i < array.size(); i++) - { - if (player->pid == array[i]) - str += std::to_string(i + 1); - else - str += '-'; - } - - return '|' + str + '|'; - }; - static const std::map player_status{ {NetPlay::SyncIdentifierComparison::SameGame, tr("OK")}, {NetPlay::SyncIdentifierComparison::DifferentVersion, tr("Wrong Version")}, @@ -599,9 +599,9 @@ void NetPlayDialog::UpdateGUI() player_status.at(p->game_status) : QStringLiteral("?")); auto* ping_item = new QTableWidgetItem(QStringLiteral("%1 ms").arg(p->ping)); - auto* mapping_item = new QTableWidgetItem( - QString::fromStdString(get_mapping_string(p, client->GetPadMapping()) + - get_mapping_string(p, client->GetWiimoteMapping()))); + auto* mapping_item = + new QTableWidgetItem(QString::fromStdString(NetPlay::GetPlayerMappingString( + p->pid, client->GetPadMapping(), client->GetGBAConfig(), client->GetWiimoteMapping()))); auto* revision_item = new QTableWidgetItem(QString::fromStdString(p->revision)); for (auto* item : {name_item, status_item, ping_item, mapping_item, revision_item}) @@ -743,6 +743,20 @@ void NetPlayDialog::OnMsgChangeGame(const NetPlay::SyncIdentifier& sync_identifi DisplayMessage(tr("Game changed to \"%1\"").arg(qname), "magenta"); } +void NetPlayDialog::OnMsgChangeGBARom(int pad, const NetPlay::GBAConfig& config) +{ + if (config.has_rom) + { + DisplayMessage( + tr("GBA%1 ROM changed to \"%2\"").arg(pad + 1).arg(QString::fromStdString(config.title)), + "magenta"); + } + else + { + DisplayMessage(tr("GBA%1 ROM disabled").arg(pad + 1), "magenta"); + } +} + void NetPlayDialog::GameStatusChanged(bool running) { QueueOnObject(this, [this, running] { SetOptionsEnabled(!running); }); @@ -976,6 +990,51 @@ NetPlayDialog::FindGameFile(const NetPlay::SyncIdentifier& sync_identifier, return nullptr; } +std::string NetPlayDialog::FindGBARomPath(const std::array& hash, std::string_view title, + int device_number) +{ +#ifdef HAS_LIBMGBA + auto result = RunOnObject(this, [&, this] { + std::string rom_path; + std::array rom_hash; + std::string rom_title; + for (size_t i = device_number; i < static_cast(device_number) + 4; ++i) + { + rom_path = Config::Get(Config::MAIN_GBA_ROM_PATHS[i % 4]); + if (!rom_path.empty() && HW::GBA::Core::GetRomInfo(rom_path.c_str(), rom_hash, rom_title) && + rom_hash == hash && rom_title == title) + { + return rom_path; + } + } + while (!(rom_path = GameCubePane::GetOpenGBARom(title)).empty()) + { + if (HW::GBA::Core::GetRomInfo(rom_path.c_str(), rom_hash, rom_title)) + { + if (rom_hash == hash && rom_title == title) + return rom_path; + ModalMessageBox::critical( + this, tr("Error"), + QString::fromStdString(Common::FmtFormatT( + "Mismatched ROMs\n" + "Selected: {0}\n- Title: {1}\n- Hash: {2:02X}\n" + "Expected:\n- Title: {3}\n- Hash: {4:02X}", + rom_path, rom_title, fmt::join(rom_hash, ""), title, fmt::join(hash, "")))); + } + else + { + ModalMessageBox::critical( + this, tr("Error"), tr("%1 is not a valid ROM").arg(QString::fromStdString(rom_path))); + } + } + return std::string(); + }); + if (result) + return *result; +#endif + return {}; +} + void NetPlayDialog::LoadSettings() { const int buffer_size = Config::Get(Config::NETPLAY_BUFFER_SIZE); @@ -987,6 +1046,7 @@ void NetPlayDialog::LoadSettings() const bool strict_settings_sync = Config::Get(Config::NETPLAY_STRICT_SETTINGS_SYNC); const bool sync_all_wii_saves = Config::Get(Config::NETPLAY_SYNC_ALL_WII_SAVES); const bool golf_mode_overlay = Config::Get(Config::NETPLAY_GOLF_MODE_OVERLAY); + const bool hide_remote_gbas = Config::Get(Config::NETPLAY_HIDE_REMOTE_GBAS); m_buffer_size_box->setValue(buffer_size); m_save_sd_action->setChecked(write_save_sdcard_data); @@ -997,6 +1057,7 @@ void NetPlayDialog::LoadSettings() m_strict_settings_sync_action->setChecked(strict_settings_sync); m_sync_all_wii_saves_action->setChecked(sync_all_wii_saves); m_golf_mode_overlay_action->setChecked(golf_mode_overlay); + m_hide_remote_gbas_action->setChecked(hide_remote_gbas); const std::string network_mode = Config::Get(Config::NETPLAY_NETWORK_MODE); @@ -1036,6 +1097,7 @@ void NetPlayDialog::SaveSettings() Config::SetBase(Config::NETPLAY_STRICT_SETTINGS_SYNC, m_strict_settings_sync_action->isChecked()); Config::SetBase(Config::NETPLAY_SYNC_ALL_WII_SAVES, m_sync_all_wii_saves_action->isChecked()); Config::SetBase(Config::NETPLAY_GOLF_MODE_OVERLAY, m_golf_mode_overlay_action->isChecked()); + Config::SetBase(Config::NETPLAY_HIDE_REMOTE_GBAS, m_hide_remote_gbas_action->isChecked()); std::string network_mode; if (m_fixed_delay_action->isChecked()) diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h index 51394e7993..34a5d7d8bc 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h @@ -46,6 +46,7 @@ public: void OnMsgChangeGame(const NetPlay::SyncIdentifier& sync_identifier, const std::string& netplay_name) override; + void OnMsgChangeGBARom(int pad, const NetPlay::GBAConfig& config) override; void OnMsgStartGame() override; void OnMsgStopGame() override; void OnMsgPowerButton() override; @@ -68,6 +69,8 @@ public: std::shared_ptr FindGameFile(const NetPlay::SyncIdentifier& sync_identifier, NetPlay::SyncIdentifierComparison* found = nullptr) override; + std::string FindGBARomPath(const std::array& hash, std::string_view title, + int device_number) override; void LoadSettings(); void SaveSettings(); @@ -138,6 +141,7 @@ private: QAction* m_golf_mode_action; QAction* m_golf_mode_overlay_action; QAction* m_fixed_delay_action; + QAction* m_hide_remote_gbas_action; QPushButton* m_quit_button; QSplitter* m_splitter; QActionGroup* m_network_mode_group; diff --git a/Source/Core/DolphinQt/NetPlay/PadMappingDialog.cpp b/Source/Core/DolphinQt/NetPlay/PadMappingDialog.cpp index aa57a1a913..90836a5db0 100644 --- a/Source/Core/DolphinQt/NetPlay/PadMappingDialog.cpp +++ b/Source/Core/DolphinQt/NetPlay/PadMappingDialog.cpp @@ -3,6 +3,7 @@ #include "DolphinQt/NetPlay/PadMappingDialog.h" +#include #include #include #include @@ -31,15 +32,19 @@ void PadMappingDialog::CreateWidgets() for (unsigned int i = 0; i < m_wii_boxes.size(); i++) { m_gc_boxes[i] = new QComboBox; + m_gba_boxes[i] = new QCheckBox(tr("GBA Port %1").arg(i + 1)); m_wii_boxes[i] = new QComboBox; m_main_layout->addWidget(new QLabel(tr("GC Port %1").arg(i + 1)), 0, i); m_main_layout->addWidget(m_gc_boxes[i], 1, i); - m_main_layout->addWidget(new QLabel(tr("Wii Remote %1").arg(i + 1)), 2, i); - m_main_layout->addWidget(m_wii_boxes[i], 3, i); +#ifdef HAS_LIBMGBA + m_main_layout->addWidget(m_gba_boxes[i], 2, i); +#endif + m_main_layout->addWidget(new QLabel(tr("Wii Remote %1").arg(i + 1)), 3, i); + m_main_layout->addWidget(m_wii_boxes[i], 4, i); } - m_main_layout->addWidget(m_button_box, 4, 0, 1, -1); + m_main_layout->addWidget(m_button_box, 5, 0, 1, -1); setLayout(m_main_layout); } @@ -55,6 +60,11 @@ void PadMappingDialog::ConnectWidgets() &PadMappingDialog::OnMappingChanged); } } + for (const auto& checkbox : m_gba_boxes) + { + connect(checkbox, qOverload(&QCheckBox::stateChanged), this, + &PadMappingDialog::OnMappingChanged); + } } int PadMappingDialog::exec() @@ -64,6 +74,7 @@ int PadMappingDialog::exec() // Load Settings m_players = client->GetPlayers(); m_pad_mapping = server->GetPadMapping(); + m_gba_config = server->GetGBAConfig(); m_wii_mapping = server->GetWiimoteMapping(); QStringList players; @@ -93,6 +104,13 @@ int PadMappingDialog::exec() } } + for (size_t i = 0; i < m_gba_boxes.size(); i++) + { + const QSignalBlocker blocker(m_gba_boxes[i]); + + m_gba_boxes[i]->setChecked(m_gba_config[i].enabled); + } + return QDialog::exec(); } @@ -101,6 +119,11 @@ NetPlay::PadMappingArray PadMappingDialog::GetGCPadArray() return m_pad_mapping; } +NetPlay::GBAConfigArray PadMappingDialog::GetGBAArray() +{ + return m_gba_config; +} + NetPlay::PadMappingArray PadMappingDialog::GetWiimoteArray() { return m_wii_mapping; @@ -114,6 +137,7 @@ void PadMappingDialog::OnMappingChanged() int wii_id = m_wii_boxes[i]->currentIndex(); m_pad_mapping[i] = gc_id > 0 ? m_players[gc_id - 1]->pid : 0; + m_gba_config[i].enabled = m_gba_boxes[i]->isChecked(); m_wii_mapping[i] = wii_id > 0 ? m_players[wii_id - 1]->pid : 0; } } diff --git a/Source/Core/DolphinQt/NetPlay/PadMappingDialog.h b/Source/Core/DolphinQt/NetPlay/PadMappingDialog.h index 5123e75c29..cd7a61c2bf 100644 --- a/Source/Core/DolphinQt/NetPlay/PadMappingDialog.h +++ b/Source/Core/DolphinQt/NetPlay/PadMappingDialog.h @@ -7,6 +7,7 @@ #include "Core/NetPlayProto.h" +class QCheckBox; class QGridLayout; class QComboBox; class QDialogButtonBox; @@ -25,6 +26,7 @@ public: int exec() override; NetPlay::PadMappingArray GetGCPadArray(); + NetPlay::GBAConfigArray GetGBAArray(); NetPlay::PadMappingArray GetWiimoteArray(); private: @@ -34,10 +36,12 @@ private: void OnMappingChanged(); NetPlay::PadMappingArray m_pad_mapping; + NetPlay::GBAConfigArray m_gba_config; NetPlay::PadMappingArray m_wii_mapping; QGridLayout* m_main_layout; std::array m_gc_boxes; + std::array m_gba_boxes; std::array m_wii_boxes; std::vector m_players; QDialogButtonBox* m_button_box; diff --git a/Source/Core/DolphinQt/Settings/GameCubePane.cpp b/Source/Core/DolphinQt/Settings/GameCubePane.cpp index fcaa4af794..8f9f57c052 100644 --- a/Source/Core/DolphinQt/Settings/GameCubePane.cpp +++ b/Source/Core/DolphinQt/Settings/GameCubePane.cpp @@ -542,6 +542,10 @@ void GameCubePane::SaveSettings() Config::SetBaseOrCurrent(Config::MAIN_GBA_ROM_PATHS[i], m_gba_rom_edits[i]->text().toStdString()); } + + auto server = Settings::Instance().GetNetPlayServer(); + if (server) + server->SetGBAConfig(server->GetGBAConfig(), true); } #endif From b8f0e97c025dc7a50c7c729185195b3577321987 Mon Sep 17 00:00:00 2001 From: Bonta <40473493+Bonta0@users.noreply.github.com> Date: Sun, 4 Jul 2021 13:38:30 +0200 Subject: [PATCH 18/18] Movie: GBA Support --- Source/Core/Core/HW/SI/SI.cpp | 6 +- Source/Core/Core/Movie.cpp | 101 ++++++++++++++++++++------- Source/Core/Core/Movie.h | 16 ++++- Source/Core/Core/NetPlayClient.cpp | 16 +++-- Source/Core/DolphinQt/MainWindow.cpp | 17 +++-- 5 files changed, 113 insertions(+), 43 deletions(-) diff --git a/Source/Core/Core/HW/SI/SI.cpp b/Source/Core/Core/HW/SI/SI.cpp index caaa95cbae..a45185527a 100644 --- a/Source/Core/Core/HW/SI/SI.cpp +++ b/Source/Core/Core/HW/SI/SI.cpp @@ -419,7 +419,11 @@ void Init() { s_desired_device_types[i] = SIDEVICE_NONE; - if (Movie::IsUsingPad(i)) + if (Movie::IsUsingGBA(i)) + { + s_desired_device_types[i] = SIDEVICE_GC_GBA_EMULATED; + } + else if (Movie::IsUsingPad(i)) { SIDevices current = SConfig::GetInstance().m_SIDevice[i]; // GC pad-compatible devices can be used for both playing and recording diff --git a/Source/Core/Core/Movie.cpp b/Source/Core/Core/Movie.cpp index 7ea8c0a56e..31e8ca3b43 100644 --- a/Source/Core/Core/Movie.cpp +++ b/Source/Core/Core/Movie.cpp @@ -82,7 +82,8 @@ static bool s_bReadOnly = true; static u32 s_rerecords = 0; static PlayMode s_playMode = MODE_NONE; -static u8 s_controllers = 0; +static std::array s_controllers{}; +static std::array s_wiimotes{}; static ControllerState s_padState; static DTMHeader tmpHeader; static std::vector s_temp_input; @@ -157,24 +158,33 @@ std::string GetInputDisplay() { if (!IsMovieActive()) { - s_controllers = 0; + s_controllers = {}; + s_wiimotes = {}; for (int i = 0; i < 4; ++i) { - if (SerialInterface::GetDeviceType(i) != SerialInterface::SIDEVICE_NONE) - s_controllers |= (1 << i); - if (WiimoteCommon::GetSource(i) != WiimoteSource::None) - s_controllers |= (1 << (i + 4)); + if (SerialInterface::GetDeviceType(i) == SerialInterface::SIDEVICE_GC_GBA_EMULATED) + s_controllers[i] = ControllerType::GBA; + else if (SerialInterface::GetDeviceType(i) != SerialInterface::SIDEVICE_NONE) + s_controllers[i] = ControllerType::GC; + else + s_controllers[i] = ControllerType::None; + s_wiimotes[i] = WiimoteCommon::GetSource(i) != WiimoteSource::None; } } std::string input_display; { std::lock_guard guard(s_input_display_lock); - for (int i = 0; i < 8; ++i) + for (int i = 0; i < 4; ++i) { - if ((s_controllers & (1 << i)) != 0) + if (IsUsingPad(i)) input_display += s_InputDisplay[i] + '\n'; } + for (int i = 0; i < 4; ++i) + { + if (IsUsingWiimote(i)) + input_display += s_InputDisplay[i + 4] + '\n'; + } } return input_display; } @@ -386,7 +396,7 @@ void SetReset(bool reset) bool IsUsingPad(int controller) { - return ((s_controllers & (1 << controller)) != 0); + return s_controllers[controller] != ControllerType::None; } bool IsUsingBongo(int controller) @@ -394,9 +404,14 @@ bool IsUsingBongo(int controller) return ((s_bongos & (1 << controller)) != 0); } +bool IsUsingGBA(int controller) +{ + return s_controllers[controller] == ControllerType::GBA; +} + bool IsUsingWiimote(int wiimote) { - return ((s_controllers & (1 << (wiimote + 4))) != 0); + return s_wiimotes[wiimote]; } bool IsConfigSaved() @@ -425,21 +440,29 @@ void ChangePads() if (!Core::IsRunning()) return; - int controllers = 0; + ControllerTypeArray controllers{}; for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i) { - if (SerialInterface::SIDevice_IsGCController(SConfig::GetInstance().m_SIDevice[i])) - controllers |= (1 << i); + if (SConfig::GetInstance().m_SIDevice[i] == SerialInterface::SIDEVICE_GC_GBA_EMULATED) + controllers[i] = ControllerType::GBA; + else if (SerialInterface::SIDevice_IsGCController(SConfig::GetInstance().m_SIDevice[i])) + controllers[i] = ControllerType::GC; + else + controllers[i] = ControllerType::None; } - if ((s_controllers & 0x0F) == controllers) + if (s_controllers == controllers) return; for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i) { SerialInterface::SIDevices device = SerialInterface::SIDEVICE_NONE; - if (IsUsingPad(i)) + if (IsUsingGBA(i)) + { + device = SerialInterface::SIDEVICE_GC_GBA_EMULATED; + } + else if (IsUsingPad(i)) { if (SerialInterface::SIDevice_IsGCController(SConfig::GetInstance().m_SIDevice[i])) { @@ -459,14 +482,15 @@ void ChangePads() // NOTE: Host / Emu Threads void ChangeWiiPads(bool instantly) { - int controllers = 0; + WiimoteEnabledArray wiimotes{}; for (int i = 0; i < MAX_WIIMOTES; ++i) - if (WiimoteCommon::GetSource(i) != WiimoteSource::None) - controllers |= (1 << i); + { + wiimotes[i] = WiimoteCommon::GetSource(i) != WiimoteSource::None; + } // This is important for Wiimotes, because they can desync easily if they get re-activated - if (instantly && (s_controllers >> 4) == controllers) + if (instantly && s_wiimotes == wiimotes) return; const auto bt = WiiUtils::GetBluetoothEmuDevice(); @@ -481,13 +505,16 @@ void ChangeWiiPads(bool instantly) } // NOTE: Host Thread -bool BeginRecordingInput(int controllers) +bool BeginRecordingInput(const ControllerTypeArray& controllers, + const WiimoteEnabledArray& wiimotes) { - if (s_playMode != MODE_NONE || controllers == 0) + if (s_playMode != MODE_NONE || + (controllers == ControllerTypeArray{} && wiimotes == WiimoteEnabledArray{})) return false; - Core::RunAsCPUThread([controllers] { + Core::RunAsCPUThread([controllers, wiimotes] { s_controllers = controllers; + s_wiimotes = wiimotes; s_currentFrame = s_totalFrames = 0; s_currentLagCount = s_totalLagCount = 0; s_currentInputCount = s_totalInputCount = 0; @@ -842,7 +869,16 @@ void RecordWiimote(int wiimote, const u8* data, u8 size) // NOTE: EmuThread / Host Thread void ReadHeader() { - s_controllers = tmpHeader.controllers; + for (int i = 0; i < 4; ++i) + { + if (tmpHeader.GBAControllers & (1 << i)) + s_controllers[i] = ControllerType::GBA; + else if (tmpHeader.controllers & (1 << i)) + s_controllers[i] = ControllerType::GC; + else + s_controllers[i] = ControllerType::None; + s_wiimotes[i] = (tmpHeader.controllers & (1 << (i + 4))) != 0; + } s_recordingStartTime = tmpHeader.recordingStartTime; if (s_rerecords < tmpHeader.numRerecords) s_rerecords = tmpHeader.numRerecords; @@ -1219,9 +1255,10 @@ bool PlayWiimote(int wiimote, WiimoteCommon::DataReportBuilder& rpt, int ext, PanicAlertFmtT( "Fatal desync. Aborting playback. (Error in PlayWiimote: {0} != {1}, byte {2}.){3}", sizeInMovie, size, s_currentByte, - (s_controllers & 0xF) ? " Try re-creating the recording with all GameCube controllers " - "disabled (in Configure > GameCube > Device Settings)." : - ""); + (s_controllers == ControllerTypeArray{}) ? + " Try re-creating the recording with all GameCube controllers " + "disabled (in Configure > GameCube > Device Settings)." : + ""); EndPlayInput(!s_bReadOnly); return false; } @@ -1296,7 +1333,17 @@ void SaveRecording(const std::string& filename) strncpy(header.gameID.data(), SConfig::GetInstance().GetGameID().c_str(), 6); header.bWii = SConfig::GetInstance().bWii; header.bFollowBranch = SConfig::GetInstance().bJITFollowBranch; - header.controllers = s_controllers & (SConfig::GetInstance().bWii ? 0xFF : 0x0F); + header.controllers = 0; + header.GBAControllers = 0; + for (int i = 0; i < 4; ++i) + { + if (IsUsingGBA(i)) + header.GBAControllers |= 1 << i; + if (IsUsingPad(i)) + header.controllers |= 1 << i; + if (IsUsingWiimote(i) && SConfig::GetInstance().bWii) + header.controllers |= 1 << (i + 4); + } header.bFromSaveState = s_bRecordingFromSaveState; header.frameCount = s_totalFrames; diff --git a/Source/Core/Core/Movie.h b/Source/Core/Core/Movie.h index dcf70cef91..0a7857c0e3 100644 --- a/Source/Core/Core/Movie.h +++ b/Source/Core/Core/Movie.h @@ -39,6 +39,15 @@ enum PlayMode MODE_PLAYING }; +enum class ControllerType +{ + None = 0, + GC, + GBA, +}; +using ControllerTypeArray = std::array; +using WiimoteEnabledArray = std::array; + // GameCube Controller State #pragma pack(push, 1) struct ControllerState @@ -116,7 +125,8 @@ struct DTMHeader u8 reserved3; bool bFollowBranch; bool bUseFMA; - std::array reserved; // Padding for any new config options + u8 GBAControllers; // GBA Controllers plugged in (the bits are ports 1-4) + std::array reserved; // Padding for any new config options std::array discChange; // Name of iso file to switch to, for two disc games. std::array revision; // Git hash u32 DSPiromHash; @@ -163,12 +173,14 @@ bool IsNetPlayRecording(); bool IsUsingPad(int controller); bool IsUsingWiimote(int wiimote); bool IsUsingBongo(int controller); +bool IsUsingGBA(int controller); void ChangePads(); void ChangeWiiPads(bool instantly = false); void SetReadOnly(bool bEnabled); -bool BeginRecordingInput(int controllers); +bool BeginRecordingInput(const ControllerTypeArray& controllers, + const WiimoteEnabledArray& wiimotes); void RecordInput(const GCPadStatus* PadStatus, int controllerID); void RecordWiimote(int wiimote, const u8* data, u8 size); diff --git a/Source/Core/Core/NetPlayClient.cpp b/Source/Core/Core/NetPlayClient.cpp index 4212d3031e..e0e5a27518 100644 --- a/Source/Core/Core/NetPlayClient.cpp +++ b/Source/Core/Core/NetPlayClient.cpp @@ -1606,15 +1606,19 @@ bool NetPlayClient::StartGame(const std::string& path) if (Movie::IsReadOnly()) Movie::SetReadOnly(false); - u8 controllers_mask = 0; + Movie::ControllerTypeArray controllers{}; + Movie::WiimoteEnabledArray wiimotes{}; for (unsigned int i = 0; i < 4; ++i) { - if (m_pad_map[i] > 0) - controllers_mask |= (1 << i); - if (m_wiimote_map[i] > 0) - controllers_mask |= (1 << (i + 4)); + if (m_pad_map[i] > 0 && m_gba_config[i].enabled) + controllers[i] = Movie::ControllerType::GBA; + else if (m_pad_map[i] > 0) + controllers[i] = Movie::ControllerType::GC; + else + controllers[i] = Movie::ControllerType::None; + wiimotes[i] = m_wiimote_map[i] > 0; } - Movie::BeginRecordingInput(controllers_mask); + Movie::BeginRecordingInput(controllers, wiimotes); } for (unsigned int i = 0; i < 4; ++i) diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index 317150812b..cd566b7624 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -1691,18 +1691,21 @@ void MainWindow::OnStartRecording() emit ReadOnlyModeChanged(true); } - int controllers = 0; + Movie::ControllerTypeArray controllers{}; + Movie::WiimoteEnabledArray wiimotes{}; for (int i = 0; i < 4; i++) { - if (SerialInterface::SIDevice_IsGCController(SConfig::GetInstance().m_SIDevice[i])) - controllers |= (1 << i); - - if (WiimoteCommon::GetSource(i) != WiimoteSource::None) - controllers |= (1 << (i + 4)); + if (SConfig::GetInstance().m_SIDevice[i] == SerialInterface::SIDEVICE_GC_GBA_EMULATED) + controllers[i] = Movie::ControllerType::GBA; + else if (SerialInterface::SIDevice_IsGCController(SConfig::GetInstance().m_SIDevice[i])) + controllers[i] = Movie::ControllerType::GC; + else + controllers[i] = Movie::ControllerType::None; + wiimotes[i] = WiimoteCommon::GetSource(i) != WiimoteSource::None; } - if (Movie::BeginRecordingInput(controllers)) + if (Movie::BeginRecordingInput(controllers, wiimotes)) { emit RecordingStatusChanged(true);