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
.DS_Store
# Ignore autogenerated source files
Externals/mGBA/version.c
Source/Core/Common/scmrev.h
# Ignore files output by build
/[Bb]uild*/

5
.gitmodules vendored
View File

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

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_VULKAN "Enables vulkan video backend" ON)
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence, show the current game on Discord" ON)
option(USE_MGBA "Enables GBA controllers emulation using libmgba" ON)
# Maintainers: if you consider blanket disabling this for your users, please
# consider the following points:
@ -826,6 +827,14 @@ if(USE_DISCORD_PRESENCE)
include_directories(Externals/discord-rpc/include)
endif()
if(NOT ENABLE_QT)
set(USE_MGBA 0)
endif()
if(USE_MGBA)
message(STATUS "Using static libmgba from Externals")
add_subdirectory(Externals/mGBA)
endif()
find_package(SYSTEMD)
if(SYSTEMD_FOUND)
message(STATUS "libsystemd found, enabling traversal server watchdog support")

View File

@ -58,6 +58,9 @@
<ProjectReference Include="$(ExternalsDir)mbedtls\mbedTLS.vcxproj">
<Project>{bdb6578b-0691-4e80-a46c-df21639fd3b8}</Project>
</ProjectReference>
<ProjectReference Include="$(ExternalsDir)mGBA\mgba.vcxproj">
<Project>{864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6}</Project>
</ProjectReference>
<ProjectReference Include="$(ExternalsDir)miniupnpc\miniupnpc.vcxproj">
<Project>{31643fdb-1bb8-4965-9de7-000fc88d35ae}</Project>
</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)
- [LZO](http://www.oberhumer.com/opensource/lzo/):
[GPLv2+](http://www.oberhumer.com/opensource/gpl.html)
- [mGBA](http://mgba.io)
[MPL 2.0](https://github.com/mgba-emu/mgba/blob/master/LICENSE)
- [MiniUPnPc](http://miniupnp.free.fr/):
[3-clause BSD](https://github.com/miniupnp/miniupnp/blob/master/miniupnpc/LICENSE)
- [Microsoft Visual C++ Runtime Library](http://www.microsoft.com/en-us/download/details.aspx?id=40784):

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());
}
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)
{
// 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()
{
// This needs to be called after AudioInterface::Init where input sample rates are set
// This needs to be called after AudioInterface::Init and SerialInterface::Init (for GBA devices)
// where input sample rates are set
UpdateSoundStream();
SetSoundStreamRunning(true);

View File

@ -47,6 +47,8 @@ void Mixer::DoState(PointerWrap& p)
m_dma_mixer.DoState(p);
m_streaming_mixer.DoState(p);
m_wiimote_speaker_mixer.DoState(p);
for (auto& mixer : m_gba_mixers)
mixer.DoState(p);
}
// Executed from sound stream thread
@ -93,20 +95,24 @@ unsigned int Mixer::MixerFifo::Mix(short* samples, unsigned int numSamples,
s32 lvolume = m_LVolume.load();
s32 rvolume = m_RVolume.load();
const auto read_buffer = [this](auto index) {
return m_little_endian ? m_buffer[index] : Common::swap16(m_buffer[index]);
};
// TODO: consider a higher-quality resampling algorithm.
for (; currentSample < numSamples * 2 && ((indexW - indexR) & INDEX_MASK) > 2; currentSample += 2)
{
u32 indexR2 = indexR + 2; // next sample
s16 l1 = Common::swap16(m_buffer[indexR & INDEX_MASK]); // current
s16 l2 = Common::swap16(m_buffer[indexR2 & INDEX_MASK]); // next
s16 l1 = read_buffer(indexR & INDEX_MASK); // current
s16 l2 = read_buffer(indexR2 & INDEX_MASK); // next
int sampleL = ((l1 << 16) + (l2 - l1) * (u16)m_frac) >> 16;
sampleL = (sampleL * lvolume) >> 8;
sampleL += samples[currentSample + 1];
samples[currentSample + 1] = std::clamp(sampleL, -32767, 32767);
s16 r1 = Common::swap16(m_buffer[(indexR + 1) & INDEX_MASK]); // current
s16 r2 = Common::swap16(m_buffer[(indexR2 + 1) & INDEX_MASK]); // next
s16 r1 = read_buffer((indexR + 1) & INDEX_MASK); // current
s16 r2 = read_buffer((indexR2 + 1) & INDEX_MASK); // next
int sampleR = ((r1 << 16) + (r2 - r1) * (u16)m_frac) >> 16;
sampleR = (sampleR * rvolume) >> 8;
sampleR += samples[currentSample];
@ -122,8 +128,8 @@ unsigned int Mixer::MixerFifo::Mix(short* samples, unsigned int numSamples,
// Padding
short s[2];
s[0] = Common::swap16(m_buffer[(indexR - 1) & INDEX_MASK]);
s[1] = Common::swap16(m_buffer[(indexR - 2) & INDEX_MASK]);
s[0] = read_buffer((indexR - 1) & INDEX_MASK);
s[1] = read_buffer((indexR - 2) & INDEX_MASK);
s[0] = (s[0] * rvolume) >> 8;
s[1] = (s[1] * lvolume) >> 8;
for (; currentSample < numSamples * 2; currentSample += 2)
@ -158,6 +164,8 @@ unsigned int Mixer::Mix(short* samples, unsigned int num_samples)
m_dma_mixer.Mix(m_scratch_buffer.data(), available_samples, false);
m_streaming_mixer.Mix(m_scratch_buffer.data(), available_samples, false);
m_wiimote_speaker_mixer.Mix(m_scratch_buffer.data(), available_samples, false);
for (auto& mixer : m_gba_mixers)
mixer.Mix(m_scratch_buffer.data(), available_samples, false);
if (!m_is_stretching)
{
@ -172,6 +180,8 @@ unsigned int Mixer::Mix(short* samples, unsigned int num_samples)
m_dma_mixer.Mix(samples, num_samples, true);
m_streaming_mixer.Mix(samples, num_samples, true);
m_wiimote_speaker_mixer.Mix(samples, num_samples, true);
for (auto& mixer : m_gba_mixers)
mixer.Mix(samples, num_samples, true);
m_is_stretching = false;
}
@ -258,14 +268,19 @@ void Mixer::PushWiimoteSpeakerSamples(const short* samples, unsigned int num_sam
for (unsigned int i = 0; i < num_samples; ++i)
{
samples_stereo[i * 2] = Common::swap16(samples[i]);
samples_stereo[i * 2 + 1] = Common::swap16(samples[i]);
samples_stereo[i * 2] = samples[i];
samples_stereo[i * 2 + 1] = samples[i];
}
m_wiimote_speaker_mixer.PushSamples(samples_stereo, num_samples);
}
}
void Mixer::PushGBASamples(int device_number, const short* samples, unsigned int num_samples)
{
m_gba_mixers[device_number].PushSamples(samples, num_samples);
}
void Mixer::SetDMAInputSampleRate(unsigned int rate)
{
m_dma_mixer.SetInputSampleRate(rate);
@ -276,6 +291,11 @@ void Mixer::SetStreamInputSampleRate(unsigned int rate)
m_streaming_mixer.SetInputSampleRate(rate);
}
void Mixer::SetGBAInputSampleRates(int device_number, unsigned int rate)
{
m_gba_mixers[device_number].SetInputSampleRate(rate);
}
void Mixer::SetStreamingVolume(unsigned int lvolume, unsigned int rvolume)
{
m_streaming_mixer.SetVolume(lvolume, rvolume);
@ -286,6 +306,11 @@ void Mixer::SetWiimoteSpeakerVolume(unsigned int lvolume, unsigned int rvolume)
m_wiimote_speaker_mixer.SetVolume(lvolume, rvolume);
}
void Mixer::SetGBAVolume(int device_number, unsigned int lvolume, unsigned int rvolume)
{
m_gba_mixers[device_number].SetVolume(lvolume, rvolume);
}
void Mixer::StartLogDTKAudio(const std::string& filename)
{
if (!m_log_dtk_audio)

View File

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

View File

@ -34,6 +34,7 @@
// Subdirs in the User dir returned by GetUserPath(D_USER_IDX)
#define GC_USER_DIR "GC"
#define GBA_USER_DIR "GBA"
#define WII_USER_DIR "Wii"
#define CONFIG_DIR "Config"
#define GAMESETTINGS_DIR "GameSettings"
@ -61,6 +62,7 @@
#define RESOURCES_DIR "Resources"
#define THEMES_DIR "Themes"
#define STYLES_DIR "Styles"
#define GBASAVES_DIR "Saves"
#define ANAGLYPH_DIR "Anaglyph"
#define PASSIVE_DIR "Passive"
#define PIPES_DIR "Pipes"
@ -119,6 +121,9 @@
#define GC_MEMCARDB "MemoryCardB"
#define GC_MEMCARD_NETPLAY "NetPlayTemp"
#define GBA_BIOS "gba_bios.bin"
#define GBA_SAVE_NETPLAY "NetPlayTemp"
#define WII_STATE "state.dat"
#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[D_MEMORYWATCHER_IDX] + MEMORYWATCHER_SOCKET;
s_user_paths[D_GBAUSER_IDX] = s_user_paths[D_USER_IDX] + GBA_USER_DIR DIR_SEP;
s_user_paths[D_GBASAVES_IDX] = s_user_paths[D_GBAUSER_IDX] + GBASAVES_DIR DIR_SEP;
s_user_paths[F_GBABIOS_IDX] = s_user_paths[D_GBAUSER_IDX] + GBA_BIOS;
// The shader cache has moved to the cache directory, so remove the old one.
// TODO: remove that someday.
File::DeleteDirRecursively(s_user_paths[D_USER_IDX] + SHADERCACHE_LEGACY_DIR DIR_SEP);

View File

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

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="..\..\VSProps\Base.props" />
</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.
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.cpp
HW/EXI/EXI.h
HW/GBAPad.cpp
HW/GBAPad.h
HW/GBAPadEmu.cpp
HW/GBAPadEmu.h
HW/GCKeyboard.cpp
HW/GCKeyboard.h
HW/GCKeyboardEmu.cpp
@ -614,6 +618,17 @@ if(ENABLE_VULKAN)
target_link_libraries(core PUBLIC videovulkan)
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)
target_sources(core PRIVATE
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_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
const Info<bool> MAIN_NETWORK_SSL_DUMP_READ{{System::Main, "Network", "SSLDumpRead"}, false};

View File

@ -3,6 +3,7 @@
#pragma once
#include <array>
#include <string>
#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_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
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"};
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_HIDE_REMOTE_GBAS{{System::Main, "NetPlay", "HideRemoteGBAs"}, false};
} // 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<bool> NETPLAY_SYNC_ALL_WII_SAVES;
extern const Info<bool> NETPLAY_GOLF_MODE_OVERLAY;
extern const Info<bool> NETPLAY_HIDE_REMOTE_GBAS;
} // namespace Config

View File

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

View File

@ -133,6 +133,11 @@ public:
layer->Set(Config::SESSION_GCI_FOLDER_CURRENT_GAME_ONLY, true);
}
for (size_t i = 0; i < m_settings.m_GBARomPaths.size(); ++i)
{
layer->Set(Config::MAIN_GBA_ROM_PATHS[i], m_settings.m_GBARomPaths[i]);
}
// Check To Override Client's Cheat Codes
if (m_settings.m_SyncCodes && !m_settings.m_IsHosting)
{

View File

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

View File

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

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();
// Load the saved controller config
s_config.LoadConfig(true);
s_config.LoadConfig(InputConfig::InputClass::GC);
}
void LoadConfig()
{
s_config.LoadConfig(true);
s_config.LoadConfig(InputConfig::InputClass::GC);
}
ControllerEmu::ControlGroup* GetGroup(int port, KeyboardGroup group)

View File

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

View File

@ -209,6 +209,7 @@ union USIEXIClockCount
static CoreTiming::EventType* s_change_device_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
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);
}
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()
{
RegisterEvents();
for (int i = 0; i < MAX_SI_CHANNELS; i++)
{
s_channel[i].out.hex = 0;
@ -382,7 +419,11 @@ void Init()
{
s_desired_device_types[i] = SIDEVICE_NONE;
if (Movie::IsUsingPad(i))
if (Movie::IsUsingGBA(i))
{
s_desired_device_types[i] = SIDEVICE_GC_GBA_EMULATED;
}
else if (Movie::IsUsingPad(i))
{
SIDevices current = SConfig::GetInstance().m_SIDevice[i];
// GC pad-compatible devices can be used for both playing and recording
@ -415,9 +456,6 @@ void Init()
// s_exi_clock_count.LOCK = 1;
s_si_buffer = {};
s_change_device_event = CoreTiming::RegisterEvent("ChangeSIDevice", ChangeDeviceCallback);
s_tranfer_pending_event = CoreTiming::RegisterEvent("SITransferPending", RunSIBuffer);
}
void Shutdown()
@ -673,7 +711,7 @@ void UpdateDevices()
SIDevices GetDeviceType(int channel)
{
if (channel < 0 || channel > 3)
if (channel < 0 || channel >= MAX_SI_CHANNELS || !s_channel[channel].device)
return SIDEVICE_NONE;
return s_channel[channel].device->GetDeviceType();

View File

@ -30,6 +30,9 @@ void DoState(PointerWrap& p);
void RegisterMMIO(MMIO::Mapping* mmio, u32 base);
void ScheduleEvent(int device_number, s64 cycles_into_future, u64 userdata = 0);
void RemoveEvent(int device_number);
void UpdateDevices();
void RemoveDevice(int device_number);

View File

@ -13,16 +13,26 @@
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"
#include "Core/HW/SI/SI_DeviceDanceMat.h"
#include "Core/HW/SI/SI_DeviceGBA.h"
#ifdef HAS_LIBMGBA
#include "Core/HW/SI/SI_DeviceGBAEmu.h"
#endif
#include "Core/HW/SI/SI_DeviceGCAdapter.h"
#include "Core/HW/SI/SI_DeviceGCController.h"
#include "Core/HW/SI/SI_DeviceGCSteeringWheel.h"
#include "Core/HW/SI/SI_DeviceKeyboard.h"
#include "Core/HW/SI/SI_DeviceNull.h"
#include "Core/HW/SystemTimers.h"
namespace SerialInterface
{
constexpr u64 GC_BITS_PER_SECOND = 200000;
constexpr u64 GBA_BITS_PER_SECOND = 250000;
constexpr u64 GC_STOP_BIT_NS = 6500;
constexpr u64 GBA_STOP_BIT_NS = 14000;
std::ostream& operator<<(std::ostream& stream, SIDevices device)
{
stream << static_cast<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
// 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
@ -139,6 +191,14 @@ std::unique_ptr<ISIDevice> SIDevice_Create(const SIDevices device, const int por
case SIDEVICE_GC_GBA:
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:
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
};
// Commands
enum class EBufferCommands : u8
{
CMD_STATUS = 0x00,
CMD_READ_GBA = 0x14,
CMD_WRITE_GBA = 0x15,
CMD_DIRECT = 0x40,
CMD_ORIGIN = 0x41,
CMD_RECALIBRATE = 0x42,
CMD_DIRECT_KB = 0x54,
CMD_RESET = 0xFF
};
enum class EDirectCommands : u8
{
CMD_FORCE = 0x30,
CMD_WRITE = 0x40,
CMD_POLL = 0x54
};
union UCommand
{
u32 hex = 0;
struct
{
u32 parameter1 : 8;
u32 parameter2 : 8;
u32 command : 8;
u32 : 8;
};
UCommand() = default;
UCommand(u32 value) : hex{value} {}
};
// For configuration use, since some devices can have the same SI Device ID
enum SIDevices : int
{
@ -59,6 +93,7 @@ enum SIDevices : int
// It's kept here so that values below will stay constant.
SIDEVICE_AM_BASEBOARD,
SIDEVICE_WIIU_ADAPTER,
SIDEVICE_GC_GBA_EMULATED,
// Not a valid device. Used for checking whether enum values are valid.
SIDEVICE_COUNT,
};
@ -88,11 +123,15 @@ public:
// Savestate support
virtual void DoState(PointerWrap& p);
// Schedulable event
virtual void OnEvent(u64 userdata, s64 cycles_late);
protected:
int m_device_number;
SIDevices m_device_type;
};
int SIDevice_GetGBATransferTime(EBufferCommands cmd);
bool SIDevice_IsGCController(SIDevices type);
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)
{
// Read the command
EBufferCommands command = static_cast<EBufferCommands>(buffer[0]);
if (command == CMD_RESET)
const auto command = static_cast<EBufferCommands>(buffer[0]);
if (command == EBufferCommands::CMD_STATUS)
{
ISIDevice::RunBuffer(buffer, request_length);

View File

@ -34,60 +34,10 @@ int s_num_connected;
Common::Flag s_server_running;
} // namespace
enum EJoybusCmds
{
CMD_RESET = 0xff,
CMD_STATUS = 0x00,
CMD_READ = 0x14,
CMD_WRITE = 0x15
};
constexpr auto GC_BITS_PER_SECOND = 200000;
constexpr auto GBA_BITS_PER_SECOND = 250000;
constexpr auto GC_STOP_BIT_NS = 6500;
constexpr auto GBA_STOP_BIT_NS = 14000;
constexpr auto SEND_MAX_SIZE = 5, RECV_MAX_SIZE = 5;
// --- 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()
{
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++)
send_data[i] = si_buffer[i];
u8 cmd = send_data[0];
const auto cmd = static_cast<EBufferCommands>(send_data[0]);
sf::Socket::Status status;
if (cmd == CMD_WRITE)
if (cmd == EBufferCommands::CMD_WRITE_GBA)
status = m_client->send(send_data.data(), send_data.size());
else
status = m_client->send(send_data.data(), 1);
@ -334,7 +284,7 @@ int CSIDevice_GBA::RunBuffer(u8* buffer, int request_length)
return -1;
}
m_last_cmd = buffer[0];
m_last_cmd = static_cast<EBufferCommands>(buffer[0]);
m_timestamp_sent = CoreTiming::GetTicks();
m_next_action = NextAction::WaitTransferTime;
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);
// Tell SI to ask again after TransferInterval() cycles
if (GetTransferTime(m_last_cmd) > elapsed_time)
if (SIDevice_GetGBATransferTime(m_last_cmd) > elapsed_time)
return 0;
m_next_action = NextAction::ReceiveResponse;
[[fallthrough]];
@ -355,11 +305,11 @@ int CSIDevice_GBA::RunBuffer(u8* buffer, int request_length)
u8 bytes = 1;
switch (m_last_cmd)
{
case CMD_RESET:
case CMD_STATUS:
case EBufferCommands::CMD_RESET:
case EBufferCommands::CMD_STATUS:
bytes = 3;
break;
case CMD_READ:
case EBufferCommands::CMD_READ_GBA:
bytes = 5;
break;
default:
@ -372,7 +322,8 @@ int CSIDevice_GBA::RunBuffer(u8* buffer, int request_length)
return -1;
#ifdef _DEBUG
const Common::Log::LOG_LEVELS log_level =
(m_last_cmd == CMD_STATUS || m_last_cmd == CMD_RESET) ? Common::Log::LERROR :
(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}] ({})",
@ -390,7 +341,7 @@ int CSIDevice_GBA::RunBuffer(u8* buffer, int request_length)
int CSIDevice_GBA::TransferInterval()
{
return GetTransferTime(m_last_cmd);
return SIDevice_GetGBATransferTime(m_last_cmd);
}
bool CSIDevice_GBA::GetData(u32& hi, u32& low)

View File

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

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);
}
HandleMoviePadStatus(&pad_status);
HandleMoviePadStatus(m_device_number, &pad_status);
// Our GCAdapter code sets PAD_GET_ORIGIN when a new device has been connected.
// Watch for this to calibrate real controllers on connection.

View File

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

View File

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

View File

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

View File

@ -17,21 +17,6 @@ public:
void SendCommand(u32 command, u8 poll) override;
private:
// Commands
enum EBufferCommands
{
CMD_RESET = 0x00,
CMD_ORIGIN = 0x41,
CMD_RECALIBRATE = 0x42,
CMD_ID = 0xff,
};
enum EDirectCommands
{
CMD_FORCE = 0x30,
CMD_WRITE = 0x40
};
enum class ForceCommandType : u8
{
MotorOn = 0x03,

View File

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

View File

@ -32,34 +32,6 @@ public:
void DoState(PointerWrap& p) override;
protected:
// Commands
enum EBufferCommands
{
CMD_RESET = 0x00,
CMD_DIRECT = 0x54,
CMD_ID = 0xff,
};
enum EDirectCommands
{
CMD_WRITE = 0x40,
CMD_POLL = 0x54
};
union UCommand
{
u32 hex = 0;
struct
{
u32 parameter1 : 8;
u32 parameter2 : 8;
u32 command : 8;
u32 : 8;
};
UCommand() = default;
UCommand(u32 value) : hex{value} {}
};
// PADAnalogMode
u8 m_mode = 0;

View File

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

View File

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

View File

@ -23,7 +23,7 @@
#include "InputCommon/GCPadStatus.h"
// 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("Change Disc"),
_trans("Eject Disc"),
@ -178,6 +178,19 @@ constexpr std::array<const char*, 126> s_hotkey_labels{{
_trans("Undo Save State"),
_trans("Save State"),
_trans("Load State"),
_trans("Load ROM"),
_trans("Unload ROM"),
_trans("Reset"),
_trans("Volume Down"),
_trans("Volume Up"),
_trans("Volume Toggle Mute"),
_trans("1x"),
_trans("2x"),
_trans("3x"),
_trans("4x"),
}};
// clang-format on
static_assert(NUM_HOTKEYS == s_hotkey_labels.size(), "Wrong count of hotkey_labels");
@ -195,10 +208,10 @@ InputConfig* GetConfig()
return &s_config;
}
void GetStatus()
void GetStatus(bool ignore_focus)
{
// Get input
static_cast<HotkeyManager*>(s_config.GetController(0))->GetInput(&s_hotkey);
static_cast<HotkeyManager*>(s_config.GetController(0))->GetInput(&s_hotkey, ignore_focus);
}
bool IsEnabled()
@ -285,7 +298,7 @@ void Initialize()
void LoadConfig()
{
s_config.LoadConfig(true);
s_config.LoadConfig(InputConfig::InputClass::GC);
LoadLegacyConfig(s_config.GetController(0));
}
@ -307,6 +320,7 @@ struct HotkeyGroupInfo
const char* name;
Hotkey first;
Hotkey last;
bool ignore_focus = false;
};
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("Select State"), HK_SELECT_STATE_SLOT_1, HK_SELECT_STATE_SLOT_10},
{_trans("Load Last State"), HK_LOAD_LAST_STATE_1, HK_LOAD_LAST_STATE_10},
{_trans("Other State Hotkeys"), HK_SAVE_FIRST_STATE, HK_LOAD_STATE_FILE}}};
{_trans("Other State Hotkeys"), HK_SAVE_FIRST_STATE, HK_LOAD_STATE_FILE},
{_trans("GBA Core"), HK_GBA_LOAD, HK_GBA_RESET, true},
{_trans("GBA Volume"), HK_GBA_VOLUME_DOWN, HK_GBA_TOGGLE_MUTE, true},
{_trans("GBA Window Size"), HK_GBA_1X, HK_GBA_4X, true}}};
HotkeyManager::HotkeyManager()
{
@ -359,11 +376,14 @@ std::string HotkeyManager::GetName() const
return "Hotkeys";
}
void HotkeyManager::GetInput(HotkeyStatus* const kb)
void HotkeyManager::GetInput(HotkeyStatus* kb, bool ignore_focus)
{
const auto lock = GetStateLock();
for (std::size_t group = 0; group < s_groups_info.size(); group++)
{
if (s_groups_info[group].ignore_focus != ignore_focus)
continue;
const int group_count = (s_groups_info[group].last - s_groups_info[group].first) + 1;
std::vector<u32> bitmasks(group_count);
for (size_t key = 0; key < bitmasks.size(); key++)
@ -441,4 +461,30 @@ void HotkeyManager::LoadDefaults(const ControllerInterface& ciface)
}
set_key_expression(HK_UNDO_LOAD_STATE, "F12");
set_key_expression(HK_UNDO_SAVE_STATE, hotkey_string({"Shift", "F12"}));
// GBA
set_key_expression(HK_GBA_LOAD, hotkey_string({"`Shift`", "`O`"}));
set_key_expression(HK_GBA_UNLOAD, hotkey_string({"`Shift`", "`W`"}));
set_key_expression(HK_GBA_RESET, hotkey_string({"`Shift`", "`R`"}));
#ifdef _WIN32
set_key_expression(HK_GBA_VOLUME_DOWN, "`SUBTRACT`");
set_key_expression(HK_GBA_VOLUME_UP, "`ADD`");
#else
set_key_expression(HK_GBA_VOLUME_DOWN, "`KP_Subtract`");
set_key_expression(HK_GBA_VOLUME_UP, "`KP_Add`");
#endif
set_key_expression(HK_GBA_TOGGLE_MUTE, "`M`");
#ifdef _WIN32
set_key_expression(HK_GBA_1X, "`NUMPAD1`");
set_key_expression(HK_GBA_2X, "`NUMPAD2`");
set_key_expression(HK_GBA_3X, "`NUMPAD3`");
set_key_expression(HK_GBA_4X, "`NUMPAD4`");
#else
set_key_expression(HK_GBA_1X, "`KP_1`");
set_key_expression(HK_GBA_2X, "`KP_2`");
set_key_expression(HK_GBA_3X, "`KP_3`");
set_key_expression(HK_GBA_4X, "`KP_4`");
#endif
}

