Merge pull request #9600 from Bonta0/mgba-pr

Full GBA Controllers Integration with mGBA
This commit is contained in:
JMC47 2021-07-21 02:36:43 -04:00 committed by GitHub
commit a1e806ed76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
99 changed files with 4327 additions and 412 deletions

1
.gitignore vendored
View File

@ -3,6 +3,7 @@ Thumbs.db
# Ignore Finder view option files created by OS X # Ignore Finder view option files created by OS X
.DS_Store .DS_Store
# Ignore autogenerated source files # Ignore autogenerated source files
Externals/mGBA/version.c
Source/Core/Common/scmrev.h Source/Core/Common/scmrev.h
# Ignore files output by build # Ignore files output by build
/[Bb]uild*/ /[Bb]uild*/

5
.gitmodules vendored
View File

@ -3,3 +3,8 @@
url = https://github.com/dolphin-emu/ext-win-qt.git url = https://github.com/dolphin-emu/ext-win-qt.git
branch = master branch = master
shallow = true shallow = true
[submodule "Externals/mGBA/mgba"]
path = Externals/mGBA/mgba
url = https://github.com/mgba-emu/mgba.git
branch = master
shallow = true

View File

@ -45,6 +45,7 @@ option(ENABLE_LLVM "Enables LLVM support, for disassembly" ON)
option(ENABLE_TESTS "Enables building the unit tests" ON) option(ENABLE_TESTS "Enables building the unit tests" ON)
option(ENABLE_VULKAN "Enables vulkan video backend" 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_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 # Maintainers: if you consider blanket disabling this for your users, please
# consider the following points: # consider the following points:
@ -826,6 +827,14 @@ if(USE_DISCORD_PRESENCE)
include_directories(Externals/discord-rpc/include) include_directories(Externals/discord-rpc/include)
endif() 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) find_package(SYSTEMD)
if(SYSTEMD_FOUND) if(SYSTEMD_FOUND)
message(STATUS "libsystemd found, enabling traversal server watchdog support") message(STATUS "libsystemd found, enabling traversal server watchdog support")

View File

@ -58,6 +58,9 @@
<ProjectReference Include="$(ExternalsDir)mbedtls\mbedTLS.vcxproj"> <ProjectReference Include="$(ExternalsDir)mbedtls\mbedTLS.vcxproj">
<Project>{bdb6578b-0691-4e80-a46c-df21639fd3b8}</Project> <Project>{bdb6578b-0691-4e80-a46c-df21639fd3b8}</Project>
</ProjectReference> </ProjectReference>
<ProjectReference Include="$(ExternalsDir)mGBA\mgba.vcxproj">
<Project>{864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6}</Project>
</ProjectReference>
<ProjectReference Include="$(ExternalsDir)miniupnpc\miniupnpc.vcxproj"> <ProjectReference Include="$(ExternalsDir)miniupnpc\miniupnpc.vcxproj">
<Project>{31643fdb-1bb8-4965-9de7-000fc88d35ae}</Project> <Project>{31643fdb-1bb8-4965-9de7-000fc88d35ae}</Project>
</ProjectReference> </ProjectReference>

View File

@ -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) [University of Illinois/NCSA Open Source license](http://llvm.org/docs/DeveloperPolicy.html#license)
- [LZO](http://www.oberhumer.com/opensource/lzo/): - [LZO](http://www.oberhumer.com/opensource/lzo/):
[GPLv2+](http://www.oberhumer.com/opensource/gpl.html) [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/): - [MiniUPnPc](http://miniupnp.free.fr/):
[3-clause BSD](https://github.com/miniupnp/miniupnp/blob/master/miniupnpc/LICENSE) [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): - [Microsoft Visual C++ Runtime Library](http://www.microsoft.com/en-us/download/details.aspx?id=40784):

13
Externals/mGBA/CMakeLists.txt vendored Normal file
View File

@ -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)

140
Externals/mGBA/make_version.c.js vendored Normal file
View File

@ -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 <mgba/core/version.h>\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);
}

1
Externals/mGBA/mgba vendored Submodule

@ -0,0 +1 @@
Subproject commit 9cccc5197ed73ba0a54f584d3121c27dc97405f5

241
Externals/mGBA/mgba.vcxproj vendored Normal file
View File

@ -0,0 +1,241 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\Source\VSProps\Base.Macros.props" />
<Import Project="$(VSPropsDir)Base.Targets.props" />
<PropertyGroup Label="Globals">
<ProjectGuid>{864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6}</ProjectGuid>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="$(VSPropsDir)Configuration.StaticLibrary.props" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings" />
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(VSPropsDir)Base.props" />
<Import Project="$(VSPropsDir)ClDisableAllWarnings.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>mgba\include;mgba\src;mgba\src\third-party\lzma;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>BUILD_STATIC;M_CORE_GB;M_CORE_GBA;USE_LZMA;_7ZIP_PPMD_SUPPPORT;HAVE_STRDUP;HAVE_SETLOCALE;HAVE_CHMOD;HAVE_UMASK;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<PreBuildEvent>
<Command>"$(CScript)" /nologo /E:JScript "make_version.c.js"</Command>
</PreBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="mgba\src\core\bitmap-cache.c" />
<ClCompile Include="mgba\src\core\cache-set.c">
<ObjectFileName>$(IntDir)/src/core/cache-set.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\core\cheats.c">
<ObjectFileName>$(IntDir)/src/core/cheats.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\core\config.c" />
<ClCompile Include="mgba\src\core\core.c">
<ObjectFileName>$(IntDir)/src/core/core.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\core\directories.c" />
<ClCompile Include="mgba\src\core\input.c">
<ObjectFileName>$(IntDir)/src/core/input.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\core\interface.c" />
<ClCompile Include="mgba\src\core\library.c" />
<ClCompile Include="mgba\src\core\lockstep.c">
<ObjectFileName>$(IntDir)/src/core/lockstep.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\core\log.c" />
<ClCompile Include="mgba\src\core\map-cache.c" />
<ClCompile Include="mgba\src\core\mem-search.c" />
<ClCompile Include="mgba\src\core\rewind.c" />
<ClCompile Include="mgba\src\core\scripting.c" />
<ClCompile Include="mgba\src\core\serialize.c">
<ObjectFileName>$(IntDir)/src/core/serialize.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\core\sync.c" />
<ClCompile Include="mgba\src\core\thread.c" />
<ClCompile Include="mgba\src\core\tile-cache.c" />
<ClCompile Include="mgba\src\core\timing.c" />
<ClCompile Include="mgba\src\sm83\decoder.c">
<ObjectFileName>$(IntDir)/src/sm83/decoder.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\sm83\isa-sm83.c" />
<ClCompile Include="mgba\src\sm83\sm83.c" />
<ClCompile Include="mgba\src\gb\audio.c">
<ObjectFileName>$(IntDir)/src/gb/audio.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\gb\cheats.c">
<ObjectFileName>$(IntDir)/src/gb/cheats.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\gb\core.c">
<ObjectFileName>$(IntDir)/src/gb/core.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\gb\gb.c" />
<ClCompile Include="mgba\src\gb\input.c">
<ObjectFileName>$(IntDir)/src/gb/input.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\gb\io.c">
<ObjectFileName>$(IntDir)/src/gb/io.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\gb\mbc.c" />
<ClCompile Include="mgba\src\gb\memory.c">
<ObjectFileName>$(IntDir)/src/gb/memory.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\gb\overrides.c">
<ObjectFileName>$(IntDir)/src/gb/overrides.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\gb\serialize.c">
<ObjectFileName>$(IntDir)/src/gb/serialize.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\gb\renderers\cache-set.c">
<ObjectFileName>$(IntDir)/src/gb/renderers/cache-set.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\gb\renderers\software.c" />
<ClCompile Include="mgba\src\gb\sio.c">
<ObjectFileName>$(IntDir)/src/gb/sio.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\gb\timer.c">
<ObjectFileName>$(IntDir)/src/gb/timer.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\gb\video.c">
<ObjectFileName>$(IntDir)/src/gb/video.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\arm\arm.c" />
<ClCompile Include="mgba\src\arm\decoder-arm.c" />
<ClCompile Include="mgba\src\arm\decoder.c">
<ObjectFileName>$(IntDir)/src/arm/decoder.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\arm\decoder-thumb.c" />
<ClCompile Include="mgba\src\arm\isa-arm.c" />
<ClCompile Include="mgba\src\arm\isa-thumb.c" />
<ClCompile Include="mgba\src\gba\audio.c">
<ObjectFileName>$(IntDir)/src/gba/audio.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\gba\bios.c" />
<ClCompile Include="mgba\src\gba\cart\ereader.c" />
<ClCompile Include="mgba\src\gba\cart\gpio.c" />
<ClCompile Include="mgba\src\gba\cart\matrix.c" />
<ClCompile Include="mgba\src\gba\cart\vfame.c" />
<ClCompile Include="mgba\src\gba\cheats.c">
<ObjectFileName>$(IntDir)/src/gba/cheats.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\gba\cheats\codebreaker.c" />
<ClCompile Include="mgba\src\gba\cheats\gameshark.c" />
<ClCompile Include="mgba\src\gba\cheats\parv3.c" />
<ClCompile Include="mgba\src\gba\core.c">
<ObjectFileName>$(IntDir)/src/gba/core.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\gba\dma.c" />
<ClCompile Include="mgba\src\gba\gba.c" />
<ClCompile Include="mgba\src\gba\hle-bios.c" />
<ClCompile Include="mgba\src\gba\input.c">
<ObjectFileName>$(IntDir)/src/gba/input.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\gba\io.c">
<ObjectFileName>$(IntDir)/src/gba/io.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\gba\memory.c">
<ObjectFileName>$(IntDir)/src/gba/memory.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\gba\overrides.c">
<ObjectFileName>$(IntDir)/src/gba/overrides.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\gba\renderers\cache-set.c">
<ObjectFileName>$(IntDir)/src/gba/renderers/cache-set.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\gba\renderers\common.c" />
<ClCompile Include="mgba\src\gba\renderers\gl.c" />
<ClCompile Include="mgba\src\gba\renderers\software-bg.c" />
<ClCompile Include="mgba\src\gba\renderers\software-mode0.c" />
<ClCompile Include="mgba\src\gba\renderers\software-obj.c" />
<ClCompile Include="mgba\src\gba\renderers\video-software.c" />
<ClCompile Include="mgba\src\gba\savedata.c" />
<ClCompile Include="mgba\src\gba\serialize.c">
<ObjectFileName>$(IntDir)/src/gba/serialize.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\gba\sharkport.c" />
<ClCompile Include="mgba\src\gba\sio.c">
<ObjectFileName>$(IntDir)/src/gba/sio.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\gba\sio\gbp.c" />
<ClCompile Include="mgba\src\gba\sio\joybus.c" />
<ClCompile Include="mgba\src\gba\timer.c">
<ObjectFileName>$(IntDir)/src/gba/timer.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\gba\video.c">
<ObjectFileName>$(IntDir)/src/gba/video.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\util\circle-buffer.c" />
<ClCompile Include="mgba\src\util\configuration.c" />
<ClCompile Include="mgba\src\util\convolve.c" />
<ClCompile Include="mgba\src\util\crc32.c" />
<ClCompile Include="mgba\src\util\elf-read.c" />
<ClCompile Include="mgba\src\util\export.c" />
<ClCompile Include="mgba\src\util\formatting.c" />
<ClCompile Include="mgba\src\util\gbk-table.c" />
<ClCompile Include="mgba\src\util\hash.c" />
<ClCompile Include="mgba\src\util\patch.c" />
<ClCompile Include="mgba\src\util\patch-fast.c" />
<ClCompile Include="mgba\src\util\patch-ips.c" />
<ClCompile Include="mgba\src\util\patch-ups.c" />
<ClCompile Include="mgba\src\util\png-io.c" />
<ClCompile Include="mgba\src\util\ring-fifo.c" />
<ClCompile Include="mgba\src\util\string.c" />
<ClCompile Include="mgba\src\util\table.c" />
<ClCompile Include="mgba\src\util\text-codec.c" />
<ClCompile Include="mgba\src\util\vfs.c" />
<ClCompile Include="version.c" />
<ClCompile Include="mgba\src\util\vfs\vfs-mem.c" />
<ClCompile Include="mgba\src\util\vfs\vfs-fifo.c" />
<ClCompile Include="mgba\src\util\vfs\vfs-fd.c" />
<ClCompile Include="mgba\src\platform\windows\vfs-w32.c" />
<ClCompile Include="mgba\src\platform\windows\memory.c">
<ObjectFileName>$(IntDir)/src/platform/windows/memory.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\third-party\inih\ini.c" />
<ClCompile Include="mgba\src\third-party\blip_buf\blip_buf.c" />
<ClCompile Include="mgba\src\util\vfs\vfs-lzma.c" />
<ClCompile Include="mgba\src\third-party\lzma\7zAlloc.c" />
<ClCompile Include="mgba\src\third-party\lzma\7zArcIn.c" />
<ClCompile Include="mgba\src\third-party\lzma\7zBuf.c" />
<ClCompile Include="mgba\src\third-party\lzma\7zBuf2.c" />
<ClCompile Include="mgba\src\third-party\lzma\7zCrc.c" />
<ClCompile Include="mgba\src\third-party\lzma\7zCrcOpt.c" />
<ClCompile Include="mgba\src\third-party\lzma\7zDec.c" />
<ClCompile Include="mgba\src\third-party\lzma\CpuArch.c" />
<ClCompile Include="mgba\src\third-party\lzma\Delta.c" />
<ClCompile Include="mgba\src\third-party\lzma\LzmaDec.c" />
<ClCompile Include="mgba\src\third-party\lzma\Lzma2Dec.c" />
<ClCompile Include="mgba\src\third-party\lzma\Bra.c" />
<ClCompile Include="mgba\src\third-party\lzma\Bra86.c" />
<ClCompile Include="mgba\src\third-party\lzma\BraIA64.c" />
<ClCompile Include="mgba\src\third-party\lzma\Bcj2.c" />
<ClCompile Include="mgba\src\third-party\lzma\Ppmd7.c" />
<ClCompile Include="mgba\src\third-party\lzma\Ppmd7Dec.c" />
<ClCompile Include="mgba\src\third-party\lzma\7zFile.c" />
<ClCompile Include="mgba\src\third-party\lzma\7zStream.c" />
<ClCompile Include="mgba\src\gba\sio\dolphin.c" />
<ClCompile Include="mgba\src\gba\sio\lockstep.c">
<ObjectFileName>$(IntDir)/src/gba/sio/lockstep.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\gb\sio\lockstep.c">
<ObjectFileName>$(IntDir)/src/gb/sio/lockstep.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\gb\sio\printer.c" />
<ClCompile Include="mgba\src\gba\extra\audio-mixer.c" />
<ClCompile Include="mgba\src\gba\extra\battlechip.c" />
<ClCompile Include="mgba\src\gba\extra\proxy.c">
<ObjectFileName>$(IntDir)/src/gba/extra/proxy.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\gb\extra\proxy.c">
<ObjectFileName>$(IntDir)/src/gb/extra/proxy.c.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="mgba\src\feature\commandline.c" />
<ClCompile Include="mgba\src\feature\thread-proxy.c" />
<ClCompile Include="mgba\src\feature\video-logger.c" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

427
Externals/mGBA/mgba.vcxproj.filters vendored Normal file
View File

@ -0,0 +1,427 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClCompile Include="mgba\src\core\bitmap-cache.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\core\cache-set.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\core\cheats.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\core\config.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\core\core.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\core\directories.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\core\input.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\core\interface.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\core\library.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\core\lockstep.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\core\log.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\core\map-cache.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\core\mem-search.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\core\rewind.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\core\scripting.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\core\serialize.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\core\sync.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\core\thread.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\core\tile-cache.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\core\timing.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\sm83\decoder.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\sm83\isa-sm83.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\sm83\sm83.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gb\audio.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gb\cheats.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gb\core.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gb\gb.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gb\input.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gb\io.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gb\mbc.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gb\memory.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gb\overrides.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gb\serialize.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gb\renderers\cache-set.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gb\renderers\software.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gb\sio.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gb\timer.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gb\video.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\arm\arm.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\arm\decoder-arm.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\arm\decoder.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\arm\decoder-thumb.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\arm\isa-arm.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\arm\isa-thumb.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\audio.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\bios.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\cart\ereader.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\cart\gpio.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\cart\matrix.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\cart\vfame.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\cheats.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\cheats\codebreaker.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\cheats\gameshark.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\cheats\parv3.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\core.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\dma.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\gba.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\hle-bios.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\input.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\io.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\memory.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\overrides.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\renderers\cache-set.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\renderers\common.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\renderers\gl.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\renderers\software-bg.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\renderers\software-mode0.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\renderers\software-obj.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\renderers\video-software.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\savedata.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\serialize.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\sharkport.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\sio.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\sio\gbp.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\sio\joybus.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\timer.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\video.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\util\circle-buffer.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\util\configuration.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\util\convolve.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\util\crc32.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\util\elf-read.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\util\export.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\util\formatting.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\util\gbk-table.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\util\hash.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\util\patch.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\util\patch-fast.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\util\patch-ips.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\util\patch-ups.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\util\png-io.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\util\ring-fifo.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\util\string.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\util\table.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\util\text-codec.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\util\vfs.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="version.c">
<Filter>Generated sources</Filter>
</ClCompile>
<ClCompile Include="mgba\src\util\vfs\vfs-mem.c">
<Filter>Virtual files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\util\vfs\vfs-fifo.c">
<Filter>Virtual files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\util\vfs\vfs-fd.c">
<Filter>Virtual files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\platform\windows\vfs-w32.c">
<Filter>Virtual files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\platform\windows\memory.c">
<Filter>Windows-specific code</Filter>
</ClCompile>
<ClCompile Include="mgba\src\third-party\inih\ini.c">
<Filter>Third-party code</Filter>
</ClCompile>
<ClCompile Include="mgba\src\third-party\blip_buf\blip_buf.c">
<Filter>Third-party code</Filter>
</ClCompile>
<ClCompile Include="mgba\src\util\vfs\vfs-lzma.c">
<Filter>Virtual files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\third-party\lzma\7zAlloc.c">
<Filter>Virtual files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\third-party\lzma\7zArcIn.c">
<Filter>Virtual files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\third-party\lzma\7zBuf.c">
<Filter>Virtual files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\third-party\lzma\7zBuf2.c">
<Filter>Virtual files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\third-party\lzma\7zCrc.c">
<Filter>Virtual files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\third-party\lzma\7zCrcOpt.c">
<Filter>Virtual files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\third-party\lzma\7zDec.c">
<Filter>Virtual files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\third-party\lzma\CpuArch.c">
<Filter>Virtual files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\third-party\lzma\Delta.c">
<Filter>Virtual files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\third-party\lzma\LzmaDec.c">
<Filter>Virtual files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\third-party\lzma\Lzma2Dec.c">
<Filter>Virtual files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\third-party\lzma\Bra.c">
<Filter>Virtual files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\third-party\lzma\Bra86.c">
<Filter>Virtual files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\third-party\lzma\BraIA64.c">
<Filter>Virtual files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\third-party\lzma\Bcj2.c">
<Filter>Virtual files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\third-party\lzma\Ppmd7.c">
<Filter>Virtual files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\third-party\lzma\Ppmd7Dec.c">
<Filter>Virtual files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\third-party\lzma\7zFile.c">
<Filter>Virtual files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\third-party\lzma\7zStream.c">
<Filter>Virtual files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\sio\dolphin.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\sio\lockstep.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gb\sio\lockstep.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gb\sio\printer.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\extra\audio-mixer.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\extra\battlechip.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gba\extra\proxy.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\gb\extra\proxy.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\feature\commandline.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\feature\thread-proxy.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mgba\src\feature\video-logger.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Filter Include="Generated sources">
<UniqueIdentifier>{57438DCC-46E8-3FBA-90F2-185F80CEBE2C}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files">
<UniqueIdentifier>{C0CFD641-7357-3B1D-B2A3-B2477AEF3147}</UniqueIdentifier>
</Filter>
<Filter Include="Third-party code">
<UniqueIdentifier>{6C07F537-79D5-3651-A634-9E523B9936B2}</UniqueIdentifier>
</Filter>
<Filter Include="Virtual files">
<UniqueIdentifier>{AFF59D0C-C624-393F-8703-2FB3784928C8}</UniqueIdentifier>
</Filter>
<Filter Include="Windows-specific code">
<UniqueIdentifier>{37E5D4D5-B263-3B94-8968-21228F26DF67}</UniqueIdentifier>
</Filter>
</ItemGroup>
</Project>

View File

@ -173,6 +173,11 @@ void Host_TitleChanged()
env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnTitleChanged()); env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnTitleChanged());
} }
std::unique_ptr<GBAHostInterface> Host_CreateGBAHost(std::weak_ptr<HW::GBA::Core> core)
{
return nullptr;
}
static bool MsgAlert(const char* caption, const char* text, bool yes_no, Common::MsgType style) 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 // If a panic alert happens very early in the execution of a game, we can crash here with

View File

@ -68,7 +68,8 @@ void InitSoundStream()
void PostInitSoundStream() 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(); UpdateSoundStream();
SetSoundStreamRunning(true); SetSoundStreamRunning(true);

View File

