Merge pull request #9600 from Bonta0/mgba-pr
Full GBA Controllers Integration with mGBA
This commit is contained in:
commit
a1e806ed76
|
@ -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*/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
|
@ -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);
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 9cccc5197ed73ba0a54f584d3121c27dc97405f5
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
@ -434,8 +438,8 @@ add_library(core
|
|||
PowerPC/Interpreter/Interpreter_Tables.cpp
|
||||
PowerPC/Interpreter/Interpreter.cpp
|
||||
PowerPC/Interpreter/Interpreter.h
|
||||
PowerPC/JitCommon/DivUtils.cpp
|
||||
PowerPC/JitCommon/DivUtils.h
|
||||
PowerPC/JitCommon/DivUtils.cpp
|
||||
PowerPC/JitCommon/DivUtils.h
|
||||
PowerPC/JitCommon/JitAsmCommon.cpp
|
||||
PowerPC/JitCommon/JitAsmCommon.h
|
||||
PowerPC/JitCommon/JitBase.cpp
|
||||
|
@ -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
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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();
|
||||
}};
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,8 +322,9 @@ int CSIDevice_GBA::RunBuffer(u8* buffer, int request_length)
|
|||
return -1;
|
||||
#ifdef _DEBUG
|
||||
const Common::Log::LOG_LEVELS log_level =
|
||||
(m_last_cmd == CMD_STATUS || m_last_cmd == CMD_RESET) ? Common::Log::LERROR :
|
||||
Common::Log::LWARNING;
|
||||
(m_last_cmd == EBufferCommands::CMD_STATUS || m_last_cmd == EBufferCommands::CMD_RESET) ?
|
||||
Common::Log::LERROR :
|
||||
Common::Log::LWARNING;
|
||||
GENERIC_LOG_FMT(Common::Log::SERIALINTERFACE, log_level,
|
||||
"{} [< {:02x}{:02x}{:02x}{:02x}{:02x}] ({})",
|
||||
m_device_number, buffer[0], buffer[1], buffer[2], buffer[3], buffer[4],
|
||||
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,9 +1255,10 @@ bool PlayWiimote(int wiimote, WiimoteCommon::DataReportBuilder& rpt, int ext,
|
|||
PanicAlertFmtT(
|
||||
"Fatal desync. Aborting playback. (Error in PlayWiimote: {0} != {1}, byte {2}.){3}",
|
||||
sizeInMovie, size, s_currentByte,
|
||||
(s_controllers & 0xF) ? " Try re-creating the recording with all GameCube controllers "
|
||||
"disabled (in Configure > GameCube > Device Settings)." :
|
||||
"");
|
||||
(s_controllers == ControllerTypeArray{}) ?
|
||||
" Try re-creating the recording with all GameCube controllers "
|
||||
"disabled (in Configure > GameCube > Device Settings)." :
|
||||
"");
|
||||
EndPlayInput(!s_bReadOnly);
|
||||
return false;
|
||||
}
|
||||
|
@ -1296,7 +1333,17 @@ void SaveRecording(const std::string& filename)
|
|||
strncpy(header.gameID.data(), SConfig::GetInstance().GetGameID().c_str(), 6);
|
||||
header.bWii = SConfig::GetInstance().bWii;
|
||||
header.bFollowBranch = SConfig::GetInstance().bJITFollowBranch;
|
||||
header.controllers = s_controllers & (SConfig::GetInstance().bWii ? 0xFF : 0x0F);
|
||||
header.controllers = 0;
|
||||
header.GBAControllers = 0;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
if (IsUsingGBA(i))
|
||||
header.GBAControllers |= 1 << i;
|
||||
if (IsUsingPad(i))
|
||||
header.controllers |= 1 << i;
|
||||
if (IsUsingWiimote(i) && SConfig::GetInstance().bWii)
|
||||
header.controllers |= 1 << (i + 4);
|
||||
}
|
||||
|
||||
header.bFromSaveState = s_bRecordingFromSaveState;
|
||||
header.frameCount = s_totalFrames;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
<< pad.substickY << pad.triggerLeft << pad.triggerRight << pad.isConnected;
|
||||
packet << pad.button;
|
||||
if (!m_gba_config[in_game_pad].enabled)
|
||||
{
|
||||
packet << pad.analogA << pad.analogB << pad.stickX << pad.stickY << pad.substickX
|
||||
<< pad.substickY << pad.triggerLeft << pad.triggerRight << pad.isConnected;
|
||||
}
|
||||
}
|
||||
|
||||
// called from ---CPU--- thread
|
||||
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
if (Core::IsRunning())
|
||||
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())
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
};
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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();
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ public:
|
|||
// GameCube
|
||||
MAPPING_GC_BONGOS,
|
||||
MAPPING_GC_DANCEMAT,
|
||||
MAPPING_GC_GBA,
|
||||
MAPPING_GC_KEYBOARD,
|
||||
MAPPING_GCPAD,
|
||||
MAPPING_GC_STEERINGWHEEL,
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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));
|
||||
}
|
|
@ -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{};
|
||||
};
|
|
@ -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
|
||||
|
|
|
@ -27,6 +27,7 @@ public:
|
|||
bool GetRenderFocus();
|
||||
bool GetRenderFullFocus();
|
||||
bool GetRenderFullscreen();
|
||||
bool GetGBAFocus();
|
||||
|
||||
void SetMainWindowHandle(void* handle);
|
||||
void SetRenderHandle(void* handle);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -65,6 +65,7 @@ signals:
|
|||
private:
|
||||
void Run();
|
||||
void CheckDebuggingHotkeys();
|
||||
void CheckGBAHotkeys();
|
||||
|
||||
Common::Flag m_stop_requested;
|
||||
std::thread m_thread;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
-->
|
||||
|
|
|
@ -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}
|
||||
|
|
Loading…
Reference in New Issue