View File

@ -164,6 +164,19 @@ enum Hotkey
HK_SAVE_STATE_FILE,
HK_LOAD_STATE_FILE,
HK_GBA_LOAD,
HK_GBA_UNLOAD,
HK_GBA_RESET,
HK_GBA_VOLUME_DOWN,
HK_GBA_VOLUME_UP,
HK_GBA_TOGGLE_MUTE,
HK_GBA_1X,
HK_GBA_2X,
HK_GBA_3X,
HK_GBA_4X,
NUM_HOTKEYS,
};
@ -192,6 +205,9 @@ enum HotkeyGroup : int
HKGP_SELECT_STATE,
HKGP_LOAD_LAST_STATE,
HKGP_STATE_MISC,
HKGP_GBA_CORE,
HKGP_GBA_VOLUME,
HKGP_GBA_SIZE,
NUM_HOTKEY_GROUPS,
};
@ -208,7 +224,7 @@ public:
HotkeyManager();
~HotkeyManager();
void GetInput(HotkeyStatus* const hk);
void GetInput(HotkeyStatus* hk, bool ignore_focus);
std::string GetName() const override;
ControllerEmu::ControlGroup* GetHotkeyGroup(HotkeyGroup group) const;
int FindGroupByID(int id) const;
@ -228,7 +244,7 @@ void LoadConfig();
InputConfig* GetConfig();
ControllerEmu::ControlGroup* GetHotkeyGroup(HotkeyGroup group);
void GetStatus();
void GetStatus(bool ignore_focus);
bool IsEnabled();
void Enable(bool enable_toggle);
bool IsPressed(int Id, bool held);