@ -47,6 +47,8 @@ void Mixer::DoState(PointerWrap& p)
m_dma_mixer.DoState(p); m_dma_mixer.DoState(p);
m_streaming_mixer.DoState(p); m_streaming_mixer.DoState(p);
m_wiimote_speaker_mixer.DoState(p); m_wiimote_speaker_mixer.DoState(p);
for (auto& mixer : m_gba_mixers)
mixer.DoState(p);
} }
// Executed from sound stream thread // 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 lvolume = m_LVolume.load();
s32 rvolume = m_RVolume.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. // TODO: consider a higher-quality resampling algorithm.
for (; currentSample < numSamples * 2 && ((indexW - indexR) & INDEX_MASK) > 2; currentSample += 2) for (; currentSample < numSamples * 2 && ((indexW - indexR) & INDEX_MASK) > 2; currentSample += 2)
{ {
u32 indexR2 = indexR + 2; // next sample u32 indexR2 = indexR + 2; // next sample
s16 l1 = Common::swap16(m_buffer[indexR & INDEX_MASK]); // current s16 l1 = read_buffer(indexR & INDEX_MASK); // current
s16 l2 = Common::swap16(m_buffer[indexR2 & INDEX_MASK]); // next s16 l2 = read_buffer(indexR2 & INDEX_MASK); // next
int sampleL = ((l1 << 16) + (l2 - l1) * (u16)m_frac) >> 16; int sampleL = ((l1 << 16) + (l2 - l1) * (u16)m_frac) >> 16;
sampleL = (sampleL * lvolume) >> 8; sampleL = (sampleL * lvolume) >> 8;
sampleL += samples[currentSample + 1]; sampleL += samples[currentSample + 1];
samples[currentSample + 1] = std::clamp(sampleL, -32767, 32767); samples[currentSample + 1] = std::clamp(sampleL, -32767, 32767);
s16 r1 = Common::swap16(m_buffer[(indexR + 1) & INDEX_MASK]); // current s16 r1 = read_buffer((indexR + 1) & INDEX_MASK); // current
s16 r2 = Common::swap16(m_buffer[(indexR2 + 1) & INDEX_MASK]); // next s16 r2 = read_buffer((indexR2 + 1) & INDEX_MASK); // next
int sampleR = ((r1 << 16) + (r2 - r1) * (u16)m_frac) >> 16; int sampleR = ((r1 << 16) + (r2 - r1) * (u16)m_frac) >> 16;
sampleR = (sampleR * rvolume) >> 8; sampleR = (sampleR * rvolume) >> 8;
sampleR += samples[currentSample]; sampleR += samples[currentSample];
@ -122,8 +128,8 @@ unsigned int Mixer::MixerFifo::Mix(short* samples, unsigned int numSamples,
// Padding // Padding
short s[2]; short s[2];
s[0] = Common::swap16(m_buffer[(indexR - 1) & INDEX_MASK]); s[0] = read_buffer((indexR - 1) & INDEX_MASK);
s[1] = Common::swap16(m_buffer[(indexR - 2) & INDEX_MASK]); s[1] = read_buffer((indexR - 2) & INDEX_MASK);
s[0] = (s[0] * rvolume) >> 8; s[0] = (s[0] * rvolume) >> 8;
s[1] = (s[1] * lvolume) >> 8; s[1] = (s[1] * lvolume) >> 8;
for (; currentSample < numSamples * 2; currentSample += 2) 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_dma_mixer.Mix(m_scratch_buffer.data(), available_samples, false);
m_streaming_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); 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) 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_dma_mixer.Mix(samples, num_samples, true);
m_streaming_mixer.Mix(samples, num_samples, true); m_streaming_mixer.Mix(samples, num_samples, true);
m_wiimote_speaker_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; 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) for (unsigned int i = 0; i < num_samples; ++i)
{ {
samples_stereo[i * 2] = Common::swap16(samples[i]); samples_stereo[i * 2] = samples[i];
samples_stereo[i * 2 + 1] = Common::swap16(samples[i]); samples_stereo[i * 2 + 1] = samples[i];
} }
m_wiimote_speaker_mixer.PushSamples(samples_stereo, num_samples); 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) void Mixer::SetDMAInputSampleRate(unsigned int rate)
{ {
m_dma_mixer.SetInputSampleRate(rate); m_dma_mixer.SetInputSampleRate(rate);
@ -276,6 +291,11 @@ void Mixer::SetStreamInputSampleRate(unsigned int rate)
m_streaming_mixer.SetInputSampleRate(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) void Mixer::SetStreamingVolume(unsigned int lvolume, unsigned int rvolume)
{ {
m_streaming_mixer.SetVolume(lvolume, 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); 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) void Mixer::StartLogDTKAudio(const std::string& filename)
{ {
if (!m_log_dtk_audio) if (!m_log_dtk_audio)

View File

@ -30,11 +30,17 @@ public:
void PushStreamingSamples(const short* samples, unsigned int num_samples); void PushStreamingSamples(const short* samples, unsigned int num_samples);
void PushWiimoteSpeakerSamples(const short* samples, unsigned int num_samples, void PushWiimoteSpeakerSamples(const short* samples, unsigned int num_samples,
unsigned int sample_rate); unsigned int sample_rate);
void PushGBASamples(int device_number, const short* samples, unsigned int num_samples);
unsigned int GetSampleRate() const { return m_sampleRate; } unsigned int GetSampleRate() const { return m_sampleRate; }
void SetDMAInputSampleRate(unsigned int rate); void SetDMAInputSampleRate(unsigned int rate);
void SetStreamInputSampleRate(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 SetStreamingVolume(unsigned int lvolume, unsigned int rvolume);
void SetWiimoteSpeakerVolume(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 StartLogDTKAudio(const std::string& filename);
void StopLogDTKAudio(); void StopLogDTKAudio();
@ -57,7 +63,8 @@ private:
class MixerFifo final class MixerFifo final
{ {
public: 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); void DoState(PointerWrap& p);
@ -71,6 +78,7 @@ private:
private: private:
Mixer* m_mixer; Mixer* m_mixer;
unsigned m_input_sample_rate; unsigned m_input_sample_rate;
bool m_little_endian;
std::array<short, MAX_SAMPLES * 2> m_buffer{}; std::array<short, MAX_SAMPLES * 2> m_buffer{};
std::atomic<u32> m_indexW{0}; std::atomic<u32> m_indexW{0};
std::atomic<u32> m_indexR{0}; std::atomic<u32> m_indexR{0};
@ -81,9 +89,11 @@ private:
u32 m_frac = 0; u32 m_frac = 0;
}; };
MixerFifo m_dma_mixer{this, 32000}; MixerFifo m_dma_mixer{this, 32000, false};
MixerFifo m_streaming_mixer{this, 48000}; MixerFifo m_streaming_mixer{this, 48000, false};
MixerFifo m_wiimote_speaker_mixer{this, 3000}; MixerFifo m_wiimote_speaker_mixer{this, 3000, true};
std::array<MixerFifo, 4> m_gba_mixers{MixerFifo{this, 48000, true}, MixerFifo{this, 48000, true},
MixerFifo{this, 48000, true}, MixerFifo{this, 48000, true}};
unsigned int m_sampleRate; unsigned int m_sampleRate;
bool m_is_stretching = false; bool m_is_stretching = false;

View File

@ -34,6 +34,7 @@
// Subdirs in the User dir returned by GetUserPath(D_USER_IDX) // Subdirs in the User dir returned by GetUserPath(D_USER_IDX)
#define GC_USER_DIR "GC" #define GC_USER_DIR "GC"
#define GBA_USER_DIR "GBA"
#define WII_USER_DIR "Wii" #define WII_USER_DIR "Wii"
#define CONFIG_DIR "Config" #define CONFIG_DIR "Config"
#define GAMESETTINGS_DIR "GameSettings" #define GAMESETTINGS_DIR "GameSettings"
@ -61,6 +62,7 @@
#define RESOURCES_DIR "Resources" #define RESOURCES_DIR "Resources"
#define THEMES_DIR "Themes" #define THEMES_DIR "Themes"
#define STYLES_DIR "Styles" #define STYLES_DIR "Styles"
#define GBASAVES_DIR "Saves"
#define ANAGLYPH_DIR "Anaglyph" #define ANAGLYPH_DIR "Anaglyph"
#define PASSIVE_DIR "Passive" #define PASSIVE_DIR "Passive"
#define PIPES_DIR "Pipes" #define PIPES_DIR "Pipes"
@ -119,6 +121,9 @@
#define GC_MEMCARDB "MemoryCardB" #define GC_MEMCARDB "MemoryCardB"
#define GC_MEMCARD_NETPLAY "NetPlayTemp" #define GC_MEMCARD_NETPLAY "NetPlayTemp"
#define GBA_BIOS "gba_bios.bin"
#define GBA_SAVE_NETPLAY "NetPlayTemp"
#define WII_STATE "state.dat" #define WII_STATE "state.dat"
#define WII_SDCARD "sd.raw" #define WII_SDCARD "sd.raw"

View File

@ -977,6 +977,10 @@ static void RebuildUserDirectories(unsigned int dir_index)
s_user_paths[F_MEMORYWATCHERSOCKET_IDX] = s_user_paths[F_MEMORYWATCHERSOCKET_IDX] =
s_user_paths[D_MEMORYWATCHER_IDX] + MEMORYWATCHER_SOCKET; 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. // The shader cache has moved to the cache directory, so remove the old one.
// TODO: remove that someday. // TODO: remove that someday.
File::DeleteDirRecursively(s_user_paths[D_USER_IDX] + SHADERCACHE_LEGACY_DIR DIR_SEP); File::DeleteDirRecursively(s_user_paths[D_USER_IDX] + SHADERCACHE_LEGACY_DIR DIR_SEP);

View File

@ -59,6 +59,8 @@ enum
D_BACKUP_IDX, D_BACKUP_IDX,
D_RESOURCEPACK_IDX, D_RESOURCEPACK_IDX,
D_DYNAMICINPUT_IDX, D_DYNAMICINPUT_IDX,
D_GBAUSER_IDX,
D_GBASAVES_IDX,
F_DOLPHINCONFIG_IDX, F_DOLPHINCONFIG_IDX,
F_GCPADCONFIG_IDX, F_GCPADCONFIG_IDX,
F_WIIPADCONFIG_IDX, F_WIIPADCONFIG_IDX,
@ -77,6 +79,7 @@ enum
F_WIISDCARD_IDX, F_WIISDCARD_IDX,
F_DUALSHOCKUDPCLIENTCONFIG_IDX, F_DUALSHOCKUDPCLIENTCONFIG_IDX,
F_FREELOOKCONFIG_IDX, F_FREELOOKCONFIG_IDX,
F_GBABIOS_IDX,
NUM_PATH_INDICES NUM_PATH_INDICES
}; };

View File

@ -35,10 +35,6 @@
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\..\VSProps\Base.props" /> <Import Project="..\..\VSProps\Base.props" />
</ImportGroup> </ImportGroup>
<PropertyGroup Label="UserMacros">
<CScript Condition="'$(ProgramFiles(x86))' != ''">%windir%\System32\cscript</CScript>
<CScript Condition="'$(ProgramFiles(x86))' == ''">%windir%\Sysnative\cscript</CScript>
</PropertyGroup>
<!-- <!--
OutDir is always created, which is annoying for SCMRevGen as it doesn't really have an outdir. OutDir is always created, which is annoying for SCMRevGen as it doesn't really have an outdir.
Here it's redirected to some other place to hide the annoyance. Here it's redirected to some other place to hide the annoyance.

View File

@ -195,6 +195,10 @@ add_library(core
HW/EXI/EXI_DeviceMic.h HW/EXI/EXI_DeviceMic.h
HW/EXI/EXI.cpp HW/EXI/EXI.cpp
HW/EXI/EXI.h HW/EXI/EXI.h
HW/GBAPad.cpp
HW/GBAPad.h
HW/GBAPadEmu.cpp
HW/GBAPadEmu.h
HW/GCKeyboard.cpp HW/GCKeyboard.cpp
HW/GCKeyboard.h HW/GCKeyboard.h
HW/GCKeyboardEmu.cpp HW/GCKeyboardEmu.cpp
@ -434,8 +438,8 @@ add_library(core
PowerPC/Interpreter/Interpreter_Tables.cpp PowerPC/Interpreter/Interpreter_Tables.cpp
PowerPC/Interpreter/Interpreter.cpp PowerPC/Interpreter/Interpreter.cpp
PowerPC/Interpreter/Interpreter.h PowerPC/Interpreter/Interpreter.h
PowerPC/JitCommon/DivUtils.cpp PowerPC/JitCommon/DivUtils.cpp
PowerPC/JitCommon/DivUtils.h PowerPC/JitCommon/DivUtils.h
PowerPC/JitCommon/JitAsmCommon.cpp PowerPC/JitCommon/JitAsmCommon.cpp
PowerPC/JitCommon/JitAsmCommon.h PowerPC/JitCommon/JitAsmCommon.h
PowerPC/JitCommon/JitBase.cpp PowerPC/JitCommon/JitBase.cpp
@ -614,6 +618,17 @@ if(ENABLE_VULKAN)
target_link_libraries(core PUBLIC videovulkan) target_link_libraries(core PUBLIC videovulkan)
endif() endif()
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)
endif()
if(WIN32) if(WIN32)
target_sources(core PRIVATE target_sources(core PRIVATE
HW/EXI/BBA/TAP_Win32.cpp HW/EXI/BBA/TAP_Win32.cpp

View File

@ -145,6 +145,18 @@ const Info<std::string> MAIN_RESOURCEPACK_PATH{{System::Main, "General", "Resour
const Info<std::string> MAIN_FS_PATH{{System::Main, "General", "NANDRootPath"}, ""}; const Info<std::string> MAIN_FS_PATH{{System::Main, "General", "NANDRootPath"}, ""};
const Info<std::string> MAIN_SD_PATH{{System::Main, "General", "WiiSDCardPath"}, ""}; const Info<std::string> MAIN_SD_PATH{{System::Main, "General", "WiiSDCardPath"}, ""};
// Main.GBA
const Info<std::string> MAIN_GBA_BIOS_PATH{{System::Main, "GBA", "BIOS"}, ""};
const std::array<Info<std::string>, 4> MAIN_GBA_ROM_PATHS{
Info<std::string>{{System::Main, "GBA", "Rom1"}, ""},
Info<std::string>{{System::Main, "GBA", "Rom2"}, ""},
Info<std::string>{{System::Main, "GBA", "Rom3"}, ""},
Info<std::string>{{System::Main, "GBA", "Rom4"}, ""}};
const Info<std::string> MAIN_GBA_SAVES_PATH{{System::Main, "GBA", "SavesPath"}, ""};
const Info<bool> MAIN_GBA_SAVES_IN_ROM_PATH{{System::Main, "GBA", "SavesInRomPath"}, false};
const Info<bool> MAIN_GBA_THREADS{{System::Main, "GBA", "Threads"}, true};
// Main.Network // Main.Network
const Info<bool> MAIN_NETWORK_SSL_DUMP_READ{{System::Main, "Network", "SSLDumpRead"}, false}; const Info<bool> MAIN_NETWORK_SSL_DUMP_READ{{System::Main, "Network", "SSLDumpRead"}, false};

View File

@ -3,6 +3,7 @@
#pragma once #pragma once
#include <array>
#include <string> #include <string>
#include "Common/Config/Config.h" #include "Common/Config/Config.h"
@ -119,6 +120,14 @@ extern const Info<std::string> MAIN_RESOURCEPACK_PATH;
extern const Info<std::string> MAIN_FS_PATH; extern const Info<std::string> MAIN_FS_PATH;
extern const Info<std::string> MAIN_SD_PATH; extern const Info<std::string> MAIN_SD_PATH;
// Main.GBA
extern const Info<std::string> MAIN_GBA_BIOS_PATH;
extern const std::array<Info<std::string>, 4> MAIN_GBA_ROM_PATHS;
extern const Info<std::string> MAIN_GBA_SAVES_PATH;
extern const Info<bool> MAIN_GBA_SAVES_IN_ROM_PATH;
extern const Info<bool> MAIN_GBA_THREADS;
// Main.Network // Main.Network
extern const Info<bool> MAIN_NETWORK_SSL_DUMP_READ; extern const Info<bool> MAIN_NETWORK_SSL_DUMP_READ;

View File

@ -57,5 +57,6 @@ const Info<std::string> NETPLAY_NETWORK_MODE{{System::Main, "NetPlay", "NetworkM
"fixeddelay"}; "fixeddelay"};
const Info<bool> NETPLAY_SYNC_ALL_WII_SAVES{{System::Main, "NetPlay", "SyncAllWiiSaves"}, false}; const Info<bool> NETPLAY_SYNC_ALL_WII_SAVES{{System::Main, "NetPlay", "SyncAllWiiSaves"}, false};
const Info<bool> NETPLAY_GOLF_MODE_OVERLAY{{System::Main, "NetPlay", "GolfModeOverlay"}, true}; const Info<bool> NETPLAY_GOLF_MODE_OVERLAY{{System::Main, "NetPlay", "GolfModeOverlay"}, true};
const Info<bool> NETPLAY_HIDE_REMOTE_GBAS{{System::Main, "NetPlay", "HideRemoteGBAs"}, false};
} // namespace Config } // namespace Config

View File

@ -50,5 +50,6 @@ extern const Info<bool> NETPLAY_STRICT_SETTINGS_SYNC;
extern const Info<std::string> NETPLAY_NETWORK_MODE; extern const Info<std::string> NETPLAY_NETWORK_MODE;
extern const Info<bool> NETPLAY_SYNC_ALL_WII_SAVES; extern const Info<bool> NETPLAY_SYNC_ALL_WII_SAVES;
extern const Info<bool> NETPLAY_GOLF_MODE_OVERLAY; extern const Info<bool> NETPLAY_GOLF_MODE_OVERLAY;
extern const Info<bool> NETPLAY_HIDE_REMOTE_GBAS;
} // namespace Config } // namespace Config

View File

@ -26,7 +26,7 @@ bool IsSettingSaveable(const Config::Location& config_location)
if (config_location.system == Config::System::Main) if (config_location.system == Config::System::Main)
{ {
for (const std::string_view section : for (const std::string_view section :
{"NetPlay", "General", "Display", "Network", "Analytics", "AndroidOverlayButtons"}) {"NetPlay", "General", "GBA", "Display", "Network", "Analytics", "AndroidOverlayButtons"})
{ {
if (config_location.section == section) if (config_location.section == section)
return true; return true;

View File

@ -133,6 +133,11 @@ public:
layer->Set(Config::SESSION_GCI_FOLDER_CURRENT_GAME_ONLY, true); 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 // Check To Override Client's Cheat Codes
if (m_settings.m_SyncCodes && !m_settings.m_IsHosting) if (m_settings.m_SyncCodes && !m_settings.m_IsHosting)
{ {

View File

@ -48,6 +48,7 @@
#include "Core/HW/CPU.h" #include "Core/HW/CPU.h"
#include "Core/HW/DSP.h" #include "Core/HW/DSP.h"
#include "Core/HW/EXI/EXI.h" #include "Core/HW/EXI/EXI.h"
#include "Core/HW/GBAPad.h"
#include "Core/HW/GCKeyboard.h" #include "Core/HW/GCKeyboard.h"
#include "Core/HW/GCPad.h" #include "Core/HW/GCPad.h"
#include "Core/HW/HW.h" #include "Core/HW/HW.h"
@ -460,6 +461,7 @@ static void EmuThread(std::unique_ptr<BootParameters> boot, WindowSystemInfo wsi
{ {
g_controller_interface.Initialize(wsi); g_controller_interface.Initialize(wsi);
Pad::Initialize(); Pad::Initialize();
Pad::InitializeGBA();
Keyboard::Initialize(); Keyboard::Initialize();
init_controllers = true; init_controllers = true;
} }
@ -467,6 +469,7 @@ static void EmuThread(std::unique_ptr<BootParameters> boot, WindowSystemInfo wsi
{ {
g_controller_interface.ChangeWindow(wsi.render_window); g_controller_interface.ChangeWindow(wsi.render_window);
Pad::LoadConfig(); Pad::LoadConfig();
Pad::LoadGBAConfig();
Keyboard::LoadConfig(); Keyboard::LoadConfig();
} }
@ -517,6 +520,7 @@ static void EmuThread(std::unique_ptr<BootParameters> boot, WindowSystemInfo wsi
Keyboard::Shutdown(); Keyboard::Shutdown();
Pad::Shutdown(); Pad::Shutdown();
Pad::ShutdownGBA();
g_controller_interface.Shutdown(); g_controller_interface.Shutdown();
}}; }};

View File

@ -309,12 +309,12 @@ void Initialize()
FreeLook::GetConfig().Refresh(); FreeLook::GetConfig().Refresh();
s_config.LoadConfig(true); s_config.LoadConfig(InputConfig::InputClass::GC);
} }
void LoadInputConfig() void LoadInputConfig()
{ {
s_config.LoadConfig(true); s_config.LoadConfig(InputConfig::InputClass::GC);
} }
bool IsInitialized() bool IsInitialized()

View File

@ -0,0 +1,715 @@
// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Core/HW/GBACore.h"
#include <mbedtls/sha1.h>
#define PYCPARSE // Remove static functions from the header
#include <mgba/core/interface.h>
#undef PYCPARSE
#include <mgba-util/vfs.h>
#include <mgba/core/blip_buf.h>
#include <mgba/core/log.h>
#include <mgba/core/timing.h>
#include <mgba/internal/gb/gb.h>
#include <mgba/internal/gba/gba.h>
#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<u32>(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<size_t>(vf_archive->size(vf_archive));
std::vector<u8> 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<u8> 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<u8, 20> GetROMHash(VFile* rom)
{
size_t size = rom->size(rom);
u8* buffer = static_cast<u8*>(rom->map(rom, size, MAP_READ));
std::array<u8, 20> 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<char, 17> game_title{};
m_core->getGameTitle(m_core, game_title.data());
m_game_title = game_title.data();
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;
}
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<std::thread>([this] { ThreadLoop(); });
}
return true;
}
void Core::Stop()
{
if (m_thread)
{
Flush();
m_exit_loop = true;
{
std::lock_guard<std::mutex> 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<GBAHostInterface> 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<u8> 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<SIODriver*>(driver)->core->m_link_enabled = true;
return true;
};
m_sio_driver.unload = [](GBASIODriver* driver) {
static_cast<SIODriver*>(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<Core*>(context);
core->m_core->setKeys(core->m_core, core->m_keys);
};
callbacks.videoFrameEnded = [](void* context) {
auto core = static_cast<Core*>(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<AVStream*>(stream)->core;
core->SetVideoBuffer();
};
m_stream.postAudioBuffer = [](mAVStream* stream, blip_t* left, blip_t* right) {
auto core = static_cast<AVStream*>(stream)->core;
std::vector<s16> 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<Core*>(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<std::mutex> lock(m_queue_mutex);
m_command_queue.push(command);
m_idle = false;
m_command_cv.notify_one();
}
else
{
RunCommand(command);
}
}
std::vector<u8> Core::GetJoybusResponse()
{
if (!IsStarted())
return {};
if (m_thread)
{
std::unique_lock<std::mutex> 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<std::mutex> 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<std::mutex> 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<GBASIOJOYCommand>(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<std::mutex> 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<s64>(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<s32>((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<u64>(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<u8> 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<u8> 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<u8> 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<u8, 20>& 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<char, 17> 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

View File

@ -0,0 +1,129 @@
// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <queue>
#include <string>
#include <string_view>
#include <thread>
#include <vector>
#define PYCPARSE // Remove static functions from the header
#include <mgba/core/interface.h>
#undef PYCPARSE
#include <mgba/core/core.h>
#include <mgba/gba/interface.h>
#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<GBAHostInterface> 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<u8> 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<u8, 20>& 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<u8, 6> 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<u8, 20> 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<u32> 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<GBAHostInterface> m_host;
std::unique_ptr<std::thread> m_thread;
bool m_exit_loop = false;
bool m_idle = false;
std::mutex m_queue_mutex;
std::condition_variable m_command_cv;
std::queue<Command> m_command_queue;
std::mutex m_response_mutex;
std::condition_variable m_response_cv;
bool m_response_ready = false;
std::vector<u8> m_response;
};
} // namespace HW::GBA

View File

@ -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<GBAPad>(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<GBAPad*>(s_config.GetController(pad_num))->GetInput();
}
void SetGBAReset(int pad_num, bool reset)
{
static_cast<GBAPad*>(s_config.GetController(pad_num))->SetReset(reset);
}
ControllerEmu::ControlGroup* GetGBAGroup(int pad_num, GBAPadGroup group)
{
return static_cast<GBAPad*>(s_config.GetController(pad_num))->GetGroup(group);
}
} // namespace Pad

View File

@ -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

View File

@ -0,0 +1,107 @@
// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Core/HW/GBAPadEmu.h"
#include <fmt/format.h>
#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
}

View File

@ -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;
};

View File

@ -41,12 +41,12 @@ void Initialize()
s_config.RegisterHotplugCallback(); s_config.RegisterHotplugCallback();
// Load the saved controller config // Load the saved controller config
s_config.LoadConfig(true); s_config.LoadConfig(InputConfig::InputClass::GC);
} }
void LoadConfig() void LoadConfig()
{ {
s_config.LoadConfig(true); s_config.LoadConfig(InputConfig::InputClass::GC);
} }
ControllerEmu::ControlGroup* GetGroup(int port, KeyboardGroup group) ControllerEmu::ControlGroup* GetGroup(int port, KeyboardGroup group)

View File

@ -38,12 +38,12 @@ void Initialize()
s_config.RegisterHotplugCallback(); s_config.RegisterHotplugCallback();
// Load the saved controller config // Load the saved controller config
s_config.LoadConfig(true); s_config.LoadConfig(InputConfig::InputClass::GC);
} }
void LoadConfig() void LoadConfig()
{ {
s_config.LoadConfig(true); s_config.LoadConfig(InputConfig::InputClass::GC);
} }
bool IsInitialized() bool IsInitialized()

View File

@ -209,6 +209,7 @@ union USIEXIClockCount
static CoreTiming::EventType* s_change_device_event; static CoreTiming::EventType* s_change_device_event;
static CoreTiming::EventType* s_tranfer_pending_event; static CoreTiming::EventType* s_tranfer_pending_event;
static std::array<CoreTiming::EventType*, MAX_SI_CHANNELS> s_device_events;
// User-configured device type. possibly overridden by TAS/Netplay // User-configured device type. possibly overridden by TAS/Netplay
static std::array<std::atomic<SIDevices>, MAX_SI_CHANNELS> s_desired_device_types; static std::array<std::atomic<SIDevices>, MAX_SI_CHANNELS> s_desired_device_types;
@ -369,8 +370,44 @@ void DoState(PointerWrap& p)
p.Do(s_si_buffer); p.Do(s_si_buffer);
} }
template <int device_number>
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<CoreTiming::TimedCallback, MAX_SI_CHANNELS> 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() void Init()
{ {
RegisterEvents();
for (int i = 0; i < MAX_SI_CHANNELS; i++) for (int i = 0; i < MAX_SI_CHANNELS; i++)
{ {
s_channel[i].out.hex = 0; s_channel[i].out.hex = 0;
@ -382,7 +419,11 @@ void Init()
{ {
s_desired_device_types[i] = SIDEVICE_NONE; 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]; SIDevices current = SConfig::GetInstance().m_SIDevice[i];
// GC pad-compatible devices can be used for both playing and recording // GC pad-compatible devices can be used for both playing and recording
@ -415,9 +456,6 @@ void Init()
// s_exi_clock_count.LOCK = 1; // s_exi_clock_count.LOCK = 1;
s_si_buffer = {}; s_si_buffer = {};
s_change_device_event = CoreTiming::RegisterEvent("ChangeSIDevice", ChangeDeviceCallback);
s_tranfer_pending_event = CoreTiming::RegisterEvent("SITransferPending", RunSIBuffer);
} }
void Shutdown() void Shutdown()
@ -673,7 +711,7 @@ void UpdateDevices()
SIDevices GetDeviceType(int channel) SIDevices GetDeviceType(int channel)
{ {
if (channel < 0 || channel > 3) if (channel < 0 || channel >= MAX_SI_CHANNELS || !s_channel[channel].device)
return SIDEVICE_NONE; return SIDEVICE_NONE;
return s_channel[channel].device->GetDeviceType(); return s_channel[channel].device->GetDeviceType();

View File

@ -30,6 +30,9 @@ void DoState(PointerWrap& p);
void RegisterMMIO(MMIO::Mapping* mmio, u32 base); 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 UpdateDevices();
void RemoveDevice(int device_number); void RemoveDevice(int device_number);

View File

@ -13,16 +13,26 @@
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"
#include "Core/HW/SI/SI_DeviceDanceMat.h" #include "Core/HW/SI/SI_DeviceDanceMat.h"
#include "Core/HW/SI/SI_DeviceGBA.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_DeviceGCAdapter.h"
#include "Core/HW/SI/SI_DeviceGCController.h" #include "Core/HW/SI/SI_DeviceGCController.h"
#include "Core/HW/SI/SI_DeviceGCSteeringWheel.h" #include "Core/HW/SI/SI_DeviceGCSteeringWheel.h"
#include "Core/HW/SI/SI_DeviceKeyboard.h" #include "Core/HW/SI/SI_DeviceKeyboard.h"
#include "Core/HW/SI/SI_DeviceNull.h" #include "Core/HW/SI/SI_DeviceNull.h"
#include "Core/HW/SystemTimers.h"
namespace SerialInterface 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) std::ostream& operator<<(std::ostream& stream, SIDevices device)
{ {
stream << static_cast<std::underlying_type_t<SIDevices>>(device); stream << static_cast<std::underlying_type_t<SIDevices>>(device);
@ -97,6 +107,48 @@ void ISIDevice::DoState(PointerWrap& p)
{ {
} }
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<int>(cycles);
}
// Check if a device class is inheriting from CSIDevice_GCController // Check if a device class is inheriting from CSIDevice_GCController
// The goal of this function is to avoid special casing a long list of // 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 // device types when there is no "real" input device, e.g. when playing
@ -139,6 +191,14 @@ std::unique_ptr<ISIDevice> SIDevice_Create(const SIDevices device, const int por
case SIDEVICE_GC_GBA: case SIDEVICE_GC_GBA:
return std::make_unique<CSIDevice_GBA>(device, port_number); return std::make_unique<CSIDevice_GBA>(device, port_number);
case SIDEVICE_GC_GBA_EMULATED:
#ifdef HAS_LIBMGBA
return std::make_unique<CSIDevice_GBAEmu>(device, port_number);
#else
PanicAlertT("Error: This build does not support emulated GBA controllers");
return std::make_unique<CSIDevice_Null>(device, port_number);
#endif
case SIDEVICE_GC_KEYBOARD: case SIDEVICE_GC_KEYBOARD:
return std::make_unique<CSIDevice_Keyboard>(device, port_number); return std::make_unique<CSIDevice_Keyboard>(device, port_number);

View File

@ -41,6 +41,40 @@ enum TSIDevices : u32
SI_AM_BASEBOARD = 0x10110800 // gets ORd with dipswitch state 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 // For configuration use, since some devices can have the same SI Device ID
enum SIDevices : int enum SIDevices : int
{ {
@ -59,6 +93,7 @@ enum SIDevices : int
// It's kept here so that values below will stay constant. // It's kept here so that values below will stay constant.
SIDEVICE_AM_BASEBOARD, SIDEVICE_AM_BASEBOARD,
SIDEVICE_WIIU_ADAPTER, SIDEVICE_WIIU_ADAPTER,
SIDEVICE_GC_GBA_EMULATED,
// Not a valid device. Used for checking whether enum values are valid. // Not a valid device. Used for checking whether enum values are valid.
SIDEVICE_COUNT, SIDEVICE_COUNT,
}; };
@ -88,11 +123,15 @@ public:
// Savestate support // Savestate support
virtual void DoState(PointerWrap& p); virtual void DoState(PointerWrap& p);
// Schedulable event
virtual void OnEvent(u64 userdata, s64 cycles_late);
protected: protected:
int m_device_number; int m_device_number;
SIDevices m_device_type; SIDevices m_device_type;
}; };
int SIDevice_GetGBATransferTime(EBufferCommands cmd);
bool SIDevice_IsGCController(SIDevices type); bool SIDevice_IsGCController(SIDevices type);
std::unique_ptr<ISIDevice> SIDevice_Create(SIDevices device, int port_number); std::unique_ptr<ISIDevice> SIDevice_Create(SIDevices device, int port_number);

View File

@ -19,8 +19,8 @@ CSIDevice_DanceMat::CSIDevice_DanceMat(SIDevices device, int device_number)
int CSIDevice_DanceMat::RunBuffer(u8* buffer, int request_length) int CSIDevice_DanceMat::RunBuffer(u8* buffer, int request_length)
{ {
// Read the command // Read the command
EBufferCommands command = static_cast<EBufferCommands>(buffer[0]); const auto command = static_cast<EBufferCommands>(buffer[0]);
if (command == CMD_RESET) if (command == EBufferCommands::CMD_STATUS)
{ {
ISIDevice::RunBuffer(buffer, request_length); ISIDevice::RunBuffer(buffer, request_length);

View File

@ -34,60 +34,10 @@ int s_num_connected;
Common::Flag s_server_running; Common::Flag s_server_running;
} // namespace } // 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;
constexpr auto GBA_STOP_BIT_NS = 14000;
constexpr auto SEND_MAX_SIZE = 5, RECV_MAX_SIZE = 5; constexpr auto SEND_MAX_SIZE = 5, RECV_MAX_SIZE = 5;
// --- GameBoy Advance "Link Cable" --- // --- GameBoy Advance "Link Cable" ---
static int GetTransferTime(u8 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 CMD_RESET:
case CMD_STATUS:
{
gba_bytes_transferred = 3;
break;
}
case CMD_READ:
{
gba_bytes_transferred = 5;
break;
}
case CMD_WRITE:
{
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<int>(cycles);
}
static void GBAConnectionWaiter() static void GBAConnectionWaiter()
{ {
s_server_running.Set(); s_server_running.Set();
@ -249,10 +199,10 @@ void GBASockServer::Send(const u8* si_buffer)
for (size_t i = 0; i < send_data.size(); i++) for (size_t i = 0; i < send_data.size(); i++)
send_data[i] = si_buffer[i]; send_data[i] = si_buffer[i];
u8 cmd = send_data[0]; const auto cmd = static_cast<EBufferCommands>(send_data[0]);
sf::Socket::Status status; sf::Socket::Status status;
if (cmd == CMD_WRITE) if (cmd == EBufferCommands::CMD_WRITE_GBA)
status = m_client->send(send_data.data(), send_data.size()); status = m_client->send(send_data.data(), send_data.size());
else else
status = m_client->send(send_data.data(), 1); status = m_client->send(send_data.data(), 1);
@ -334,7 +284,7 @@ int CSIDevice_GBA::RunBuffer(u8* buffer, int request_length)
return -1; return -1;
} }
m_last_cmd = buffer[0]; m_last_cmd = static_cast<EBufferCommands>(buffer[0]);
m_timestamp_sent = CoreTiming::GetTicks(); m_timestamp_sent = CoreTiming::GetTicks();
m_next_action = NextAction::WaitTransferTime; m_next_action = NextAction::WaitTransferTime;
return 0; return 0;
@ -344,7 +294,7 @@ int CSIDevice_GBA::RunBuffer(u8* buffer, int request_length)
{ {
int elapsed_time = static_cast<int>(CoreTiming::GetTicks() - m_timestamp_sent); int elapsed_time = static_cast<int>(CoreTiming::GetTicks() - m_timestamp_sent);
// Tell SI to ask again after TransferInterval() cycles // 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; return 0;
m_next_action = NextAction::ReceiveResponse; m_next_action = NextAction::ReceiveResponse;
[[fallthrough]]; [[fallthrough]];
@ -355,11 +305,11 @@ int CSIDevice_GBA::RunBuffer(u8* buffer, int request_length)
u8 bytes = 1; u8 bytes = 1;
switch (m_last_cmd) switch (m_last_cmd)
{ {
case CMD_RESET: case EBufferCommands::CMD_RESET:
case CMD_STATUS: case EBufferCommands::CMD_STATUS:
bytes = 3; bytes = 3;
break; break;
case CMD_READ: case EBufferCommands::CMD_READ_GBA:
bytes = 5; bytes = 5;
break; break;
default: default:
@ -372,8 +322,9 @@ int CSIDevice_GBA::RunBuffer(u8* buffer, int request_length)
return -1; return -1;
#ifdef _DEBUG #ifdef _DEBUG
const Common::Log::LOG_LEVELS log_level = const Common::Log::LOG_LEVELS log_level =
(m_last_cmd == CMD_STATUS || m_last_cmd == CMD_RESET) ? Common::Log::LERROR : (m_last_cmd == EBufferCommands::CMD_STATUS || m_last_cmd == EBufferCommands::CMD_RESET) ?
Common::Log::LWARNING; Common::Log::LERROR :
Common::Log::LWARNING;
GENERIC_LOG_FMT(Common::Log::SERIALINTERFACE, log_level, GENERIC_LOG_FMT(Common::Log::SERIALINTERFACE, log_level,
"{} [< {:02x}{:02x}{:02x}{:02x}{:02x}] ({})", "{} [< {:02x}{:02x}{:02x}{:02x}{:02x}] ({})",
m_device_number, buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], m_device_number, buffer[0], buffer[1], buffer[2], buffer[3], buffer[4],
@ -390,7 +341,7 @@ int CSIDevice_GBA::RunBuffer(u8* buffer, int request_length)
int CSIDevice_GBA::TransferInterval() int CSIDevice_GBA::TransferInterval()
{ {
return GetTransferTime(m_last_cmd); return SIDevice_GetGBATransferTime(m_last_cmd);
} }
bool CSIDevice_GBA::GetData(u32& hi, u32& low) bool CSIDevice_GBA::GetData(u32& hi, u32& low)

View File

@ -60,7 +60,7 @@ private:
GBASockServer m_sock_server; GBASockServer m_sock_server;
NextAction m_next_action = NextAction::SendCommand; NextAction m_next_action = NextAction::SendCommand;
u8 m_last_cmd; EBufferCommands m_last_cmd;
u64 m_timestamp_sent = 0; u64 m_timestamp_sent = 0;
}; };
} // namespace SerialInterface } // namespace SerialInterface

View File

@ -0,0 +1,167 @@
// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <vector>
#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<HW::GBA::Core>(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<EBufferCommands>(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<u64>(TransferInterval()));
}
m_next_action = NextAction::WaitTransferTime;
[[fallthrough]];
}
case NextAction::WaitTransferTime:
{
int elapsed_time = static_cast<int>(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<u8> 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<int>(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<PadButton, 10> 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<u16>(static_cast<bool>((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

View File

@ -0,0 +1,49 @@
// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#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<HW::GBA::Core> m_core;
std::shared_ptr<GBAHostInterface> m_gbahost;
};
} // namespace SerialInterface

View File

@ -38,7 +38,7 @@ GCPadStatus CSIDevice_GCAdapter::GetPadStatus()
pad_status = GCAdapter::Input(m_device_number); 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. // Our GCAdapter code sets PAD_GET_ORIGIN when a new device has been connected.
// Watch for this to calibrate real controllers on connection. // Watch for this to calibrate real controllers on connection.

View File

@ -45,20 +45,20 @@ int CSIDevice_GCController::RunBuffer(u8* buffer, int request_length)
return -1; return -1;
// Read the command // Read the command
EBufferCommands command = static_cast<EBufferCommands>(buffer[0]); const auto command = static_cast<EBufferCommands>(buffer[0]);
// Handle it // Handle it
switch (command) switch (command)
{ {
case CMD_RESET: case EBufferCommands::CMD_STATUS:
case CMD_ID: case EBufferCommands::CMD_RESET:
{ {
u32 id = Common::swap32(SI_GC_CONTROLLER); u32 id = Common::swap32(SI_GC_CONTROLLER);
std::memcpy(buffer, &id, sizeof(id)); std::memcpy(buffer, &id, sizeof(id));
return sizeof(id); return sizeof(id);
} }
case CMD_DIRECT: case EBufferCommands::CMD_DIRECT:
{ {
INFO_LOG_FMT(SERIALINTERFACE, "PAD - Direct (Request length: {})", request_length); INFO_LOG_FMT(SERIALINTERFACE, "PAD - Direct (Request length: {})", request_length);
u32 high, low; u32 high, low;
@ -71,7 +71,7 @@ int CSIDevice_GCController::RunBuffer(u8* buffer, int request_length)
return sizeof(high) + sizeof(low); return sizeof(high) + sizeof(low);
} }
case CMD_ORIGIN: case EBufferCommands::CMD_ORIGIN:
{ {
INFO_LOG_FMT(SERIALINTERFACE, "PAD - Get 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) // Recalibrate (FiRES: i am not 100 percent sure about this)
case CMD_RECALIBRATE: case EBufferCommands::CMD_RECALIBRATE:
{ {
INFO_LOG_FMT(SERIALINTERFACE, "PAD - Recalibrate"); INFO_LOG_FMT(SERIALINTERFACE, "PAD - Recalibrate");
@ -108,27 +108,27 @@ int CSIDevice_GCController::RunBuffer(u8* buffer, int request_length)
return 0; 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(); Movie::SetPolledDevice();
if (NetPlay_GetInput(m_device_number, pad_status)) if (NetPlay_GetInput(device_number, pad_status))
{ {
} }
else if (Movie::IsPlayingInput()) else if (Movie::IsPlayingInput())
{ {
Movie::PlayController(pad_status, m_device_number); Movie::PlayController(pad_status, device_number);
Movie::InputUpdate(); Movie::InputUpdate();
} }
else if (Movie::IsRecordingInput()) else if (Movie::IsRecordingInput())
{ {
Movie::RecordInput(pad_status, m_device_number); Movie::RecordInput(pad_status, device_number);
Movie::InputUpdate(); Movie::InputUpdate();
} }
else 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); 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. // Our GCAdapter code sets PAD_GET_ORIGIN when a new device has been connected.
// Watch for this to calibrate real controllers on connection. // Watch for this to calibrate real controllers on connection.
@ -290,13 +290,7 @@ void CSIDevice_GCController::SendCommand(u32 command, u8 poll)
{ {
UCommand controller_command(command); UCommand controller_command(command);
switch (controller_command.command) if (static_cast<EDirectCommands>(controller_command.command) == EDirectCommands::CMD_WRITE)
{
// Costis sent it in some demos :)
case 0x00:
break;
case CMD_WRITE:
{ {
const u32 type = controller_command.parameter1; // 0 = stop, 1 = rumble, 2 = stop hard 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); INFO_LOG_FMT(SERIALINTERFACE, "PAD {} set to mode {}", m_device_number, m_mode);
} }
} }
break; else if (controller_command.command != 0x00)
default:
{ {
// Costis sent 0x00 in some demos :)
ERROR_LOG_FMT(SERIALINTERFACE, "Unknown direct command ({:#x})", command); ERROR_LOG_FMT(SERIALINTERFACE, "Unknown direct command ({:#x})", command);
PanicAlertFmt("SI: Unknown direct command"); PanicAlertFmt("SI: Unknown direct command");
} }
break;
}
} }
// Savestate support // Savestate support

View File

@ -12,16 +12,6 @@ namespace SerialInterface
class CSIDevice_GCController : public ISIDevice class CSIDevice_GCController : public ISIDevice
{ {
protected: protected:
// Commands
enum EBufferCommands
{
CMD_RESET = 0x00,
CMD_DIRECT = 0x40,
CMD_ORIGIN = 0x41,
CMD_RECALIBRATE = 0x42,
CMD_ID = 0xff,
};
struct SOrigin struct SOrigin
{ {
u16 button; u16 button;
@ -35,25 +25,6 @@ protected:
u8 unk_5; 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 enum EButtonCombo
{ {
COMBO_NONE = 0, COMBO_NONE = 0,
@ -105,8 +76,9 @@ public:
// Direct rumble to the right GC Controller // Direct rumble to the right GC Controller
static void Rumble(int pad_num, ControlState strength); static void Rumble(int pad_num, ControlState strength);
static void HandleMoviePadStatus(int device_number, GCPadStatus* pad_status);
protected: protected:
void HandleMoviePadStatus(GCPadStatus* pad_status);
void SetOrigin(const GCPadStatus& pad_status); void SetOrigin(const GCPadStatus& pad_status);
}; };

View File

@ -25,13 +25,13 @@ int CSIDevice_GCSteeringWheel::RunBuffer(u8* buffer, int request_length)
ISIDevice::RunBuffer(buffer, request_length); ISIDevice::RunBuffer(buffer, request_length);
// Read the command // Read the command
EBufferCommands command = static_cast<EBufferCommands>(buffer[0]); const auto command = static_cast<EBufferCommands>(buffer[0]);
// Handle it // Handle it
switch (command) switch (command)
{ {
case CMD_RESET: case EBufferCommands::CMD_STATUS:
case CMD_ID: case EBufferCommands::CMD_RESET:
{ {
u32 id = Common::swap32(SI_GC_STEERING); u32 id = Common::swap32(SI_GC_STEERING);
std::memcpy(buffer, &id, sizeof(id)); std::memcpy(buffer, &id, sizeof(id));
@ -101,7 +101,7 @@ void CSIDevice_GCSteeringWheel::SendCommand(u32 command, u8 poll)
{ {
UCommand wheel_command(command); UCommand wheel_command(command);
if (wheel_command.command == CMD_FORCE) if (static_cast<EDirectCommands>(wheel_command.command) == EDirectCommands::CMD_FORCE)
{ {
// get the correct pad number that should rumble locally when using netplay // get the correct pad number that should rumble locally when using netplay
const int pad_num = NetPlay_InGamePadToLocalPad(m_device_number); const int pad_num = NetPlay_InGamePadToLocalPad(m_device_number);

View File

@ -17,21 +17,6 @@ public:
void SendCommand(u32 command, u8 poll) override; void SendCommand(u32 command, u8 poll) override;
private: 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 enum class ForceCommandType : u8
{ {
MotorOn = 0x03, MotorOn = 0x03,

View File

@ -31,15 +31,15 @@ int CSIDevice_Keyboard::RunBuffer(u8* buffer, int request_length)
// Handle it // Handle it
switch (command) switch (command)
{ {
case CMD_RESET: case EBufferCommands::CMD_STATUS:
case CMD_ID: case EBufferCommands::CMD_RESET:
{ {
u32 id = Common::swap32(SI_GC_KEYBOARD); u32 id = Common::swap32(SI_GC_KEYBOARD);
std::memcpy(buffer, &id, sizeof(id)); std::memcpy(buffer, &id, sizeof(id));
return sizeof(id); return sizeof(id);
} }
case CMD_DIRECT: case EBufferCommands::CMD_DIRECT_KB:
{ {
INFO_LOG_FMT(SERIALINTERFACE, "Keyboard - Direct (Request Length: {})", request_length); INFO_LOG_FMT(SERIALINTERFACE, "Keyboard - Direct (Request Length: {})", request_length);
u32 high, low; u32 high, low;
@ -84,23 +84,15 @@ void CSIDevice_Keyboard::SendCommand(u32 command, u8 poll)
{ {
UCommand keyboard_command(command); UCommand keyboard_command(command);
switch (keyboard_command.command) if (static_cast<EDirectCommands>(keyboard_command.command) == EDirectCommands::CMD_POLL)
{
case 0x00:
break;
case CMD_POLL:
{ {
m_counter++; m_counter++;
m_counter &= 15; m_counter &= 15;
} }
break; else if (keyboard_command.command != 0x00)
default:
{ {
ERROR_LOG_FMT(SERIALINTERFACE, "Unknown direct command ({:#x})", command); ERROR_LOG_FMT(SERIALINTERFACE, "Unknown direct command ({:#x})", command);
} }
break;
}
} }
void CSIDevice_Keyboard::DoState(PointerWrap& p) void CSIDevice_Keyboard::DoState(PointerWrap& p)

View File

@ -32,34 +32,6 @@ public:
void DoState(PointerWrap& p) override; void DoState(PointerWrap& p) override;
protected: 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 // PADAnalogMode
u8 m_mode = 0; u8 m_mode = 0;

View File

@ -173,7 +173,7 @@ void ResetAllWiimotes()
void LoadConfig() void LoadConfig()
{ {
s_config.LoadConfig(false); s_config.LoadConfig(InputConfig::InputClass::Wii);
s_last_connect_request_counter.fill(0); s_last_connect_request_counter.fill(0);
} }

View File

@ -3,9 +3,12 @@
#pragma once #pragma once
#include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include "Common/CommonTypes.h"
// Host - defines an interface for the emulator core to communicate back to the // Host - defines an interface for the emulator core to communicate back to the
// OS-specific layer // 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 // The host can be just a command line app that opens a window, or a full blown debugger
// interface. // 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<u32>& video_buffer) = 0;
};
enum class HostMessageID enum class HostMessageID
{ {
// Begin at 10 in case there is already messages with wParam = 0, 1, 2 and so on // 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_UpdateTitle(const std::string& title);
void Host_YieldToUI(); void Host_YieldToUI();
void Host_TitleChanged(); void Host_TitleChanged();
std::unique_ptr<GBAHostInterface> Host_CreateGBAHost(std::weak_ptr<HW::GBA::Core> core);

View File

@ -23,7 +23,7 @@
#include "InputCommon/GCPadStatus.h" #include "InputCommon/GCPadStatus.h"
// clang-format off // clang-format off
constexpr std::array<const char*, 126> s_hotkey_labels{{ constexpr std::array<const char*, NUM_HOTKEYS> s_hotkey_labels{{
_trans("Open"), _trans("Open"),
_trans("Change Disc"), _trans("Change Disc"),
_trans("Eject Disc"), _trans("Eject Disc"),
@ -178,6 +178,19 @@ constexpr std::array<const char*, 126> s_hotkey_labels{{
_trans("Undo Save State"), _trans("Undo Save State"),
_trans("Save State"), _trans("Save State"),
_trans("Load 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 // clang-format on
static_assert(NUM_HOTKEYS == s_hotkey_labels.size(), "Wrong count of hotkey_labels"); static_assert(NUM_HOTKEYS == s_hotkey_labels.size(), "Wrong count of hotkey_labels");
@ -195,10 +208,10 @@ InputConfig* GetConfig()
return &s_config; return &s_config;
} }
void GetStatus() void GetStatus(bool ignore_focus)
{ {
// Get input // Get input
static_cast<HotkeyManager*>(s_config.GetController(0))->GetInput(&s_hotkey); static_cast<HotkeyManager*>(s_config.GetController(0))->GetInput(&s_hotkey, ignore_focus);
} }
bool IsEnabled() bool IsEnabled()
@ -285,7 +298,7 @@ void Initialize()
void LoadConfig() void LoadConfig()
{ {
s_config.LoadConfig(true); s_config.LoadConfig(InputConfig::InputClass::GC);
LoadLegacyConfig(s_config.GetController(0)); LoadLegacyConfig(s_config.GetController(0));
} }
@ -307,6 +320,7 @@ struct HotkeyGroupInfo
const char* name; const char* name;
Hotkey first; Hotkey first;
Hotkey last; Hotkey last;
bool ignore_focus = false;
}; };
constexpr std::array<HotkeyGroupInfo, NUM_HOTKEY_GROUPS> s_groups_info = { constexpr std::array<HotkeyGroupInfo, NUM_HOTKEY_GROUPS> s_groups_info = {
@ -334,7 +348,10 @@ constexpr std::array<HotkeyGroupInfo, NUM_HOTKEY_GROUPS> s_groups_info = {
{_trans("Save State"), HK_SAVE_STATE_SLOT_1, HK_SAVE_STATE_SLOT_SELECTED}, {_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("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("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() HotkeyManager::HotkeyManager()
{ {
@ -359,11 +376,14 @@ std::string HotkeyManager::GetName() const
return "Hotkeys"; return "Hotkeys";
} }
void HotkeyManager::GetInput(HotkeyStatus* const kb) void HotkeyManager::GetInput(HotkeyStatus* kb, bool ignore_focus)
{ {
const auto lock = GetStateLock(); const auto lock = GetStateLock();
for (std::size_t group = 0; group < s_groups_info.size(); group++) 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; const int group_count = (s_groups_info[group].last - s_groups_info[group].first) + 1;
std::vector<u32> bitmasks(group_count); std::vector<u32> bitmasks(group_count);
for (size_t key = 0; key < bitmasks.size(); key++) 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_LOAD_STATE, "F12");
set_key_expression(HK_UNDO_SAVE_STATE, hotkey_string({"Shift", "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
} }

View File

@ -164,6 +164,19 @@ enum Hotkey
HK_SAVE_STATE_FILE, HK_SAVE_STATE_FILE,
HK_LOAD_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, NUM_HOTKEYS,
}; };
@ -192,6 +205,9 @@ enum HotkeyGroup : int
HKGP_SELECT_STATE, HKGP_SELECT_STATE,
HKGP_LOAD_LAST_STATE, HKGP_LOAD_LAST_STATE,
HKGP_STATE_MISC, HKGP_STATE_MISC,
HKGP_GBA_CORE,
HKGP_GBA_VOLUME,
HKGP_GBA_SIZE,
NUM_HOTKEY_GROUPS, NUM_HOTKEY_GROUPS,
}; };
@ -208,7 +224,7 @@ public:
HotkeyManager(); HotkeyManager();
~HotkeyManager(); ~HotkeyManager();
void GetInput(HotkeyStatus* const hk); void GetInput(HotkeyStatus* hk, bool ignore_focus);
std::string GetName() const override; std::string GetName() const override;
ControllerEmu::ControlGroup* GetHotkeyGroup(HotkeyGroup group) const; ControllerEmu::ControlGroup* GetHotkeyGroup(HotkeyGroup group) const;
int FindGroupByID(int id) const; int FindGroupByID(int id) const;
@ -228,7 +244,7 @@ void LoadConfig();
InputConfig* GetConfig(); InputConfig* GetConfig();
ControllerEmu::ControlGroup* GetHotkeyGroup(HotkeyGroup group); ControllerEmu::ControlGroup* GetHotkeyGroup(HotkeyGroup group);
void GetStatus(); void GetStatus(bool ignore_focus);
bool IsEnabled(); bool IsEnabled();
void Enable(bool enable_toggle); void Enable(bool enable_toggle);
bool IsPressed(int Id, bool held); bool IsPressed(int Id, bool held);

View File

@ -82,7 +82,8 @@ static bool s_bReadOnly = true;
static u32 s_rerecords = 0; static u32 s_rerecords = 0;
static PlayMode s_playMode = MODE_NONE; static PlayMode s_playMode = MODE_NONE;
static u8 s_controllers = 0; static std::array<ControllerType, 4> s_controllers{};
static std::array<bool, 4> s_wiimotes{};
static ControllerState s_padState; static ControllerState s_padState;
static DTMHeader tmpHeader; static DTMHeader tmpHeader;
static std::vector<u8> s_temp_input; static std::vector<u8> s_temp_input;
@ -157,24 +158,33 @@ std::string GetInputDisplay()
{ {
if (!IsMovieActive()) if (!IsMovieActive())
{ {
s_controllers = 0; s_controllers = {};
s_wiimotes = {};
for (int i = 0; i < 4; ++i) for (int i = 0; i < 4; ++i)
{ {
if (SerialInterface::GetDeviceType(i) != SerialInterface::SIDEVICE_NONE) if (SerialInterface::GetDeviceType(i) == SerialInterface::SIDEVICE_GC_GBA_EMULATED)
s_controllers |= (1 << i); s_controllers[i] = ControllerType::GBA;
if (WiimoteCommon::GetSource(i) != WiimoteSource::None) else if (SerialInterface::GetDeviceType(i) != SerialInterface::SIDEVICE_NONE)
s_controllers |= (1 << (i + 4)); s_controllers[i] = ControllerType::GC;
else
s_controllers[i] = ControllerType::None;
s_wiimotes[i] = WiimoteCommon::GetSource(i) != WiimoteSource::None;
} }
} }
std::string input_display; std::string input_display;
{ {
std::lock_guard guard(s_input_display_lock); 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'; 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; return input_display;
} }
@ -386,7 +396,7 @@ void SetReset(bool reset)
bool IsUsingPad(int controller) bool IsUsingPad(int controller)
{ {
return ((s_controllers & (1 << controller)) != 0); return s_controllers[controller] != ControllerType::None;
} }
bool IsUsingBongo(int controller) bool IsUsingBongo(int controller)
@ -394,9 +404,14 @@ bool IsUsingBongo(int controller)
return ((s_bongos & (1 << controller)) != 0); return ((s_bongos & (1 << controller)) != 0);
} }
bool IsUsingGBA(int controller)
{
return s_controllers[controller] == ControllerType::GBA;
}
bool IsUsingWiimote(int wiimote) bool IsUsingWiimote(int wiimote)
{ {
return ((s_controllers & (1 << (wiimote + 4))) != 0); return s_wiimotes[wiimote];
} }
bool IsConfigSaved() bool IsConfigSaved()
@ -425,21 +440,29 @@ void ChangePads()
if (!Core::IsRunning()) if (!Core::IsRunning())
return; return;
int controllers = 0; ControllerTypeArray controllers{};
for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i) for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i)
{ {
if (SerialInterface::SIDevice_IsGCController(SConfig::GetInstance().m_SIDevice[i])) if (SConfig::GetInstance().m_SIDevice[i] == SerialInterface::SIDEVICE_GC_GBA_EMULATED)
controllers |= (1 << i); 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; return;
for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i) for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i)
{ {
SerialInterface::SIDevices device = SerialInterface::SIDEVICE_NONE; 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])) if (SerialInterface::SIDevice_IsGCController(SConfig::GetInstance().m_SIDevice[i]))
{ {
@ -459,14 +482,15 @@ void ChangePads()
// NOTE: Host / Emu Threads // NOTE: Host / Emu Threads
void ChangeWiiPads(bool instantly) void ChangeWiiPads(bool instantly)
{ {
int controllers = 0; WiimoteEnabledArray wiimotes{};
for (int i = 0; i < MAX_WIIMOTES; ++i) 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 // 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; return;
const auto bt = WiiUtils::GetBluetoothEmuDevice(); const auto bt = WiiUtils::GetBluetoothEmuDevice();
@ -481,13 +505,16 @@ void ChangeWiiPads(bool instantly)
} }
// NOTE: Host Thread // 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; return false;
Core::RunAsCPUThread([controllers] { Core::RunAsCPUThread([controllers, wiimotes] {
s_controllers = controllers; s_controllers = controllers;
s_wiimotes = wiimotes;
s_currentFrame = s_totalFrames = 0; s_currentFrame = s_totalFrames = 0;
s_currentLagCount = s_totalLagCount = 0; s_currentLagCount = s_totalLagCount = 0;
s_currentInputCount = s_totalInputCount = 0; s_currentInputCount = s_totalInputCount = 0;
@ -842,7 +869,16 @@ void RecordWiimote(int wiimote, const u8* data, u8 size)
// NOTE: EmuThread / Host Thread // NOTE: EmuThread / Host Thread
void ReadHeader() 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; s_recordingStartTime = tmpHeader.recordingStartTime;
if (s_rerecords < tmpHeader.numRerecords) if (s_rerecords < tmpHeader.numRerecords)
s_rerecords = tmpHeader.numRerecords; s_rerecords = tmpHeader.numRerecords;
@ -1219,9 +1255,10 @@ bool PlayWiimote(int wiimote, WiimoteCommon::DataReportBuilder& rpt, int ext,
PanicAlertFmtT( PanicAlertFmtT(
"Fatal desync. Aborting playback. (Error in PlayWiimote: {0} != {1}, byte {2}.){3}", "Fatal desync. Aborting playback. (Error in PlayWiimote: {0} != {1}, byte {2}.){3}",
sizeInMovie, size, s_currentByte, sizeInMovie, size, s_currentByte,
(s_controllers & 0xF) ? " Try re-creating the recording with all GameCube controllers " (s_controllers == ControllerTypeArray{}) ?
"disabled (in Configure > GameCube > Device Settings)." : " Try re-creating the recording with all GameCube controllers "
""); "disabled (in Configure > GameCube > Device Settings)." :
"");
EndPlayInput(!s_bReadOnly); EndPlayInput(!s_bReadOnly);
return false; return false;
} }
@ -1296,7 +1333,17 @@ void SaveRecording(const std::string& filename)
strncpy(header.gameID.data(), SConfig::GetInstance().GetGameID().c_str(), 6); strncpy(header.gameID.data(), SConfig::GetInstance().GetGameID().c_str(), 6);
header.bWii = SConfig::GetInstance().bWii; header.bWii = SConfig::GetInstance().bWii;
header.bFollowBranch = SConfig::GetInstance().bJITFollowBranch; 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.bFromSaveState = s_bRecordingFromSaveState;
header.frameCount = s_totalFrames; header.frameCount = s_totalFrames;

View File

@ -39,6 +39,15 @@ enum PlayMode
MODE_PLAYING MODE_PLAYING
}; };
enum class ControllerType
{
None = 0,
GC,
GBA,
};
using ControllerTypeArray = std::array<ControllerType, 4>;
using WiimoteEnabledArray = std::array<bool, 4>;
// GameCube Controller State // GameCube Controller State
#pragma pack(push, 1) #pragma pack(push, 1)
struct ControllerState struct ControllerState
@ -116,7 +125,8 @@ struct DTMHeader
u8 reserved3; u8 reserved3;
bool bFollowBranch; bool bFollowBranch;
bool bUseFMA; bool bUseFMA;
std::array<u8, 8> reserved; // Padding for any new config options u8 GBAControllers; // GBA Controllers plugged in (the bits are ports 1-4)
std::array<u8, 7> reserved; // Padding for any new config options
std::array<char, 40> discChange; // Name of iso file to switch to, for two disc games. std::array<char, 40> discChange; // Name of iso file to switch to, for two disc games.
std::array<u8, 20> revision; // Git hash std::array<u8, 20> revision; // Git hash
u32 DSPiromHash; u32 DSPiromHash;
@ -163,12 +173,14 @@ bool IsNetPlayRecording();
bool IsUsingPad(int controller); bool IsUsingPad(int controller);
bool IsUsingWiimote(int wiimote); bool IsUsingWiimote(int wiimote);
bool IsUsingBongo(int controller); bool IsUsingBongo(int controller);
bool IsUsingGBA(int controller);
void ChangePads(); void ChangePads();
void ChangeWiiPads(bool instantly = false); void ChangeWiiPads(bool instantly = false);
void SetReadOnly(bool bEnabled); void SetReadOnly(bool bEnabled);
bool BeginRecordingInput(int controllers); bool BeginRecordingInput(const ControllerTypeArray& controllers,
const WiimoteEnabledArray& wiimotes);
void RecordInput(const GCPadStatus* PadStatus, int controllerID); void RecordInput(const GCPadStatus* PadStatus, int controllerID);
void RecordWiimote(int wiimote, const u8* data, u8 size); void RecordWiimote(int wiimote, const u8* data, u8 size);

View File

@ -33,12 +33,18 @@
#include "Common/Version.h" #include "Common/Version.h"
#include "Core/ActionReplay.h" #include "Core/ActionReplay.h"
#include "Core/Config/MainSettings.h"
#include "Core/Config/NetplaySettings.h" #include "Core/Config/NetplaySettings.h"
#include "Core/Config/SessionSettings.h" #include "Core/Config/SessionSettings.h"
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
#include "Core/GeckoCode.h" #include "Core/GeckoCode.h"
#include "Core/HW/EXI/EXI_DeviceIPL.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/GCMemcard/GCMemcard.h"
#include "Core/HW/GCPad.h"
#include "Core/HW/SI/SI.h" #include "Core/HW/SI/SI.h"
#include "Core/HW/SI/SI_Device.h" #include "Core/HW/SI/SI_Device.h"
#include "Core/HW/SI/SI_DeviceGCController.h" #include "Core/HW/SI/SI_DeviceGCController.h"
@ -71,7 +77,7 @@ static std::mutex crit_netplay_client;
static NetPlayClient* netplay_client = nullptr; static NetPlayClient* netplay_client = nullptr;
static std::unique_ptr<IOS::HLE::FS::FileSystem> s_wii_sync_fs; static std::unique_ptr<IOS::HLE::FS::FileSystem> s_wii_sync_fs;
static std::vector<u64> s_wii_sync_titles; static std::vector<u64> s_wii_sync_titles;
static bool s_si_poll_batching; static bool s_si_poll_batching = false;
// called from ---GUI--- thread // called from ---GUI--- thread
NetPlayClient::~NetPlayClient() NetPlayClient::~NetPlayClient()
@ -463,6 +469,35 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
} }
break; 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<int>(i), config);
m_net_settings.m_GBARomPaths[i] =
config.has_rom ?
m_dialog->FindGBARomPath(config.hash, config.title, static_cast<int>(i)) :
"";
}
}
SendGameStatus();
UpdateDevices();
m_dialog->Update();
}
break;
case NP_MSG_WIIMOTE_MAPPING: case NP_MSG_WIIMOTE_MAPPING:
{ {
for (PlayerId& mapping : m_wiimote_map) for (PlayerId& mapping : m_wiimote_map)
@ -482,8 +517,12 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
packet >> map; packet >> map;
GCPadStatus pad; GCPadStatus pad;
packet >> pad.button >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> packet >> pad.button;
pad.substickX >> pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected; 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) // Trusting server for good map value (>=0 && <4)
// add to pad buffer // add to pad buffer
@ -501,8 +540,12 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
packet >> map; packet >> map;
GCPadStatus pad; GCPadStatus pad;
packet >> pad.button >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> packet >> pad.button;
pad.substickX >> pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected; 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) // Trusting server for good map value (>=0 && <4)
// write to last status // write to last status
@ -602,14 +645,7 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
// update gui // update gui
m_dialog->OnMsgChangeGame(m_selected_game, netplay_name); m_dialog->OnMsgChangeGame(m_selected_game, netplay_name);
sf::Packet game_status_packet; SendGameStatus();
game_status_packet << static_cast<MessageId>(NP_MSG_GAME_STATUS);
SyncIdentifierComparison result;
m_dialog->FindGameFile(m_selected_game, &result);
game_status_packet << static_cast<u32>(result);
Send(game_status_packet);
sf::Packet client_capabilities_packet; sf::Packet client_capabilities_packet;
client_capabilities_packet << static_cast<MessageId>(NP_MSG_CLIENT_CAPABILITIES); client_capabilities_packet << static_cast<MessageId>(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_GolfMode;
packet >> m_net_settings.m_UseFMA; 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_IsHosting = m_local_player->IsHost();
m_net_settings.m_HostInputAuthority = m_host_input_authority; m_net_settings.m_HostInputAuthority = m_host_input_authority;
@ -1055,6 +1092,29 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
} }
break; 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: default:
PanicAlertFmtT("Unknown SYNC_SAVE_DATA message received with id: {0}", sub_id); PanicAlertFmtT("Unknown SYNC_SAVE_DATA message received with id: {0}", sub_id);
break; break;
@ -1411,26 +1471,13 @@ void NetPlayClient::GetPlayerList(std::string& list, std::vector<int>& pid_list)
std::ostringstream ss; 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) for (const auto& entry : m_players)
{ {
const Player& player = entry.second; const Player& player = entry.second;
ss << player.name << "[" << static_cast<int>(player.pid) << "] : " << player.revision << " | "; ss << player.name << "[" << static_cast<int>(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); ss << "Ping: " << player.ping << "ms\n";
enumerate_player_controller_mappings(m_wiimote_map, player);
ss << " |\nPing: " << player.ping << "ms\n";
ss << "Status: "; ss << "Status: ";
switch (player.game_status) switch (player.game_status)
@ -1492,8 +1539,12 @@ void NetPlayClient::AddPadStateToPacket(const int in_game_pad, const GCPadStatus
sf::Packet& packet) sf::Packet& packet)
{ {
packet << static_cast<PadIndex>(in_game_pad); packet << static_cast<PadIndex>(in_game_pad);
packet << pad.button << pad.analogA << pad.analogB << pad.stickX << pad.stickY << pad.substickX packet << pad.button;
<< pad.substickY << pad.triggerLeft << pad.triggerRight << pad.isConnected; 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 // called from ---CPU--- thread
@ -1555,15 +1606,19 @@ bool NetPlayClient::StartGame(const std::string& path)
if (Movie::IsReadOnly()) if (Movie::IsReadOnly())
Movie::SetReadOnly(false); Movie::SetReadOnly(false);
u8 controllers_mask = 0; Movie::ControllerTypeArray controllers{};
Movie::WiimoteEnabledArray wiimotes{};
for (unsigned int i = 0; i < 4; ++i) for (unsigned int i = 0; i < 4; ++i)
{ {
if (m_pad_map[i] > 0) if (m_pad_map[i] > 0 && m_gba_config[i].enabled)
controllers_mask |= (1 << i); controllers[i] = Movie::ControllerType::GBA;
if (m_wiimote_map[i] > 0) else if (m_pad_map[i] > 0)
controllers_mask |= (1 << (i + 4)); 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) for (unsigned int i = 0; i < 4; ++i)
@ -1648,11 +1703,13 @@ void NetPlayClient::UpdateDevices()
for (auto player_id : m_pad_map) for (auto player_id : m_pad_map)
{ {
// Use local controller types for local controllers if they are compatible if (m_gba_config[pad].enabled && player_id > 0)
// Only GCController-like controllers are supported, GBA and similar
// exotic devices are not supported on netplay.
if (player_id == m_local_player->pid)
{ {
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])) if (SerialInterface::SIDevice_IsGCController(SConfig::GetInstance().m_SIDevice[local_pad]))
{ {
SerialInterface::ChangeDevice(SConfig::GetInstance().m_SIDevice[local_pad], pad); SerialInterface::ChangeDevice(SConfig::GetInstance().m_SIDevice[local_pad], pad);
@ -1968,21 +2025,22 @@ bool NetPlayClient::WiimoteUpdate(int _number, u8* data, const std::size_t size,
bool NetPlayClient::PollLocalPad(const int local_pad, sf::Packet& packet) 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); const int ingame_pad = LocalPadToInGamePad(local_pad);
bool data_added = false; 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) if (m_host_input_authority)
{ {
@ -2234,6 +2292,26 @@ bool NetPlayClient::IsLocalPlayer(const PlayerId pid) const
return pid == m_local_player->pid; return pid == m_local_player->pid;
} }
void NetPlayClient::SendGameStatus()
{
sf::Packet packet;
packet << static_cast<MessageId>(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<u32>(result);
Send(packet);
}
void NetPlayClient::SendTimeBase() void NetPlayClient::SendTimeBase()
{ {
std::lock_guard lk(crit_netplay_client); std::lock_guard lk(crit_netplay_client);
@ -2309,6 +2387,11 @@ const PadMappingArray& NetPlayClient::GetPadMapping() const
return m_pad_map; return m_pad_map;
} }
const GBAConfigArray& NetPlayClient::GetGBAConfig() const
{
return m_gba_config;
}
const PadMappingArray& NetPlayClient::GetWiimoteMapping() const const PadMappingArray& NetPlayClient::GetWiimoteMapping() const
{ {
return m_wiimote_map; return m_wiimote_map;
@ -2325,6 +2408,32 @@ SyncIdentifier NetPlayClient::GetSDCardIdentifier()
return SyncIdentifier{{}, "sd", {}, {}, {}, {}}; return SyncIdentifier{{}, "sd", {}, {}, {}, {}};
} }
std::string GetPlayerMappingString(PlayerId pid, const PadMappingArray& pad_map,
const GBAConfigArray& gba_config,
const PadMappingArray& wiimote_map)
{
std::vector<size_t> 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<std::string> 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() bool IsNetPlayRunning()
{ {
return netplay_client != nullptr; return netplay_client != nullptr;
@ -2401,6 +2510,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) void NetPlay_Enable(NetPlayClient* const np)
{ {
std::lock_guard lk(crit_netplay_client); std::lock_guard lk(crit_netplay_client);

View File

@ -43,6 +43,7 @@ public:
virtual void OnMsgChangeGame(const SyncIdentifier& sync_identifier, virtual void OnMsgChangeGame(const SyncIdentifier& sync_identifier,
const std::string& netplay_name) = 0; const std::string& netplay_name) = 0;
virtual void OnMsgChangeGBARom(int pad, const NetPlay::GBAConfig& config) = 0;
virtual void OnMsgStartGame() = 0; virtual void OnMsgStartGame() = 0;
virtual void OnMsgStopGame() = 0; virtual void OnMsgStopGame() = 0;
virtual void OnMsgPowerButton() = 0; virtual void OnMsgPowerButton() = 0;
@ -62,6 +63,8 @@ public:
virtual std::shared_ptr<const UICommon::GameFile> virtual std::shared_ptr<const UICommon::GameFile>
FindGameFile(const SyncIdentifier& sync_identifier, FindGameFile(const SyncIdentifier& sync_identifier,
SyncIdentifierComparison* found = nullptr) = 0; SyncIdentifierComparison* found = nullptr) = 0;
virtual std::string FindGBARomPath(const std::array<u8, 20>& hash, std::string_view title,
int device_number) = 0;
virtual void ShowMD5Dialog(const std::string& title) = 0; virtual void ShowMD5Dialog(const std::string& title) = 0;
virtual void SetMD5Progress(int pid, int progress) = 0; virtual void SetMD5Progress(int pid, int progress) = 0;
virtual void SetMD5Result(int pid, const std::string& result) = 0; virtual void SetMD5Result(int pid, const std::string& result) = 0;
@ -139,6 +142,7 @@ public:
bool DoAllPlayersHaveGame(); bool DoAllPlayersHaveGame();
const PadMappingArray& GetPadMapping() const; const PadMappingArray& GetPadMapping() const;
const GBAConfigArray& GetGBAConfig() const;
const PadMappingArray& GetWiimoteMapping() const; const PadMappingArray& GetWiimoteMapping() const;
void AdjustPadBufferSize(unsigned int size); void AdjustPadBufferSize(unsigned int size);
@ -199,8 +203,9 @@ protected:
u32 m_current_game = 0; u32 m_current_game = 0;
PadMappingArray m_pad_map; PadMappingArray m_pad_map{};
PadMappingArray m_wiimote_map; GBAConfigArray m_gba_config{};
PadMappingArray m_wiimote_map{};
bool m_is_recording = false; bool m_is_recording = false;
@ -231,6 +236,7 @@ private:
void Send(const sf::Packet& packet, u8 channel_id = DEFAULT_CHANNEL); void Send(const sf::Packet& packet, u8 channel_id = DEFAULT_CHANNEL);
void Disconnect(); void Disconnect();
bool Connect(); bool Connect();
void SendGameStatus();
void ComputeMD5(const SyncIdentifier& sync_identifier); void ComputeMD5(const SyncIdentifier& sync_identifier);
void DisplayPlayersPing(); void DisplayPlayersPing();
u32 GetPlayersMaxPing() const; u32 GetPlayersMaxPing() const;

View File

@ -4,6 +4,7 @@
#pragma once #pragma once
#include <array> #include <array>
#include <string>
#include <vector> #include <vector>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
@ -97,10 +98,12 @@ struct NetSettings
std::array<int, 4> m_WiimoteExtension; std::array<int, 4> m_WiimoteExtension;
bool m_GolfMode; bool m_GolfMode;
bool m_UseFMA; bool m_UseFMA;
bool m_HideRemoteGBAs;
// These aren't sent over the network directly // These aren't sent over the network directly
bool m_IsHosting; bool m_IsHosting;
bool m_HostInputAuthority; bool m_HostInputAuthority;
std::array<std::string, 4> m_GBARomPaths;
}; };
struct NetTraversalConfig struct NetTraversalConfig
@ -136,6 +139,7 @@ enum
NP_MSG_PAD_MAPPING = 0x61, NP_MSG_PAD_MAPPING = 0x61,
NP_MSG_PAD_BUFFER = 0x62, NP_MSG_PAD_BUFFER = 0x62,
NP_MSG_PAD_HOST_DATA = 0x63, NP_MSG_PAD_HOST_DATA = 0x63,
NP_MSG_GBA_CONFIG = 0x64,
NP_MSG_WIIMOTE_DATA = 0x70, NP_MSG_WIIMOTE_DATA = 0x70,
NP_MSG_WIIMOTE_MAPPING = 0x71, NP_MSG_WIIMOTE_MAPPING = 0x71,
@ -191,7 +195,8 @@ enum
SYNC_SAVE_DATA_FAILURE = 2, SYNC_SAVE_DATA_FAILURE = 2,
SYNC_SAVE_DATA_RAW = 3, SYNC_SAVE_DATA_RAW = 3,
SYNC_SAVE_DATA_GCI = 4, SYNC_SAVE_DATA_GCI = 4,
SYNC_SAVE_DATA_WII = 5 SYNC_SAVE_DATA_WII = 5,
SYNC_SAVE_DATA_GBA = 6
}; };
enum enum
@ -225,7 +230,26 @@ using PlayerId = u8;
using FrameNum = u32; using FrameNum = u32;
using PadIndex = s8; using PadIndex = s8;
using PadMappingArray = std::array<PlayerId, 4>; using PadMappingArray = std::array<PlayerId, 4>;
struct GBAConfig
{
bool enabled;
bool has_rom;
std::string title;
std::array<u8, 20> hash;
};
using GBAConfigArray = std::array<GBAConfig, 4>;
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(); bool IsNetPlayRunning();
// Precondition: A netplay client instance must be present. In other words, // Precondition: A netplay client instance must be present. In other words,
// IsNetPlayRunning() must be true before calling this. // IsNetPlayRunning() must be true before calling this.
@ -238,4 +262,6 @@ void SetSIPollBatching(bool state);
void SendPowerButtonEvent(); void SendPowerButtonEvent();
bool IsSyncingAllWiiSaves(); bool IsSyncingAllWiiSaves();
void SetupWiimotes(); void SetupWiimotes();
std::string GetGBASavePath(int pad_num);
PadDetails GetPadDetails(int pad_num);
} // namespace NetPlay } // namespace NetPlay

View File

@ -40,6 +40,9 @@
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
#include "Core/GeckoCode.h" #include "Core/GeckoCode.h"
#include "Core/GeckoCodeConfig.h" #include "Core/GeckoCodeConfig.h"
#ifdef HAS_LIBMGBA
#include "Core/HW/GBACore.h"
#endif
#include "Core/HW/GCMemcard/GCMemcard.h" #include "Core/HW/GCMemcard/GCMemcard.h"
#include "Core/HW/GCMemcard/GCMemcardDirectory.h" #include "Core/HW/GCMemcard/GCMemcardDirectory.h"
#include "Core/HW/GCMemcard/GCMemcardRaw.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_pad_map.fill(0);
m_gba_config.fill({});
m_wiimote_map.fill(0); m_wiimote_map.fill(0);
if (traversal_config.use_traversal) 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); std::lock_guard lkp(m_crit.players);
m_players.emplace(*PeerPlayerId(player.socket), std::move(player)); m_players.emplace(*PeerPlayerId(player.socket), std::move(player));
UpdatePadMapping(); // sync pad mappings with everyone UpdatePadMapping(); // sync pad mappings with everyone
UpdateGBAConfig();
UpdateWiimoteMapping(); UpdateWiimoteMapping();
} }
@ -530,12 +535,14 @@ unsigned int NetPlayServer::OnDisconnect(const Client& player)
// alert other players of disconnect // alert other players of disconnect
SendToClients(spac); 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(); UpdatePadMapping();
UpdateGBAConfig();
} }
} }
@ -557,6 +564,11 @@ PadMappingArray NetPlayServer::GetPadMapping() const
return m_pad_map; return m_pad_map;
} }
GBAConfigArray NetPlayServer::GetGBAConfig() const
{
return m_gba_config;
}
PadMappingArray NetPlayServer::GetWiimoteMapping() const PadMappingArray NetPlayServer::GetWiimoteMapping() const
{ {
return m_wiimote_map; return m_wiimote_map;
@ -569,6 +581,26 @@ void NetPlayServer::SetPadMapping(const PadMappingArray& mappings)
UpdatePadMapping(); 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 // called from ---GUI--- thread
void NetPlayServer::SetWiimoteMapping(const PadMappingArray& mappings) void NetPlayServer::SetWiimoteMapping(const PadMappingArray& mappings)
{ {
@ -588,6 +620,20 @@ void NetPlayServer::UpdatePadMapping()
SendToClients(spac); SendToClients(spac);
} }
// called from ---GUI--- thread and ---NETPLAY--- thread
void NetPlayServer::UpdateGBAConfig()
{
sf::Packet spac;
spac << static_cast<MessageId>(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 // called from ---NETPLAY--- thread
void NetPlayServer::UpdateWiimoteMapping() void NetPlayServer::UpdateWiimoteMapping()
{ {
@ -751,12 +797,16 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player)
} }
GCPadStatus pad; GCPadStatus pad;
packet >> pad.button >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> packet >> pad.button;
pad.substickX >> pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected; 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 spac << pad.analogA << pad.analogB << pad.stickX << pad.stickY << pad.substickX
<< pad.substickX << pad.substickY << pad.triggerLeft << pad.triggerRight << pad.substickY << pad.triggerLeft << pad.triggerRight << pad.isConnected;
<< pad.isConnected; }
} }
if (m_host_input_authority) if (m_host_input_authority)
@ -787,12 +837,16 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player)
packet >> map; packet >> map;
GCPadStatus pad; GCPadStatus pad;
packet >> pad.button >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> packet >> pad.button;
pad.substickX >> pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected; 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 spac << pad.analogA << pad.analogB << pad.stickX << pad.stickY << pad.substickX
<< pad.substickX << pad.substickY << pad.triggerLeft << pad.triggerRight << pad.substickY << pad.triggerLeft << pad.triggerRight << pad.isConnected;
<< pad.isConnected; }
} }
SendToClients(spac, player.pid); 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); 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_GolfMode = Config::Get(Config::NETPLAY_NETWORK_MODE) == "golf";
settings.m_UseFMA = DoAllPlayersHaveHardwareFMA(); settings.m_UseFMA = DoAllPlayersHaveHardwareFMA();
settings.m_HideRemoteGBAs = Config::Get(Config::NETPLAY_HIDE_REMOTE_GBAS);
// Unload GameINI to restore things to normal // Unload GameINI to restore things to normal
Config::RemoveLayer(Config::LayerType::GlobalGame); Config::RemoveLayer(Config::LayerType::GlobalGame);
@ -1501,6 +1556,7 @@ bool NetPlayServer::StartGame()
spac << m_settings.m_GolfMode; spac << m_settings.m_GolfMode;
spac << m_settings.m_UseFMA; spac << m_settings.m_UseFMA;
spac << m_settings.m_HideRemoteGBAs;
SendAsyncToClients(std::move(spac)); SendAsyncToClients(std::move(spac));
@ -1556,6 +1612,12 @@ bool NetPlayServer::SyncSaveData()
save_count++; save_count++;
} }
for (const auto& config : m_gba_config)
{
if (config.enabled && config.has_rom)
save_count++;
}
{ {
sf::Packet pac; sf::Packet pac;
pac << static_cast<MessageId>(NP_MSG_SYNC_SAVE_DATA); pac << static_cast<MessageId>(NP_MSG_SYNC_SAVE_DATA);
@ -1758,6 +1820,36 @@ bool NetPlayServer::SyncSaveData()
SendChunkedToClients(std::move(pac), 1, "Wii Save Synchronization"); 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<MessageId>(NP_MSG_SYNC_SAVE_DATA);
pac << static_cast<MessageId>(SYNC_SAVE_DATA_GBA);
pac << static_cast<u8>(i);
std::string path;
#ifdef HAS_LIBMGBA
path = HW::GBA::Core::GetSavePath(Config::Get(Config::MAIN_GBA_ROM_PATHS[i]),
static_cast<int>(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; return true;
} }

View File

@ -58,6 +58,9 @@ public:
PadMappingArray GetPadMapping() const; PadMappingArray GetPadMapping() const;
void SetPadMapping(const PadMappingArray& mappings); void SetPadMapping(const PadMappingArray& mappings);
GBAConfigArray GetGBAConfig() const;
void SetGBAConfig(const GBAConfigArray& configs, bool update_rom);
PadMappingArray GetWiimoteMapping() const; PadMappingArray GetWiimoteMapping() const;
void SetWiimoteMapping(const PadMappingArray& mappings); void SetWiimoteMapping(const PadMappingArray& mappings);
@ -134,6 +137,7 @@ private:
void OnConnectReady(ENetAddress) override {} void OnConnectReady(ENetAddress) override {}
void OnConnectFailed(TraversalConnectFailedReason) override {} void OnConnectFailed(TraversalConnectFailedReason) override {}
void UpdatePadMapping(); void UpdatePadMapping();
void UpdateGBAConfig();
void UpdateWiimoteMapping(); void UpdateWiimoteMapping();
std::vector<std::pair<std::string, std::string>> GetInterfaceListInternal() const; std::vector<std::pair<std::string, std::string>> GetInterfaceListInternal() const;
void ChunkedDataThreadFunc(); void ChunkedDataThreadFunc();
@ -153,6 +157,7 @@ private:
u32 m_current_game = 0; u32 m_current_game = 0;
unsigned int m_target_buffer_size = 0; unsigned int m_target_buffer_size = 0;
PadMappingArray m_pad_map; PadMappingArray m_pad_map;
GBAConfigArray m_gba_config;
PadMappingArray m_wiimote_map; PadMappingArray m_wiimote_map;
unsigned int m_save_data_synced_players = 0; unsigned int m_save_data_synced_players = 0;
unsigned int m_codes_synced_players = 0; unsigned int m_codes_synced_players = 0;

View File

@ -73,7 +73,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
static std::thread g_save_thread; static std::thread g_save_thread;
// Don't forget to increase this after doing changes on the savestate system // 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. // Maps savestate versions to Dolphin versions.
// Versions after 42 don't need to be added to this list, // Versions after 42 don't need to be added to this list,

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<ClInclude Include="AudioCommon\AudioCommon.h" /> <ClInclude Include="AudioCommon\AudioCommon.h" />
@ -263,6 +263,9 @@
<ClInclude Include="Core\HW\EXI\EXI_DeviceMemoryCard.h" /> <ClInclude Include="Core\HW\EXI\EXI_DeviceMemoryCard.h" />
<ClInclude Include="Core\HW\EXI\EXI_DeviceMic.h" /> <ClInclude Include="Core\HW\EXI\EXI_DeviceMic.h" />
<ClInclude Include="Core\HW\EXI\EXI.h" /> <ClInclude Include="Core\HW\EXI\EXI.h" />
<ClInclude Include="Core\HW\GBACore.h" />
<ClInclude Include="Core\HW\GBAPad.h" />
<ClInclude Include="Core\HW\GBAPadEmu.h" />
<ClInclude Include="Core\HW\GCKeyboard.h" /> <ClInclude Include="Core\HW\GCKeyboard.h" />
<ClInclude Include="Core\HW\GCKeyboardEmu.h" /> <ClInclude Include="Core\HW\GCKeyboardEmu.h" />
<ClInclude Include="Core\HW\GCMemcard\GCIFile.h" /> <ClInclude Include="Core\HW\GCMemcard\GCIFile.h" />
@ -283,6 +286,7 @@
<ClInclude Include="Core\HW\SI\SI_Device.h" /> <ClInclude Include="Core\HW\SI\SI_Device.h" />
<ClInclude Include="Core\HW\SI\SI_DeviceDanceMat.h" /> <ClInclude Include="Core\HW\SI\SI_DeviceDanceMat.h" />
<ClInclude Include="Core\HW\SI\SI_DeviceGBA.h" /> <ClInclude Include="Core\HW\SI\SI_DeviceGBA.h" />
<ClInclude Include="Core\HW\SI\SI_DeviceGBAEmu.h" />
<ClInclude Include="Core\HW\SI\SI_DeviceGCAdapter.h" /> <ClInclude Include="Core\HW\SI\SI_DeviceGCAdapter.h" />
<ClInclude Include="Core\HW\SI\SI_DeviceGCController.h" /> <ClInclude Include="Core\HW\SI\SI_DeviceGCController.h" />
<ClInclude Include="Core\HW\SI\SI_DeviceGCSteeringWheel.h" /> <ClInclude Include="Core\HW\SI\SI_DeviceGCSteeringWheel.h" />
@ -842,6 +846,9 @@
<ClCompile Include="Core\HW\EXI\EXI_DeviceMemoryCard.cpp" /> <ClCompile Include="Core\HW\EXI\EXI_DeviceMemoryCard.cpp" />
<ClCompile Include="Core\HW\EXI\EXI_DeviceMic.cpp" /> <ClCompile Include="Core\HW\EXI\EXI_DeviceMic.cpp" />
<ClCompile Include="Core\HW\EXI\EXI.cpp" /> <ClCompile Include="Core\HW\EXI\EXI.cpp" />
<ClCompile Include="Core\HW\GBACore.cpp" />
<ClCompile Include="Core\HW\GBAPad.cpp" />
<ClCompile Include="Core\HW\GBAPadEmu.cpp" />
<ClCompile Include="Core\HW\GCKeyboard.cpp" /> <ClCompile Include="Core\HW\GCKeyboard.cpp" />
<ClCompile Include="Core\HW\GCKeyboardEmu.cpp" /> <ClCompile Include="Core\HW\GCKeyboardEmu.cpp" />
<ClCompile Include="Core\HW\GCMemcard\GCIFile.cpp" /> <ClCompile Include="Core\HW\GCMemcard\GCIFile.cpp" />
@ -860,6 +867,7 @@
<ClCompile Include="Core\HW\SI\SI_Device.cpp" /> <ClCompile Include="Core\HW\SI\SI_Device.cpp" />
<ClCompile Include="Core\HW\SI\SI_DeviceDanceMat.cpp" /> <ClCompile Include="Core\HW\SI\SI_DeviceDanceMat.cpp" />
<ClCompile Include="Core\HW\SI\SI_DeviceGBA.cpp" /> <ClCompile Include="Core\HW\SI\SI_DeviceGBA.cpp" />
<ClCompile Include="Core\HW\SI\SI_DeviceGBAEmu.cpp" />
<ClCompile Include="Core\HW\SI\SI_DeviceGCAdapter.cpp" /> <ClCompile Include="Core\HW\SI\SI_DeviceGCAdapter.cpp" />
<ClCompile Include="Core\HW\SI\SI_DeviceGCController.cpp" /> <ClCompile Include="Core\HW\SI\SI_DeviceGCController.cpp" />
<ClCompile Include="Core\HW\SI\SI_DeviceGCSteeringWheel.cpp" /> <ClCompile Include="Core\HW\SI\SI_DeviceGCSteeringWheel.cpp" />

View File

@ -119,6 +119,11 @@ void Host_TitleChanged()
#endif #endif
} }
std::unique_ptr<GBAHostInterface> Host_CreateGBAHost(std::weak_ptr<HW::GBA::Core> core)
{
return nullptr;
}
static std::unique_ptr<Platform> GetPlatform(const optparse::Values& options) static std::unique_ptr<Platform> GetPlatform(const optparse::Values& options)
{ {
std::string platform_name = static_cast<const char*>(options.get("platform")); std::string platform_name = static_cast<const char*>(options.get("platform"));

View File

@ -124,6 +124,8 @@ add_executable(dolphin-emu
Config/Mapping/FreeLookGeneral.h Config/Mapping/FreeLookGeneral.h
Config/Mapping/FreeLookRotation.cpp Config/Mapping/FreeLookRotation.cpp
Config/Mapping/FreeLookRotation.h Config/Mapping/FreeLookRotation.h
Config/Mapping/GBAPadEmu.cpp
Config/Mapping/GBAPadEmu.h
Config/Mapping/GCKeyboardEmu.cpp Config/Mapping/GCKeyboardEmu.cpp
Config/Mapping/GCKeyboardEmu.h Config/Mapping/GCKeyboardEmu.h
Config/Mapping/GCMicrophone.cpp Config/Mapping/GCMicrophone.cpp
@ -138,6 +140,8 @@ add_executable(dolphin-emu
Config/Mapping/HotkeyControllerProfile.h Config/Mapping/HotkeyControllerProfile.h
Config/Mapping/HotkeyDebugging.cpp Config/Mapping/HotkeyDebugging.cpp
Config/Mapping/HotkeyDebugging.h Config/Mapping/HotkeyDebugging.h
Config/Mapping/HotkeyGBA.cpp
Config/Mapping/HotkeyGBA.h
Config/Mapping/HotkeyGeneral.cpp Config/Mapping/HotkeyGeneral.cpp
Config/Mapping/HotkeyGeneral.h Config/Mapping/HotkeyGeneral.h
Config/Mapping/HotkeyGraphics.cpp Config/Mapping/HotkeyGraphics.cpp
@ -538,6 +542,15 @@ else()
install(TARGETS dolphin-emu RUNTIME DESTINATION ${bindir}) install(TARGETS dolphin-emu RUNTIME DESTINATION ${bindir})
endif() endif()
if(USE_MGBA)
target_sources(dolphin-emu PRIVATE
GBAHost.cpp
GBAHost.h
GBAWidget.cpp
GBAWidget.h
)
endif()
if(USE_DISCORD_PRESENCE) if(USE_DISCORD_PRESENCE)
target_compile_definitions(dolphin-emu PRIVATE -DUSE_DISCORD_PRESENCE) target_compile_definitions(dolphin-emu PRIVATE -DUSE_DISCORD_PRESENCE)
endif() endif()

View File

@ -10,8 +10,9 @@
#include <QPushButton> #include <QPushButton>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <map>
#include <optional> #include <optional>
#include <utility>
#include <vector>
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
#include "Core/Core.h" #include "Core/Core.h"
@ -24,23 +25,37 @@
#include "InputCommon/GCAdapter.h" #include "InputCommon/GCAdapter.h"
static const std::map<SerialInterface::SIDevices, int> s_gc_types = { static const std::vector<std::pair<SerialInterface::SIDevices, const char*>> s_gc_types = {
{SerialInterface::SIDEVICE_NONE, 0}, {SerialInterface::SIDEVICE_GC_CONTROLLER, 1}, {SerialInterface::SIDEVICE_NONE, _trans("None")},
{SerialInterface::SIDEVICE_WIIU_ADAPTER, 2}, {SerialInterface::SIDEVICE_GC_STEERING, 3}, {SerialInterface::SIDEVICE_GC_CONTROLLER, _trans("Standard Controller")},
{SerialInterface::SIDEVICE_DANCEMAT, 4}, {SerialInterface::SIDEVICE_GC_TARUKONGA, 5}, {SerialInterface::SIDEVICE_WIIU_ADAPTER, _trans("GameCube Adapter for Wii U")},
{SerialInterface::SIDEVICE_GC_GBA, 6}, {SerialInterface::SIDEVICE_GC_KEYBOARD, 7}}; {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<int> ToGCMenuIndex(const SerialInterface::SIDevices sidevice) static std::optional<int> ToGCMenuIndex(const SerialInterface::SIDevices sidevice)
{ {
auto it = s_gc_types.find(sidevice); for (size_t i = 0; i < s_gc_types.size(); ++i)
return it != s_gc_types.end() ? it->second : std::optional<int>(); {
if (s_gc_types[i].first == sidevice)
return static_cast<int>(i);
}
return {};
} }
static std::optional<SerialInterface::SIDevices> FromGCMenuIndex(const int menudevice) static SerialInterface::SIDevices FromGCMenuIndex(const int menudevice)
{ {
auto it = std::find_if(s_gc_types.begin(), s_gc_types.end(), return s_gc_types[menudevice].first;
[=](auto pair) { return pair.second == menudevice; }); }
return it != s_gc_types.end() ? it->first : std::optional<SerialInterface::SIDevices>();
static bool IsConfigurable(SerialInterface::SIDevices sidevice)
{
return sidevice != SerialInterface::SIDEVICE_NONE && sidevice != SerialInterface::SIDEVICE_GC_GBA;
} }
GamecubeControllersWidget::GamecubeControllersWidget(QWidget* parent) : QWidget(parent) 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_box = m_gc_controller_boxes[i] = new QComboBox();
auto* gc_button = m_gc_buttons[i] = new QPushButton(tr("Configure")); auto* gc_button = m_gc_buttons[i] = new QPushButton(tr("Configure"));
for (const auto& item : for (const auto& item : s_gc_types)
{tr("None"), tr("Standard Controller"), tr("GameCube Adapter for Wii U"),
tr("Steering Wheel"), tr("Dance Mat"), tr("DK Bongos"), tr("GBA"), tr("Keyboard")})
{ {
gc_box->addItem(item); gc_box->addItem(tr(item.second));
} }
int controller_row = m_gc_layout->rowCount(); int controller_row = m_gc_layout->rowCount();
@ -105,8 +118,8 @@ void GamecubeControllersWidget::OnGCTypeChanged(int type)
{ {
if (m_gc_controller_boxes[i] == box) if (m_gc_controller_boxes[i] == box)
{ {
const int index = box->currentIndex(); const SerialInterface::SIDevices si_device = FromGCMenuIndex(box->currentIndex());
m_gc_buttons[i]->setEnabled(index != 0 && index != 6); m_gc_buttons[i]->setEnabled(IsConfigurable(si_device));
return; return;
} }
} }
@ -125,27 +138,30 @@ void GamecubeControllersWidget::OnGCPadConfigure()
MappingWindow::Type type; MappingWindow::Type type;
switch (m_gc_controller_boxes[index]->currentIndex()) switch (FromGCMenuIndex(m_gc_controller_boxes[index]->currentIndex()))
{ {
case 0: // None case SerialInterface::SIDEVICE_NONE:
case 6: // GBA case SerialInterface::SIDEVICE_GC_GBA:
return; return;
case 1: // Standard Controller case SerialInterface::SIDEVICE_GC_CONTROLLER:
type = MappingWindow::Type::MAPPING_GCPAD; type = MappingWindow::Type::MAPPING_GCPAD;
break; break;
case 2: // GameCube Adapter for Wii U case SerialInterface::SIDEVICE_WIIU_ADAPTER:
GCPadWiiUConfigDialog(static_cast<int>(index), this).exec(); GCPadWiiUConfigDialog(static_cast<int>(index), this).exec();
return; return;
case 3: // Steering Wheel case SerialInterface::SIDEVICE_GC_STEERING:
type = MappingWindow::Type::MAPPING_GC_STEERINGWHEEL; type = MappingWindow::Type::MAPPING_GC_STEERINGWHEEL;
break; break;
case 4: // Dance Mat case SerialInterface::SIDEVICE_DANCEMAT:
type = MappingWindow::Type::MAPPING_GC_DANCEMAT; type = MappingWindow::Type::MAPPING_GC_DANCEMAT;
break; break;
case 5: // DK Bongos case SerialInterface::SIDEVICE_GC_TARUKONGA:
type = MappingWindow::Type::MAPPING_GC_BONGOS; type = MappingWindow::Type::MAPPING_GC_BONGOS;
break; 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; type = MappingWindow::Type::MAPPING_GC_KEYBOARD;
break; break;
default: default:
@ -162,11 +178,12 @@ void GamecubeControllersWidget::LoadSettings()
{ {
for (size_t i = 0; i < m_gc_groups.size(); i++) for (size_t i = 0; i < m_gc_groups.size(); i++)
{ {
const std::optional<int> gc_index = ToGCMenuIndex(SConfig::GetInstance().m_SIDevice[i]); const SerialInterface::SIDevices si_device = SConfig::GetInstance().m_SIDevice[i];
const std::optional<int> gc_index = ToGCMenuIndex(si_device);
if (gc_index) if (gc_index)
{ {
m_gc_controller_boxes[i]->setCurrentIndex(*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++) for (size_t i = 0; i < m_gc_groups.size(); i++)
{ {
const int index = m_gc_controller_boxes[i]->currentIndex(); const int index = m_gc_controller_boxes[i]->currentIndex();
const std::optional<SerialInterface::SIDevices> si_device = FromGCMenuIndex(index); const SerialInterface::SIDevices si_device = FromGCMenuIndex(index);
if (si_device) SConfig::GetInstance().m_SIDevice[i] = si_device;
{
SConfig::GetInstance().m_SIDevice[i] = *si_device;
if (Core::IsRunning()) if (Core::IsRunning())
SerialInterface::ChangeDevice(*si_device, static_cast<s32>(i)); SerialInterface::ChangeDevice(si_device, static_cast<s32>(i));
}
m_gc_buttons[i]->setEnabled(index != 0 && index != 6); m_gc_buttons[i]->setEnabled(IsConfigurable(si_device));
} }
if (GCAdapter::UseAdapter()) if (GCAdapter::UseAdapter())

View File

@ -0,0 +1,45 @@
// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/Config/Mapping/GBAPadEmu.h"
#include <QGridLayout>
#include <QGroupBox>
#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();
}

View File

@ -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();
};

View File

@ -0,0 +1,43 @@
// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/Config/Mapping/HotkeyGBA.h"
#include <QGroupBox>
#include <QHBoxLayout>
#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();
}

View File

@ -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;
};

View File

@ -6,6 +6,7 @@
#include <QCheckBox> #include <QCheckBox>
#include <QFormLayout> #include <QFormLayout>
#include <QGroupBox> #include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel> #include <QLabel>
#include <QPushButton> #include <QPushButton>
@ -118,16 +119,7 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con
} }
for (auto& control : group->controls) for (auto& control : group->controls)
{ CreateControl(control.get(), form_layout, !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);
form_layout->addRow(translated_name, button);
}
for (auto& setting : group->numeric_settings) for (auto& setting : group->numeric_settings)
{ {
@ -186,6 +178,42 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con
return group_box; 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<QFormLayout*> 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 ControllerEmu::EmulatedController* MappingWidget::GetController() const
{ {
return m_parent->GetController(); return m_parent->GetController();

View File

@ -16,6 +16,7 @@ class InputConfig;
class MappingButton; class MappingButton;
class MappingNumeric; class MappingNumeric;
class MappingWindow; class MappingWindow;
class QFormLayout;
class QPushButton; class QPushButton;
class QGroupBox; class QGroupBox;
@ -52,6 +53,9 @@ protected:
QGroupBox* CreateGroupBox(ControllerEmu::ControlGroup* group); QGroupBox* CreateGroupBox(ControllerEmu::ControlGroup* group);
QGroupBox* CreateGroupBox(const QString& name, 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); QPushButton* CreateSettingAdvancedMappingButton(ControllerEmu::NumericSettingBase& setting);
private: private:

View File

@ -23,12 +23,14 @@
#include "DolphinQt/Config/Mapping/FreeLookGeneral.h" #include "DolphinQt/Config/Mapping/FreeLookGeneral.h"
#include "DolphinQt/Config/Mapping/FreeLookRotation.h" #include "DolphinQt/Config/Mapping/FreeLookRotation.h"
#include "DolphinQt/Config/Mapping/GBAPadEmu.h"
#include "DolphinQt/Config/Mapping/GCKeyboardEmu.h" #include "DolphinQt/Config/Mapping/GCKeyboardEmu.h"
#include "DolphinQt/Config/Mapping/GCMicrophone.h" #include "DolphinQt/Config/Mapping/GCMicrophone.h"
#include "DolphinQt/Config/Mapping/GCPadEmu.h" #include "DolphinQt/Config/Mapping/GCPadEmu.h"
#include "DolphinQt/Config/Mapping/Hotkey3D.h" #include "DolphinQt/Config/Mapping/Hotkey3D.h"
#include "DolphinQt/Config/Mapping/HotkeyControllerProfile.h" #include "DolphinQt/Config/Mapping/HotkeyControllerProfile.h"
#include "DolphinQt/Config/Mapping/HotkeyDebugging.h" #include "DolphinQt/Config/Mapping/HotkeyDebugging.h"
#include "DolphinQt/Config/Mapping/HotkeyGBA.h"
#include "DolphinQt/Config/Mapping/HotkeyGeneral.h" #include "DolphinQt/Config/Mapping/HotkeyGeneral.h"
#include "DolphinQt/Config/Mapping/HotkeyGraphics.h" #include "DolphinQt/Config/Mapping/HotkeyGraphics.h"
#include "DolphinQt/Config/Mapping/HotkeyStates.h" #include "DolphinQt/Config/Mapping/HotkeyStates.h"
@ -374,10 +376,15 @@ void MappingWindow::SetMappingType(MappingWindow::Type type)
switch (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: case Type::MAPPING_GC_KEYBOARD:
widget = new GCKeyboardEmu(this); widget = new GCKeyboardEmu(this);
AddWidget(tr("GameCube Keyboard"), widget);
setWindowTitle(tr("GameCube Keyboard at Port %1").arg(GetPort() + 1)); setWindowTitle(tr("GameCube Keyboard at Port %1").arg(GetPort() + 1));
AddWidget(tr("GameCube Keyboard"), widget);
break; break;
case Type::MAPPING_GC_BONGOS: case Type::MAPPING_GC_BONGOS:
case Type::MAPPING_GC_STEERINGWHEEL: case Type::MAPPING_GC_STEERINGWHEEL:
@ -429,6 +436,7 @@ void MappingWindow::SetMappingType(MappingWindow::Type type)
AddWidget(tr("3D"), new Hotkey3D(this)); AddWidget(tr("3D"), new Hotkey3D(this));
AddWidget(tr("Save and Load State"), new HotkeyStates(this)); AddWidget(tr("Save and Load State"), new HotkeyStates(this));
AddWidget(tr("Other State Management"), new HotkeyStatesOther(this)); AddWidget(tr("Other State Management"), new HotkeyStatesOther(this));
AddWidget(tr("GameBoy Advance"), new HotkeyGBA(this));
setWindowTitle(tr("Hotkey Settings")); setWindowTitle(tr("Hotkey Settings"));
break; break;
} }

View File

@ -34,6 +34,7 @@ public:
// GameCube // GameCube
MAPPING_GC_BONGOS, MAPPING_GC_BONGOS,
MAPPING_GC_DANCEMAT, MAPPING_GC_DANCEMAT,
MAPPING_GC_GBA,
MAPPING_GC_KEYBOARD, MAPPING_GC_KEYBOARD,
MAPPING_GCPAD, MAPPING_GCPAD,
MAPPING_GC_STEERINGWHEEL, MAPPING_GC_STEERINGWHEEL,

View File

@ -81,6 +81,7 @@
<ClCompile Include="Config\LogWidget.cpp" /> <ClCompile Include="Config\LogWidget.cpp" />
<ClCompile Include="Config\Mapping\FreeLookGeneral.cpp" /> <ClCompile Include="Config\Mapping\FreeLookGeneral.cpp" />
<ClCompile Include="Config\Mapping\FreeLookRotation.cpp" /> <ClCompile Include="Config\Mapping\FreeLookRotation.cpp" />
<ClCompile Include="Config\Mapping\GBAPadEmu.cpp" />
<ClCompile Include="Config\Mapping\GCKeyboardEmu.cpp" /> <ClCompile Include="Config\Mapping\GCKeyboardEmu.cpp" />
<ClCompile Include="Config\Mapping\GCMicrophone.cpp" /> <ClCompile Include="Config\Mapping\GCMicrophone.cpp" />
<ClCompile Include="Config\Mapping\GCPadEmu.cpp" /> <ClCompile Include="Config\Mapping\GCPadEmu.cpp" />
@ -89,6 +90,7 @@
<ClCompile Include="Config\Mapping\HotkeyControllerProfile.cpp" /> <ClCompile Include="Config\Mapping\HotkeyControllerProfile.cpp" />
<ClCompile Include="Config\Mapping\HotkeyDebugging.cpp" /> <ClCompile Include="Config\Mapping\HotkeyDebugging.cpp" />
<ClCompile Include="Config\Mapping\HotkeyGeneral.cpp" /> <ClCompile Include="Config\Mapping\HotkeyGeneral.cpp" />
<ClCompile Include="Config\Mapping\HotkeyGBA.cpp" />
<ClCompile Include="Config\Mapping\HotkeyGraphics.cpp" /> <ClCompile Include="Config\Mapping\HotkeyGraphics.cpp" />
<ClCompile Include="Config\Mapping\HotkeyStates.cpp" /> <ClCompile Include="Config\Mapping\HotkeyStates.cpp" />
<ClCompile Include="Config\Mapping\HotkeyStatesOther.cpp" /> <ClCompile Include="Config\Mapping\HotkeyStatesOther.cpp" />
@ -141,6 +143,8 @@
<ClCompile Include="GameList\GameTracker.cpp" /> <ClCompile Include="GameList\GameTracker.cpp" />
<ClCompile Include="GameList\GridProxyModel.cpp" /> <ClCompile Include="GameList\GridProxyModel.cpp" />
<ClCompile Include="GameList\ListProxyModel.cpp" /> <ClCompile Include="GameList\ListProxyModel.cpp" />
<ClCompile Include="GBAHost.cpp" />
<ClCompile Include="GBAWidget.cpp" />
<ClCompile Include="GCMemcardCreateNewDialog.cpp" /> <ClCompile Include="GCMemcardCreateNewDialog.cpp" />
<ClCompile Include="GCMemcardManager.cpp" /> <ClCompile Include="GCMemcardManager.cpp" />
<ClCompile Include="Host.cpp" /> <ClCompile Include="Host.cpp" />
@ -207,6 +211,7 @@
<ClInclude Include="Config\NewPatchDialog.h" /> <ClInclude Include="Config\NewPatchDialog.h" />
<ClInclude Include="Config\PatchesWidget.h" /> <ClInclude Include="Config\PatchesWidget.h" />
<ClInclude Include="Debugger\RegisterColumn.h" /> <ClInclude Include="Debugger\RegisterColumn.h" />
<ClInclude Include="GBAHost.h" />
<ClInclude Include="QtUtils\ActionHelper.h" /> <ClInclude Include="QtUtils\ActionHelper.h" />
<ClInclude Include="QtUtils\FlowLayout.h" /> <ClInclude Include="QtUtils\FlowLayout.h" />
<ClInclude Include="QtUtils\ImageConverter.h" /> <ClInclude Include="QtUtils\ImageConverter.h" />
@ -256,6 +261,7 @@
<QtMoc Include="Config\LogWidget.h" /> <QtMoc Include="Config\LogWidget.h" />
<QtMoc Include="Config\Mapping\FreeLookGeneral.h" /> <QtMoc Include="Config\Mapping\FreeLookGeneral.h" />
<QtMoc Include="Config\Mapping\FreeLookRotation.h" /> <QtMoc Include="Config\Mapping\FreeLookRotation.h" />
<QtMoc Include="Config\Mapping\GBAPadEmu.h" />
<QtMoc Include="Config\Mapping\GCKeyboardEmu.h" /> <QtMoc Include="Config\Mapping\GCKeyboardEmu.h" />
<QtMoc Include="Config\Mapping\GCMicrophone.h" /> <QtMoc Include="Config\Mapping\GCMicrophone.h" />
<QtMoc Include="Config\Mapping\GCPadEmu.h" /> <QtMoc Include="Config\Mapping\GCPadEmu.h" />
@ -263,6 +269,7 @@
<QtMoc Include="Config\Mapping\Hotkey3D.h" /> <QtMoc Include="Config\Mapping\Hotkey3D.h" />
<QtMoc Include="Config\Mapping\HotkeyControllerProfile.h" /> <QtMoc Include="Config\Mapping\HotkeyControllerProfile.h" />
<QtMoc Include="Config\Mapping\HotkeyDebugging.h" /> <QtMoc Include="Config\Mapping\HotkeyDebugging.h" />
<QtMoc Include="Config\Mapping\HotkeyGBA.h" />
<QtMoc Include="Config\Mapping\HotkeyGeneral.h" /> <QtMoc Include="Config\Mapping\HotkeyGeneral.h" />
<QtMoc Include="Config\Mapping\HotkeyGraphics.h" /> <QtMoc Include="Config\Mapping\HotkeyGraphics.h" />
<QtMoc Include="Config\Mapping\HotkeyStates.h" /> <QtMoc Include="Config\Mapping\HotkeyStates.h" />
@ -311,6 +318,7 @@
<QtMoc Include="GameList\GameTracker.h" /> <QtMoc Include="GameList\GameTracker.h" />
<QtMoc Include="GameList\GridProxyModel.h" /> <QtMoc Include="GameList\GridProxyModel.h" />
<QtMoc Include="GameList\ListProxyModel.h" /> <QtMoc Include="GameList\ListProxyModel.h" />
<QtMoc Include="GBAWidget.h" />
<QtMoc Include="GCMemcardCreateNewDialog.h" /> <QtMoc Include="GCMemcardCreateNewDialog.h" />
<QtMoc Include="GCMemcardManager.h" /> <QtMoc Include="GCMemcardManager.h" />
<QtMoc Include="Host.h" /> <QtMoc Include="Host.h" />

View File

@ -0,0 +1,61 @@
// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/GBAHost.h"
#include <QApplication>
#include "Core/HW/GBACore.h"
#include "DolphinQt/GBAWidget.h"
#include "DolphinQt/QtUtils/QueueOnObject.h"
GBAHost::GBAHost(std::weak_ptr<HW::GBA::Core> 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<u32>& video_buffer)
{
QueueOnObject(m_widget_controller, [widget_controller = m_widget_controller, video_buffer] {
widget_controller->FrameEnded(video_buffer);
});
}
std::unique_ptr<GBAHostInterface> Host_CreateGBAHost(std::weak_ptr<HW::GBA::Core> core)
{
return std::make_unique<GBAHost>(core);
}

View File

@ -0,0 +1,28 @@
// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <vector>
#include "Core/Host.h"
namespace HW::GBA
{
class Core;
} // namespace HW::GBA
class GBAWidgetController;
class GBAHost : public GBAHostInterface
{
public:
explicit GBAHost(std::weak_ptr<HW::GBA::Core> core);
~GBAHost();
void GameChanged() override;
void FrameEnded(const std::vector<u32>& video_buffer) override;
private:
GBAWidgetController* m_widget_controller{};
std::weak_ptr<HW::GBA::Core> m_core;
};

View File

@ -0,0 +1,432 @@
// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/GBAWidget.h"
#include <fmt/format.h>
#include <QAction>
#include <QCloseEvent>
#include <QContextMenuEvent>
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QFileDialog>
#include <QIcon>
#include <QImage>
#include <QMenu>
#include <QMimeData>
#include <QPainter>
#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<HW::GBA::Core>& 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<HW::GBA::Core> 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;
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);
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<u32> 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<size_t>(m_width * m_height))
{
QImage image(reinterpret_cast<const uchar*>(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<float>(m_width) / m_height >
static_cast<float>(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<HW::GBA::Core> 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<u32> video_buffer)
{
m_widget->SetVideoBuffer(std::move(video_buffer));
}

View File

@ -0,0 +1,96 @@
// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include <QWidget>
#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<HW::GBA::Core> 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<u32> 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<HW::GBA::Core> m_core;
std::vector<u32> 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<HW::GBA::Core> 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<u32> video_buffer);
private:
GBAWidget* m_widget{};
};

View File

@ -24,6 +24,9 @@
#include "Core/PowerPC/PowerPC.h" #include "Core/PowerPC/PowerPC.h"
#include "Core/State.h" #include "Core/State.h"
#ifdef HAS_LIBMGBA
#include "DolphinQt/GBAWidget.h"
#endif
#include "DolphinQt/QtUtils/QueueOnObject.h" #include "DolphinQt/QtUtils/QueueOnObject.h"
#include "DolphinQt/Settings.h" #include "DolphinQt/Settings.h"
@ -115,6 +118,15 @@ void Host::SetRenderFullFocus(bool focus)
m_render_full_focus = focus; m_render_full_focus = focus;
} }
bool Host::GetGBAFocus()
{
#ifdef HAS_LIBMGBA
return qobject_cast<GBAWidget*>(QApplication::activeWindow()) != nullptr;
#else
return false;
#endif
}
bool Host::GetRenderFullscreen() bool Host::GetRenderFullscreen()
{ {
return m_render_fullscreen; return m_render_fullscreen;
@ -167,7 +179,7 @@ void Host_UpdateTitle(const std::string& title)
bool Host_RendererHasFocus() bool Host_RendererHasFocus()
{ {
return Host::GetInstance()->GetRenderFocus(); return Host::GetInstance()->GetRenderFocus() || Host::GetInstance()->GetGBAFocus();
} }
bool Host_RendererHasFullFocus() bool Host_RendererHasFullFocus()
@ -229,3 +241,10 @@ void Host_TitleChanged()
Discord::UpdateDiscordPresence(); Discord::UpdateDiscordPresence();
#endif #endif
} }
#ifndef HAS_LIBMGBA
std::unique_ptr<GBAHostInterface> Host_CreateGBAHost(std::weak_ptr<HW::GBA::Core> core)
{
return nullptr;
}
#endif

View File

@ -27,6 +27,7 @@ public:
bool GetRenderFocus(); bool GetRenderFocus();
bool GetRenderFullFocus(); bool GetRenderFullFocus();
bool GetRenderFullscreen(); bool GetRenderFullscreen();
bool GetGBAFocus();
void SetMainWindowHandle(void* handle); void SetMainWindowHandle(void* handle);
void SetRenderHandle(void* handle); void SetRenderHandle(void* handle);

View File

@ -7,6 +7,7 @@
#include <cmath> #include <cmath>
#include <thread> #include <thread>
#include <QApplication>
#include <QCoreApplication> #include <QCoreApplication>
#include "AudioCommon/AudioCommon.h" #include "AudioCommon/AudioCommon.h"
@ -28,6 +29,10 @@
#include "Core/State.h" #include "Core/State.h"
#include "Core/WiiUtils.h" #include "Core/WiiUtils.h"
#ifdef HAS_LIBMGBA
#include "DolphinQt/GBAWidget.h"
#endif
#include "DolphinQt/QtUtils/QueueOnObject.h"
#include "DolphinQt/Settings.h" #include "DolphinQt/Settings.h"
#include "InputCommon/ControlReference/ControlReference.h" #include "InputCommon/ControlReference/ControlReference.h"
@ -157,11 +162,13 @@ void HotkeyScheduler::Run()
// Obey window focus (config permitting) before checking hotkeys. // Obey window focus (config permitting) before checking hotkeys.
Core::UpdateInputGate(Config::Get(Config::MAIN_FOCUSED_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. // Everything else on the host thread (controller config dialog) should always get input.
ControlReference::SetInputGate(true); ControlReference::SetInputGate(true);
HotkeyManagerEmu::GetStatus(true);
if (!Core::IsRunningAndStarted()) if (!Core::IsRunningAndStarted())
continue; continue;
@ -520,6 +527,8 @@ void HotkeyScheduler::Run()
Config::SetCurrent(Config::GFX_ENHANCE_POST_SHADER, ""); Config::SetCurrent(Config::GFX_ENHANCE_POST_SHADER, "");
} }
} }
CheckGBAHotkeys();
} }
const auto stereo_depth = Config::Get(Config::GFX_STEREO_DEPTH); const auto stereo_depth = Config::Get(Config::GFX_STEREO_DEPTH);
@ -607,3 +616,42 @@ void HotkeyScheduler::CheckDebuggingHotkeys()
if (IsHotkey(HK_BP_ADD)) if (IsHotkey(HK_BP_ADD))
emit AddBreakpoint(); emit AddBreakpoint();
} }
void HotkeyScheduler::CheckGBAHotkeys()
{
#ifdef HAS_LIBMGBA
GBAWidget* gba_widget = qobject_cast<GBAWidget*>(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
}

View File

@ -65,6 +65,7 @@ signals:
private: private:
void Run(); void Run();
void CheckDebuggingHotkeys(); void CheckDebuggingHotkeys();
void CheckGBAHotkeys();
Common::Flag m_stop_requested; Common::Flag m_stop_requested;
std::thread m_thread; std::thread m_thread;

View File

@ -42,6 +42,7 @@
#include "Core/Core.h" #include "Core/Core.h"
#include "Core/FreeLookManager.h" #include "Core/FreeLookManager.h"
#include "Core/HW/DVD/DVDInterface.h" #include "Core/HW/DVD/DVDInterface.h"
#include "Core/HW/GBAPad.h"
#include "Core/HW/GCKeyboard.h" #include "Core/HW/GCKeyboard.h"
#include "Core/HW/GCPad.h" #include "Core/HW/GCPad.h"
#include "Core/HW/ProcessorInterface.h" #include "Core/HW/ProcessorInterface.h"
@ -306,6 +307,7 @@ void MainWindow::InitControllers()
g_controller_interface.Initialize(GetWindowSystemInfo(windowHandle())); g_controller_interface.Initialize(GetWindowSystemInfo(windowHandle()));
Pad::Initialize(); Pad::Initialize();
Pad::InitializeGBA();
Keyboard::Initialize(); Keyboard::Initialize();
Wiimote::Initialize(Wiimote::InitializeMode::DO_NOT_WAIT_FOR_WIIMOTES); Wiimote::Initialize(Wiimote::InitializeMode::DO_NOT_WAIT_FOR_WIIMOTES);
FreeLook::Initialize(); FreeLook::Initialize();
@ -320,6 +322,9 @@ void MainWindow::InitControllers()
Pad::LoadConfig(); Pad::LoadConfig();
Pad::GetConfig()->SaveConfig(); Pad::GetConfig()->SaveConfig();
Pad::LoadGBAConfig();
Pad::GetGBAConfig()->SaveConfig();
Keyboard::LoadConfig(); Keyboard::LoadConfig();
Keyboard::GetConfig()->SaveConfig(); Keyboard::GetConfig()->SaveConfig();
@ -332,6 +337,7 @@ void MainWindow::ShutdownControllers()
m_hotkey_scheduler->Stop(); m_hotkey_scheduler->Stop();
Pad::Shutdown(); Pad::Shutdown();
Pad::ShutdownGBA();
Keyboard::Shutdown(); Keyboard::Shutdown();
Wiimote::Shutdown(); Wiimote::Shutdown();
HotkeyManagerEmu::Shutdown(); HotkeyManagerEmu::Shutdown();
@ -1685,18 +1691,21 @@ void MainWindow::OnStartRecording()
emit ReadOnlyModeChanged(true); emit ReadOnlyModeChanged(true);
} }
int controllers = 0; Movie::ControllerTypeArray controllers{};
Movie::WiimoteEnabledArray wiimotes{};
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
if (SerialInterface::SIDevice_IsGCController(SConfig::GetInstance().m_SIDevice[i])) if (SConfig::GetInstance().m_SIDevice[i] == SerialInterface::SIDEVICE_GC_GBA_EMULATED)
controllers |= (1 << i); controllers[i] = Movie::ControllerType::GBA;
else if (SerialInterface::SIDevice_IsGCController(SConfig::GetInstance().m_SIDevice[i]))
if (WiimoteCommon::GetSource(i) != WiimoteSource::None) controllers[i] = Movie::ControllerType::GC;
controllers |= (1 << (i + 4)); 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); emit RecordingStatusChanged(true);

View File

@ -8,6 +8,7 @@
#include <QApplication> #include <QApplication>
#include <QClipboard> #include <QClipboard>
#include <QComboBox> #include <QComboBox>
#include <QFileDialog>
#include <QGridLayout> #include <QGridLayout>
#include <QGroupBox> #include <QGroupBox>
#include <QHeaderView> #include <QHeaderView>
@ -35,6 +36,9 @@
#include "Core/Config/NetplaySettings.h" #include "Core/Config/NetplaySettings.h"
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
#include "Core/Core.h" #include "Core/Core.h"
#ifdef HAS_LIBMGBA
#include "Core/HW/GBACore.h"
#endif
#include "Core/NetPlayServer.h" #include "Core/NetPlayServer.h"
#include "Core/SyncIdentifier.h" #include "Core/SyncIdentifier.h"
@ -47,6 +51,7 @@
#include "DolphinQt/QtUtils/RunOnObject.h" #include "DolphinQt/QtUtils/RunOnObject.h"
#include "DolphinQt/Resources.h" #include "DolphinQt/Resources.h"
#include "DolphinQt/Settings.h" #include "DolphinQt/Settings.h"
#include "DolphinQt/Settings/GameCubePane.h"
#include "UICommon/DiscordPresence.h" #include "UICommon/DiscordPresence.h"
#include "UICommon/GameFile.h" #include "UICommon/GameFile.h"
@ -171,6 +176,8 @@ void NetPlayDialog::CreateMainLayout()
m_record_input_action->setCheckable(true); 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 = m_other_menu->addAction(tr("Show Golf Mode Overlay"));
m_golf_mode_overlay_action->setCheckable(true); 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->setDefault(false);
m_game_button->setAutoDefault(false); m_game_button->setAutoDefault(false);
@ -279,6 +286,7 @@ void NetPlayDialog::ConnectWidgets()
m_pad_mapping->exec(); m_pad_mapping->exec();
Settings::Instance().GetNetPlayServer()->SetPadMapping(m_pad_mapping->GetGCPadArray()); 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()); 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_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings);
connect(m_golf_mode_overlay_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_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) 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_data_menu->menuAction()->setVisible(is_hosting);
m_network_menu->menuAction()->setVisible(is_hosting); m_network_menu->menuAction()->setVisible(is_hosting);
m_md5_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_start_button->setHidden(!is_hosting);
m_kick_button->setHidden(!is_hosting); m_kick_button->setHidden(!is_hosting);
m_assign_ports_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")}); {tr("Player"), tr("Game Status"), tr("Ping"), tr("Mapping"), tr("Revision")});
m_players_list->setRowCount(m_player_count); 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<NetPlay::SyncIdentifierComparison, QString> player_status{ static const std::map<NetPlay::SyncIdentifierComparison, QString> player_status{
{NetPlay::SyncIdentifierComparison::SameGame, tr("OK")}, {NetPlay::SyncIdentifierComparison::SameGame, tr("OK")},
{NetPlay::SyncIdentifierComparison::DifferentVersion, tr("Wrong Version")}, {NetPlay::SyncIdentifierComparison::DifferentVersion, tr("Wrong Version")},
@ -599,9 +599,9 @@ void NetPlayDialog::UpdateGUI()
player_status.at(p->game_status) : player_status.at(p->game_status) :
QStringLiteral("?")); QStringLiteral("?"));
auto* ping_item = new QTableWidgetItem(QStringLiteral("%1 ms").arg(p->ping)); auto* ping_item = new QTableWidgetItem(QStringLiteral("%1 ms").arg(p->ping));
auto* mapping_item = new QTableWidgetItem( auto* mapping_item =
QString::fromStdString(get_mapping_string(p, client->GetPadMapping()) + new QTableWidgetItem(QString::fromStdString(NetPlay::GetPlayerMappingString(
get_mapping_string(p, client->GetWiimoteMapping()))); p->pid, client->GetPadMapping(), client->GetGBAConfig(), client->GetWiimoteMapping())));
auto* revision_item = new QTableWidgetItem(QString::fromStdString(p->revision)); auto* revision_item = new QTableWidgetItem(QString::fromStdString(p->revision));
for (auto* item : {name_item, status_item, ping_item, mapping_item, revision_item}) 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"); 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) void NetPlayDialog::GameStatusChanged(bool running)
{ {
QueueOnObject(this, [this, running] { SetOptionsEnabled(!running); }); QueueOnObject(this, [this, running] { SetOptionsEnabled(!running); });
@ -976,6 +990,51 @@ NetPlayDialog::FindGameFile(const NetPlay::SyncIdentifier& sync_identifier,
return nullptr; return nullptr;
} }
std::string NetPlayDialog::FindGBARomPath(const std::array<u8, 20>& hash, std::string_view title,
int device_number)
{
#ifdef HAS_LIBMGBA
auto result = RunOnObject(this, [&, this] {
std::string rom_path;
std::array<u8, 20> rom_hash;
std::string rom_title;
for (size_t i = device_number; i < static_cast<size_t>(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() void NetPlayDialog::LoadSettings()
{ {
const int buffer_size = Config::Get(Config::NETPLAY_BUFFER_SIZE); 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 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 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 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_buffer_size_box->setValue(buffer_size);
m_save_sd_action->setChecked(write_save_sdcard_data); 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_strict_settings_sync_action->setChecked(strict_settings_sync);
m_sync_all_wii_saves_action->setChecked(sync_all_wii_saves); m_sync_all_wii_saves_action->setChecked(sync_all_wii_saves);
m_golf_mode_overlay_action->setChecked(golf_mode_overlay); 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); 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_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_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_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; std::string network_mode;
if (m_fixed_delay_action->isChecked()) if (m_fixed_delay_action->isChecked())

View File

@ -46,6 +46,7 @@ public:
void OnMsgChangeGame(const NetPlay::SyncIdentifier& sync_identifier, void OnMsgChangeGame(const NetPlay::SyncIdentifier& sync_identifier,
const std::string& netplay_name) override; const std::string& netplay_name) override;
void OnMsgChangeGBARom(int pad, const NetPlay::GBAConfig& config) override;
void OnMsgStartGame() override; void OnMsgStartGame() override;
void OnMsgStopGame() override; void OnMsgStopGame() override;
void OnMsgPowerButton() override; void OnMsgPowerButton() override;
@ -68,6 +69,8 @@ public:
std::shared_ptr<const UICommon::GameFile> std::shared_ptr<const UICommon::GameFile>
FindGameFile(const NetPlay::SyncIdentifier& sync_identifier, FindGameFile(const NetPlay::SyncIdentifier& sync_identifier,
NetPlay::SyncIdentifierComparison* found = nullptr) override; NetPlay::SyncIdentifierComparison* found = nullptr) override;
std::string FindGBARomPath(const std::array<u8, 20>& hash, std::string_view title,
int device_number) override;
void LoadSettings(); void LoadSettings();
void SaveSettings(); void SaveSettings();
@ -138,6 +141,7 @@ private:
QAction* m_golf_mode_action; QAction* m_golf_mode_action;
QAction* m_golf_mode_overlay_action; QAction* m_golf_mode_overlay_action;
QAction* m_fixed_delay_action; QAction* m_fixed_delay_action;
QAction* m_hide_remote_gbas_action;
QPushButton* m_quit_button; QPushButton* m_quit_button;
QSplitter* m_splitter; QSplitter* m_splitter;
QActionGroup* m_network_mode_group; QActionGroup* m_network_mode_group;

View File

@ -3,6 +3,7 @@
#include "DolphinQt/NetPlay/PadMappingDialog.h" #include "DolphinQt/NetPlay/PadMappingDialog.h"
#include <QCheckBox>
#include <QComboBox> #include <QComboBox>
#include <QDialogButtonBox> #include <QDialogButtonBox>
#include <QGridLayout> #include <QGridLayout>
@ -31,15 +32,19 @@ void PadMappingDialog::CreateWidgets()
for (unsigned int i = 0; i < m_wii_boxes.size(); i++) for (unsigned int i = 0; i < m_wii_boxes.size(); i++)
{ {
m_gc_boxes[i] = new QComboBox; 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_wii_boxes[i] = new QComboBox;
m_main_layout->addWidget(new QLabel(tr("GC Port %1").arg(i + 1)), 0, i); 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(m_gc_boxes[i], 1, i);
m_main_layout->addWidget(new QLabel(tr("Wii Remote %1").arg(i + 1)), 2, i); #ifdef HAS_LIBMGBA
m_main_layout->addWidget(m_wii_boxes[i], 3, i); 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); setLayout(m_main_layout);
} }
@ -55,6 +60,11 @@ void PadMappingDialog::ConnectWidgets()
&PadMappingDialog::OnMappingChanged); &PadMappingDialog::OnMappingChanged);
} }
} }
for (const auto& checkbox : m_gba_boxes)
{
connect(checkbox, qOverload<int>(&QCheckBox::stateChanged), this,
&PadMappingDialog::OnMappingChanged);
}
} }
int PadMappingDialog::exec() int PadMappingDialog::exec()
@ -64,6 +74,7 @@ int PadMappingDialog::exec()
// Load Settings // Load Settings
m_players = client->GetPlayers(); m_players = client->GetPlayers();
m_pad_mapping = server->GetPadMapping(); m_pad_mapping = server->GetPadMapping();
m_gba_config = server->GetGBAConfig();
m_wii_mapping = server->GetWiimoteMapping(); m_wii_mapping = server->GetWiimoteMapping();
QStringList players; 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(); return QDialog::exec();
} }
@ -101,6 +119,11 @@ NetPlay::PadMappingArray PadMappingDialog::GetGCPadArray()
return m_pad_mapping; return m_pad_mapping;
} }
NetPlay::GBAConfigArray PadMappingDialog::GetGBAArray()
{
return m_gba_config;
}
NetPlay::PadMappingArray PadMappingDialog::GetWiimoteArray() NetPlay::PadMappingArray PadMappingDialog::GetWiimoteArray()
{ {
return m_wii_mapping; return m_wii_mapping;
@ -114,6 +137,7 @@ void PadMappingDialog::OnMappingChanged()
int wii_id = m_wii_boxes[i]->currentIndex(); int wii_id = m_wii_boxes[i]->currentIndex();
m_pad_mapping[i] = gc_id > 0 ? m_players[gc_id - 1]->pid : 0; 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; m_wii_mapping[i] = wii_id > 0 ? m_players[wii_id - 1]->pid : 0;
} }
} }

View File

@ -7,6 +7,7 @@
#include "Core/NetPlayProto.h" #include "Core/NetPlayProto.h"
class QCheckBox;
class QGridLayout; class QGridLayout;
class QComboBox; class QComboBox;
class QDialogButtonBox; class QDialogButtonBox;
@ -25,6 +26,7 @@ public:
int exec() override; int exec() override;
NetPlay::PadMappingArray GetGCPadArray(); NetPlay::PadMappingArray GetGCPadArray();
NetPlay::GBAConfigArray GetGBAArray();
NetPlay::PadMappingArray GetWiimoteArray(); NetPlay::PadMappingArray GetWiimoteArray();
private: private:
@ -34,10 +36,12 @@ private:
void OnMappingChanged(); void OnMappingChanged();
NetPlay::PadMappingArray m_pad_mapping; NetPlay::PadMappingArray m_pad_mapping;
NetPlay::GBAConfigArray m_gba_config;
NetPlay::PadMappingArray m_wii_mapping; NetPlay::PadMappingArray m_wii_mapping;
QGridLayout* m_main_layout; QGridLayout* m_main_layout;
std::array<QComboBox*, 4> m_gc_boxes; std::array<QComboBox*, 4> m_gc_boxes;
std::array<QCheckBox*, 4> m_gba_boxes;
std::array<QComboBox*, 4> m_wii_boxes; std::array<QComboBox*, 4> m_wii_boxes;
std::vector<const NetPlay::Player*> m_players; std::vector<const NetPlay::Player*> m_players;
QDialogButtonBox* m_button_box; QDialogButtonBox* m_button_box;

View File

@ -11,6 +11,7 @@
#include <QGroupBox> #include <QGroupBox>
#include <QInputDialog> #include <QInputDialog>
#include <QLabel> #include <QLabel>
#include <QLineEdit>
#include <QPushButton> #include <QPushButton>
#include <QVBoxLayout> #include <QVBoxLayout>
@ -19,16 +20,19 @@
#include "Common/CommonPaths.h" #include "Common/CommonPaths.h"
#include "Common/Config/Config.h" #include "Common/Config/Config.h"
#include "Common/FileUtil.h" #include "Common/FileUtil.h"
#include "Common/MsgHandler.h"
#include "Core/Config/MainSettings.h" #include "Core/Config/MainSettings.h"
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
#include "Core/Core.h" #include "Core/Core.h"
#include "Core/HW/EXI/EXI.h" #include "Core/HW/EXI/EXI.h"
#include "Core/HW/GCMemcard/GCMemcard.h" #include "Core/HW/GCMemcard/GCMemcard.h"
#include "Core/NetPlayServer.h"
#include "DolphinQt/Config/Mapping/MappingWindow.h" #include "DolphinQt/Config/Mapping/MappingWindow.h"
#include "DolphinQt/GCMemcardManager.h" #include "DolphinQt/GCMemcardManager.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h" #include "DolphinQt/QtUtils/ModalMessageBox.h"
#include "DolphinQt/Settings.h"
enum enum
{ {
@ -123,8 +127,51 @@ void GameCubePane::CreateWidgets()
device_layout->addWidget(m_slot_combos[2], 2, 1); device_layout->addWidget(m_slot_combos[2], 2, 1);
device_layout->addWidget(m_slot_buttons[2], 2, 2); 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(ipl_box);
layout->addWidget(device_box); layout->addWidget(device_box);
#ifdef HAS_LIBMGBA
layout->addWidget(gba_box);
#endif
layout->addStretch(); layout->addStretch();
@ -147,6 +194,44 @@ void GameCubePane::ConnectWidgets()
&GameCubePane::SaveSettings); &GameCubePane::SaveSettings);
connect(m_slot_buttons[i], &QPushButton::clicked, [this, i] { OnConfigPressed(i); }); 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) 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() void GameCubePane::LoadSettings()
{ {
const SConfig& params = SConfig::GetInstance(); const SConfig& params = SConfig::GetInstance();
@ -346,6 +472,16 @@ void GameCubePane::LoadSettings()
m_slot_combos[i]->findData(SConfig::GetInstance().m_EXIDevice[i])); m_slot_combos[i]->findData(SConfig::GetInstance().m_EXIDevice[i]));
UpdateButton(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() void GameCubePane::SaveSettings()
@ -360,6 +496,7 @@ void GameCubePane::SaveSettings()
params.SelectedLanguage = m_language_combo->currentData().toInt(); params.SelectedLanguage = m_language_combo->currentData().toInt();
Config::SetBaseOrCurrent(Config::MAIN_GC_LANGUAGE, 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++) for (int i = 0; i < SLOT_COUNT; i++)
{ {
const auto dev = ExpansionInterface::TEXIDevices(m_slot_combos[i]->currentData().toInt()); const auto dev = ExpansionInterface::TEXIDevices(m_slot_combos[i]->currentData().toInt());
@ -389,5 +526,41 @@ void GameCubePane::SaveSettings()
break; 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());
}
auto server = Settings::Instance().GetNetPlayServer();
if (server)
server->SetGBAConfig(server->GetGBAConfig(), true);
}
#endif
LoadSettings(); 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();
}

View File

@ -3,10 +3,15 @@
#pragma once #pragma once
#include <array>
#include <string>
#include <string_view>
#include <QWidget> #include <QWidget>
class QCheckBox; class QCheckBox;
class QComboBox; class QComboBox;
class QLineEdit;
class QPushButton; class QPushButton;
class GameCubePane : public QWidget class GameCubePane : public QWidget
@ -15,6 +20,8 @@ class GameCubePane : public QWidget
public: public:
explicit GameCubePane(); explicit GameCubePane();
static std::string GetOpenGBARom(std::string_view title);
private: private:
void CreateWidgets(); void CreateWidgets();
void ConnectWidgets(); void ConnectWidgets();
@ -22,12 +29,28 @@ private:
void LoadSettings(); void LoadSettings();
void SaveSettings(); void SaveSettings();
void OnEmulationStateChanged();
void UpdateButton(int slot); void UpdateButton(int slot);
void OnConfigPressed(int slot); void OnConfigPressed(int slot);
void BrowseGBABios();
void BrowseGBARom(size_t index);
void SaveRomPathChanged();
void BrowseGBASaves();
QCheckBox* m_skip_main_menu; QCheckBox* m_skip_main_menu;
QComboBox* m_language_combo; QComboBox* m_language_combo;
QPushButton* m_slot_buttons[3]; QPushButton* m_slot_buttons[3];
QComboBox* m_slot_combos[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<QPushButton*, 4> m_gba_browse_roms;
std::array<QLineEdit*, 4> m_gba_rom_edits;
QPushButton* m_gba_browse_saves;
QLineEdit* m_gba_saves_edit;
}; };

View File

@ -26,7 +26,7 @@ InputConfig::InputConfig(const std::string& ini_name, const std::string& gui_nam
InputConfig::~InputConfig() = default; InputConfig::~InputConfig() = default;
bool InputConfig::LoadConfig(bool isGC) bool InputConfig::LoadConfig(InputClass type)
{ {
IniFile inifile; IniFile inifile;
bool useProfile[MAX_BBMOTES] = {false, false, false, false, false}; bool useProfile[MAX_BBMOTES] = {false, false, false, false, false};
@ -43,16 +43,22 @@ bool InputConfig::LoadConfig(bool isGC)
if (SConfig::GetInstance().GetGameID() != "00000000") if (SConfig::GetInstance().GetGameID() != "00000000")
{ {
std::string type; std::string type_str;
if (isGC) switch (type)
{ {
type = "Pad"; case InputClass::GBA:
path = "Profiles/GCPad/"; type_str = "GBA";
} path = "Profiles/GBA/";
else break;
{ case InputClass::Wii:
type = "Wiimote"; type_str = "Wiimote";
path = "Profiles/Wiimote/"; path = "Profiles/Wiimote/";
break;
case InputClass::GC:
default:
type_str = "Pad";
path = "Profiles/GCPad/";
break;
} }
IniFile game_ini = SConfig::GetInstance().LoadGameIni(); IniFile game_ini = SConfig::GetInstance().LoadGameIni();
@ -60,7 +66,7 @@ bool InputConfig::LoadConfig(bool isGC)
for (int i = 0; i < 4; i++) 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)) if (control_section->Exists(profile_name))
{ {
@ -124,7 +130,7 @@ bool InputConfig::LoadConfig(bool isGC)
} }
#if defined(ANDROID) #if defined(ANDROID)
// Only set for wii pads // 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 Yaw", ir_values[0]);
config.Set("IR/Total Pitch", ir_values[1]); config.Set("IR/Total Pitch", ir_values[1]);

View File

@ -24,7 +24,14 @@ public:
~InputConfig(); ~InputConfig();
bool LoadConfig(bool isGC); enum class InputClass
{
GC,
Wii,
GBA,
};
bool LoadConfig(InputClass type);
void SaveConfig(); void SaveConfig();
template <typename T, typename... Args> template <typename T, typename... Args>

View File

@ -81,9 +81,10 @@ static void InitCustomPaths()
CreateLoadPath(Config::Get(Config::MAIN_LOAD_PATH)); CreateLoadPath(Config::Get(Config::MAIN_LOAD_PATH));
CreateDumpPath(Config::Get(Config::MAIN_DUMP_PATH)); CreateDumpPath(Config::Get(Config::MAIN_DUMP_PATH));
CreateResourcePackPath(Config::Get(Config::MAIN_RESOURCEPACK_PATH)); CreateResourcePackPath(Config::Get(Config::MAIN_RESOURCEPACK_PATH));
const std::string sd_path = Config::Get(Config::MAIN_SD_PATH); File::SetUserPath(F_WIISDCARD_IDX, Config::Get(Config::MAIN_SD_PATH));
if (!sd_path.empty()) File::SetUserPath(F_GBABIOS_IDX, Config::Get(Config::MAIN_GBA_BIOS_PATH));
File::SetUserPath(F_WIISDCARD_IDX, sd_path); File::SetUserPath(D_GBASAVES_IDX, Config::Get(Config::MAIN_GBA_SAVES_PATH));
File::CreateFullPath(File::GetUserPath(D_GBASAVES_IDX));
} }
void Init() void Init()

View File

@ -52,6 +52,10 @@ void Host_YieldToUI()
void Host_TitleChanged() void Host_TitleChanged()
{ {
} }
std::unique_ptr<GBAHostInterface> Host_CreateGBAHost(std::weak_ptr<HW::GBA::Core> core)
{
return nullptr;
}
bool Host_UIBlocksControllerState() bool Host_UIBlocksControllerState()
{ {
return false; return false;

View File

@ -56,3 +56,7 @@ void Host_YieldToUI()
void Host_TitleChanged() void Host_TitleChanged()
{ {
} }
std::unique_ptr<GBAHostInterface> Host_CreateGBAHost(std::weak_ptr<HW::GBA::Core> core)
{
return nullptr;
}

View File

@ -11,6 +11,8 @@
<ExternalsDir>$(DolphinRootDir)Externals\</ExternalsDir> <ExternalsDir>$(DolphinRootDir)Externals\</ExternalsDir>
<SourceDir>$(DolphinRootDir)Source\</SourceDir> <SourceDir>$(DolphinRootDir)Source\</SourceDir>
<CoreDir>$(SourceDir)Core\</CoreDir> <CoreDir>$(SourceDir)Core\</CoreDir>
<CScript Condition="'$(ProgramFiles(x86))' != ''">%windir%\System32\cscript</CScript>
<CScript Condition="'$(ProgramFiles(x86))' == ''">%windir%\Sysnative\cscript</CScript>
<VSPropsDir>$(SourceDir)VSProps\</VSPropsDir> <VSPropsDir>$(SourceDir)VSProps\</VSPropsDir>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View File

@ -30,6 +30,7 @@
<ExternalIncludePath>$(ExternalsDir)libpng;$(ExternalIncludePath)</ExternalIncludePath> <ExternalIncludePath>$(ExternalsDir)libpng;$(ExternalIncludePath)</ExternalIncludePath>
<ExternalIncludePath>$(ExternalsDir)libusb\libusb;$(ExternalIncludePath)</ExternalIncludePath> <ExternalIncludePath>$(ExternalsDir)libusb\libusb;$(ExternalIncludePath)</ExternalIncludePath>
<ExternalIncludePath>$(ExternalsDir)LZO;$(ExternalIncludePath)</ExternalIncludePath> <ExternalIncludePath>$(ExternalsDir)LZO;$(ExternalIncludePath)</ExternalIncludePath>
<ExternalIncludePath>$(ExternalsDir)mGBA\mgba\include;$(ExternalIncludePath)</ExternalIncludePath>
<ExternalIncludePath>$(ExternalsDir)miniupnpc\src;$(ExternalIncludePath)</ExternalIncludePath> <ExternalIncludePath>$(ExternalsDir)miniupnpc\src;$(ExternalIncludePath)</ExternalIncludePath>
<ExternalIncludePath>$(ExternalsDir)minizip;$(ExternalIncludePath)</ExternalIncludePath> <ExternalIncludePath>$(ExternalsDir)minizip;$(ExternalIncludePath)</ExternalIncludePath>
<ExternalIncludePath>$(ExternalsDir)mbedtls\include;$(ExternalIncludePath)</ExternalIncludePath> <ExternalIncludePath>$(ExternalsDir)mbedtls\include;$(ExternalIncludePath)</ExternalIncludePath>
@ -76,6 +77,7 @@
<PreprocessorDefinitions>USE_GDBSTUB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>USE_GDBSTUB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(Platform)'!='ARM64'">HAS_OPENGL;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions Condition="'$(Platform)'!='ARM64'">HAS_OPENGL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>HAS_VULKAN;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>HAS_VULKAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>HAS_LIBMGBA;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<!-- <!--
Make sure we include a clean version of windows.h. Make sure we include a clean version of windows.h.
--> -->

View File

@ -73,6 +73,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "liblzma", "..\Externals\lib
EndProject EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zstd", "..\Externals\zstd\zstd.vcxproj", "{1BEA10F3-80CE-4BC4-9331-5769372CDF99}" Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zstd", "..\Externals\zstd\zstd.vcxproj", "{1BEA10F3-80CE-4BC4-9331-5769372CDF99}"
EndProject EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mgba", "..\Externals\mGBA\mgba.vcxproj", "{864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64 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|ARM64.Build.0 = Release|ARM64
{1BEA10F3-80CE-4BC4-9331-5769372CDF99}.Release|x64.ActiveCfg = Release|x64 {1BEA10F3-80CE-4BC4-9331-5769372CDF99}.Release|x64.ActiveCfg = Release|x64
{1BEA10F3-80CE-4BC4-9331-5769372CDF99}.Release|x64.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -379,6 +389,7 @@ Global
{1D8C51D2-FFA4-418E-B183-9F42B6A6717E} = {87ADDFF9-5768-4DA2-A33B-2477593D6677} {1D8C51D2-FFA4-418E-B183-9F42B6A6717E} = {87ADDFF9-5768-4DA2-A33B-2477593D6677}
{055A775F-B4F5-4970-9240-F6CF7661F37B} = {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} {1BEA10F3-80CE-4BC4-9331-5769372CDF99} = {87ADDFF9-5768-4DA2-A33B-2477593D6677}
{864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6} = {87ADDFF9-5768-4DA2-A33B-2477593D6677}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {64B0A343-3B94-4522-9C24-6937FE5EFB22} SolutionGuid = {64B0A343-3B94-4522-9C24-6937FE5EFB22}