View File

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

View File

@ -39,6 +39,15 @@ enum PlayMode
MODE_PLAYING
};
enum class ControllerType
{
None = 0,
GC,
GBA,
};
using ControllerTypeArray = std::array<ControllerType, 4>;
using WiimoteEnabledArray = std::array<bool, 4>;
// GameCube Controller State
#pragma pack(push, 1)
struct ControllerState
@ -116,7 +125,8 @@ struct DTMHeader
u8 reserved3;
bool bFollowBranch;
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<u8, 20> revision; // Git hash
u32 DSPiromHash;
@ -163,12 +173,14 @@ bool IsNetPlayRecording();
bool IsUsingPad(int controller);
bool IsUsingWiimote(int wiimote);
bool IsUsingBongo(int controller);
bool IsUsingGBA(int controller);
void ChangePads();
void ChangeWiiPads(bool instantly = false);
void SetReadOnly(bool bEnabled);
bool BeginRecordingInput(int controllers);
bool BeginRecordingInput(const ControllerTypeArray& controllers,
const WiimoteEnabledArray& wiimotes);
void RecordInput(const GCPadStatus* PadStatus, int controllerID);
void RecordWiimote(int wiimote, const u8* data, u8 size);

View File

@ -33,12 +33,18 @@
#include "Common/Version.h"
#include "Core/ActionReplay.h"
#include "Core/Config/MainSettings.h"
#include "Core/Config/NetplaySettings.h"
#include "Core/Config/SessionSettings.h"
#include "Core/ConfigManager.h"
#include "Core/GeckoCode.h"
#include "Core/HW/EXI/EXI_DeviceIPL.h"
#ifdef HAS_LIBMGBA
#include "Core/HW/GBACore.h"
#endif
#include "Core/HW/GBAPad.h"
#include "Core/HW/GCMemcard/GCMemcard.h"
#include "Core/HW/GCPad.h"
#include "Core/HW/SI/SI.h"
#include "Core/HW/SI/SI_Device.h"
#include "Core/HW/SI/SI_DeviceGCController.h"
@ -71,7 +77,7 @@ static std::mutex crit_netplay_client;
static NetPlayClient* netplay_client = nullptr;
static std::unique_ptr<IOS::HLE::FS::FileSystem> s_wii_sync_fs;
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
NetPlayClient::~NetPlayClient()
@ -463,6 +469,35 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
}
break;
case NP_MSG_GBA_CONFIG:
{
for (size_t i = 0; i < m_gba_config.size(); ++i)
{
auto& config = m_gba_config[i];
const auto old_config = config;
packet >> config.enabled >> config.has_rom >> config.title;
for (auto& data : config.hash)
packet >> data;
if (std::tie(config.has_rom, config.title, config.hash) !=
std::tie(old_config.has_rom, old_config.title, old_config.hash))
{
m_dialog->OnMsgChangeGBARom(static_cast<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:
{
for (PlayerId& mapping : m_wiimote_map)
@ -482,8 +517,12 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
packet >> map;
GCPadStatus pad;
packet >> pad.button >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >>
pad.substickX >> pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected;
packet >> pad.button;
if (!m_gba_config.at(map).enabled)
{
packet >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> pad.substickX >>
pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected;
}
// Trusting server for good map value (>=0 && <4)
// add to pad buffer
@ -501,8 +540,12 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
packet >> map;
GCPadStatus pad;
packet >> pad.button >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >>
pad.substickX >> pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected;
packet >> pad.button;
if (!m_gba_config.at(map).enabled)
{
packet >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> pad.substickX >>
pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected;
}
// Trusting server for good map value (>=0 && <4)
// write to last status
@ -602,14 +645,7 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
// update gui
m_dialog->OnMsgChangeGame(m_selected_game, netplay_name);
sf::Packet game_status_packet;
game_status_packet << static_cast<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);
SendGameStatus();
sf::Packet client_capabilities_packet;
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_UseFMA;
packet >> m_net_settings.m_HideRemoteGBAs;
m_net_settings.m_IsHosting = m_local_player->IsHost();
m_net_settings.m_HostInputAuthority = m_host_input_authority;
@ -1055,6 +1092,29 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
}
break;
case SYNC_SAVE_DATA_GBA:
{
if (m_local_player->IsHost())
return 0;
u8 slot;
packet >> slot;
const std::string path =
fmt::format("{}{}{}.sav", File::GetUserPath(D_GBAUSER_IDX), GBA_SAVE_NETPLAY, slot + 1);
if (File::Exists(path) && !File::Delete(path))
{
PanicAlertFmtT("Failed to delete NetPlay GBA{0} save file. Verify your write permissions.",
slot + 1);
SyncSaveDataResponse(false);
return 0;
}
const bool success = DecompressPacketIntoFile(packet, path);
SyncSaveDataResponse(success);
}
break;
default:
PanicAlertFmtT("Unknown SYNC_SAVE_DATA message received with id: {0}", sub_id);
break;
@ -1411,26 +1471,13 @@ void NetPlayClient::GetPlayerList(std::string& list, std::vector<int>& pid_list)
std::ostringstream ss;
const auto enumerate_player_controller_mappings = [&ss](const PadMappingArray& mappings,
const Player& player) {
for (size_t i = 0; i < mappings.size(); i++)
{
if (mappings[i] == player.pid)
ss << i + 1;
else
ss << '-';
}
};
for (const auto& entry : m_players)
{
const Player& player = entry.second;
ss << player.name << "[" << static_cast<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);
enumerate_player_controller_mappings(m_wiimote_map, player);
ss << " |\nPing: " << player.ping << "ms\n";
ss << "Ping: " << player.ping << "ms\n";
ss << "Status: ";
switch (player.game_status)
@ -1492,8 +1539,12 @@ void NetPlayClient::AddPadStateToPacket(const int in_game_pad, const GCPadStatus
sf::Packet& packet)
{
packet << static_cast<PadIndex>(in_game_pad);
packet << pad.button << pad.analogA << pad.analogB << pad.stickX << pad.stickY << pad.substickX
packet << pad.button;
if (!m_gba_config[in_game_pad].enabled)
{
packet << pad.analogA << pad.analogB << pad.stickX << pad.stickY << pad.substickX
<< pad.substickY << pad.triggerLeft << pad.triggerRight << pad.isConnected;
}
}
// called from ---CPU--- thread
@ -1555,15 +1606,19 @@ bool NetPlayClient::StartGame(const std::string& path)
if (Movie::IsReadOnly())
Movie::SetReadOnly(false);
u8 controllers_mask = 0;
Movie::ControllerTypeArray controllers{};
Movie::WiimoteEnabledArray wiimotes{};
for (unsigned int i = 0; i < 4; ++i)
{
if (m_pad_map[i] > 0)
controllers_mask |= (1 << i);
if (m_wiimote_map[i] > 0)
controllers_mask |= (1 << (i + 4));
if (m_pad_map[i] > 0 && m_gba_config[i].enabled)
controllers[i] = Movie::ControllerType::GBA;
else if (m_pad_map[i] > 0)
controllers[i] = Movie::ControllerType::GC;
else
controllers[i] = Movie::ControllerType::None;
wiimotes[i] = m_wiimote_map[i] > 0;
}
Movie::BeginRecordingInput(controllers_mask);
Movie::BeginRecordingInput(controllers, wiimotes);
}
for (unsigned int i = 0; i < 4; ++i)
@ -1648,11 +1703,13 @@ void NetPlayClient::UpdateDevices()
for (auto player_id : m_pad_map)
{
// Use local controller types for local controllers if they are compatible
// Only GCController-like controllers are supported, GBA and similar
// exotic devices are not supported on netplay.
if (player_id == m_local_player->pid)
if (m_gba_config[pad].enabled && player_id > 0)
{
SerialInterface::ChangeDevice(SerialInterface::SIDEVICE_GC_GBA_EMULATED, pad);
}
else if (player_id == m_local_player->pid)
{
// Use local controller types for local controllers if they are compatible
if (SerialInterface::SIDevice_IsGCController(SConfig::GetInstance().m_SIDevice[local_pad]))
{
SerialInterface::ChangeDevice(SConfig::GetInstance().m_SIDevice[local_pad], pad);
@ -1968,21 +2025,22 @@ bool NetPlayClient::WiimoteUpdate(int _number, u8* data, const std::size_t size,
bool NetPlayClient::PollLocalPad(const int local_pad, sf::Packet& packet)
{
GCPadStatus pad_status;
switch (SConfig::GetInstance().m_SIDevice[local_pad])
{
case SerialInterface::SIDEVICE_WIIU_ADAPTER:
pad_status = GCAdapter::Input(local_pad);
break;
case SerialInterface::SIDEVICE_GC_CONTROLLER:
default:
pad_status = Pad::GetStatus(local_pad);
break;
}
const int ingame_pad = LocalPadToInGamePad(local_pad);
bool data_added = false;
GCPadStatus pad_status;
if (m_gba_config[ingame_pad].enabled)
{
pad_status = Pad::GetGBAStatus(local_pad);
}
else if (SConfig::GetInstance().m_SIDevice[local_pad] == SerialInterface::SIDEVICE_WIIU_ADAPTER)
{
pad_status = GCAdapter::Input(local_pad);
}
else
{
pad_status = Pad::GetStatus(local_pad);
}
if (m_host_input_authority)
{
@ -2234,6 +2292,26 @@ bool NetPlayClient::IsLocalPlayer(const PlayerId pid) const
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()
{
std::lock_guard lk(crit_netplay_client);
@ -2309,6 +2387,11 @@ const PadMappingArray& NetPlayClient::GetPadMapping() const
return m_pad_map;
}
const GBAConfigArray& NetPlayClient::GetGBAConfig() const
{
return m_gba_config;
}
const PadMappingArray& NetPlayClient::GetWiimoteMapping() const
{
return m_wiimote_map;
@ -2325,6 +2408,32 @@ SyncIdentifier NetPlayClient::GetSDCardIdentifier()
return SyncIdentifier{{}, "sd", {}, {}, {}, {}};
}
std::string GetPlayerMappingString(PlayerId pid, const PadMappingArray& pad_map,
const GBAConfigArray& gba_config,
const PadMappingArray& wiimote_map)
{
std::vector<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()
{
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)
{
std::lock_guard lk(crit_netplay_client);

View File

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

View File

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

View File

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

View File

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

View File

@ -73,7 +73,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
static std::thread g_save_thread;
// Don't forget to increase this after doing changes on the savestate system
constexpr u32 STATE_VERSION = 132; // Last changed in PR 9532
constexpr u32 STATE_VERSION = 133; // Last changed in PR 9600
// Maps savestate versions to Dolphin versions.
// Versions after 42 don't need to be added to this list,

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">
<ItemGroup>
<ClInclude Include="AudioCommon\AudioCommon.h" />
@ -263,6 +263,9 @@
<ClInclude Include="Core\HW\EXI\EXI_DeviceMemoryCard.h" />
<ClInclude Include="Core\HW\EXI\EXI_DeviceMic.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\GCKeyboardEmu.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_DeviceDanceMat.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_DeviceGCController.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_DeviceMic.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\GCKeyboardEmu.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_DeviceDanceMat.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_DeviceGCController.cpp" />
<ClCompile Include="Core\HW\SI\SI_DeviceGCSteeringWheel.cpp" />

View File

@ -119,6 +119,11 @@ void Host_TitleChanged()
#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)
{
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/FreeLookRotation.cpp
Config/Mapping/FreeLookRotation.h
Config/Mapping/GBAPadEmu.cpp
Config/Mapping/GBAPadEmu.h
Config/Mapping/GCKeyboardEmu.cpp
Config/Mapping/GCKeyboardEmu.h
Config/Mapping/GCMicrophone.cpp
@ -138,6 +140,8 @@ add_executable(dolphin-emu
Config/Mapping/HotkeyControllerProfile.h
Config/Mapping/HotkeyDebugging.cpp
Config/Mapping/HotkeyDebugging.h
Config/Mapping/HotkeyGBA.cpp
Config/Mapping/HotkeyGBA.h
Config/Mapping/HotkeyGeneral.cpp
Config/Mapping/HotkeyGeneral.h
Config/Mapping/HotkeyGraphics.cpp
@ -538,6 +542,15 @@ else()
install(TARGETS dolphin-emu RUNTIME DESTINATION ${bindir})
endif()
if(USE_MGBA)
target_sources(dolphin-emu PRIVATE
GBAHost.cpp
GBAHost.h
GBAWidget.cpp
GBAWidget.h
)
endif()
if(USE_DISCORD_PRESENCE)
target_compile_definitions(dolphin-emu PRIVATE -DUSE_DISCORD_PRESENCE)
endif()

View File

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

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 <QFormLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
@ -118,16 +119,7 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con
}
for (auto& control : group->controls)
{
auto* button = new MappingButton(this, control->control_ref.get(), !indicator);
button->setMinimumWidth(100);
button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
const bool translate = control->translate == ControllerEmu::Translate;
const QString translated_name =
translate ? tr(control->ui_name.c_str()) : QString::fromStdString(control->ui_name);
form_layout->addRow(translated_name, button);
}
CreateControl(control.get(), form_layout, !indicator);
for (auto& setting : group->numeric_settings)
{
@ -186,6 +178,42 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con
return group_box;
}
QGroupBox* MappingWidget::CreateControlsBox(const QString& name, ControllerEmu::ControlGroup* group,
int columns)
{
auto* group_box = new QGroupBox(name);
auto* hbox_layout = new QHBoxLayout();
group_box->setLayout(hbox_layout);
std::vector<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
{
return m_parent->GetController();

View File

@ -16,6 +16,7 @@ class InputConfig;
class MappingButton;
class MappingNumeric;
class MappingWindow;
class QFormLayout;
class QPushButton;
class QGroupBox;
@ -52,6 +53,9 @@ protected:
QGroupBox* CreateGroupBox(ControllerEmu::ControlGroup* group);
QGroupBox* CreateGroupBox(const QString& name, ControllerEmu::ControlGroup* group);
QGroupBox* CreateControlsBox(const QString& name, ControllerEmu::ControlGroup* group,
int columns);
void CreateControl(const ControllerEmu::Control* control, QFormLayout* layout, bool indicator);
QPushButton* CreateSettingAdvancedMappingButton(ControllerEmu::NumericSettingBase& setting);
private:

View File

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

View File

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

View File

@ -81,6 +81,7 @@
<ClCompile Include="Config\LogWidget.cpp" />
<ClCompile Include="Config\Mapping\FreeLookGeneral.cpp" />
<ClCompile Include="Config\Mapping\FreeLookRotation.cpp" />
<ClCompile Include="Config\Mapping\GBAPadEmu.cpp" />
<ClCompile Include="Config\Mapping\GCKeyboardEmu.cpp" />
<ClCompile Include="Config\Mapping\GCMicrophone.cpp" />
<ClCompile Include="Config\Mapping\GCPadEmu.cpp" />
@ -89,6 +90,7 @@
<ClCompile Include="Config\Mapping\HotkeyControllerProfile.cpp" />
<ClCompile Include="Config\Mapping\HotkeyDebugging.cpp" />
<ClCompile Include="Config\Mapping\HotkeyGeneral.cpp" />
<ClCompile Include="Config\Mapping\HotkeyGBA.cpp" />
<ClCompile Include="Config\Mapping\HotkeyGraphics.cpp" />
<ClCompile Include="Config\Mapping\HotkeyStates.cpp" />
<ClCompile Include="Config\Mapping\HotkeyStatesOther.cpp" />
@ -141,6 +143,8 @@
<ClCompile Include="GameList\GameTracker.cpp" />
<ClCompile Include="GameList\GridProxyModel.cpp" />
<ClCompile Include="GameList\ListProxyModel.cpp" />
<ClCompile Include="GBAHost.cpp" />
<ClCompile Include="GBAWidget.cpp" />
<ClCompile Include="GCMemcardCreateNewDialog.cpp" />
<ClCompile Include="GCMemcardManager.cpp" />
<ClCompile Include="Host.cpp" />
@ -207,6 +211,7 @@
<ClInclude Include="Config\NewPatchDialog.h" />
<ClInclude Include="Config\PatchesWidget.h" />
<ClInclude Include="Debugger\RegisterColumn.h" />
<ClInclude Include="GBAHost.h" />
<ClInclude Include="QtUtils\ActionHelper.h" />
<ClInclude Include="QtUtils\FlowLayout.h" />
<ClInclude Include="QtUtils\ImageConverter.h" />
@ -256,6 +261,7 @@
<QtMoc Include="Config\LogWidget.h" />
<QtMoc Include="Config\Mapping\FreeLookGeneral.h" />
<QtMoc Include="Config\Mapping\FreeLookRotation.h" />
<QtMoc Include="Config\Mapping\GBAPadEmu.h" />
<QtMoc Include="Config\Mapping\GCKeyboardEmu.h" />
<QtMoc Include="Config\Mapping\GCMicrophone.h" />
<QtMoc Include="Config\Mapping\GCPadEmu.h" />
@ -263,6 +269,7 @@
<QtMoc Include="Config\Mapping\Hotkey3D.h" />
<QtMoc Include="Config\Mapping\HotkeyControllerProfile.h" />
<QtMoc Include="Config\Mapping\HotkeyDebugging.h" />
<QtMoc Include="Config\Mapping\HotkeyGBA.h" />
<QtMoc Include="Config\Mapping\HotkeyGeneral.h" />
<QtMoc Include="Config\Mapping\HotkeyGraphics.h" />
<QtMoc Include="Config\Mapping\HotkeyStates.h" />
@ -311,6 +318,7 @@
<QtMoc Include="GameList\GameTracker.h" />
<QtMoc Include="GameList\GridProxyModel.h" />
<QtMoc Include="GameList\ListProxyModel.h" />
<QtMoc Include="GBAWidget.h" />
<QtMoc Include="GCMemcardCreateNewDialog.h" />
<QtMoc Include="GCMemcardManager.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/State.h"
#ifdef HAS_LIBMGBA
#include "DolphinQt/GBAWidget.h"
#endif
#include "DolphinQt/QtUtils/QueueOnObject.h"
#include "DolphinQt/Settings.h"
@ -115,6 +118,15 @@ void Host::SetRenderFullFocus(bool focus)
m_render_full_focus = focus;
}
bool Host::GetGBAFocus()
{
#ifdef HAS_LIBMGBA
return qobject_cast<GBAWidget*>(QApplication::activeWindow()) != nullptr;
#else
return false;
#endif
}
bool Host::GetRenderFullscreen()
{
return m_render_fullscreen;
@ -167,7 +179,7 @@ void Host_UpdateTitle(const std::string& title)
bool Host_RendererHasFocus()
{
return Host::GetInstance()->GetRenderFocus();
return Host::GetInstance()->GetRenderFocus() || Host::GetInstance()->GetGBAFocus();
}
bool Host_RendererHasFullFocus()
@ -229,3 +241,10 @@ void Host_TitleChanged()
Discord::UpdateDiscordPresence();
#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 GetRenderFullFocus();
bool GetRenderFullscreen();
bool GetGBAFocus();
void SetMainWindowHandle(void* handle);
void SetRenderHandle(void* handle);

View File

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

View File

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

View File

@ -8,6 +8,7 @@
#include <QApplication>
#include <QClipboard>
#include <QComboBox>
#include <QFileDialog>
#include <QGridLayout>
#include <QGroupBox>
#include <QHeaderView>
@ -35,6 +36,9 @@
#include "Core/Config/NetplaySettings.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#ifdef HAS_LIBMGBA
#include "Core/HW/GBACore.h"
#endif
#include "Core/NetPlayServer.h"
#include "Core/SyncIdentifier.h"
@ -47,6 +51,7 @@
#include "DolphinQt/QtUtils/RunOnObject.h"
#include "DolphinQt/Resources.h"
#include "DolphinQt/Settings.h"
#include "DolphinQt/Settings/GameCubePane.h"
#include "UICommon/DiscordPresence.h"
#include "UICommon/GameFile.h"
@ -171,6 +176,8 @@ void NetPlayDialog::CreateMainLayout()
m_record_input_action->setCheckable(true);
m_golf_mode_overlay_action = m_other_menu->addAction(tr("Show Golf Mode Overlay"));
m_golf_mode_overlay_action->setCheckable(true);
m_hide_remote_gbas_action = m_other_menu->addAction(tr("Hide Remote GBAs"));
m_hide_remote_gbas_action->setCheckable(true);
m_game_button->setDefault(false);
m_game_button->setAutoDefault(false);
@ -279,6 +286,7 @@ void NetPlayDialog::ConnectWidgets()
m_pad_mapping->exec();
Settings::Instance().GetNetPlayServer()->SetPadMapping(m_pad_mapping->GetGCPadArray());
Settings::Instance().GetNetPlayServer()->SetGBAConfig(m_pad_mapping->GetGBAArray(), true);
Settings::Instance().GetNetPlayServer()->SetWiimoteMapping(m_pad_mapping->GetWiimoteArray());
});
@ -365,6 +373,7 @@ void NetPlayDialog::ConnectWidgets()
connect(m_golf_mode_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings);
connect(m_golf_mode_overlay_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings);
connect(m_fixed_delay_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings);
connect(m_hide_remote_gbas_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings);
}
void NetPlayDialog::SendMessage(const std::string& msg)
@ -471,6 +480,11 @@ void NetPlayDialog::show(std::string nickname, bool use_traversal)
m_data_menu->menuAction()->setVisible(is_hosting);
m_network_menu->menuAction()->setVisible(is_hosting);
m_md5_menu->menuAction()->setVisible(is_hosting);
#ifdef HAS_LIBMGBA
m_hide_remote_gbas_action->setVisible(is_hosting);
#else
m_hide_remote_gbas_action->setVisible(false);
#endif
m_start_button->setHidden(!is_hosting);
m_kick_button->setHidden(!is_hosting);
m_assign_ports_button->setHidden(!is_hosting);
@ -570,20 +584,6 @@ void NetPlayDialog::UpdateGUI()
{tr("Player"), tr("Game Status"), tr("Ping"), tr("Mapping"), tr("Revision")});
m_players_list->setRowCount(m_player_count);
const auto get_mapping_string = [](const NetPlay::Player* player,
const NetPlay::PadMappingArray& array) {
std::string str;
for (size_t i = 0; i < array.size(); i++)
{
if (player->pid == array[i])
str += std::to_string(i + 1);
else
str += '-';
}
return '|' + str + '|';
};
static const std::map<NetPlay::SyncIdentifierComparison, QString> player_status{
{NetPlay::SyncIdentifierComparison::SameGame, tr("OK")},
{NetPlay::SyncIdentifierComparison::DifferentVersion, tr("Wrong Version")},
@ -599,9 +599,9 @@ void NetPlayDialog::UpdateGUI()
player_status.at(p->game_status) :
QStringLiteral("?"));
auto* ping_item = new QTableWidgetItem(QStringLiteral("%1 ms").arg(p->ping));
auto* mapping_item = new QTableWidgetItem(
QString::fromStdString(get_mapping_string(p, client->GetPadMapping()) +
get_mapping_string(p, client->GetWiimoteMapping())));
auto* mapping_item =
new QTableWidgetItem(QString::fromStdString(NetPlay::GetPlayerMappingString(
p->pid, client->GetPadMapping(), client->GetGBAConfig(), client->GetWiimoteMapping())));
auto* revision_item = new QTableWidgetItem(QString::fromStdString(p->revision));
for (auto* item : {name_item, status_item, ping_item, mapping_item, revision_item})
@ -743,6 +743,20 @@ void NetPlayDialog::OnMsgChangeGame(const NetPlay::SyncIdentifier& sync_identifi
DisplayMessage(tr("Game changed to \"%1\"").arg(qname), "magenta");
}
void NetPlayDialog::OnMsgChangeGBARom(int pad, const NetPlay::GBAConfig& config)
{
if (config.has_rom)
{
DisplayMessage(
tr("GBA%1 ROM changed to \"%2\"").arg(pad + 1).arg(QString::fromStdString(config.title)),
"magenta");
}
else
{
DisplayMessage(tr("GBA%1 ROM disabled").arg(pad + 1), "magenta");
}
}
void NetPlayDialog::GameStatusChanged(bool running)
{
QueueOnObject(this, [this, running] { SetOptionsEnabled(!running); });
@ -976,6 +990,51 @@ NetPlayDialog::FindGameFile(const NetPlay::SyncIdentifier& sync_identifier,
return nullptr;
}
std::string NetPlayDialog::FindGBARomPath(const std::array<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()
{
const int buffer_size = Config::Get(Config::NETPLAY_BUFFER_SIZE);
@ -987,6 +1046,7 @@ void NetPlayDialog::LoadSettings()
const bool strict_settings_sync = Config::Get(Config::NETPLAY_STRICT_SETTINGS_SYNC);
const bool sync_all_wii_saves = Config::Get(Config::NETPLAY_SYNC_ALL_WII_SAVES);
const bool golf_mode_overlay = Config::Get(Config::NETPLAY_GOLF_MODE_OVERLAY);
const bool hide_remote_gbas = Config::Get(Config::NETPLAY_HIDE_REMOTE_GBAS);
m_buffer_size_box->setValue(buffer_size);
m_save_sd_action->setChecked(write_save_sdcard_data);
@ -997,6 +1057,7 @@ void NetPlayDialog::LoadSettings()
m_strict_settings_sync_action->setChecked(strict_settings_sync);
m_sync_all_wii_saves_action->setChecked(sync_all_wii_saves);
m_golf_mode_overlay_action->setChecked(golf_mode_overlay);
m_hide_remote_gbas_action->setChecked(hide_remote_gbas);
const std::string network_mode = Config::Get(Config::NETPLAY_NETWORK_MODE);
@ -1036,6 +1097,7 @@ void NetPlayDialog::SaveSettings()
Config::SetBase(Config::NETPLAY_STRICT_SETTINGS_SYNC, m_strict_settings_sync_action->isChecked());
Config::SetBase(Config::NETPLAY_SYNC_ALL_WII_SAVES, m_sync_all_wii_saves_action->isChecked());
Config::SetBase(Config::NETPLAY_GOLF_MODE_OVERLAY, m_golf_mode_overlay_action->isChecked());
Config::SetBase(Config::NETPLAY_HIDE_REMOTE_GBAS, m_hide_remote_gbas_action->isChecked());
std::string network_mode;
if (m_fixed_delay_action->isChecked())

View File

@ -46,6 +46,7 @@ public:
void OnMsgChangeGame(const NetPlay::SyncIdentifier& sync_identifier,
const std::string& netplay_name) override;
void OnMsgChangeGBARom(int pad, const NetPlay::GBAConfig& config) override;
void OnMsgStartGame() override;
void OnMsgStopGame() override;
void OnMsgPowerButton() override;
@ -68,6 +69,8 @@ public:
std::shared_ptr<const UICommon::GameFile>
FindGameFile(const NetPlay::SyncIdentifier& sync_identifier,
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 SaveSettings();
@ -138,6 +141,7 @@ private:
QAction* m_golf_mode_action;
QAction* m_golf_mode_overlay_action;
QAction* m_fixed_delay_action;
QAction* m_hide_remote_gbas_action;
QPushButton* m_quit_button;
QSplitter* m_splitter;
QActionGroup* m_network_mode_group;

View File

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

View File

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

View File

@ -11,6 +11,7 @@
#include <QGroupBox>
#include <QInputDialog>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>
@ -19,16 +20,19 @@
#include "Common/CommonPaths.h"
#include "Common/Config/Config.h"
#include "Common/FileUtil.h"
#include "Common/MsgHandler.h"
#include "Core/Config/MainSettings.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/HW/EXI/EXI.h"
#include "Core/HW/GCMemcard/GCMemcard.h"
#include "Core/NetPlayServer.h"
#include "DolphinQt/Config/Mapping/MappingWindow.h"
#include "DolphinQt/GCMemcardManager.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h"
#include "DolphinQt/Settings.h"
enum
{
@ -123,8 +127,51 @@ void GameCubePane::CreateWidgets()
device_layout->addWidget(m_slot_combos[2], 2, 1);
device_layout->addWidget(m_slot_buttons[2], 2, 2);
#ifdef HAS_LIBMGBA
// GBA Settings
auto* gba_box = new QGroupBox(tr("GBA Settings"), this);
auto* gba_layout = new QGridLayout(gba_box);
gba_box->setLayout(gba_layout);
int gba_row = 0;
m_gba_threads = new QCheckBox(tr("Run GBA Cores in Dedicated Threads"));
gba_layout->addWidget(m_gba_threads, gba_row, 0, 1, -1);
gba_row++;
m_gba_bios_edit = new QLineEdit();
m_gba_browse_bios = new QPushButton(QStringLiteral("..."));
gba_layout->addWidget(new QLabel(tr("BIOS:")), gba_row, 0);
gba_layout->addWidget(m_gba_bios_edit, gba_row, 1);
gba_layout->addWidget(m_gba_browse_bios, gba_row, 2);
gba_row++;
for (size_t i = 0; i < m_gba_rom_edits.size(); ++i)
{
m_gba_rom_edits[i] = new QLineEdit();
m_gba_browse_roms[i] = new QPushButton(QStringLiteral("..."));
gba_layout->addWidget(new QLabel(tr("Port %1 ROM:").arg(i + 1)), gba_row, 0);
gba_layout->addWidget(m_gba_rom_edits[i], gba_row, 1);
gba_layout->addWidget(m_gba_browse_roms[i], gba_row, 2);
gba_row++;
}
m_gba_save_rom_path = new QCheckBox(tr("Save in Same Directory as the ROM"));
gba_layout->addWidget(m_gba_save_rom_path, gba_row, 0, 1, -1);
gba_row++;
m_gba_saves_edit = new QLineEdit();
m_gba_browse_saves = new QPushButton(QStringLiteral("..."));
gba_layout->addWidget(new QLabel(tr("Saves:")), gba_row, 0);
gba_layout->addWidget(m_gba_saves_edit, gba_row, 1);
gba_layout->addWidget(m_gba_browse_saves, gba_row, 2);
gba_row++;
#endif
layout->addWidget(ipl_box);
layout->addWidget(device_box);
#ifdef HAS_LIBMGBA
layout->addWidget(gba_box);
#endif
layout->addStretch();
@ -147,6 +194,44 @@ void GameCubePane::ConnectWidgets()
&GameCubePane::SaveSettings);
connect(m_slot_buttons[i], &QPushButton::clicked, [this, i] { OnConfigPressed(i); });
}
#ifdef HAS_LIBMGBA
// GBA Settings
connect(m_gba_threads, &QCheckBox::stateChanged, this, &GameCubePane::SaveSettings);
connect(m_gba_bios_edit, &QLineEdit::editingFinished, this, &GameCubePane::SaveSettings);
connect(m_gba_browse_bios, &QPushButton::clicked, this, &GameCubePane::BrowseGBABios);
connect(m_gba_save_rom_path, &QCheckBox::stateChanged, this, &GameCubePane::SaveRomPathChanged);
connect(m_gba_saves_edit, &QLineEdit::editingFinished, this, &GameCubePane::SaveSettings);
connect(m_gba_browse_saves, &QPushButton::clicked, this, &GameCubePane::BrowseGBASaves);
for (size_t i = 0; i < m_gba_browse_roms.size(); ++i)
{
connect(m_gba_rom_edits[i], &QLineEdit::editingFinished, this, &GameCubePane::SaveSettings);
connect(m_gba_browse_roms[i], &QPushButton::clicked, this, [this, i] { BrowseGBARom(i); });
}
#endif
// Emulation State
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
&GameCubePane::OnEmulationStateChanged);
OnEmulationStateChanged();
}
void GameCubePane::OnEmulationStateChanged()
{
#ifdef HAS_LIBMGBA
bool gba_enabled = !NetPlay::IsNetPlayRunning();
m_gba_threads->setEnabled(gba_enabled);
m_gba_bios_edit->setEnabled(gba_enabled);
m_gba_browse_bios->setEnabled(gba_enabled);
m_gba_save_rom_path->setEnabled(gba_enabled);
m_gba_saves_edit->setEnabled(gba_enabled);
m_gba_browse_saves->setEnabled(gba_enabled);
for (size_t i = 0; i < m_gba_browse_roms.size(); ++i)
{
m_gba_rom_edits[i]->setEnabled(gba_enabled);
m_gba_browse_roms[i]->setEnabled(gba_enabled);
}
#endif
}
void GameCubePane::UpdateButton(int slot)
@ -312,6 +397,47 @@ void GameCubePane::OnConfigPressed(int slot)
}
}
void GameCubePane::BrowseGBABios()
{
QString file = QDir::toNativeSeparators(QFileDialog::getOpenFileName(
this, tr("Select GBA BIOS"), QString::fromStdString(File::GetUserPath(F_GBABIOS_IDX)),
tr("All Files (*)")));
if (!file.isEmpty())
{
m_gba_bios_edit->setText(file);
SaveSettings();
}
}
void GameCubePane::BrowseGBARom(size_t index)
{
QString file = QString::fromStdString(GetOpenGBARom({}));
if (!file.isEmpty())
{
m_gba_rom_edits[index]->setText(file);
SaveSettings();
}
}
void GameCubePane::SaveRomPathChanged()
{
m_gba_saves_edit->setEnabled(!m_gba_save_rom_path->isChecked());
m_gba_browse_saves->setEnabled(!m_gba_save_rom_path->isChecked());
SaveSettings();
}
void GameCubePane::BrowseGBASaves()
{
QString dir = QDir::toNativeSeparators(
QFileDialog::getExistingDirectory(this, tr("Select GBA Saves Path"),
QString::fromStdString(File::GetUserPath(D_GBASAVES_IDX))));
if (!dir.isEmpty())
{
m_gba_saves_edit->setText(dir);
SaveSettings();
}
}
void GameCubePane::LoadSettings()
{
const SConfig& params = SConfig::GetInstance();
@ -346,6 +472,16 @@ void GameCubePane::LoadSettings()
m_slot_combos[i]->findData(SConfig::GetInstance().m_EXIDevice[i]));
UpdateButton(i);
}
#ifdef HAS_LIBMGBA
// GBA Settings
m_gba_threads->setChecked(Config::Get(Config::MAIN_GBA_THREADS));
m_gba_bios_edit->setText(QString::fromStdString(File::GetUserPath(F_GBABIOS_IDX)));
m_gba_save_rom_path->setChecked(Config::Get(Config::MAIN_GBA_SAVES_IN_ROM_PATH));
m_gba_saves_edit->setText(QString::fromStdString(File::GetUserPath(D_GBASAVES_IDX)));
for (size_t i = 0; i < m_gba_rom_edits.size(); ++i)
m_gba_rom_edits[i]->setText(QString::fromStdString(Config::Get(Config::MAIN_GBA_ROM_PATHS[i])));
#endif
}
void GameCubePane::SaveSettings()
@ -360,6 +496,7 @@ void GameCubePane::SaveSettings()
params.SelectedLanguage = m_language_combo->currentData().toInt();
Config::SetBaseOrCurrent(Config::MAIN_GC_LANGUAGE, m_language_combo->currentData().toInt());
// Device Settings
for (int i = 0; i < SLOT_COUNT; i++)
{
const auto dev = ExpansionInterface::TEXIDevices(m_slot_combos[i]->currentData().toInt());
@ -389,5 +526,41 @@ void GameCubePane::SaveSettings()
break;
}
}
#ifdef HAS_LIBMGBA
// GBA Settings
if (!NetPlay::IsNetPlayRunning())
{
Config::SetBaseOrCurrent(Config::MAIN_GBA_THREADS, m_gba_threads->isChecked());
Config::SetBaseOrCurrent(Config::MAIN_GBA_BIOS_PATH, m_gba_bios_edit->text().toStdString());
Config::SetBaseOrCurrent(Config::MAIN_GBA_SAVES_IN_ROM_PATH, m_gba_save_rom_path->isChecked());
Config::SetBaseOrCurrent(Config::MAIN_GBA_SAVES_PATH, m_gba_saves_edit->text().toStdString());
File::SetUserPath(F_GBABIOS_IDX, Config::Get(Config::MAIN_GBA_BIOS_PATH));
File::SetUserPath(D_GBASAVES_IDX, Config::Get(Config::MAIN_GBA_SAVES_PATH));
for (size_t i = 0; i < m_gba_rom_edits.size(); ++i)
{
Config::SetBaseOrCurrent(Config::MAIN_GBA_ROM_PATHS[i],
m_gba_rom_edits[i]->text().toStdString());
}
auto server = Settings::Instance().GetNetPlayServer();
if (server)
server->SetGBAConfig(server->GetGBAConfig(), true);
}
#endif
LoadSettings();
}
std::string GameCubePane::GetOpenGBARom(std::string_view title)
{
QString caption = tr("Select GBA ROM");
if (!title.empty())
caption += QStringLiteral(": %1").arg(QString::fromStdString(std::string(title)));
return QDir::toNativeSeparators(
QFileDialog::getOpenFileName(
nullptr, caption, QString(),
tr("Game Boy Advance ROMs (*.gba *.gbc *.gb *.7z *.zip *.agb *.mb *.rom *.bin);;"
"All Files (*)")))
.toStdString();
}

View File

@ -3,10 +3,15 @@
#pragma once
#include <array>
#include <string>
#include <string_view>
#include <QWidget>
class QCheckBox;
class QComboBox;
class QLineEdit;
class QPushButton;
class GameCubePane : public QWidget
@ -15,6 +20,8 @@ class GameCubePane : public QWidget
public:
explicit GameCubePane();
static std::string GetOpenGBARom(std::string_view title);
private:
void CreateWidgets();
void ConnectWidgets();
@ -22,12 +29,28 @@ private:
void LoadSettings();
void SaveSettings();
void OnEmulationStateChanged();
void UpdateButton(int slot);
void OnConfigPressed(int slot);
void BrowseGBABios();
void BrowseGBARom(size_t index);
void SaveRomPathChanged();
void BrowseGBASaves();
QCheckBox* m_skip_main_menu;
QComboBox* m_language_combo;
QPushButton* m_slot_buttons[3];
QComboBox* m_slot_combos[3];
QCheckBox* m_gba_threads;
QCheckBox* m_gba_save_rom_path;
QPushButton* m_gba_browse_bios;
QLineEdit* m_gba_bios_edit;
std::array<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;
bool InputConfig::LoadConfig(bool isGC)
bool InputConfig::LoadConfig(InputClass type)
{
IniFile inifile;
bool useProfile[MAX_BBMOTES] = {false, false, false, false, false};
@ -43,16 +43,22 @@ bool InputConfig::LoadConfig(bool isGC)
if (SConfig::GetInstance().GetGameID() != "00000000")
{
std::string type;
if (isGC)
std::string type_str;
switch (type)
{
type = "Pad";
path = "Profiles/GCPad/";
}
else
{
type = "Wiimote";
case InputClass::GBA:
type_str = "GBA";
path = "Profiles/GBA/";
break;
case InputClass::Wii:
type_str = "Wiimote";
path = "Profiles/Wiimote/";
break;
case InputClass::GC:
default:
type_str = "Pad";
path = "Profiles/GCPad/";
break;
}
IniFile game_ini = SConfig::GetInstance().LoadGameIni();
@ -60,7 +66,7 @@ bool InputConfig::LoadConfig(bool isGC)
for (int i = 0; i < 4; i++)
{
const auto profile_name = fmt::format("{}Profile{}", type, num[i]);
const auto profile_name = fmt::format("{}Profile{}", type_str, num[i]);
if (control_section->Exists(profile_name))
{
@ -124,7 +130,7 @@ bool InputConfig::LoadConfig(bool isGC)
}
#if defined(ANDROID)
// Only set for wii pads
if (!isGC && use_ir_config)
if (type == InputClass::Wii && use_ir_config)
{
config.Set("IR/Total Yaw", ir_values[0]);
config.Set("IR/Total Pitch", ir_values[1]);

View File

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

View File

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

View File

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

View File

@ -56,3 +56,7 @@ void Host_YieldToUI()
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>
<SourceDir>$(DolphinRootDir)Source\</SourceDir>
<CoreDir>$(SourceDir)Core\</CoreDir>
<CScript Condition="'$(ProgramFiles(x86))' != ''">%windir%\System32\cscript</CScript>
<CScript Condition="'$(ProgramFiles(x86))' == ''">%windir%\Sysnative\cscript</CScript>
<VSPropsDir>$(SourceDir)VSProps\</VSPropsDir>
</PropertyGroup>
<PropertyGroup>

View File

@ -30,6 +30,7 @@
<ExternalIncludePath>$(ExternalsDir)libpng;$(ExternalIncludePath)</ExternalIncludePath>
<ExternalIncludePath>$(ExternalsDir)libusb\libusb;$(ExternalIncludePath)</ExternalIncludePath>
<ExternalIncludePath>$(ExternalsDir)LZO;$(ExternalIncludePath)</ExternalIncludePath>
<ExternalIncludePath>$(ExternalsDir)mGBA\mgba\include;$(ExternalIncludePath)</ExternalIncludePath>
<ExternalIncludePath>$(ExternalsDir)miniupnpc\src;$(ExternalIncludePath)</ExternalIncludePath>
<ExternalIncludePath>$(ExternalsDir)minizip;$(ExternalIncludePath)</ExternalIncludePath>
<ExternalIncludePath>$(ExternalsDir)mbedtls\include;$(ExternalIncludePath)</ExternalIncludePath>
@ -76,6 +77,7 @@
<PreprocessorDefinitions>USE_GDBSTUB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(Platform)'!='ARM64'">HAS_OPENGL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>HAS_VULKAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>HAS_LIBMGBA;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<!--
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
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zstd", "..\Externals\zstd\zstd.vcxproj", "{1BEA10F3-80CE-4BC4-9331-5769372CDF99}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mgba", "..\Externals\mGBA\mgba.vcxproj", "{864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@ -349,6 +351,14 @@ Global
{1BEA10F3-80CE-4BC4-9331-5769372CDF99}.Release|ARM64.Build.0 = Release|ARM64
{1BEA10F3-80CE-4BC4-9331-5769372CDF99}.Release|x64.ActiveCfg = Release|x64
{1BEA10F3-80CE-4BC4-9331-5769372CDF99}.Release|x64.Build.0 = Release|x64
{864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6}.Debug|ARM64.ActiveCfg = Debug|ARM64
{864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6}.Debug|ARM64.Build.0 = Debug|ARM64
{864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6}.Debug|x64.ActiveCfg = Debug|x64
{864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6}.Debug|x64.Build.0 = Debug|x64
{864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6}.Release|ARM64.ActiveCfg = Release|ARM64
{864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6}.Release|ARM64.Build.0 = Release|ARM64
{864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6}.Release|x64.ActiveCfg = Release|x64
{864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -379,6 +389,7 @@ Global
{1D8C51D2-FFA4-418E-B183-9F42B6A6717E} = {87ADDFF9-5768-4DA2-A33B-2477593D6677}
{055A775F-B4F5-4970-9240-F6CF7661F37B} = {87ADDFF9-5768-4DA2-A33B-2477593D6677}
{1BEA10F3-80CE-4BC4-9331-5769372CDF99} = {87ADDFF9-5768-4DA2-A33B-2477593D6677}
{864C4C8E-296D-3DBC-AD83-F1D5CB6E8EC6} = {87ADDFF9-5768-4DA2-A33B-2477593D6677}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {64B0A343-3B94-4522-9C24-6937FE5EFB22}