From 2c62214875e4d409136229421c44aa8b88fbb635 Mon Sep 17 00:00:00 2001 From: crediar Date: Wed, 16 Jul 2025 19:55:59 +0200 Subject: [PATCH 01/71] Added Triforce support --- Data/Sys/GameSettings/GEYJ6E.ini | 4 + Data/Sys/GameSettings/GEYP6E.ini | 4 + Data/Sys/GameSettings/GGGE6E.ini | 5 + Data/Sys/GameSettings/GHAE6E.ini | 5 + Data/Sys/GameSettings/GHZW6E.ini | 4 + Data/Sys/GameSettings/GJAP6E.ini | 4 + Data/Sys/GameSettings/GLKJ6E.ini | 4 + Data/Sys/GameSettings/GLLP6E.ini | 4 + Source/Core/Common/CommonPaths.h | 3 + Source/Core/Common/FileUtil.cpp | 1 + Source/Core/Common/FileUtil.h | 3 +- Source/Core/Common/Logging/Log.h | 5 + Source/Core/Common/Logging/LogManager.cpp | 5 + Source/Core/Core/Boot/Boot.cpp | 19 +- Source/Core/Core/Boot/Boot_BS2Emu.cpp | 15 +- Source/Core/Core/CMakeLists.txt | 4 + Source/Core/Core/Config/MainSettings.cpp | 17 + Source/Core/Core/ConfigManager.cpp | 30 +- Source/Core/Core/ConfigManager.h | 6 +- Source/Core/Core/Core.cpp | 8 + Source/Core/Core/HW/DVD/AMMediaboard.cpp | 1850 +++++++++++++ Source/Core/Core/HW/DVD/AMMediaboard.h | 237 ++ Source/Core/Core/HW/DVD/DVDInterface.cpp | 43 +- Source/Core/Core/HW/EXI/EXI.cpp | 24 +- Source/Core/Core/HW/EXI/EXI.h | 1 - .../Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp | 305 ++- Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.h | 46 +- Source/Core/Core/HW/GCPadEmu.cpp | 18 + Source/Core/Core/HW/GCPadEmu.h | 10 +- Source/Core/Core/HW/Memmap.h | 1 - Source/Core/Core/HW/SI/SI.h | 1 + Source/Core/Core/HW/SI/SI_Device.cpp | 4 + Source/Core/Core/HW/SI/SI_Device.h | 2 - .../Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp | 2383 +++++++++++++++++ Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h | 309 +++ .../Core/Core/HW/SI/SI_DeviceGCController.h | 3 + Source/Core/Core/HW/VideoInterface.cpp | 4 +- Source/Core/Core/NetPlayClient.cpp | 33 +- Source/Core/Core/State.cpp | 2 +- Source/Core/Core/TitleDatabase.cpp | 28 +- Source/Core/Core/TitleDatabase.h | 3 +- Source/Core/DiscIO/Enums.h | 9 +- Source/Core/DiscIO/Volume.h | 1 - Source/Core/DiscIO/VolumeDisc.cpp | 107 +- Source/Core/DiscIO/VolumeDisc.h | 24 + Source/Core/DiscIO/VolumeGC.cpp | 35 +- Source/Core/DiscIO/VolumeGC.h | 14 +- Source/Core/DolphinLib.props | 4 + .../Config/GamecubeControllersWidget.cpp | 2 + Source/Core/DolphinQt/Config/InfoWidget.cpp | 6 - .../DolphinQt/Config/Mapping/GCPadEmu.cpp | 15 +- .../Config/Mapping/MappingWindow.cpp | 16 +- Source/Core/DolphinQt/MainWindow.cpp | 44 + Source/Core/DolphinQt/MenuBar.cpp | 4 + Source/Core/DolphinQt/MenuBar.h | 1 + .../Core/DolphinQt/Settings/GameCubePane.cpp | 43 +- Source/Core/InputCommon/GCPadStatus.h | 9 + Source/Core/UICommon/GameFile.cpp | 5 +- Source/Core/UICommon/GameFile.h | 2 - Source/Core/UICommon/GameFileCache.cpp | 2 +- Source/Core/UICommon/UICommon.cpp | 1 + 61 files changed, 5627 insertions(+), 174 deletions(-) create mode 100644 Data/Sys/GameSettings/GEYJ6E.ini create mode 100644 Data/Sys/GameSettings/GEYP6E.ini create mode 100644 Data/Sys/GameSettings/GGGE6E.ini create mode 100644 Data/Sys/GameSettings/GHAE6E.ini create mode 100644 Data/Sys/GameSettings/GHZW6E.ini create mode 100644 Data/Sys/GameSettings/GJAP6E.ini create mode 100644 Data/Sys/GameSettings/GLKJ6E.ini create mode 100644 Data/Sys/GameSettings/GLLP6E.ini create mode 100644 Source/Core/Core/HW/DVD/AMMediaboard.cpp create mode 100644 Source/Core/Core/HW/DVD/AMMediaboard.h create mode 100644 Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp create mode 100644 Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h diff --git a/Data/Sys/GameSettings/GEYJ6E.ini b/Data/Sys/GameSettings/GEYJ6E.ini new file mode 100644 index 0000000000..c410c05bd7 --- /dev/null +++ b/Data/Sys/GameSettings/GEYJ6E.ini @@ -0,0 +1,4 @@ +# GEYJ6E - Virtua Striker 2002 (Japan) + +[Core] +FPRF = True diff --git a/Data/Sys/GameSettings/GEYP6E.ini b/Data/Sys/GameSettings/GEYP6E.ini new file mode 100644 index 0000000000..ccfcf0a9e4 --- /dev/null +++ b/Data/Sys/GameSettings/GEYP6E.ini @@ -0,0 +1,4 @@ +# GEYP6E - Virtua Striker 2002 (Export, Type3) + +[Core] +FPRF = True diff --git a/Data/Sys/GameSettings/GGGE6E.ini b/Data/Sys/GameSettings/GGGE6E.ini new file mode 100644 index 0000000000..8391b80aca --- /dev/null +++ b/Data/Sys/GameSettings/GGGE6E.ini @@ -0,0 +1,5 @@ +# GGGE6E - F-ZERO AX + +[Core] +FPRF = True + diff --git a/Data/Sys/GameSettings/GHAE6E.ini b/Data/Sys/GameSettings/GHAE6E.ini new file mode 100644 index 0000000000..4f6821b253 --- /dev/null +++ b/Data/Sys/GameSettings/GHAE6E.ini @@ -0,0 +1,5 @@ +# GHAE6E - F-ZERO AX (Monster) + +[Core] +FPRF = True + diff --git a/Data/Sys/GameSettings/GHZW6E.ini b/Data/Sys/GameSettings/GHZW6E.ini new file mode 100644 index 0000000000..afd22fb56c --- /dev/null +++ b/Data/Sys/GameSettings/GHZW6E.ini @@ -0,0 +1,4 @@ +# GHZW6E - VIRTUA STRIKER 4 (Asia) + +[Core] +FPRF = True diff --git a/Data/Sys/GameSettings/GJAP6E.ini b/Data/Sys/GameSettings/GJAP6E.ini new file mode 100644 index 0000000000..071cb0d5b9 --- /dev/null +++ b/Data/Sys/GameSettings/GJAP6E.ini @@ -0,0 +1,4 @@ +# GJAP6E - VIRTUA STRIKER 4 (Export) + +[Core] +FPRF = True diff --git a/Data/Sys/GameSettings/GLKJ6E.ini b/Data/Sys/GameSettings/GLKJ6E.ini new file mode 100644 index 0000000000..9b981daee6 --- /dev/null +++ b/Data/Sys/GameSettings/GLKJ6E.ini @@ -0,0 +1,4 @@ +# GLKJ6E - VIRTUA STRIKER 4 Ver.2006 (Japan) + +[Core] +FPRF = True diff --git a/Data/Sys/GameSettings/GLLP6E.ini b/Data/Sys/GameSettings/GLLP6E.ini new file mode 100644 index 0000000000..b607f28de4 --- /dev/null +++ b/Data/Sys/GameSettings/GLLP6E.ini @@ -0,0 +1,4 @@ +# GLLP6E - VIRTUA STRIKER 4 Ver.2006 (Export) + +[Core] +FPRF = True diff --git a/Source/Core/Common/CommonPaths.h b/Source/Core/Common/CommonPaths.h index d0efa66006..474890c4d8 100644 --- a/Source/Core/Common/CommonPaths.h +++ b/Source/Core/Common/CommonPaths.h @@ -41,11 +41,13 @@ #define USA_DIR "USA" #define JAP_DIR "JAP" #define JPN_DIR "JPN" +#define DEV_DIR "DEV" // 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 TRI_USER_DIR "Triforce" #define CONFIG_DIR "Config" #define GAMESETTINGS_DIR "GameSettings" #define MAPS_DIR "Maps" @@ -155,6 +157,7 @@ // Subdirs in Sys #define GC_SYS_DIR "GC" #define WII_SYS_DIR "Wii" +#define TRI_SYS_DIR "Triforce" // Subdirs in Config #define GRAPHICSMOD_CONFIG_DIR "GraphicMods" diff --git a/Source/Core/Common/FileUtil.cpp b/Source/Core/Common/FileUtil.cpp index 05be18b610..1dbbb4ac41 100644 --- a/Source/Core/Common/FileUtil.cpp +++ b/Source/Core/Common/FileUtil.cpp @@ -838,6 +838,7 @@ static void RebuildUserDirectories(unsigned int dir_index) case D_USER_IDX: s_user_paths[D_GCUSER_IDX] = s_user_paths[D_USER_IDX] + GC_USER_DIR DIR_SEP; s_user_paths[D_WIIROOT_IDX] = s_user_paths[D_USER_IDX] + WII_USER_DIR DIR_SEP; + s_user_paths[D_TRIUSER_IDX] = s_user_paths[D_USER_IDX] + TRI_USER_DIR DIR_SEP; s_user_paths[D_CONFIG_IDX] = s_user_paths[D_USER_IDX] + CONFIG_DIR DIR_SEP; s_user_paths[D_GAMESETTINGS_IDX] = s_user_paths[D_USER_IDX] + GAMESETTINGS_DIR DIR_SEP; s_user_paths[D_MAPS_IDX] = s_user_paths[D_USER_IDX] + MAPS_DIR DIR_SEP; diff --git a/Source/Core/Common/FileUtil.h b/Source/Core/Common/FileUtil.h index 49f6847768..cfe6eddffb 100644 --- a/Source/Core/Common/FileUtil.h +++ b/Source/Core/Common/FileUtil.h @@ -26,7 +26,8 @@ enum { D_USER_IDX, D_GCUSER_IDX, - D_WIIROOT_IDX, // always points to User/Wii or global user-configured directory + D_WIIROOT_IDX, // always points to User/Wii or global user-configured directory + D_TRIUSER_IDX, D_SESSION_WIIROOT_IDX, // may point to minimal temporary directory for determinism D_CONFIG_IDX, // global settings D_GAMESETTINGS_IDX, // user-specified settings which override both the global and the default diff --git a/Source/Core/Common/Logging/Log.h b/Source/Core/Common/Logging/Log.h index 9e55f779cd..64d353af00 100644 --- a/Source/Core/Common/Logging/Log.h +++ b/Source/Core/Common/Logging/Log.h @@ -27,6 +27,8 @@ enum class LogType : int DSP_MAIL, DSPINTERFACE, DVDINTERFACE, + AMMEDIABOARD, + AMMEDIABOARD_NET, DYNA_REC, EXPANSIONINTERFACE, FILEMON, @@ -58,6 +60,9 @@ enum class LogType : int PROCESSORINTERFACE, POWERPC, SERIALINTERFACE, + SERIALINTERFACE_AMBB, + SERIALINTERFACE_CARD, + SERIALINTERFACE_JVSIO, SP1, SYMBOLS, VIDEO, diff --git a/Source/Core/Common/Logging/LogManager.cpp b/Source/Core/Common/Logging/LogManager.cpp index 0755dc92ab..6c15608420 100644 --- a/Source/Core/Common/Logging/LogManager.cpp +++ b/Source/Core/Common/Logging/LogManager.cpp @@ -111,6 +111,8 @@ LogManager::LogManager() m_log[LogType::DSP_MAIL] = {"DSPMails", "DSP Mails"}; m_log[LogType::DSPINTERFACE] = {"DSP", "DSP Interface"}; m_log[LogType::DVDINTERFACE] = {"DVD", "DVD Interface"}; + m_log[LogType::AMMEDIABOARD] = {"AMMB", "AMMB Interface"}; + m_log[LogType::AMMEDIABOARD_NET] = {"AMMB_NET", "AMMB - Network"}; m_log[LogType::DYNA_REC] = {"JIT", "JIT Dynamic Recompiler"}; m_log[LogType::EXPANSIONINTERFACE] = {"EXI", "Expansion Interface"}; m_log[LogType::FILEMON] = {"FileMon", "File Monitor"}; @@ -142,6 +144,9 @@ LogManager::LogManager() m_log[LogType::PROCESSORINTERFACE] = {"PI", "Processor Interface"}; m_log[LogType::POWERPC] = {"PowerPC", "PowerPC IBM CPU"}; m_log[LogType::SERIALINTERFACE] = {"SI", "Serial Interface"}; + m_log[LogType::SERIALINTERFACE_AMBB] = {"SI_AMBB", "AMBB Interface"}; + m_log[LogType::SERIALINTERFACE_CARD] = {"SI_CARD", "CARD Interface"}; + m_log[LogType::SERIALINTERFACE_JVSIO] = {"SI_JVS", "JVS-I/O"}; m_log[LogType::SP1] = {"SP1", "Serial Port 1"}; m_log[LogType::SYMBOLS] = {"SYMBOLS", "Symbols"}; m_log[LogType::VIDEO] = {"Video", "Video Backend"}; diff --git a/Source/Core/Core/Boot/Boot.cpp b/Source/Core/Core/Boot/Boot.cpp index 1f87d06281..2ccf0cdcc4 100644 --- a/Source/Core/Core/Boot/Boot.cpp +++ b/Source/Core/Core/Boot/Boot.cpp @@ -36,6 +36,7 @@ #include "Core/ConfigManager.h" #include "Core/FifoPlayer/FifoPlayer.h" #include "Core/HLE/HLE.h" +#include "Core/HW/DVD/AMMediaboard.h" #include "Core/HW/DVD/DVDInterface.h" #include "Core/HW/EXI/EXI_DeviceIPL.h" #include "Core/HW/Memmap.h" @@ -373,7 +374,8 @@ bool CBoot::Load_BS2(Core::System& system, const std::string& boot_rom_filename) constexpr u32 PAL_v1_0 = 0x4F319F43; // DOL-101(EUR) (PAL Revision 1.2) constexpr u32 PAL_v1_2 = 0xAD1B7F16; - constexpr u32 Triforce = 0xD1883221; // The Triforce's special IPL + // Triforce Arcade IPL (DEV Revision 1.0) + constexpr u32 Triforce = 0xD1883221; // Load the IPL ROM dump, limited to 2MiB which is the size of the official IPLs. constexpr size_t max_ipl_size = 2 * 1024 * 1024; @@ -407,6 +409,7 @@ bool CBoot::Load_BS2(Core::System& system, const std::string& boot_rom_filename) break; case Triforce: known_ipl = true; + break; default: PanicAlertFmtT("The IPL file is not a known good dump. (CRC32: {0:x})", ipl_hash); break; @@ -457,6 +460,14 @@ bool CBoot::Load_BS2(Core::System& system, const std::string& boot_rom_filename) ppc_state.spr[SPR_DBAT3L] = 0xfff00001; SetupBAT(system, /*is_wii*/ false); + if (ipl_hash == Triforce) + { + HLE::Patch(system, 0x813048B8, "OSReport"); + HLE::Patch(system, 0x8130095C, "OSReport"); // Apploader + + AMMediaboard::FirmwareMap(true); + } + ppc_state.pc = 0x81200150; system.GetPowerPC().MSRUpdated(); @@ -490,6 +501,12 @@ bool CBoot::BootUp(Core::System& system, const Core::CPUThreadGuard& guard, { SConfig& config = SConfig::GetInstance(); + // Triforce systems are region free and always must use the NTSC video mode + if (system.IsTriforce()) + { + config.m_region = DiscIO::Region::NTSC_J; + } + // PAL Wii uses NTSC framerate and linecount in 60Hz modes system.GetVideoInterface().Preset(DiscIO::IsNTSC(config.m_region) || (system.IsWii() && Config::Get(Config::SYSCONF_PAL60))); diff --git a/Source/Core/Core/Boot/Boot_BS2Emu.cpp b/Source/Core/Core/Boot/Boot_BS2Emu.cpp index 3f88b29e3b..31ce5c8c8b 100644 --- a/Source/Core/Core/Boot/Boot_BS2Emu.cpp +++ b/Source/Core/Core/Boot/Boot_BS2Emu.cpp @@ -21,6 +21,7 @@ #include "Core/Core.h" #include "Core/Debugger/BranchWatch.h" #include "Core/HLE/HLE.h" +#include "Core/HW/DVD/AMMediaboard.h" #include "Core/HW/DVD/DVDInterface.h" #include "Core/HW/EXI/EXI_DeviceIPL.h" #include "Core/HW/Memmap.h" @@ -228,12 +229,20 @@ bool CBoot::RunApploader(Core::System& system, const Core::CPUThreadGuard& guard ppc_state.pc = ppc_state.gpr[3]; branch_watch.SetRecordingActive(guard, resume_branch_watch); - // Blank out session key (https://debugmo.de/2008/05/part-2-dumping-the-media-board/) - if (volume.GetVolumeType() == DiscIO::Platform::Triforce) + + if (system.IsTriforce()) { auto& memory = system.GetMemory(); + const u32 disc_size = volume.GetDataSize(); - memory.Memset(0, 0, 12); + // Load game into RAM, like on the actual Triforce + u8* dimm = AMMediaboard::InitDIMM(disc_size); + + volume.Read(0, disc_size, dimm, DiscIO::PARTITION_NONE); + + // Triforce disc register obfuscation + AMMediaboard::InitKeys(memory.Read_U32(0), memory.Read_U32(4), memory.Read_U32(8)); + AMMediaboard::FirmwareMap(false); } return true; diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index 85e446e2a0..e0cad85863 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -181,6 +181,8 @@ add_library(core HW/DSPLLE/DSPSymbols.h HW/DVD/DVDInterface.cpp HW/DVD/DVDInterface.h + HW/DVD/AMMediaboard.cpp + HW/DVD/AMMediaboard.h HW/DVD/DVDMath.cpp HW/DVD/DVDMath.h HW/DVD/DVDThread.cpp @@ -274,6 +276,8 @@ add_library(core HW/SI/SI_DeviceGCSteeringWheel.h HW/SI/SI_DeviceKeyboard.cpp HW/SI/SI_DeviceKeyboard.h + HW/SI/SI_DeviceAMBaseboard.cpp + HW/SI/SI_DeviceAMBaseboard.h HW/SI/SI_DeviceNull.cpp HW/SI/SI_DeviceNull.h HW/SI/SI.cpp diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index d11d48c898..3d370c8b1e 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -20,6 +20,7 @@ #include "Common/Version.h" #include "Core/AchievementManager.h" #include "Core/Config/DefaultLocale.h" +#include "Core/Core.h" #include "Core/HW/EXI/EXI.h" #include "Core/HW/EXI/EXI_Device.h" #include "Core/HW/GCMemcard/GCMemcard.h" @@ -27,6 +28,7 @@ #include "Core/HW/Memmap.h" #include "Core/HW/SI/SI_Device.h" #include "Core/PowerPC/PowerPC.h" +#include "Core/System.h" #include "Core/USBUtils.h" #include "DiscIO/Enums.h" #include "VideoCommon/VideoBackendBase.h" @@ -672,6 +674,9 @@ const char* GetDirectoryForRegion(DiscIO::Region region, RegionDirectoryStyle st ASSERT_MSG(BOOT, false, "NTSC-K is not a valid GameCube region"); return style == RegionDirectoryStyle::Legacy ? JAP_DIR : JPN_DIR; + case DiscIO::Region::DEV: + return DEV_DIR; + default: ASSERT_MSG(BOOT, false, "Default case should not be reached"); return EUR_DIR; @@ -725,6 +730,7 @@ std::string GetMemcardPath(std::string configured_filename, ExpansionInterface:: constexpr std::string_view us_region = "." USA_DIR; constexpr std::string_view jp_region = "." JAP_DIR; constexpr std::string_view eu_region = "." EUR_DIR; + constexpr std::string_view dv_region = "." DEV_DIR; std::optional path_region = std::nullopt; if (name.ends_with(us_region)) { @@ -741,6 +747,11 @@ std::string GetMemcardPath(std::string configured_filename, ExpansionInterface:: name = name.substr(0, name.size() - eu_region.size()); path_region = DiscIO::Region::PAL; } + else if (name.ends_with(dv_region)) + { + name = name.substr(0, name.size() - dv_region.size()); + path_region = DiscIO::Region::DEV; + } const DiscIO::Region used_region = region ? *region : (path_region ? *path_region : Config::Get(Config::MAIN_FALLBACK_REGION)); @@ -784,6 +795,7 @@ std::string GetGCIFolderPath(std::string configured_folder, ExpansionInterface:: constexpr std::string_view us_region = "/" USA_DIR; constexpr std::string_view jp_region = "/" JPN_DIR; constexpr std::string_view eu_region = "/" EUR_DIR; + constexpr std::string_view dv_region = "/" DEV_DIR; std::string_view base_path = configured_folder; std::optional path_region = std::nullopt; if (base_path.ends_with(us_region)) @@ -801,6 +813,11 @@ std::string GetGCIFolderPath(std::string configured_folder, ExpansionInterface:: base_path = base_path.substr(0, base_path.size() - eu_region.size()); path_region = DiscIO::Region::PAL; } + else if (base_path.ends_with(dv_region)) + { + base_path = base_path.substr(0, base_path.size() - dv_region.size()); + path_region = DiscIO::Region::DEV; + } const DiscIO::Region used_region = region ? *region : (path_region ? *path_region : Config::Get(Config::MAIN_FALLBACK_REGION)); diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index 4268b41481..8404cfff07 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -142,12 +142,6 @@ const std::string SConfig::GetTitleDescription() const return m_title_description; } -std::string SConfig::GetTriforceID() const -{ - std::lock_guard lock(m_metadata_lock); - return m_triforce_id; -} - u64 SConfig::GetTitleID() const { std::lock_guard lock(m_metadata_lock); @@ -163,7 +157,7 @@ u16 SConfig::GetRevision() const void SConfig::ResetRunningGameMetadata() { std::lock_guard lock(m_metadata_lock); - SetRunningGameMetadata("00000000", "", "", 0, 0, DiscIO::Region::Unknown); + SetRunningGameMetadata("00000000", "", 0, 0, DiscIO::Region::Unknown); } void SConfig::SetRunningGameMetadata(const DiscIO::Volume& volume, @@ -172,14 +166,14 @@ void SConfig::SetRunningGameMetadata(const DiscIO::Volume& volume, std::lock_guard lock(m_metadata_lock); if (partition == volume.GetGamePartition()) { - SetRunningGameMetadata(volume.GetGameID(), volume.GetGameTDBID(), volume.GetTriforceID(), + SetRunningGameMetadata(volume.GetGameID(), volume.GetGameTDBID(), volume.GetTitleID().value_or(0), volume.GetRevision().value_or(0), volume.GetRegion()); } else { SetRunningGameMetadata(volume.GetGameID(partition), volume.GetGameTDBID(partition), - volume.GetTriforceID(), volume.GetTitleID(partition).value_or(0), + volume.GetTitleID(partition).value_or(0), volume.GetRevision(partition).value_or(0), volume.GetRegion()); } } @@ -197,28 +191,25 @@ void SConfig::SetRunningGameMetadata(const IOS::ES::TMDReader& tmd, DiscIO::Plat !Core::System::GetInstance().GetDVDInterface().UpdateRunningGameMetadata(tmd_title_id)) { // If not launching a disc game, just read everything from the TMD. - SetRunningGameMetadata(tmd.GetGameID(), tmd.GetGameTDBID(), "", tmd_title_id, - tmd.GetTitleVersion(), tmd.GetRegion()); + SetRunningGameMetadata(tmd.GetGameID(), tmd.GetGameTDBID(), tmd_title_id, tmd.GetTitleVersion(), + tmd.GetRegion()); } } void SConfig::SetRunningGameMetadata(const std::string& game_id) { std::lock_guard lock(m_metadata_lock); - SetRunningGameMetadata(game_id, "", "", 0, 0, DiscIO::Region::Unknown); + SetRunningGameMetadata(game_id, "", 0, 0, DiscIO::Region::Unknown); } void SConfig::SetRunningGameMetadata(const std::string& game_id, const std::string& gametdb_id, - std::string triforce_id, u64 title_id, u16 revision, - DiscIO::Region region) + u64 title_id, u16 revision, DiscIO::Region region) { std::lock_guard lock(m_metadata_lock); const bool was_changed = m_game_id != game_id || m_gametdb_id != gametdb_id || - m_triforce_id != triforce_id || m_title_id != title_id || - m_revision != revision; + m_title_id != title_id || m_revision != revision; m_game_id = game_id; m_gametdb_id = gametdb_id; - m_triforce_id = triforce_id; m_title_id = title_id; m_revision = revision; @@ -251,7 +242,7 @@ void SConfig::SetRunningGameMetadata(const std::string& game_id, const std::stri const Core::TitleDatabase title_database; auto& system = Core::System::GetInstance(); const DiscIO::Language language = GetLanguageAdjustedForRegion(system.IsWii(), region); - m_title_name = title_database.GetTitleName(m_gametdb_id, m_triforce_id, language); + m_title_name = title_database.GetTitleName(m_gametdb_id, language); m_title_description = title_database.Describe(m_gametdb_id, language); NOTICE_LOG_FMT(CORE, "Active title: {}", m_title_description); Host_TitleChanged(); @@ -458,6 +449,9 @@ bool SConfig::SetPathsAndGameMetadata(Core::System& system, const BootParameters if (m_region == DiscIO::Region::Unknown) m_region = Config::Get(Config::MAIN_FALLBACK_REGION); + if (system.IsTriforce()) + m_region = DiscIO::Region::DEV; + // Set up paths const std::string region_dir = Config::GetDirectoryForRegion(Config::ToGameCubeRegion(m_region)); m_strSRAM = File::GetUserPath(F_GCSRAM_IDX); diff --git a/Source/Core/Core/ConfigManager.h b/Source/Core/Core/ConfigManager.h index b2e27892b9..39dbc6d39a 100644 --- a/Source/Core/Core/ConfigManager.h +++ b/Source/Core/Core/ConfigManager.h @@ -54,7 +54,6 @@ struct SConfig // files std::string m_strBootROM; std::string m_strSRAM; - std::string m_debugger_game_id; // TODO: remove this as soon as the ticket view hack in IOS/ES/Views is dropped. @@ -64,7 +63,6 @@ struct SConfig const std::string GetGameTDBID() const; const std::string GetTitleName() const; const std::string GetTitleDescription() const; - std::string GetTriforceID() const; u64 GetTitleID() const; u16 GetRevision() const; void ResetRunningGameMetadata(); @@ -121,15 +119,13 @@ private: static void ReloadTextures(Core::System& system); void SetRunningGameMetadata(const std::string& game_id, const std::string& gametdb_id, - std::string triforce_id, u64 title_id, u16 revision, - DiscIO::Region region); + u64 title_id, u16 revision, DiscIO::Region region); static SConfig* m_Instance; mutable std::recursive_mutex m_metadata_lock; std::string m_game_id; std::string m_gametdb_id; - std::string m_triforce_id; std::string m_title_name; std::string m_title_description; u64 m_title_id; diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index bdae112865..0ddf5b11d1 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -51,6 +51,7 @@ #include "Core/HLE/HLE.h" #include "Core/HW/CPU.h" #include "Core/HW/DSP.h" +#include "Core/HW/DVD/AMMediaboard.h" #include "Core/HW/EXI/EXI.h" #include "Core/HW/GBAPad.h" #include "Core/HW/GCKeyboard.h" @@ -288,6 +289,13 @@ void Stop(Core::System& system) // - Hammertime! // Stop the CPU INFO_LOG_FMT(CONSOLE, "{}", StopMessage(true, "Stop CPU")); system.GetCPU().Stop(); + + const ExpansionInterface::EXIDeviceType type = Config::Get(Config::MAIN_SERIAL_PORT_1); + + if ((type == ExpansionInterface::EXIDeviceType::Baseboard)) + { + AMMediaboard::Shutdown(); + } } void DeclareAsCPUThread() diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp new file mode 100644 index 0000000000..5bd3e2c036 --- /dev/null +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -0,0 +1,1850 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/HW/DVD/AMMediaboard.h" + +#include +#include + +#include + +#include "Common/Buffer.h" +#include "Common/CommonTypes.h" +#include "Common/FileUtil.h" +#include "Common/IOFile.h" +#include "Common/Logging/Log.h" + +#include "Core/Boot/Boot.h" +#include "Core/BootManager.h" +#include "Core/ConfigManager.h" +#include "Core/Core.h" +#include "Core/HLE/HLE.h" +#include "Core/HW/DVD/DVDInterface.h" +#include "Core/HW/DVD/DVDThread.h" +#include "Core/HW/EXI/EXI.h" +#include "Core/HW/EXI/EXI_DeviceBaseboard.h" +#include "Core/HW/MMIO.h" +#include "Core/HW/Memmap.h" +#include "Core/HW/SI/SI.h" +#include "Core/HW/SI/SI_Device.h" +#include "Core/IOS/Network/Socket.h" +#include "Core/Movie.h" +#include "Core/System.h" + +#include "DiscIO/DirectoryBlob.h" + +#if defined(__linux__) or defined(__APPLE__) or defined(__FreeBSD__) or defined(__NetBSD__) or \ + defined(__HAIKU__) + +#include + +#define closesocket close +#define ioctlsocket ioctl + +#define WSAEWOULDBLOCK 10035L +#define SOCKET_ERROR (-1) + +typedef int SOCKET; + +#define INVALID_SOCKET (SOCKET)(~0) + +static int WSAGetLastError(void) +{ + switch (errno) + { + case EWOULDBLOCK: + return WSAEWOULDBLOCK; + default: + break; + } + + return errno; +} + +#endif + +namespace AMMediaboard +{ + +static bool s_firmware_map = false; +static bool s_test_menu = false; +static SOCKET s_namco_cam = 0; +static u32 s_timeouts[3] = {20000, 20000, 20000}; +static u32 s_last_error = SSC_SUCCESS; + +static u32 s_GCAM_key_a = 0; +static u32 s_GCAM_key_b = 0; +static u32 s_GCAM_key_c = 0; + +static File::IOFile s_netcfg; +static File::IOFile s_netctrl; +static File::IOFile s_extra; +static File::IOFile s_backup; +static File::IOFile s_dimm; + +Common::UniqueBuffer s_dimm_disc; + +static u8 s_firmware[2 * 1024 * 1024]; +static u32 s_media_buffer_32[192]; +static u8* const s_media_buffer = reinterpret_cast(s_media_buffer_32); +static u8 s_network_command_buffer[0x4FFE00]; +static u8 s_network_buffer[512 * 1024]; +static u8 s_allnet_buffer[4096]; +static u8 s_allnet_settings[0x8500]; + +constexpr char s_allnet_reply[] = { + "uri=http://" + "sega.com&host=sega.com&nickname=sega&name=sega&year=2025&month=08&day=16&hour=21&minute=10&" + "second=12&place_id=1234&setting=0x123®ion0=jap®ion_name0=japan®ion_name1=usa®ion_" + "name2=asia®ion_name3=export&end"}; + +// Sockets FDs are required to go from 0 to 63. +// Games use the FD as indexes so we have to workaround it. + +static SOCKET s_sockets[64]; + +static SOCKET socket_(int af, int type, int protocol) +{ + for (u32 i = 1; i < 64; ++i) + { + if (s_sockets[i] == SOCKET_ERROR) + { + s_sockets[i] = socket(af, type, protocol); + return i; + } + } + + // Out of sockets + return SOCKET_ERROR; +} + +static SOCKET accept_(int fd, sockaddr* addr, int* len) +{ + for (u32 i = 1; i < 64; ++i) + { + if (s_sockets[i] == SOCKET_ERROR) + { + s_sockets[i] = accept(fd, addr, (socklen_t*)len); + if (s_sockets[i] == SOCKET_ERROR) + return SOCKET_ERROR; + return i; + } + } + + // Out of sockets + return SOCKET_ERROR; +} + +static inline void PrintMBBuffer(u32 address, u32 length) +{ + const auto& system = Core::System::GetInstance(); + const auto& memory = system.GetMemory(); + + for (u32 i = 0; i < length; i += 0x10) + { + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: {:08x} {:08x} {:08x} {:08x}", memory.Read_U32(address + i), + memory.Read_U32(address + i + 4), memory.Read_U32(address + i + 8), + memory.Read_U32(address + i + 12)); + } +} + +void FirmwareMap(bool on) +{ + s_firmware_map = on; +} + +void InitKeys(u32 key_a, u32 key_b, u32 key_c) +{ + s_GCAM_key_a = key_a; + s_GCAM_key_b = key_b; + s_GCAM_key_c = key_c; +} + +static File::IOFile OpenOrCreateFile(const std::string& filename) +{ + // Try opening for read/write first + if (File::Exists(filename)) + return File::IOFile(filename, "rb+"); + + // Create new file + return File::IOFile(filename, "wb+"); +} + +void Init() +{ + std::ranges::fill(s_media_buffer_32, 0); + std::ranges::fill(s_network_buffer, 0); + std::ranges::fill(s_network_command_buffer, 0); + std::ranges::fill(s_firmware, -1); + std::ranges::fill(s_sockets, SOCKET_ERROR); + std::ranges::fill(s_allnet_buffer, 0); + std::ranges::fill(s_allnet_settings, 0); + + s_firmware_map = false; + s_test_menu = false; + + s_last_error = SSC_SUCCESS; + + s_GCAM_key_a = 0; + s_GCAM_key_b = 0; + s_GCAM_key_c = 0; + + const std::string base_path = File::GetUserPath(D_TRIUSER_IDX); + + s_netcfg = OpenOrCreateFile(base_path + "trinetcfg.bin"); + s_netctrl = OpenOrCreateFile(base_path + "trinetctrl.bin"); + s_extra = OpenOrCreateFile(base_path + "triextra.bin"); + s_dimm = OpenOrCreateFile(base_path + "tridimm_" + SConfig::GetInstance().GetGameID() + ".bin"); + s_backup = OpenOrCreateFile(base_path + "backup_" + SConfig::GetInstance().GetGameID() + ".bin"); + + if (!s_netcfg.IsOpen()) + PanicAlertFmt("Failed to open/create: {}", base_path + "trinetcfg.bin"); + if (!s_netctrl.IsOpen()) + PanicAlertFmt("Failed to open/create: {}", base_path + "trinetctrl.bin"); + if (!s_extra.IsOpen()) + PanicAlertFmt("Failed to open/create: {}", base_path + "triextra.bin"); + if (!s_dimm.IsOpen()) + PanicAlertFmt("Failed to open/create: {}", base_path + "tridimm.bin"); + if (!s_backup.IsOpen()) + PanicAlertFmt("Failed to open/create: {}", base_path + "backup.bin"); + + // This is the firmware for the Triforce + const std::string sega_boot_filename = base_path + "segaboot.gcm"; + + if (!File::Exists(sega_boot_filename)) + { + PanicAlertFmt("Failed to open segaboot.gcm({}), which is required for test menus.", + sega_boot_filename.c_str()); + return; + } + + File::IOFile sega_boot(sega_boot_filename, "rb+"); + if (!sega_boot.IsOpen()) + { + PanicAlertFmt("Failed to read: {}", sega_boot_filename); + return; + } + + const u64 length = std::min(sega_boot.GetSize(), sizeof(s_firmware)); + sega_boot.ReadBytes(s_firmware, length); + + s_test_menu = true; +} + +u8* InitDIMM(u32 size) +{ + if (size == 0) + return nullptr; + + if (!s_dimm_disc.size()) + { + s_dimm_disc.reset(size); + if (!s_dimm_disc.size()) + { + PanicAlertFmt("Failed to allocate DIMM memory."); + return nullptr; + } + } + + s_firmware_map = 0; + return s_dimm_disc.data(); +} + +static s32 NetDIMMAccept(int fd, sockaddr* addr, int* len) +{ + SOCKET clientSock = INVALID_SOCKET; + fd_set readfds; + + FD_ZERO(&readfds); + FD_SET(fd, &readfds); + + timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 10000; // 10 milliseconds + + const int result = select(fd + 1, &readfds, nullptr, nullptr, &timeout); + if (result > 0 && FD_ISSET(fd, &readfds)) + { + clientSock = accept_(fd, addr, len); + if (clientSock != INVALID_SOCKET) + { + s_last_error = SSC_SUCCESS; + return clientSock; + } + else + { + s_last_error = SOCKET_ERROR; + return SOCKET_ERROR; + } + } + else if (result == 0) + { + // Timeout + s_last_error = SSC_EWOULDBLOCK; + } + else + { + // select() failed + s_last_error = SOCKET_ERROR; + } + + return SOCKET_ERROR; +} + +static s32 NetDIMMConnect(int fd, sockaddr_in* addr, int len) +{ + // All.Net Connect IP + if (addr->sin_addr.s_addr == inet_addr("192.168.150.16")) + { + addr->sin_addr.s_addr = inet_addr("127.0.0.1"); + } + + // CyCraft Connect IP + if (addr->sin_addr.s_addr == inet_addr("192.168.11.111")) + { + addr->sin_addr.s_addr = inet_addr("127.0.0.1"); + } + + // NAMCO Camera ( IPs are: 192.168.29.104-108 ) + if ((addr->sin_addr.s_addr & 0xFFFFFF00) == 0xC0A81D00) + { + addr->sin_addr.s_addr = inet_addr("127.0.0.1"); + + // BUG: An invalid family value is being used + addr->sin_family = htons(AF_INET); + s_namco_cam = fd; + } + + // Key of Avalon Client + if (addr->sin_addr.s_addr == inet_addr("192.168.13.1")) + { + addr->sin_addr.s_addr = inet_addr("10.0.0.45"); + } + + addr->sin_family = Common::swap16(addr->sin_family); + + u_long val = 1; + // Set socket to non-blocking + ioctlsocket(fd, FIONBIO, &val); + + int ret = connect(fd, reinterpret_cast(addr), len); + const int err = WSAGetLastError(); + + if (ret == SOCKET_ERROR && err == WSAEWOULDBLOCK) + { + fd_set writefds; + FD_ZERO(&writefds); + FD_SET(fd, &writefds); + + timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = s_timeouts[0]; + + ret = select(fd + 1, nullptr, &writefds, nullptr, &timeout); + if (ret > 0 && FD_ISSET(fd, &writefds)) + { + int so_error = 0; + socklen_t optlen = sizeof(so_error); + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, reinterpret_cast(&so_error), &optlen) == 0 && + so_error == 0) + { + s_last_error = SSC_SUCCESS; + ret = 0; + } + else + { + s_last_error = SOCKET_ERROR; + ret = SOCKET_ERROR; + } + } + else if (ret == 0) + { + // Timeout + s_last_error = SSC_EWOULDBLOCK; + ret = SOCKET_ERROR; + } + else + { + // select() failed + s_last_error = SOCKET_ERROR; + ret = SOCKET_ERROR; + } + } + else if (ret == SOCKET_ERROR) + { + // Immediate failure (e.g. WSAECONNREFUSED) + s_last_error = ret; + } + else + { + s_last_error = SSC_SUCCESS; + } + + // Restore blocking mode + val = 0; + ioctlsocket(fd, FIONBIO, &val); + + return ret; +} + +static void FileWriteData(File::IOFile* file, u32 seek_pos, const u8* data, std::size_t length) +{ + file->Seek(seek_pos, File::SeekOrigin::Begin); + file->WriteBytes(data, length); + file->Flush(); +} + +u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 length) +{ + auto& system = Core::System::GetInstance(); + auto& memory = system.GetMemory(); + + DICMDBUF[0] ^= s_GCAM_key_a; + DICMDBUF[1] ^= s_GCAM_key_b; + DICMDBUF[2] ^= s_GCAM_key_c; + + const u32 seed = DICMDBUF[0] >> 16; + + s_GCAM_key_a *= seed; + s_GCAM_key_b *= seed; + s_GCAM_key_c *= seed; + + // Key setup for Triforce IPL: + // These RAM offsets always hold the key for the next command. Since two dummy commands are sent + // before any real ones, you can just use the key from RAM without missing a real command. + + if (s_GCAM_key_a == 0) + { + if (memory.Read_U32(0)) + { + HLE::Patch(system, 0x813048B8, "OSReport"); + HLE::Patch(system, 0x8130095C, "OSReport"); // Apploader + + InitKeys(memory.Read_U32(0), memory.Read_U32(4), memory.Read_U32(8)); + } + } + + const u32 command = DICMDBUF[0] << 24; + const u32 offset = DICMDBUF[1] << 2; + + DEBUG_LOG_FMT(AMMEDIABOARD, + "GC-AM: {:08x} {:08x} DMA=addr:{:08x},len:{:08x} Keys: {:08x} {:08x} {:08x}", + command, offset, address, length, s_GCAM_key_a, s_GCAM_key_b, s_GCAM_key_c); + + // Test mode + if (offset == 0x0002440) + { + // Set by OSResetSystem + if (memory.Read_U32(0x811FFF00) == 1) + { + // Don't map firmware while in SegaBoot + if (memory.Read_U32(0x8006BF70) != 0x0A536567) + { + s_firmware_map = 1; + } + } + } + + switch (AMMBDICommand(command >> 24)) + { + case AMMBDICommand::Inquiry: + if (s_firmware_map) + { + s_firmware_map = false; + } + + // Returned value is used to set the protocol version. + switch (GetGameType()) + { + default: + *DIIMMBUF = Version1; + return 0; + case VirtuaStriker4_2006: + case KeyOfAvalon: + case MarioKartGP: + case MarioKartGP2: + case FirmwareUpdate: + *DIIMMBUF = Version2; + return 0; + } + break; + case AMMBDICommand::Read: + if ((offset & 0x8FFF0000) == 0x80000000) + { + switch (offset) + { + case MediaBoardStatus1: + memory.Write_U16(Common::swap16(0x0100), address); + break; + case MediaBoardStatus2: + memset(memory.GetSpanForAddress(address).data(), 0, length); + break; + case MediaBoardStatus3: + memset(memory.GetSpanForAddress(address).data(), 0xFF, length); + // DIMM size (512MB) + memory.Write_U32(Common::swap32(0x20000000), address); + // GCAM signature + memory.Write_U32(0x4743414D, address + 4); + break; + case 0x80000100: + memory.Write_U32(Common::swap32((u32)0x001F1F1F), address); + break; + case FirmwareStatus1: + memory.Write_U32(Common::swap32((u32)0x01FA), address); + break; + case FirmwareStatus2: + memory.Write_U32(Common::swap32((u32)1), address); + break; + case 0x80000160: + memory.Write_U32(0x00001E00, address); + break; + case 0x80000180: + memory.Write_U32(0, address); + break; + case 0x800001A0: + memory.Write_U32(0xFFFFFFFF, address); + break; + default: + PrintMBBuffer(address, length); + PanicAlertFmtT("Unhandled Media Board Read:{0:08x}", offset); + break; + } + return 0; + } + + // Network configuration + if (offset == 0x00000000 && length == 0x80) + { + s_netcfg.Seek(0, File::SeekOrigin::Begin); + s_netcfg.ReadBytes(memory.GetSpanForAddress(address).data(), length); + return 0; + } + + // media crc check on/off + if (offset == DIMMExtraSettings && length == 0x20) + { + s_extra.Seek(0, File::SeekOrigin::Begin); + s_extra.ReadBytes(memory.GetSpanForAddress(address).data(), length); + return 0; + } + + // DIMM memory (8MB) + if (offset >= DIMMMemory && offset <= 0x1F800000) + { + const u32 dimm_offset = offset - DIMMMemory; + s_dimm.Seek(dimm_offset, File::SeekOrigin::Begin); + s_dimm.ReadBytes(memory.GetSpanForAddress(address).data(), length); + return 0; + } + + if (offset >= AllNetBuffer && offset < 0x89011000) + { + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read All.Net Buffer ({:08x},{})", offset, length); + + // Fake reply + memcpy(memory.GetSpanForAddress(address).data(), s_allnet_reply, sizeof(s_allnet_reply)); + return 0; + } + + if (offset >= DIMMCommandVersion1 && offset < 0x1F900040) + { + const u32 dimm_offset = offset - DIMMCommandVersion1; + memcpy(memory.GetSpanForAddress(address).data(), s_media_buffer + dimm_offset, length); + + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read MEDIA BOARD COMM AREA (1) ({:08x},{})", offset, + length); + PrintMBBuffer(address, length); + return 0; + } + + if (offset >= NetworkBufferAddress4 && offset < 0x89240000) + { + const u32 dimm_offset = offset - NetworkBufferAddress4; + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read NETWORK BUFFER (4) ({:08x},{})", offset, length); + memcpy(memory.GetSpanForAddress(address).data(), s_network_buffer + dimm_offset, length); + return 0; + } + + if (offset >= NetworkBufferAddress5 && offset < 0x1FB10000) + { + const u32 dimm_offset = offset - NetworkBufferAddress5; + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read NETWORK BUFFER (5) ({:08x},{})", offset, length); + memcpy(memory.GetSpanForAddress(address).data(), s_network_buffer + dimm_offset, length); + return 0; + } + + if (offset >= NetworkCommandAddress && offset < NetworkBufferAddress2) + { + const u32 dimm_offset = offset - NetworkCommandAddress; + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read NETWORK COMMAND BUFFER ({:08x},{})", offset, length); + memcpy(memory.GetSpanForAddress(address).data(), s_network_command_buffer + dimm_offset, + length); + return 0; + } + + if (offset >= NetworkCommandAddress2 && offset < 0x89060200) + { + const u32 dimm_offset = offset - NetworkCommandAddress2; + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read NETWORK COMMAND BUFFER (2) ({:08x},{})", offset, + length); + memcpy(memory.GetSpanForAddress(address).data(), s_network_command_buffer + dimm_offset, + length); + return 0; + } + + if (offset >= NetworkBufferAddress1 && offset < 0x1FA10000) + { + const u32 dimm_offset = offset - NetworkBufferAddress1; + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read NETWORK BUFFER (1) ({:08x},{})", offset, length); + memcpy(memory.GetSpanForAddress(address).data(), s_network_buffer + dimm_offset, length); + return 0; + } + + if (offset >= NetworkBufferAddress2 && offset < 0x1FD10000) + { + const u32 dimm_offset = offset - NetworkBufferAddress2; + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read NETWORK BUFFER (2) ({:08x},{})", offset, length); + memcpy(memory.GetSpanForAddress(address).data(), s_network_buffer + dimm_offset, length); + return 0; + } + + if (offset >= NetworkBufferAddress3 && offset < 0x89110000) + { + const u32 dimm_offset = offset - NetworkBufferAddress3; + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read NETWORK BUFFER (3) ({:08x},{})", offset, length); + memcpy(memory.GetSpanForAddress(address).data(), s_network_buffer + dimm_offset, length); + return 0; + } + + if (offset >= DIMMCommandVersion2 && offset < 0x84000060) + { + const u32 dimm_offset = offset - DIMMCommandVersion2; + memcpy(memory.GetSpanForAddress(address).data(), s_media_buffer + dimm_offset, length); + + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read MEDIA BOARD COMM AREA (2) ({:08x},{})", offset, + length); + PrintMBBuffer(address, length); + return 0; + } + + if (offset == DIMMCommandExecute2) + { + const AMMBCommand ammb_command = + static_cast(*reinterpret_cast(s_media_buffer + 0x202)); + + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Execute command: (2){0:04X}", + static_cast(ammb_command)); + + memcpy(s_media_buffer, s_media_buffer + 0x200, 0x20); + memset(s_media_buffer + 0x200, 0, 0x20); + s_media_buffer[0x204] = 1; + + switch (ammb_command) + { + case AMMBCommand::Unknown_001: + s_media_buffer_32[1] = 1; + break; + case AMMBCommand::GetNetworkFirmVersion: + s_media_buffer_32[1] = 0x1305; // Version: 13.05 + s_media_buffer[6] = 1; // Type: VxWorks + break; + case AMMBCommand::GetSystemFlags: + s_media_buffer[4] = 1; + s_media_buffer[6] = NANDMaskBoardNAND; + s_media_buffer[7] = 1; + break; + // Empty reply + case AMMBCommand::Unknown_103: + break; + case AMMBCommand::Unknown_104: + s_media_buffer[4] = 1; + break; + case AMMBCommand::Accept: + { + const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + int ret = -1; + sockaddr addr; + int len = 0; + + // Handle optional parameters + if (s_media_buffer_32[3] == 0 || s_media_buffer_32[4] == 0) + { + ret = NetDIMMAccept(fd, nullptr, nullptr); + } + else + { + const u32 addr_off = s_media_buffer_32[3] - NetworkCommandAddress2; + const u32 len_off = s_media_buffer_32[4] - NetworkCommandAddress2; + + ret = NetDIMMAccept(fd, &addr, &len); + if (len) + { + memcpy((s_network_command_buffer + addr_off), &addr, len); + memcpy((s_network_command_buffer + len_off), &len, sizeof(int)); + } + } + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: accept( {}({}) ):{}\n", fd, s_media_buffer_32[2], + ret); + s_media_buffer_32[1] = ret; + break; + } + case AMMBCommand::Bind: + { + sockaddr_in addr; + + const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const u32 off = s_media_buffer_32[3] - NetworkCommandAddress2; + const u32 len = s_media_buffer_32[4]; + + memcpy((void*)&addr, s_network_command_buffer + off, sizeof(sockaddr_in)); + + addr.sin_family = Common::swap16(addr.sin_family); + + // Triforce titles typically rely on hardcoded IP addresses. + // This behavior has been modified to bind to the wildcard address instead. + // + // addr.sin_addr.s_addr = htonl(addr.sin_addr.s_addr); + + addr.sin_addr.s_addr = INADDR_ANY; + + const int ret = bind(fd, (const sockaddr*)&addr, len); + const int err = WSAGetLastError(); + + if (ret < 0) + PanicAlertFmt("Socket Bind Failed with{0}", err); + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: bind( {}, ({},{:08x}:{}), {} ):{} ({})\n", fd, + addr.sin_family, addr.sin_addr.s_addr, Common::swap16(addr.sin_port), len, + ret, err); + + s_media_buffer_32[1] = ret; + s_last_error = SSC_SUCCESS; + break; + } + case AMMBCommand::Closesocket: + { + const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + + const int ret = closesocket(fd); + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: closesocket( {}({}) ):{}\n", fd, + s_media_buffer_32[2], ret); + + s_sockets[SocketCheck(s_media_buffer_32[2])] = SOCKET_ERROR; + + s_media_buffer_32[1] = ret; + s_last_error = SSC_SUCCESS; + break; + } + case AMMBCommand::Connect: + { + sockaddr_in addr; + + const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const u32 off = s_media_buffer_32[3] - NetworkCommandAddress2; + const u32 len = s_media_buffer_32[4]; + + memcpy((void*)&addr, s_network_command_buffer + off, sizeof(sockaddr_in)); + + const int ret = NetDIMMConnect(fd, &addr, len); + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: connect( {}({}), ({},{}:{}), {} ):{}\n", fd, + s_media_buffer_32[2], addr.sin_family, inet_ntoa(addr.sin_addr), + Common::swap16(addr.sin_port), len, ret); + + s_media_buffer[1] = s_media_buffer[8]; + s_media_buffer_32[1] = ret; + break; + } + case AMMBCommand::InetAddr: + { + const u32 ip = inet_addr((char*)s_network_command_buffer); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: InetAddr( {} )\n", + (char*)s_network_command_buffer); + + s_media_buffer[1] = s_media_buffer[8]; + s_media_buffer_32[1] = Common::swap32(ip); + break; + } + case AMMBCommand::Listen: + { + const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const u32 backlog = s_media_buffer_32[3]; + + const int ret = listen(fd, backlog); + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: listen( {}, {} ):{:d}\n", fd, backlog, ret); + + s_media_buffer[1] = s_media_buffer[8]; + s_media_buffer_32[1] = ret; + break; + } + case AMMBCommand::Recv: + { + const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const u32 off = s_media_buffer_32[3]; + u32 len = s_media_buffer_32[4]; + + if (len >= sizeof(s_network_buffer)) + { + len = sizeof(s_network_buffer); + } + + u8* buffer = reinterpret_cast(s_network_buffer + off); + + if (off >= NetworkBufferAddress4 && off < NetworkBufferAddress4 + sizeof(s_network_buffer)) + { + buffer = reinterpret_cast(s_network_buffer + off - NetworkBufferAddress4); + } + else + { + PanicAlertFmt("RECV: Buffer overrun:{0} {1} ", off, len); + } + + const int ret = recv(fd, reinterpret_cast(buffer), len, 0); + const int err = WSAGetLastError(); + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: recv( {}, 0x{:08x}, {} ):{} {}\n", fd, off, len, + ret, err); + + s_media_buffer[1] = s_media_buffer[8]; + s_media_buffer_32[1] = ret; + break; + } + case AMMBCommand::Send: + { + const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + u32 off = s_media_buffer_32[3]; + const u32 len = s_media_buffer_32[4]; + + if (off >= NetworkBufferAddress3 && off < NetworkBufferAddress3 + sizeof(s_network_buffer)) + { + off -= NetworkBufferAddress3; + } + else + { + ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: send(error) unhandled destination:{:08x}\n", off); + } + + const int ret = send(fd, reinterpret_cast(s_network_buffer + off), len, 0); + const int err = WSAGetLastError(); + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: send( {}({}), 0x{:08x}, {} ): {} {}\n", fd, + s_media_buffer_32[2], off, len, ret, err); + + s_media_buffer[1] = s_media_buffer[8]; + s_media_buffer_32[1] = ret; + break; + } + case AMMBCommand::Socket: + { + // Protocol is not sent + const u32 af = s_media_buffer_32[2]; + const u32 type = s_media_buffer_32[3]; + + const SOCKET fd = socket_(af, type, IPPROTO_TCP); + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: socket( {}, {}, IPPROTO_TCP ):{}\n", af, type, fd); + + s_media_buffer[1] = 0; + s_media_buffer_32[1] = fd; + break; + } + case AMMBCommand::Select: + { + u32 fd = s_sockets[SocketCheck(s_media_buffer_32[2] - 1)]; + + // BUG: NAMCAM is hardcoded to call this with socket ID 0x100 which might be some magic + // thing? A valid value is needed so we use the socket from the connect. + + if (AMMediaboard::GetGameType() == MarioKartGP2) + { + if (s_media_buffer_32[2] == 256) + { + fd = s_namco_cam; + } + } + + fd_set* readfds = nullptr; + fd_set* writefds = nullptr; + fd_set* exceptfds = nullptr; + timeval timeout = {}; + timeval* timeout_src = nullptr; + + // Only one of 3, 4, 5 is ever set alongside 6 + if (s_media_buffer_32[3] && s_media_buffer_32[6]) + { + const u32 ROffset = s_media_buffer_32[6] - NetworkCommandAddress2; + readfds = reinterpret_cast(s_network_command_buffer + ROffset); + FD_ZERO(readfds); + FD_SET(fd, readfds); + + timeout_src = reinterpret_cast(s_network_command_buffer + s_media_buffer_32[3] - + NetworkCommandAddress2); + } + else if (s_media_buffer_32[4] && s_media_buffer_32[6]) + { + const u32 WOffset = s_media_buffer_32[6] - NetworkCommandAddress2; + writefds = reinterpret_cast(s_network_command_buffer + WOffset); + FD_ZERO(writefds); + FD_SET(fd, writefds); + + timeout_src = reinterpret_cast(s_network_command_buffer + s_media_buffer_32[4] - + NetworkCommandAddress2); + } + else if (s_media_buffer_32[5] && s_media_buffer_32[6]) + { + const u32 EOffset = s_media_buffer_32[6] - NetworkCommandAddress2; + exceptfds = reinterpret_cast(s_network_command_buffer + EOffset); + FD_ZERO(exceptfds); + FD_SET(fd, exceptfds); + + timeout_src = reinterpret_cast(s_network_command_buffer + s_media_buffer_32[5] - + NetworkCommandAddress2); + } + + // Copy timeout if set + if (timeout_src) + { + std::memcpy(&timeout, timeout_src, sizeof(timeval)); + } + + // BUG: The game sets timeout to two seconds + if (AMMediaboard::GetGameType() == KeyOfAvalon) + { + timeout.tv_sec = 0; + timeout.tv_usec = 1800; + } + + const int ret = + select(fd + 1, readfds, writefds, exceptfds, timeout_src ? &timeout : nullptr); + + const int err = WSAGetLastError(); + + NOTICE_LOG_FMT( + AMMEDIABOARD_NET, + "GC-AM: select( {}({}), 0x{:08x} 0x{:08x} 0x{:08x} 0x{:08x} ):{} {} {}:{} \n", fd, + s_media_buffer_32[2], s_media_buffer_32[3], s_media_buffer_32[4], s_media_buffer_32[5], + s_media_buffer_32[6], ret, err, timeout.tv_sec, timeout.tv_usec); + // hexdump( s_network_command_buffer, 0x40 ); + + s_media_buffer[1] = 0; + s_media_buffer_32[1] = ret; + break; + } + case AMMBCommand::SetSockOpt: + { + const SOCKET fd = (SOCKET)(s_sockets[SocketCheck(s_media_buffer_32[2])]); + const int level = (int)(s_media_buffer_32[3]); + const int optname = (int)(s_media_buffer_32[4]); + const char* optval = + (char*)(s_network_command_buffer + s_media_buffer_32[5] - NetworkCommandAddress2); + const int optlen = (int)(s_media_buffer_32[6]); + + const int ret = setsockopt(fd, level, optname, optval, optlen); + + const int err = WSAGetLastError(); + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, + "GC-AM: setsockopt( {:d}, {:04x}, {}, {:p}, {} ):{:d} ({})\n", fd, level, + optname, optval, optlen, ret, err); + + s_media_buffer[1] = s_media_buffer[8]; + s_media_buffer_32[1] = ret; + break; + } + case AMMBCommand::SetTimeOuts: + { + const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const u32 timeoutA = s_media_buffer_32[3]; + const u32 timeoutB = s_media_buffer_32[4]; + const u32 timeoutC = s_media_buffer_32[5]; + + s_timeouts[0] = timeoutA; + s_timeouts[1] = timeoutB; + s_timeouts[2] = timeoutC; + + int ret = 0; + + if (fd != INVALID_SOCKET) + { + ret = setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeoutB, sizeof(int)); + if (ret < 0) + { + ret = WSAGetLastError(); + } + else + { + ret = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeoutC, sizeof(int)); + if (ret < 0) + ret = WSAGetLastError(); + } + } + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: SetTimeOuts( {}, {}, {}, {} ):{}\n", fd, timeoutA, + timeoutB, timeoutC, ret); + + s_media_buffer[1] = s_media_buffer[8]; + s_media_buffer_32[1] = ret; + break; + } + case AMMBCommand::GetParambyDHCPExec: + { + const u32 value = s_media_buffer_32[2]; + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: GetParambyDHCPExec({})\n", value); + + s_media_buffer[1] = 0; + s_media_buffer_32[1] = 0; + break; + } + case AMMBCommand::ModifyMyIPaddr: + { + const u32 NetBufferOffset = *(u32*)(s_media_buffer + 8) - NetworkCommandAddress2; + + const char* IP = (char*)(s_network_command_buffer + NetBufferOffset); + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: modifyMyIPaddr({})\n", IP); + break; + } + case AMMBCommand::GetLastError: + { + const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: GetLastError( {}({}) ):{}\n", fd, + s_media_buffer_32[2], s_last_error); + + s_media_buffer[1] = s_media_buffer[8]; + s_media_buffer_32[1] = s_last_error; + } + break; + case AMMBCommand::InitLink: + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: InitLink"); + break; + case AMMBCommand::AllNetInit: + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: AllNetInit"); + break; + default: + ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: Command:{0:04x}", static_cast(ammb_command)); + ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: Command Unhandled!"); + break; + } + + s_media_buffer[3] |= 0x80; // Command complete flag + + memset(memory.GetSpanForAddress(address).data(), 0, length); + + ExpansionInterface::GenerateInterrupt(0x10); + return 0; + } + + if (offset >= DIMMCommandVersion2_2 && offset <= 0x89000200) + { + const u32 dimm_offset = offset - DIMMCommandVersion2_2; + memcpy(memory.GetSpanForAddress(address).data(), s_media_buffer + dimm_offset, length); + + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read MEDIA BOARD COMM AREA (3) ({:08x})", dimm_offset); + PrintMBBuffer(address, length); + return 0; + } + + // DIMM memory (8MB) + if (offset >= DIMMMemory2 && offset <= 0xFF800000) + { + const u32 dimm_offset = offset - DIMMMemory2; + s_dimm.Seek(dimm_offset, File::SeekOrigin::Begin); + s_dimm.ReadBytes(memory.GetSpanForAddress(address).data(), length); + return 0; + } + + if (offset >= AllNetSettings && offset <= 0x1F000000) + { + const u32 allnet_offset = offset - AllNetSettings; + memcpy(memory.GetSpanForAddress(address).data(), s_allnet_settings + allnet_offset, length); + return 0; + } + + if (offset == NetworkControl && length == 0x20) + { + s_netctrl.Seek(0, File::SeekOrigin::Begin); + s_netctrl.ReadBytes(memory.GetSpanForAddress(address).data(), length); + return 0; + } + + // Max GC disc offset + if (offset >= 0x57058000) + { + PanicAlertFmtT("Unhandled Media Board Read:{0:08x}", offset); + return 0; + } + + if (s_firmware_map) + { + memcpy(memory.GetSpanForAddress(address).data(), s_firmware + offset, length); + return 0; + } + + if (s_dimm_disc.size()) + { + memcpy(memory.GetSpanForAddress(address).data(), s_dimm_disc.data() + offset, length); + return 0; + } + + return 1; + break; + case AMMBDICommand::Write: + + // These two magic writes allow a new firmware to be programmed + if ((offset == FirmwareMagicWrite1) && (length == 0x20)) + { + s_firmware_map = true; + return 0; + } + + if ((offset == FirmwareMagicWrite2) && (length == 0x20)) + { + s_firmware_map = true; + return 0; + } + + if (s_firmware_map) + { + // Firmware memory (2MB) + if ((offset >= 0x00400000) && (offset <= 0x600000)) + { + const u32 fwoffset = offset - 0x00400000; + memcpy(s_firmware + fwoffset, memory.GetSpanForAddress(address).data(), length); + return 0; + } + } + + // Network configuration + if ((offset == 0x00000000) && (length == 0x80)) + { + FileWriteData(&s_netcfg, 0, memory.GetSpanForAddress(address).data(), length); + return 0; + } + + // media crc check on/off + if ((offset == DIMMExtraSettings) && (length == 0x20)) + { + FileWriteData(&s_extra, 0, memory.GetSpanForAddress(address).data(), length); + return 0; + } + + // Backup memory (8MB) + if ((offset >= BackupMemory) && (offset <= 0x00800000)) + { + FileWriteData(&s_backup, 0, memory.GetSpanForAddress(address).data(), length); + return 0; + } + + // DIMM memory (8MB) + if ((offset >= DIMMMemory) && (offset <= 0x1F800000)) + { + const u32 dimm_offset = offset - DIMMMemory; + FileWriteData(&s_dimm, dimm_offset, memory.GetSpanForAddress(address).data(), length); + return 0; + } + + if ((offset >= AllNetBuffer) && (offset <= 0x89011000)) + { + const u32 allnet_offset = offset - AllNetBuffer; + memcpy(s_allnet_buffer + allnet_offset, memory.GetSpanForAddress(address).data(), length); + return 0; + } + + if ((offset >= NetworkCommandAddress) && (offset < 0x1F801240)) + { + const u32 dimm_offset = offset - NetworkCommandAddress; + + memcpy(s_network_command_buffer + dimm_offset, memory.GetSpanForAddress(address).data(), + length); + + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write NETWORK COMMAND BUFFER ({:08x},{})", dimm_offset, + length); + PrintMBBuffer(address, length); + return 0; + } + + if ((offset >= NetworkCommandAddress2) && (offset <= 0x890601FF)) + { + const u32 dimm_offset = offset - NetworkCommandAddress2; + + memcpy(s_network_command_buffer + dimm_offset, memory.GetSpanForAddress(address).data(), + length); + + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write NETWORK COMMAND BUFFER (2) ({:08x},{})", dimm_offset, + length); + PrintMBBuffer(address, length); + return 0; + } + + if ((offset >= NetworkBufferAddress1) && (offset <= 0x1FA1FFFF)) + { + const u32 dimm_offset = offset - NetworkBufferAddress1; + + memcpy(s_network_buffer + dimm_offset, memory.GetSpanForAddress(address).data(), length); + + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write NETWORK BUFFER (1) ({:08x},{})", dimm_offset, + length); + PrintMBBuffer(address, length); + return 0; + } + + if ((offset >= NetworkBufferAddress2) && (offset <= 0x1FD0FFFF)) + { + const u32 dimm_offset = offset - NetworkBufferAddress2; + + memcpy(s_network_buffer + dimm_offset, memory.GetSpanForAddress(address).data(), length); + + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write NETWORK BUFFER (2) ({:08x},{})", dimm_offset, + length); + PrintMBBuffer(address, length); + return 0; + } + + if ((offset >= NetworkBufferAddress3) && (offset <= 0x8910FFFF)) + { + const u32 dimm_offset = offset - NetworkBufferAddress3; + + memcpy(s_network_buffer + dimm_offset, memory.GetSpanForAddress(address).data(), length); + + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write NETWORK BUFFER (3) ({:08x},{})", dimm_offset, + length); + PrintMBBuffer(address, length); + return 0; + } + + if ((offset >= DIMMCommandVersion1) && (offset <= 0x1F90003F)) + { + const u32 dimm_offset = offset - DIMMCommandVersion1; + memcpy(s_media_buffer + dimm_offset, memory.GetSpanForAddress(address).data(), length); + + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write MEDIA BOARD COMM AREA (1) ({:08x},{})", offset, + length); + PrintMBBuffer(address, length); + return 0; + } + + if ((offset >= DIMMCommandVersion2) && (offset <= 0x8400005F)) + { + const u32 dimm_offset = offset - DIMMCommandVersion2; + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write MEDIA BOARD COMM AREA (2) ({:08x},{})", offset, + length); + PrintMBBuffer(address, length); + + const u8 cmd_flag = memory.Read_U8(address); + + if (dimm_offset == 0x40 && cmd_flag == 1) + { + const AMMBCommand ammb_command = + static_cast(*reinterpret_cast(s_media_buffer + 0x22)); + + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Execute command (2):{0:04X}", + static_cast(ammb_command)); + + memset(s_media_buffer, 0, 0x20); + + // Counter/Command + s_media_buffer_32[0] = s_media_buffer_32[8] | 0x80000000; // Set command okay flag + + // Handle command + switch (ammb_command) + { + case AMMBCommand::Unknown_000: + s_media_buffer_32[1] = 1; + break; + case AMMBCommand::GetDIMMSize: + s_media_buffer_32[1] = 0x20000000; + break; + case AMMBCommand::GetMediaBoardStatus: + s_media_buffer_32[1] = LoadedGameProgram; + s_media_buffer_32[2] = 100; + break; + // SegaBoot version: 3.09 + case AMMBCommand::GetSegaBootVersion: + // Version + s_media_buffer[4] = 0x03; + s_media_buffer[5] = 0x09; + // Unknown + s_media_buffer[6] = 1; + s_media_buffer_32[2] = 1; + s_media_buffer_32[4] = 0xFF; + break; + case AMMBCommand::GetSystemFlags: + s_media_buffer[4] = 1; + s_media_buffer[5] = GDROM; + // Enable development mode (Sega Boot) + // This also allows region free booting + s_media_buffer[6] = 1; + s_media_buffer[8] = 0; // Access Count + break; + case AMMBCommand::GetMediaBoardSerial: + memcpy(s_media_buffer + 4, "A89E-27A50364511", 16); + break; + case AMMBCommand::Unknown_104: + s_media_buffer[4] = 1; + break; + default: + PanicAlertFmtT("Unhandled Media Board Command:{0:04x}", static_cast(ammb_command)); + break; + } + + memset(s_media_buffer + 0x20, 0, 0x20); + + ExpansionInterface::GenerateInterrupt(0x04); + return 0; + } + else + { + memcpy(s_media_buffer + dimm_offset, memory.GetSpanForAddress(address).data(), length); + } + return 0; + } + + if ((offset >= DIMMCommandVersion2_2) && (offset <= 0x89000200)) + { + const u32 dimm_offset = offset - DIMMCommandVersion2_2; + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write MEDIA BOARD COMM AREA (3) ({:08x})", dimm_offset); + PrintMBBuffer(address, length); + + memcpy(s_media_buffer + dimm_offset, memory.GetSpanForAddress(address).data(), length); + + return 0; + } + + // Firmware Write + if ((offset >= FirmwareAddress) && (offset <= 0x84818000)) + { + const u32 dimm_offset = offset - FirmwareAddress; + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write Firmware ({:08x})", dimm_offset); + PrintMBBuffer(address, length); + return 0; + } + + // DIMM memory (8MB) + if ((offset >= DIMMMemory2) && (offset <= 0xFF800000)) + { + const u32 dimm_offset = offset - DIMMMemory2; + FileWriteData(&s_dimm, dimm_offset, memory.GetSpanForAddress(address).data(), length); + return 0; + } + + if ((offset == NetworkControl) && (length == 0x20)) + { + FileWriteData(&s_netctrl, 0, memory.GetSpanForAddress(address).data(), length); + return 0; + } + + // Max GC disc offset + if (offset >= 0x57058000) + { + PrintMBBuffer(address, length); + PanicAlertFmtT("Unhandled Media Board Write:{0:08x}", offset); + } + break; + case AMMBDICommand::Execute: + { + const AMMBCommand ammb_command = static_cast(s_media_buffer_32[8] >> 16); + if (ammb_command == AMMBCommand::Unknown_000) + break; + + if (offset == 0 && length == 0) + { + memset(s_media_buffer, 0, 0x20); + + // Counter/Command + s_media_buffer_32[0] = s_media_buffer_32[8] | 0x80000000; // Set command okay flag + + switch (ammb_command) + { + case AMMBCommand::Unknown_000: + s_media_buffer_32[1] = 1; + break; + case AMMBCommand::GetDIMMSize: + s_media_buffer_32[1] = 0x20000000; + break; + case AMMBCommand::GetMediaBoardStatus: + { + // Fake loading the game to have a chance to enter test mode + static u32 status = LoadingGameProgram; + static u32 progress = 80; + + s_media_buffer_32[1] = status; + s_media_buffer_32[2] = progress; + if (progress < 100) + { + progress++; + } + else + { + status = LoadedGameProgram; + } + } + break; + // SegaBoot version: 3.11 + case AMMBCommand::GetSegaBootVersion: + // Version + s_media_buffer[4] = 0x03; + s_media_buffer[5] = 0x11; + // Unknown + s_media_buffer[6] = 1; + s_media_buffer_32[2] = 1; + s_media_buffer_32[4] = 0xFF; + break; + case AMMBCommand::GetSystemFlags: + s_media_buffer[4] = 1; + s_media_buffer[5] = GDROM; + // Enable development mode (Sega Boot) + // This also allows region free booting + s_media_buffer[6] = 1; + s_media_buffer[8] = 0; // Access Count + break; + case AMMBCommand::GetMediaBoardSerial: + memcpy(s_media_buffer + 4, "A89E-27A50364511", 16); + break; + case AMMBCommand::Unknown_104: + s_media_buffer[4] = 1; + break; + case AMMBCommand::NetworkReInit: + break; + case AMMBCommand::TestHardware: + // Test type + + // 0x01: Media board + // 0x04: Network + + // DEBUG_LOG_FMT(AMMEDIABOARD, "GC-AM: 0x301: ({:08x})", s_media_buffer_32[9] ) + // ); + + // Pointer to a memory address that is directly displayed on screen as a string + // DEBUG_LOG_FMT(AMMEDIABOARD, "GC-AM: ({:08x})", s_media_buffer_32[10]) + // ); + + // On real system it shows the status about the DIMM/GD-ROM here + // We just show "TEST OK" + memory.Write_U32(0x54534554, s_media_buffer_32[12]); + memory.Write_U32(0x004B4F20, s_media_buffer_32[12] + 4); + + s_media_buffer_32[1] = s_media_buffer_32[9]; + break; + case AMMBCommand::Closesocket: + { + const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; + + const int ret = closesocket(fd); + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: closesocket( {}({}) ):{}\n", fd, + s_media_buffer_32[10], ret); + + s_sockets[SocketCheck(s_media_buffer_32[10])] = SOCKET_ERROR; + + s_media_buffer_32[1] = ret; + s_last_error = SSC_SUCCESS; + } + break; + case AMMBCommand::Connect: + { + sockaddr_in addr; + + const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; + const u32 off = s_media_buffer_32[11] - NetworkCommandAddress; + const u32 len = s_media_buffer_32[12]; + + memcpy((void*)&addr, s_network_command_buffer + off, sizeof(sockaddr_in)); + + const int ret = NetDIMMConnect(fd, &addr, len); + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: connect( {}({}), ({},{}:{}), {} ):{}\n", fd, + s_media_buffer_32[10], addr.sin_family, inet_ntoa(addr.sin_addr), + Common::swap16(addr.sin_port), len, ret); + + s_media_buffer[1] = s_media_buffer[8]; + s_media_buffer_32[1] = ret; + } + break; + case AMMBCommand::Recv: + { + const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; + const u32 off = s_media_buffer_32[11]; + u32 len = s_media_buffer_32[12]; + + if (len >= sizeof(s_network_buffer)) + { + len = sizeof(s_network_buffer); + } + + char* buffer = (char*)(s_network_buffer + off); + + if (off >= NetworkBufferAddress5 && off < NetworkBufferAddress5 + sizeof(s_network_buffer)) + { + buffer = (char*)(s_network_buffer + off - NetworkBufferAddress5); + } + else + { + PanicAlertFmt("RECV: Buffer overrun:{0} {1} ", off, len); + } + + const int ret = recv(fd, buffer, len, 0); + const int err = WSAGetLastError(); + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: recv( {}, 0x{:08x}, {} ):{} {}\n", fd, off, len, + ret, err); + + s_media_buffer[1] = s_media_buffer[8]; + s_media_buffer_32[1] = ret; + } + break; + case AMMBCommand::Send: + { + const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; + u32 off = s_media_buffer_32[11]; + const u32 len = s_media_buffer_32[12]; + + if (off >= NetworkBufferAddress1 && off < NetworkBufferAddress1 + sizeof(s_network_buffer)) + { + off -= NetworkBufferAddress1; + } + else + { + ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: send(error) unhandled destination:{:08x}\n", off); + } + + const int ret = send(fd, (char*)(s_network_buffer + off), len, 0); + const int err = WSAGetLastError(); + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: send( {}({}), 0x{:08x}, {} ): {} {}\n", fd, + s_media_buffer_32[10], off, len, ret, err); + + s_media_buffer[1] = s_media_buffer[8]; + s_media_buffer_32[1] = ret; + } + break; + case AMMBCommand::Socket: + { + // Protocol is not sent + const u32 af = s_media_buffer_32[10]; + const u32 type = s_media_buffer_32[11]; + + const SOCKET fd = socket_(af, type, IPPROTO_TCP); + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: socket( {}, {}, 6 ):{}\n", af, type, fd); + + s_media_buffer[1] = 0; + s_media_buffer_32[1] = fd; + } + break; + case AMMBCommand::Select: + { + const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[10] - 1)]; + + fd_set* readfds = nullptr; + fd_set* writefds = nullptr; + fd_set* exceptfds = nullptr; + timeval timeout = {}; + timeval* timeout_src = nullptr; + + // Only one of 11, 12, 13 is ever set alongside 14 + if (s_media_buffer_32[11] && s_media_buffer_32[14]) + { + const u32 ROffset = s_media_buffer_32[14] - NetworkCommandAddress; + readfds = reinterpret_cast(s_network_command_buffer + ROffset); + FD_ZERO(readfds); + FD_SET(fd, readfds); + + timeout_src = reinterpret_cast(s_network_command_buffer + + s_media_buffer_32[11] - NetworkCommandAddress); + } + else if (s_media_buffer_32[12] && s_media_buffer_32[14]) + { + const u32 WOffset = s_media_buffer_32[14] - NetworkCommandAddress; + writefds = reinterpret_cast(s_network_command_buffer + WOffset); + FD_ZERO(writefds); + FD_SET(fd, writefds); + + timeout_src = reinterpret_cast(s_network_command_buffer + + s_media_buffer_32[12] - NetworkCommandAddress); + } + else if (s_media_buffer_32[13] && s_media_buffer_32[14]) + { + const u32 EOffset = s_media_buffer_32[14] - NetworkCommandAddress; + exceptfds = reinterpret_cast(s_network_command_buffer + EOffset); + FD_ZERO(exceptfds); + FD_SET(fd, exceptfds); + + timeout_src = reinterpret_cast(s_network_command_buffer + + s_media_buffer_32[13] - NetworkCommandAddress); + } + + // Copy timeout if set + if (timeout_src) + { + std::memcpy(&timeout, timeout_src, sizeof(timeval)); + } + + // BUG?: F-Zero AX Monster calls select with a two second timeout + // for unknown reasons, which slows down the game a lot + if (AMMediaboard::GetGameType() == FZeroAXMonster) + { + timeout.tv_sec = 0; + timeout.tv_usec = 1800; + } + + const int ret = + select(fd + 1, readfds, writefds, exceptfds, timeout_src ? &timeout : nullptr); + const int err = WSAGetLastError(); + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, + "GC-AM: select( {}({}), 0x{:08x} 0x{:08x} 0x{:08x} 0x{:08x} ):{} {} \n", fd, + s_media_buffer_32[10], s_media_buffer_32[11], s_media_buffer_32[14], + s_media_buffer_32[15], s_media_buffer_32[16], ret, err); + // hexdump( NetworkCMDBuffer, 0x40 ); + + s_media_buffer[1] = 0; + s_media_buffer_32[1] = ret; + } + break; + case AMMBCommand::SetSockOpt: + { + const SOCKET fd = (SOCKET)(s_sockets[SocketCheck(s_media_buffer_32[10])]); + const int level = (int)(s_media_buffer_32[11]); + const int optname = (int)(s_media_buffer_32[12]); + const char* optval = + (char*)(s_network_command_buffer + s_media_buffer_32[13] - NetworkCommandAddress); + const int optlen = (int)(s_media_buffer_32[14]); + + const int ret = setsockopt(fd, level, optname, optval, optlen); + const int err = WSAGetLastError(); + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, + "GC-AM: setsockopt( {:d}, {:04x}, {}, {:p}, {} ):{:d} ({})\n", fd, level, + optname, optval, optlen, ret, err); + + s_media_buffer[1] = s_media_buffer[8]; + s_media_buffer_32[1] = ret; + } + break; + case AMMBCommand::ModifyMyIPaddr: + { + const u32 NetBufferOffset = s_media_buffer_32[10] - NetworkCommandAddress; + + const char* IP = (char*)(s_network_command_buffer + NetBufferOffset); + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: modifyMyIPaddr({})\n", IP); + } + break; + // Empty reply + case AMMBCommand::InitLink: + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: 0x601"); + break; + case AMMBCommand::Unknown_605: + NOTICE_LOG_FMT(AMMEDIABOARD, "GC-AM: 0x605"); + break; + case AMMBCommand::SetupLink: + { + sockaddr_in addra; + sockaddr_in addrb; + + addra.sin_addr.s_addr = s_media_buffer_32[12]; + addrb.sin_addr.s_addr = s_media_buffer_32[13]; + + u16 size = s_media_buffer[0x24] | s_media_buffer[0x25] << 8; + u16 port = Common::swap16(s_media_buffer[0x27] | s_media_buffer[0x26] << 8); + u16 unknown = s_media_buffer[0x2D] | s_media_buffer[0x2C] << 8; + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: SetupLink:"); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: Size: ({}) ", size); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: Port: ({})", port); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM:LinkNum:({:02x})", s_media_buffer[0x28]); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: ({:02x})", s_media_buffer[0x2A]); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: ({:04x})", unknown); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: IP: ({})", inet_ntoa(addra.sin_addr)); // IP ? + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: IP: ({})", + inet_ntoa(addrb.sin_addr)); // Target IP + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: ({:08x})", + Common::swap32(s_media_buffer_32[14])); // some RAM address + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: ({:08x})", + Common::swap32(s_media_buffer_32[15])); // some RAM address + + s_media_buffer_32[1] = 0; + } + break; + // This sends a UDP packet to previously defined Target IP/Port + case AMMBCommand::SearchDevices: + { + u16 unknown = s_media_buffer[0x25] | s_media_buffer[0x24] << 8; + u16 off = s_media_buffer[0x26] | s_media_buffer[0x27] << 8; + u32 addr = s_media_buffer_32[10]; + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: SearchDevices: ({})", unknown); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: Offset: ({:04x})", off); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: ({:08x})", addr); + + const u8* data = (u8*)(s_network_buffer + off + addr - NetworkBufferAddress2); + + for (u32 i = 0; i < 0x20; i += 0x10) + { + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: {:08x} {:08x} {:08x} {:08x}", *(u32*)(data + i), + *(u32*)(data + i + 4), *(u32*)(data + i + 8), *(u32*)(data + i + 12)); + } + + s_media_buffer_32[1] = 0; + } + break; + case AMMBCommand::Unknown_608: + { + const u32 ip = s_media_buffer_32[10]; + u16 port = Common::swap16(s_media_buffer[6] | s_media_buffer[7] << 8); + u16 flag = s_media_buffer[10] | s_media_buffer[11] << 8; + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: 0x608( {} {} {} )", ip, port, flag); + } + break; + case AMMBCommand::Unknown_614: + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: 0x614"); + break; + default: + ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: execute buffer UNKNOWN:{0:04x}", + static_cast(ammb_command)); + break; + } + + memset(s_media_buffer + 0x20, 0, 0x20); + return 0; + } + + PanicAlertFmtT("Unhandled Media Board Execute:{0:04x}", static_cast(ammb_command)); + break; + } + default: + PanicAlertFmtT("Unhandled Media Board Command:{0:02x}", command); + break; + } + + return 0; +} + +u32 GetMediaType() +{ + switch (GetGameType()) + { + default: + case FZeroAX: + case VirtuaStriker3: + case VirtuaStriker4: + case VirtuaStriker4_2006: + case GekitouProYakyuu: + case KeyOfAvalon: + return GDROM; + break; + + case MarioKartGP: + case MarioKartGP2: + case FZeroAXMonster: + return NAND; + break; + } + // Never reached +} + +u32 GetGameType() +{ + u64 game_id = 0; + + if (SConfig::GetInstance().GetGameID().length() > 6) + { + game_id = 0x32323232; + } + else + { + // Convert game ID into hex + sscanf(SConfig::GetInstance().GetGameID().c_str(), "%s", (char*)&game_id); + } + + // This is checking for the real game IDs (See boot.id within the game) + switch ((Common::swap32((u32)game_id) >> 8) & 0xFFFF) + { + // SBGG - F-ZERO AX + case 0x4747: + return FZeroAX; + // SBHA - F-ZERO AX (Monster) + case 0x4841: + return FZeroAXMonster; + // SBKJ/SBKP - MARIOKART ARCADE GP + case 0x4B50: + case 0x4B5A: + return MarioKartGP; + // SBNJ/SBNL - MARIOKART ARCADE GP2 + case 0x4E4A: + case 0x4E4C: + return MarioKartGP2; + // SBEJ/SBEY - Virtua Striker 2002 + case 0x454A: + case 0x4559: + return VirtuaStriker3; + // SBLJ/SBLK/SBLL - VIRTUA STRIKER 4 Ver.2006 + case 0x4C4A: + case 0x4C4B: + case 0x4C4C: + return VirtuaStriker4_2006; + // SBHJ/SBHN/SBHZ - VIRTUA STRIKER 4 VER.A + case 0x484A: + case 0x484E: + case 0x485A: + // SBJA/SBJJ - VIRTUA STRIKER 4 + case 0x4A41: + case 0x4A4A: + return VirtuaStriker4; + // SBFX/SBJN - Key of Avalon + case 0x4658: + case 0x4A4E: + return KeyOfAvalon; + // SBGX - Gekitou Pro Yakyuu (DIMM Upgrade 3.17) + case 0x4758: + return GekitouProYakyuu; + default: + PanicAlertFmtT("Unknown game ID:{0:08x}, using default controls.", game_id); + // GSBJ/G12U - VIRTUA STRIKER 3 + // RELS/RELJ - SegaBoot (does not have a boot.id) + case 0x5342: + case 0x3132: + case 0x454C: + return VirtuaStriker3; + // S000 - Firmware update + case 0x3030: + return FirmwareUpdate; + } + // Never reached +} + +bool GetTestMenu() +{ + return s_test_menu; +} + +void Shutdown() +{ + s_netcfg.Close(); + s_netctrl.Close(); + s_extra.Close(); + s_backup.Close(); + s_dimm.Close(); + + if (s_dimm_disc.size()) + { + s_dimm_disc.clear(); + } + + // Close all sockets + for (u32 i = 1; i < 64; ++i) + { + if (s_sockets[i] != SOCKET_ERROR) + { + closesocket(s_sockets[i]); + } + } +} + +} // namespace AMMediaboard diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.h b/Source/Core/Core/HW/DVD/AMMediaboard.h new file mode 100644 index 0000000000..3cfa79fef6 --- /dev/null +++ b/Source/Core/Core/HW/DVD/AMMediaboard.h @@ -0,0 +1,237 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include "Common/CommonTypes.h" + +class PointerWrap; + +namespace Core +{ +class System; +} + +namespace File +{ +class IOFile; +} + +enum GameType +{ + FZeroAX = 1, + FZeroAXMonster, + MarioKartGP, + MarioKartGP2, + VirtuaStriker3, + VirtuaStriker4, + VirtuaStriker4_2006, + GekitouProYakyuu, + KeyOfAvalon, + FirmwareUpdate, +}; + +enum MediaType +{ + GDROM = 1, + NAND, +}; + +enum MediaBoardType +{ + NANDMaskBoardHDD = 0, + NANDMaskBoardMask = 1, + NANDMaskBoardNAND = 2, + DIMMBoardType3 = 4, +}; + +enum MediaBoardStatus +{ + Initializing = 0, + CheckingNetwork = 1, + SystemDisc = 2, + TestingGameProgram = 3, + LoadingGameProgram = 4, + LoadedGameProgram = 5, + Error = 6, +}; + +enum InquiryType +{ + Version1 = 0x21484100, + Version2 = 0x29484100, +}; + +#define SocketCheck(x) (x <= 0x3F ? x : 0) + +namespace AMMediaboard +{ + +enum class AMMBDICommand : u16 +{ + Inquiry = 0x12, + Read = 0xA8, + Write = 0xAA, + Execute = 0xAB, +}; + +enum class AMMBCommand : u16 +{ + Unknown_000 = 0x000, + GetDIMMSize = 0x001, + + GetMediaBoardStatus = 0x100, + GetSegaBootVersion = 0x101, + GetSystemFlags = 0x102, + GetMediaBoardSerial = 0x103, + Unknown_104 = 0x104, + + NetworkReInit = 0x204, + + TestHardware = 0x301, + + // Network used by Mario Kart GPs + Accept = 0x401, + Bind = 0x402, + Closesocket = 0x403, + Connect = 0x404, + GetIPbyDNS = 0x405, + InetAddr = 0x406, + Ioctl = 0x407, // Unused + Listen = 0x408, + Recv = 0x409, + Send = 0x40A, + Socket = 0x40B, + Select = 0x40C, + Shutdown = 0x40D, // Unused + SetSockOpt = 0x40E, + GetSockOpt = 0x40F, // Unused + SetTimeOuts = 0x410, + GetLastError = 0x411, + RouteAdd = 0x412, // Unused + RouteDelete = 0x413, // Unused + GetParambyDHCPExec = 0x414, + ModifyMyIPaddr = 0x415, + Recvfrom = 0x416, // Unused + Sendto = 0x417, // Unused + RecvDimmImage = 0x418, // Unused + SendDimmImage = 0x419, // Unused + + // Network used by F-Zero AX + InitLink = 0x601, + Unknown_605 = 0x605, + SetupLink = 0x606, + SearchDevices = 0x607, + Unknown_608 = 0x608, + Unknown_614 = 0x614, + + // All.Net used by Virtua Striker 4 2006 (Japan) + AllNetInit = 0x700, + Unknown_701 = 0x701, + Unknown_702 = 0x702, + Unknown_703 = 0x703, + Unknown_704 = 0x704, + + // NETDIMM Commands + Unknown_001 = 0x001, + GetNetworkFirmVersion = 0x101, + Unknown_103 = 0x103, +}; + +enum MediaBoardAddress : u32 +{ + MediaBoardStatus1 = 0x80000000, + MediaBoardStatus2 = 0x80000020, + MediaBoardStatus3 = 0x80000040, + + FirmwareStatus1 = 0x80000120, + FirmwareStatus2 = 0x80000140, + + BackupMemory = 0x000006A0, + + DIMMMemory = 0x1F000000, + DIMMMemory2 = 0xFF000000, + + DIMMExtraSettings = 0x1FFEFFE0, + + AllNetSettings = 0x1EFF8000, + + NetworkControl = 0xFFFF0000, + + DIMMCommandVersion1 = 0x1F900000, + DIMMCommandVersion2 = 0x84000000, + DIMMCommandVersion2_2 = 0x89000000, + DIMMCommandExecute2 = 0x88000000, + + NetworkCommandAddress = 0x1F800200, + NetworkCommandAddress2 = 0x89040200, + + NetworkBufferAddress1 = 0x1FA00000, + NetworkBufferAddress2 = 0x1FD00000, + NetworkBufferAddress3 = 0x89100000, + NetworkBufferAddress4 = 0x89180000, + NetworkBufferAddress5 = 0x1FB00000, + + AllNetBuffer = 0x89010000, + + FirmwareAddress = 0x84800000, + + FirmwareMagicWrite1 = 0x00600000, + FirmwareMagicWrite2 = 0x00700000, +}; + +// Mario Kart GP2 has a complete list of them +// but in japanese +// They somewhat match WSA errors codes +enum SocketStatusCodes +{ + SSC_E_4 = -4, // Failure (abnormal argument) + SSC_E_3 = -3, // Success (unsupported command) + SSC_E_2 = + -2, // Failure (failed to send, abnormal argument, or communication condition violation) + SSC_E_1 = -1, // Failure (error termination) + + SSC_EINTR = 4, // An interrupt occurred before data reception was completed + SSC_EBADF = 9, // Invalid descriptor + SSC_E_11 = 11, // Send operation was blocked on a non-blocking mode socket + SSC_EACCES = 13, // The socket does not support broadcast addresses, but the destination address + // is a broadcast address + SSC_EFAULT = + 14, // The name argument specifies a location other than an address used by the process. + SSC_E_23 = 23, // System file table is full. + SSC_AEMFILE = 24, // Process descriptor table is full. + SSC_EMSGSIZE = + 36, // Socket tried to send message without splitting, but message size is too large. + SSC_EAFNOSUPPORT = 47, // Address prohibited for use on this socket. + SSC_EADDRINUSE = 48, // Address already in use. + SSC_EADDRNOTAVAIL = 49, // Prohibited address. + SSC_E_50 = 50, // Non-socket descriptor. + SSC_ENETUNREACH = 51, // Cannot access specified network. + SSC_ENOBUFS = 55, // Insufficient buffer + SSC_EISCONN = 56, // Already connected socket + SSC_ENOTCONN = 57, // No connection for connection-type socket + SSC_ETIMEDOUT = 60, // Timeout + SSC_ECONNREFUSED = 61, // Connection request forcibly rejected + SSC_EHOSTUNREACH = 65, // Remote host cannot be reached + SSC_EHOSTDOWN = 67, // Remote host is down + SSC_EWOULDBLOCK = 70, // Socket is in non-blocking mode and connection has not been completed + SSC_E_69 = 69, // Socket is in non-blocking mode and a previously issued Connect command has not + // been completed + SSC_SUCCESS = 70, +}; + +void Init(); +void FirmwareMap(bool on); +u8* InitDIMM(u32 size); +void InitKeys(u32 KeyA, u32 KeyB, u32 KeyC); +u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 Address, u32 Length); +u32 GetGameType(); +u32 GetMediaType(); +bool GetTestMenu(); +void Shutdown(); +}; // namespace AMMediaboard diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index efb279cfac..e88a61084b 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -26,6 +26,7 @@ #include "Core/CoreTiming.h" #include "Core/DolphinAnalytics.h" #include "Core/HW/AudioInterface.h" +#include "Core/HW/DVD/AMMediaboard.h" #include "Core/HW/DVD/DVDMath.h" #include "Core/HW/DVD/DVDThread.h" #include "Core/HW/EXI/EXI_DeviceIPL.h" @@ -281,6 +282,16 @@ void DVDInterface::Init() u64 userdata = PackFinishExecutingCommandUserdata(ReplyType::DTK, DIInterruptType::TCINT); core_timing.ScheduleEvent(0, m_finish_executing_command, userdata); + + if (m_system.IsTriforce()) + { + AMMediaboard::Init(); + + // The Triforce IPL expects the cover to be closed + m_DICVR.Hex = 0; + m_DICFG.Hex |= 8; /* The Triforce IPL checks this bit + to set the physical memory to either 50MB(unset) or 24MB(set) */ + } } // Resets state on the MN102 chip in the drive itself, but not the DI registers exposed on the @@ -310,7 +321,7 @@ void DVDInterface::ResetDrive(bool spinup) else if (!spinup) { // Wii hardware tests indicate that this is used when ejecting and inserting a new disc, or - // performing a reset without spinup. + // performing a reset without spin up. SetDriveState(DriveState::DiscChangeDetected); } else @@ -697,11 +708,16 @@ bool DVDInterface::CheckReadPreconditions() SetDriveError(DriveError::MotorStopped); return false; } - if (m_drive_state == DriveState::DiscIdNotRead) + + // SegaBoot doesn't read the Disc ID + if (!m_system.IsTriforce()) { - ERROR_LOG_FMT(DVDINTERFACE, "Disc id not read."); - SetDriveError(DriveError::NoDiscID); - return false; + if (m_drive_state == DriveState::DiscIdNotRead) + { + ERROR_LOG_FMT(DVDINTERFACE, "Disc id not read."); + SetDriveError(DriveError::NoDiscID); + return false; + } } return true; } @@ -753,12 +769,19 @@ void DVDInterface::ExecuteCommand(ReplyType reply_type) DIInterruptType interrupt_type = DIInterruptType::TCINT; bool command_handled_by_thread = false; - // Swaps endian of Triforce DI commands, and zeroes out random bytes to prevent unknown read - // subcommand errors - auto& dvd_thread = m_system.GetDVDThread(); - if (dvd_thread.HasDisc() && dvd_thread.GetDiscType() == DiscIO::Platform::Triforce) + if (m_system.IsTriforce()) { - // TODO(C++23): Use std::byteswap and a bitwise AND for increased clarity + if (!AMMediaboard::ExecuteCommand(m_DICMDBUF, &m_DIIMMBUF, m_DIMAR, m_DILENGTH)) + { + // Transfer is done + m_DICR.TSTART = 0; + m_DIMAR += m_DILENGTH; + m_DILENGTH = 0; + GenerateDIInterrupt(DIInterruptType::TCINT); + m_error_code = DriveError::None; + return; + } + // Normal read command pass on to normal handling m_DICMDBUF[0] <<= 24; } diff --git a/Source/Core/Core/HW/EXI/EXI.cpp b/Source/Core/Core/HW/EXI/EXI.cpp index d676dc4845..144fdd57b9 100644 --- a/Source/Core/Core/HW/EXI/EXI.cpp +++ b/Source/Core/Core/HW/EXI/EXI.cpp @@ -66,18 +66,6 @@ void ExpansionInterfaceManager::AddMemoryCard(Slot slot) m_channels[SlotToEXIChannel(slot)]->AddDevice(memorycard_device, SlotToEXIDevice(slot)); } -void ExpansionInterfaceManager::AddSP1Device() -{ - EXIDeviceType sp1_device = EXIDeviceType::Baseboard; - auto& system = Core::System::GetInstance(); - if (system.IsTriforce()) - { - sp1_device = Config::Get(Config::MAIN_SERIAL_PORT_1); - } - - m_channels[0]->AddDevice(sp1_device, SlotToEXIDevice(Slot::SP1)); -} - u8 SlotToEXIChannel(Slot slot) { switch (slot) @@ -157,9 +145,15 @@ void ExpansionInterfaceManager::Init(const Sram* override_sram) AddMemoryCard(slot); m_channels[0]->AddDevice(EXIDeviceType::MaskROM, 1); - AddSP1Device(); - m_channels[SlotToEXIChannel(Slot::SP1)]->AddDevice(Config::Get(Config::MAIN_SERIAL_PORT_1), - SlotToEXIDevice(Slot::SP1)); + + // Automatically connect the Triforce Baseboard in Triforce mode + EXIDeviceType sp1_device = Config::Get(Config::MAIN_SERIAL_PORT_1); + if (m_system.IsTriforce()) + { + sp1_device = EXIDeviceType::Baseboard; + } + + m_channels[SlotToEXIChannel(Slot::SP1)]->AddDevice(sp1_device, SlotToEXIDevice(Slot::SP1)); m_channels[SlotToEXIChannel(Slot::SP2)]->AddDevice(Config::Get(Config::MAIN_SERIAL_PORT_2), SlotToEXIDevice(Slot::SP2)); diff --git a/Source/Core/Core/HW/EXI/EXI.h b/Source/Core/Core/HW/EXI/EXI.h index f0ea5fbfb5..7ded905b98 100644 --- a/Source/Core/Core/HW/EXI/EXI.h +++ b/Source/Core/Core/HW/EXI/EXI.h @@ -89,7 +89,6 @@ public: private: void AddMemoryCard(Slot slot); - void AddSP1Device(); static void ChangeDeviceCallback(Core::System& system, u64 userdata, s64 cycles_late); static void UpdateInterruptsCallback(Core::System& system, u64 userdata, s64 cycles_late); diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp index 7df13f5020..a489d97a72 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp @@ -1,20 +1,137 @@ -// Copyright 2013 Dolphin Emulator Project +// Copyright 2025 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "Core/HW/EXI/EXI_DeviceBaseboard.h" -#include "Common/ChunkFile.h" +#include +#include +#include +#include +#include + +#include + +#include "Common/Buffer.h" #include "Common/CommonTypes.h" +#include "Common/Config/Config.h" +#include "Common/FileUtil.h" +#include "Common/IOFile.h" +#include "Common/IniFile.h" #include "Common/Logging/Log.h" +#include "Core/Boot/Boot.h" +#include "Core/BootManager.h" +#include "Core/Config/MainSettings.h" +#include "Core/Config/SYSCONFSettings.h" +#include "Core/ConfigLoaders/BaseConfigLoader.h" +#include "Core/ConfigLoaders/NetPlayConfigLoader.h" +#include "Core/ConfigManager.h" +#include "Core/Core.h" +#include "Core/HW/DVD/AMMediaboard.h" +#include "Core/HW/DVD/DVDInterface.h" +#include "Core/HW/EXI/EXI.h" +#include "Core/HW/EXI/EXI_Device.h" +#include "Core/HW/MMIO.h" +#include "Core/HW/Memmap.h" +#include "Core/HW/SI/SI.h" +#include "Core/HW/SI/SI_Device.h" +#include "Core/HW/Sram.h" +#include "Core/HW/WiimoteReal/WiimoteReal.h" +#include "Core/Movie.h" +#include "Core/NetPlayProto.h" +#include "Core/PowerPC/PowerPC.h" +#include "Core/System.h" +#include "Core/WiiRoot.h" + +#include "DiscIO/Enums.h" + +static bool s_interrupt_set = false; +static u32 s_irq_timer = 0; +static u32 s_irq_status = 0; + +static u16 CheckSum(u8* data, u32 length) +{ + u16 check = 0; + + for (u32 i = 0; i < length; i++) + { + check += data[i]; + } + + return check; +} + namespace ExpansionInterface { -CEXIBaseboard::CEXIBaseboard(Core::System& system) : IEXIDevice(system) + +void GenerateInterrupt(int flag) { + auto& system = Core::System::GetInstance(); + + s_interrupt_set = true; + s_irq_timer = 0; + s_irq_status = flag; + + system.GetExpansionInterface().UpdateInterrupts(); +} + +CEXIBaseboard::CEXIBaseboard(Core::System& system) : IEXIDevice(system), m_position(0) +{ + std::string backup_filename(fmt::format("{}tribackup_{}.bin", File::GetUserPath(D_TRIUSER_IDX), + SConfig::GetInstance().GetGameID())); + + m_backup = File::IOFile(backup_filename, "rb+"); + if (!m_backup.IsOpen()) + { + m_backup = File::IOFile(backup_filename, "wb+"); + } + + // Some games share the same ID Client/Server + if (!m_backup.IsGood()) + { + PanicAlertFmt("Failed to open {}\nFile might be in use.", backup_filename.c_str()); + + std::srand(static_cast(std::time(nullptr))); + + backup_filename = fmt::format("{}tribackup_tmp_{}{}.bin", File::GetUserPath(D_TRIUSER_IDX), + rand(), SConfig::GetInstance().GetGameID()); + + m_backup = File::IOFile(backup_filename, "wb+"); + } + + // Virtua Striker 4 and Gekitou Pro Yakyuu need a higher FIRM version + // Which is read from the backup data?! + if (AMMediaboard::GetGameType() == VirtuaStriker4 || + AMMediaboard::GetGameType() == GekitouProYakyuu) + { + if (m_backup.GetSize()) + { + Common::UniqueBuffer data(m_backup.GetSize()); + m_backup.ReadBytes(data.data(), data.size()); + + // Set FIRM version + reinterpret_cast(data[0x12]) = 0x1703; + reinterpret_cast(data[0x212]) = 0x1703; + + // Update checksum + reinterpret_cast(data[0x0A]) = Common::swap16(CheckSum(&data[0xC], 0x1F4)); + reinterpret_cast(data[0x20A]) = Common::swap16(CheckSum(&data[0x20C], 0x1F4)); + + m_backup.Seek(0, File::SeekOrigin::Begin); + m_backup.WriteBytes(data.data(), data.size()); + m_backup.Flush(); + } + } +} + +CEXIBaseboard::~CEXIBaseboard() +{ + m_backup.Close(); } void CEXIBaseboard::SetCS(int cs) { + DEBUG_LOG_FMT(SP1, "AM-BB: ChipSelect={}", cs); if (cs) m_position = 0; } @@ -24,33 +141,197 @@ bool CEXIBaseboard::IsPresent() const return true; } -void CEXIBaseboard::TransferByte(u8& byte) +bool CEXIBaseboard::IsInterruptSet() { - if (m_position == 0) + if (s_interrupt_set) { - m_command = byte; + DEBUG_LOG_FMT(SP1, "AM-BB: IRQ"); + if (++s_irq_timer > 12) + s_interrupt_set = false; + return 1; } else { - switch (m_command) + return 0; + } +} + +void CEXIBaseboard::DMAWrite(u32 addr, u32 size) +{ + const auto& system = Core::System::GetInstance(); + const auto& memory = system.GetMemory(); + + NOTICE_LOG_FMT(SP1, "AM-BB: COMMAND: Backup DMA Write: {:08x} {:x}", addr, size); + + m_backup.Seek(m_backup_offset, File::SeekOrigin::Begin); + + m_backup.WriteBytes(memory.GetSpanForAddress(addr).data(), size); + + m_backup.Flush(); +} + +void CEXIBaseboard::DMARead(u32 addr, u32 size) +{ + const auto& system = Core::System::GetInstance(); + const auto& memory = system.GetMemory(); + + NOTICE_LOG_FMT(SP1, "AM-BB: COMMAND: Backup DMA Read: {:08x} {:x}", addr, size); + + m_backup.Seek(m_backup_offset, File::SeekOrigin::Begin); + + m_backup.Flush(); + + m_backup.ReadBytes(memory.GetSpanForAddress(addr).data(), size); +} + +void CEXIBaseboard::TransferByte(u8& byte) +{ + DEBUG_LOG_FMT(SP1, "AM-BB: > {:02x}", byte); + if (m_position < 4) + { + m_command[m_position] = byte; + byte = 0xFF; + } + + if ((m_position >= 2) && (m_command[0] == 0 && m_command[1] == 0)) + { + // Read serial ID + byte = "\x06\x04\x10\x00"[(m_position - 2) & 3]; + } + else if (m_position == 3) + { + u32 checksum = (m_command[0] << 24) | (m_command[1] << 16) | (m_command[2] << 8); + u32 bit = 0x80000000UL; + u32 check = 0x8D800000UL; + while (bit >= 0x100) { - case 0x00: + if (checksum & bit) + checksum ^= check; + check >>= 1; + bit >>= 1; + } + + if (m_command[3] != (checksum & 0xFF)) + DEBUG_LOG_FMT(SP1, "AM-BB: cs: {:02x}, w: {:02x}", m_command[3], checksum & 0xFF); + } + else if (m_position == 4) + { + switch (m_command[0]) { - static constexpr std::array ID = {0x06, 0x04, 0x10, 0x00}; - byte = ID[(m_position - 2) & 3]; + case BackupOffsetSet: + m_backup_offset = (m_command[1] << 8) | m_command[2]; + DEBUG_LOG_FMT(SP1, "AM-BB: COMMAND: BackupOffsetSet:{:04x}", m_backup_offset); + m_backup.Seek(m_backup_offset, File::SeekOrigin::Begin); + byte = 0x01; + break; + case BackupWrite: + DEBUG_LOG_FMT(SP1, "AM-BB: COMMAND: BackupWrite:{:04x}-{:02x}", m_backup_offset, + m_command[1]); + m_backup.WriteBytes(&m_command[1], 1); + m_backup.Flush(); + byte = 0x01; + break; + case BackupRead: + DEBUG_LOG_FMT(SP1, "AM-BB: COMMAND: BackupRead :{:04x}", m_backup_offset); + byte = 0x01; + break; + case DMAOffsetLengthSet: + m_backup_dma_offset = (m_command[1] << 8) | m_command[2]; + m_backup_dma_length = m_command[3]; + NOTICE_LOG_FMT(SP1, "AM-BB: COMMAND: DMAOffsetLengthSet :{:04x} {:02x}", m_backup_dma_offset, + m_backup_dma_length); + byte = 0x01; + break; + case ReadISR: + NOTICE_LOG_FMT(SP1, "AM-BB: COMMAND: ReadISR :{:02x} {:02x}:{:02x} {:02x}", m_command[1], + m_command[2], 4, s_irq_status); + byte = 0x04; + break; + case WriteISR: + NOTICE_LOG_FMT(SP1, "AM-BB: COMMAND: WriteISR :{:02x} {:02x}", m_command[1], m_command[2]); + s_irq_status &= ~(m_command[2]); + byte = 0x04; + break; + // 2 byte out + case ReadIMR: + NOTICE_LOG_FMT(SP1, "AM-BB: COMMAND: ReadIMR :{:02x} {:02x}", m_command[1], m_command[2]); + byte = 0x04; + break; + case WriteIMR: + NOTICE_LOG_FMT(SP1, "AM-BB: COMMAND: WriteIMR :{:02x} {:02x}", m_command[1], m_command[2]); + byte = 0x04; + break; + case WriteLANCNT: + NOTICE_LOG_FMT(SP1, "AM-BB: COMMAND: WriteLANCNT :{:02x} {:02x}", m_command[1], m_command[2]); + if ((m_command[1] == 0) && (m_command[2] == 0)) + { + s_interrupt_set = true; + s_irq_timer = 0; + s_irq_status = 0x02; + } + if ((m_command[1] == 2) && (m_command[2] == 1)) + { + s_irq_status = 0; + } + byte = 0x08; + break; + default: + byte = 4; + ERROR_LOG_FMT(SP1, "AM-BB: COMMAND: {:02x} {:02x} {:02x}", m_command[0], m_command[1], + m_command[2]); break; } + } + else if (m_position > 4) + { + switch (m_command[0]) + { + // 1 byte out + case BackupRead: + m_backup.Flush(); + m_backup.ReadBytes(&byte, 1); + break; + case DMAOffsetLengthSet: + byte = 0x01; + break; + // 2 byte out + case ReadISR: + if (m_position == 6) + { + byte = s_irq_status; + s_interrupt_set = false; + } + else + { + byte = 0x04; + } + break; + // 2 byte out + case ReadIMR: + if (m_position == 5) + byte = 0xFF; + if (m_position == 6) + byte = 0x81; + break; default: - ERROR_LOG_FMT(EXPANSIONINTERFACE, "EXI BASEBOARD: Unhandled command {:#x} {:#x}", m_command, - m_position); + ERROR_LOG_FMT(SP1, "Unknown AM-BB command: {:02x}", m_command[0]); + break; } } + else + { + byte = 0xFF; + } + + DEBUG_LOG_FMT(SP1, "AM-BB < {:02x}", byte); m_position++; } void CEXIBaseboard::DoState(PointerWrap& p) { p.Do(m_position); + p.Do(s_interrupt_set); p.Do(m_command); } + } // namespace ExpansionInterface diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.h b/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.h index 4c534428c9..6b32da191c 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.h @@ -1,27 +1,59 @@ -// Copyright 2013 Dolphin Emulator Project +// Copyright 2025 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include "Common/CommonTypes.h" +#include "Common/IOFile.h" #include "Core/HW/EXI/EXI_Device.h" -class PointerWrap; +namespace Core +{ +class System; +} namespace ExpansionInterface { +void GenerateInterrupt(int flag); + class CEXIBaseboard : public IEXIDevice { public: - CEXIBaseboard(Core::System& system); + explicit CEXIBaseboard(Core::System& system); + virtual ~CEXIBaseboard(); + void SetCS(int cs) override; + bool IsInterruptSet() override; bool IsPresent() const override; void DoState(PointerWrap& p) override; + void DMAWrite(u32 addr, u32 size) override; + void DMARead(u32 addr, u32 size) override; private: - // STATE_TO_SAVE - u32 m_position = 0; - u32 m_command = 0; + enum Command + { + BackupOffsetSet = 0x01, + BackupWrite = 0x02, + BackupRead = 0x03, - void TransferByte(u8& byte) override; + DMAOffsetLengthSet = 0x05, + + ReadISR = 0x82, + WriteISR = 0x83, + ReadIMR = 0x86, + WriteIMR = 0x87, + + WriteLANCNT = 0xFF, + }; + + u32 m_position; + u32 m_backup_dma_offset; + u32 m_backup_dma_length; + u8 m_command[4]; + u16 m_backup_offset; + File::IOFile m_backup; + +protected: + void TransferByte(u8& _uByte) override; }; } // namespace ExpansionInterface diff --git a/Source/Core/Core/HW/GCPadEmu.cpp b/Source/Core/Core/HW/GCPadEmu.cpp index 0058bc74e4..35cfeda2a8 100644 --- a/Source/Core/Core/HW/GCPadEmu.cpp +++ b/Source/Core/Core/HW/GCPadEmu.cpp @@ -34,6 +34,7 @@ static const u16 trigger_bitmasks[] = { static const u16 dpad_bitmasks[] = {PAD_BUTTON_UP, PAD_BUTTON_DOWN, PAD_BUTTON_LEFT, PAD_BUTTON_RIGHT}; +static const u8 triforce_bitmask[] = {SWITCH_TEST, SWITCH_SERVICE, SWITCH_COIN}; GCPad::GCPad(const unsigned int index) : m_index(index) { @@ -68,6 +69,13 @@ GCPad::GCPad(const unsigned int index) : m_index(index) m_dpad->AddInput(Translatability::Translate, named_direction); } + // triforce + groups.emplace_back(m_triforce = new ControllerEmu::Buttons(TRIFORCE_GROUP)); + for (const char* named_button : {TEST_BUTTON, SERVICE_BUTTON, COIN_BUTTON}) + { + m_triforce->AddInput(Translatability::DoNotTranslate, named_button); + } + // Microphone groups.emplace_back(m_mic = new ControllerEmu::Buttons(MIC_GROUP)); m_mic->AddInput(Translatability::Translate, _trans("Button")); @@ -119,6 +127,8 @@ ControllerEmu::ControlGroup* GCPad::GetGroup(PadGroup group) return m_mic; case PadGroup::Options: return m_options; + case PadGroup::Triforce: + return m_triforce; default: return nullptr; } @@ -150,6 +160,9 @@ GCPadStatus GCPad::GetInput() const // dpad m_dpad->GetState(&pad.button, dpad_bitmasks, m_input_override_function); + // triforce + m_triforce->GetState(&pad.switches, triforce_bitmask, m_input_override_function); + // sticks const auto main_stick_state = m_main_stick->GetState(m_input_override_function); pad.stickX = MapFloat(main_stick_state.x, GCPadStatus::MAIN_STICK_CENTER_X, 1); @@ -202,6 +215,11 @@ void GCPad::LoadDefaults(const ControllerInterface& ciface) m_dpad->SetControlExpression(2, "`F`"); // Left m_dpad->SetControlExpression(3, "`H`"); // Right + // Triforce + m_triforce->SetControlExpression(0, "`1`"); // Test + m_triforce->SetControlExpression(1, "`2`"); // Service + m_triforce->SetControlExpression(2, "`3`"); // Coin + // C Stick m_c_stick->SetControlExpression(0, "`I`"); // Up m_c_stick->SetControlExpression(1, "`K`"); // Down diff --git a/Source/Core/Core/HW/GCPadEmu.h b/Source/Core/Core/HW/GCPadEmu.h index 2555ee05b1..6e244d139f 100644 --- a/Source/Core/Core/HW/GCPadEmu.h +++ b/Source/Core/Core/HW/GCPadEmu.h @@ -29,7 +29,8 @@ enum class PadGroup Triggers, Rumble, Mic, - Options + Options, + Triforce, }; class GCPad : public ControllerEmu::EmulatedController @@ -61,6 +62,7 @@ public: static constexpr const char* RUMBLE_GROUP = _trans("Rumble"); static constexpr const char* MIC_GROUP = _trans("Microphone"); static constexpr const char* OPTIONS_GROUP = _trans("Options"); + static constexpr const char* TRIFORCE_GROUP = _trans("Triforce"); static constexpr const char* A_BUTTON = "A"; static constexpr const char* B_BUTTON = "B"; @@ -69,6 +71,11 @@ public: static constexpr const char* Z_BUTTON = "Z"; static constexpr const char* START_BUTTON = "Start"; + // Special Triforce buttons + static constexpr const char* TEST_BUTTON = "Test"; + static constexpr const char* SERVICE_BUTTON = "Service"; + static constexpr const char* COIN_BUTTON = "Coin"; + // i18n: The left trigger button (labeled L on real controllers) static constexpr const char* L_DIGITAL = _trans("L"); // i18n: The right trigger button (labeled R on real controllers) @@ -87,6 +94,7 @@ private: ControllerEmu::ControlGroup* m_rumble; ControllerEmu::Buttons* m_mic; ControllerEmu::ControlGroup* m_options; + ControllerEmu::Buttons* m_triforce; ControllerEmu::SettingValue m_always_connected_setting; diff --git a/Source/Core/Core/HW/Memmap.h b/Source/Core/Core/HW/Memmap.h index ce45d73034..4b33776a62 100644 --- a/Source/Core/Core/HW/Memmap.h +++ b/Source/Core/Core/HW/Memmap.h @@ -123,7 +123,6 @@ public: // If the specified range is within a single valid memory region, returns a pointer to the start // of the corresponding range in host memory. Otherwise, returns nullptr. u8* GetPointerForRange(u32 address, size_t size) const; - void CopyFromEmu(void* data, u32 address, size_t size) const; void CopyToEmu(u32 address, const void* data, size_t size); void Memset(u32 address, u8 value, size_t size); diff --git a/Source/Core/Core/HW/SI/SI.h b/Source/Core/Core/HW/SI/SI.h index 731b9b5c19..0a10b54a6a 100644 --- a/Source/Core/Core/HW/SI/SI.h +++ b/Source/Core/Core/HW/SI/SI.h @@ -56,6 +56,7 @@ public: void RemoveEvent(int device_number); void UpdateDevices(); + u32 GetInLength(void) const { return m_com_csr.INLNGTH; } void RemoveDevice(int device_number); void AddDevice(SIDevices device, int device_number); diff --git a/Source/Core/Core/HW/SI/SI_Device.cpp b/Source/Core/Core/HW/SI/SI_Device.cpp index 451523624b..8614bb156c 100644 --- a/Source/Core/Core/HW/SI/SI_Device.cpp +++ b/Source/Core/Core/HW/SI/SI_Device.cpp @@ -17,6 +17,7 @@ #ifdef HAS_LIBMGBA #include "Core/HW/SI/SI_DeviceGBAEmu.h" #endif +#include "Core/HW/SI/SI_DeviceAMBaseboard.h" #include "Core/HW/SI/SI_DeviceGCAdapter.h" #include "Core/HW/SI/SI_DeviceGCController.h" #include "Core/HW/SI/SI_DeviceGCSteeringWheel.h" @@ -149,6 +150,7 @@ bool SIDevice_IsGCController(SIDevices type) case SIDEVICE_GC_TARUKONGA: case SIDEVICE_DANCEMAT: case SIDEVICE_GC_STEERING: + case SIDEVICE_AM_BASEBOARD: return true; default: return false; @@ -191,6 +193,8 @@ std::unique_ptr SIDevice_Create(Core::System& system, const SIDevices return std::make_unique(system, device, port_number); case SIDEVICE_AM_BASEBOARD: + return std::make_unique(system, device, port_number); + case SIDEVICE_NONE: default: return std::make_unique(system, device, port_number); diff --git a/Source/Core/Core/HW/SI/SI_Device.h b/Source/Core/Core/HW/SI/SI_Device.h index 234c475e5f..c3171f42e8 100644 --- a/Source/Core/Core/HW/SI/SI_Device.h +++ b/Source/Core/Core/HW/SI/SI_Device.h @@ -97,8 +97,6 @@ enum SIDevices : int SIDEVICE_GC_STEERING, SIDEVICE_DANCEMAT, SIDEVICE_GC_TARUKONGA, - // Was used for Triforce in the past, but the implementation is no longer in Dolphin. - // It's kept here so that values below will stay constant. SIDEVICE_AM_BASEBOARD, SIDEVICE_WIIU_ADAPTER, SIDEVICE_GC_GBA_EMULATED, diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp new file mode 100644 index 0000000000..971c15e87a --- /dev/null +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp @@ -0,0 +1,2383 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/HW/SI/SI_DeviceAMBaseboard.h" + +#include +#include +#include +#include +#include + +#include + +#include "Common/Buffer.h" +#include "Common/ChunkFile.h" +#include "Common/CommonTypes.h" +#include "Common/Config/Config.h" +#include "Common/FileUtil.h" +#include "Common/Hash.h" +#include "Common/IOFile.h" +#include "Common/IniFile.h" +#include "Common/Logging/Log.h" +#include "Common/MsgHandler.h" +#include "Common/Swap.h" + +#include "Core/Boot/Boot.h" +#include "Core/BootManager.h" +#include "Core/Config/MainSettings.h" +#include "Core/Config/SYSCONFSettings.h" +#include "Core/ConfigLoaders/BaseConfigLoader.h" +#include "Core/ConfigLoaders/NetPlayConfigLoader.h" +#include "Core/ConfigManager.h" +#include "Core/Core.h" +#include "Core/CoreTiming.h" +#include "Core/HW/DVD/AMMediaboard.h" +#include "Core/HW/DVD/DVDInterface.h" +#include "Core/HW/EXI/EXI.h" +#include "Core/HW/GCPad.h" +#include "Core/HW/MMIO.h" +#include "Core/HW/Memmap.h" +#include "Core/HW/ProcessorInterface.h" +#include "Core/HW/SI/SI.h" +#include "Core/HW/SI/SI_Device.h" +#include "Core/HW/SI/SI_DeviceGCController.h" +#include "Core/HW/Sram.h" +#include "Core/HW/SystemTimers.h" +#include "Core/HW/WiimoteReal/WiimoteReal.h" +#include "Core/Movie.h" +#include "Core/NetPlayProto.h" +#include "Core/PowerPC/PowerPC.h" +#include "Core/System.h" +#include "Core/WiiRoot.h" + +#include "DiscIO/Enums.h" + +#include "InputCommon/GCPadStatus.h" + +namespace SerialInterface +{ + +JVSIOMessage::JVSIOMessage() +{ + m_ptr = 0; + m_last_start = 0; +} + +void JVSIOMessage::Start(int node) +{ + m_last_start = m_ptr; + const u8 hdr[3] = {0xE0, (u8)node, 0}; + m_csum = 0; + AddData(hdr, 3, 1); +} + +void JVSIOMessage::AddData(const u8* dst, std::size_t len, int sync = 0) +{ + while (len--) + { + const u8 c = *dst++; + if (!sync && ((c == 0xE0) || (c == 0xD0))) + { + m_msg[m_ptr++] = 0xD0; + m_msg[m_ptr++] = c - 1; + } + else + { + m_msg[m_ptr++] = c; + } + + if (!sync) + m_csum += c; + sync = 0; + if (m_ptr >= 0x80) + PanicAlertFmt("JVSIOMessage overrun!"); + } +} + +void JVSIOMessage::AddData(const void* data, std::size_t len) +{ + AddData((const u8*)data, len); +} + +void JVSIOMessage::AddData(const char* data) +{ + AddData(data, strlen(data)); +} + +void JVSIOMessage::AddData(u32 n) +{ + const u8 cs = n; + AddData(&cs, 1); +} + +void JVSIOMessage::End() +{ + const u32 len = m_ptr - m_last_start; + m_msg[m_last_start + 2] = len - 2; // assuming len <0xD0 + AddData(m_csum + len - 2); +} + +static u8 CheckSumXOR(u8* Data, u32 Length) +{ + u8 check = 0; + + for (u32 i = 0; i < Length; i++) + { + check ^= Data[i]; + } + + return check; +} + +// Reply has to be delayed due a bug in the parser +static void swap_buffers(u8* buffer, u32* buffer_length) +{ + static u8 last[2][0x80]; + static u32 lastptr[2]; + + memcpy(last[1], buffer, 0x80); // Save current buffer + memcpy(buffer, last[0], 0x80); // Load previous buffer + memcpy(last[0], last[1], 0x80); // Update history + + lastptr[1] = *buffer_length; // Swap lengths + *buffer_length = lastptr[0]; + lastptr[0] = lastptr[1]; +} + +static const char s_cdr_program_version[] = {" Version 1.22,2003/09/19,171-8213B"}; +static const char s_cdr_boot_version[] = {" Version 1.04,2003/06/17,171-8213B"}; +static const u8 s_cdr_card_data[] = { + 0x00, 0x6E, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x00, 0x00, 0x0B, + 0x00, 0x00, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x00, 0x00, 0x19, 0x00, 0x00, + 0x1A, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x20, 0x00, + 0x00, 0x22, 0x00, 0x00, 0x23, 0x00, 0x00, 0x24, 0x00, 0x00, 0x27, 0x00, 0x00, 0x28, + 0x00, 0x00, 0x2C, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x34, 0x00, 0x00, 0x35, 0x00, 0x00, + 0x37, 0x00, 0x00, 0x38, 0x00, 0x00, 0x39, 0x00, 0x00, 0x3D, 0x00}; + +const static u8 s_region_flags[] = "\x00\x00\x30\x00" + // "\x01\xfe\x00\x00" // JAPAN + "\x02\xfd\x00\x00" // USA + //"\x03\xfc\x00\x00" // export + "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"; +// AM-Baseboard device on SI +CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices device, + int device_number) + : ISIDevice(system, device, device_number) +{ + std::ranges::fill(m_coin, 0); + + // Setup IC-card + m_ic_card_state = 0x20; + m_ic_card_status = ICCARDStatus::Okay; + m_ic_card_session = 0x23; + + m_ic_write_size = 0; + m_ic_write_offset = 0; + + memset(m_ic_write_buffer, 0, sizeof(m_ic_write_buffer)); + memset(m_ic_card_data, 0, sizeof(m_ic_card_data)); + + // Card ID + m_ic_card_data[0x20] = 0x95; + m_ic_card_data[0x21] = 0x71; + + if (AMMediaboard::GetGameType() == KeyOfAvalon) + { + m_ic_card_data[0x22] = 0x26; + m_ic_card_data[0x23] = 0x40; + } + else if (AMMediaboard::GetGameType() == VirtuaStriker4) + { + m_ic_card_data[0x22] = 0x44; + m_ic_card_data[0x23] = 0x00; + } + + // Use count + m_ic_card_data[0x28] = 0xFF; + m_ic_card_data[0x29] = 0xFF; + + // Setup CARD + m_card_memory_size = 0; + m_card_is_inserted = false; + + m_card_offset = 0; + m_card_command = 0; + m_card_clean = 0; + + m_card_write_length = 0; + m_card_wrote = 0; + + m_card_read_length = 0; + m_card_read = 0; + + m_card_bit = 0; + m_card_shutter = true; // Open + m_card_state_call_count = 0; + + // Serial + m_wheel_init = 0; + + m_motor_init = 0; + m_motor_force_y = 0; + + m_fzdx_seatbelt = true; + m_fzdx_motion_stop = false; + m_fzdx_sensor_right = false; + m_fzdx_sensor_left = false; + + m_rx_reply = 0xF0; + + m_fzcc_seatbelt = true; + m_fzcc_sensor = false; + m_fzcc_emergency = false; + m_fzcc_service = false; + + memset(m_motor_reply, 0, sizeof(m_motor_reply)); +} + +constexpr u32 SI_XFER_LENGTH_MASK = 0x7f; + +// Translate [0,1,2,...,126,127] to [128,1,2,...,126,127] +constexpr s32 ConvertSILengthField(u32 field) +{ + return ((field - 1) & SI_XFER_LENGTH_MASK) + 1; +} + +void CSIDevice_AMBaseboard::ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length) +{ + iccommand->status = Common::swap16(iccommand->status); + + const u8 crc = CheckSumXOR(iccommand->data + 2, iccommand->pktlen - 1); + + for (u32 i = 0; i < iccommand->pktlen + 1; ++i) + { + buffer[(*length)++] = iccommand->data[i]; + } + + buffer[(*length)++] = crc; +} + +int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) +{ + const auto& serial_interface = m_system.GetSerialInterface(); + u32 buffer_length = ConvertSILengthField(serial_interface.GetInLength()); + + // Debug logging + ISIDevice::RunBuffer(buffer, buffer_length); + + u32 buffer_position = 0; + while (buffer_position < buffer_length) + { + BaseBoardCommand command = static_cast(buffer[buffer_position]); + buffer_position++; + + switch (command) + { + case BaseBoardCommand::GCAM_Reset: // Returns ID and dip switches + { + const u32 id = Common::swap32(SI_AM_BASEBOARD | 0x100); + std::memcpy(buffer, &id, sizeof(id)); + return sizeof(id); + } + break; + case BaseBoardCommand::GCAM_Command: + { + u32 checksum = 0; + for (u32 i = 0; i < buffer_length; ++i) + checksum += buffer[i]; + + u8 data_out[0x80]{}; + u32 data_offset = 0; + + static u32 dip_switch_1 = 0xFE; + static u32 dip_switch_0 = 0xFF; + + data_out[data_offset++] = 1; + data_out[data_offset++] = 1; + + u8* data_in = buffer + 2; + const u8* data_in_end = buffer + buffer[buffer_position] + 2; + + while (data_in < data_in_end) + { + const u32 gcam_command = *data_in++; + switch (GCAMCommand(gcam_command)) + { + case GCAMCommand::StatusSwitches: + { + const u8 status = *data_in++; + DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x10, {:02x} (READ STATUS&SWITCHES)", + status); + + data_out[data_offset++] = gcam_command; + data_out[data_offset++] = 0x2; + + // We read Test/Service from the JVS-I/O SwitchesInput instead + // + // const GCPadStatus PadStatus = Pad::GetStatus(ISIDevice::m_device_number); + // baseboard test/service switches + // if (PadStatus.button & PAD_BUTTON_Y) // Test + // dip_switch_0 &= ~0x80; + // if (PadStatus.button & PAD_BUTTON_X) // Service + // dip_switch_0 &= ~0x40; + + // Horizontal Scanning Frequency switch + // Required for F-Zero AX booting via Sega Boot + if (AMMediaboard::GetGameType() == FZeroAX || + AMMediaboard::GetGameType() == FZeroAXMonster) + { + dip_switch_0 &= ~0x20; + } + + // Disable camera in MKGP1/2 + if (AMMediaboard::GetGameType() == MarioKartGP || + AMMediaboard::GetGameType() == MarioKartGP2) + { + dip_switch_0 &= ~0x10; + } + + data_out[data_offset++] = dip_switch_0; + data_out[data_offset++] = dip_switch_1; + break; + } + case GCAMCommand::SerialNumber: + { + NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x11, {:02x} (READ SERIAL NR)", + *data_in++); + + data_out[data_offset++] = gcam_command; + data_out[data_offset++] = 16; + memcpy(data_out + data_offset, "AADE-01B98394904", 16); + + data_offset += 16; + break; + } + case GCAMCommand::Unknown_12: + NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x12, {:02x} {:02x}", data_in[0], + data_in[1]); + + data_out[data_offset++] = gcam_command; + data_out[data_offset++] = 0x00; + + data_in += 2; + break; + case GCAMCommand::Unknown_14: + NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x14, {:02x} {:02x}", data_in[0], + data_in[1]); + + data_out[data_offset++] = gcam_command; + data_out[data_offset++] = 0x00; + + data_in += 2; + break; + case GCAMCommand::FirmVersion: + NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x15, {:02x} (READ FIRM VERSION)", + *data_in++); + + data_out[data_offset++] = gcam_command; + data_out[data_offset++] = 0x02; + // 00.26 + data_out[data_offset++] = 0x00; + data_out[data_offset++] = 0x26; + break; + case GCAMCommand::FPGAVersion: + NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x16, {:02x} (READ FPGA VERSION)", + *data_in++); + + data_out[data_offset++] = gcam_command; + data_out[data_offset++] = 0x02; + // 07.06 + data_out[data_offset++] = 0x07; + data_out[data_offset++] = 0x06; + break; + case GCAMCommand::RegionSettings: + { + // Used by SegaBoot for region checks (dev mode skips this check) + // In some games this also controls the displayed language + NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, + "GC-AM: Command 0x1F, {:02x} {:02x} {:02x} {:02x} {:02x} (REGION)", + data_in[0], data_in[1], data_in[2], data_in[3], data_in[4]); + + data_out[data_offset++] = gcam_command; + data_out[data_offset++] = 0x14; + + for (int i = 0; i < 0x14; ++i) + data_out[data_offset++] = s_region_flags[i]; + + data_in += 5; + } + break; + // No reply + // Note: Always sends three bytes even though size is set to two + case GCAMCommand::Unknown_21: + { + DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x21, {:02x}, {:02x}, {:02x}, {:02x}", + data_in[0], data_in[1], data_in[2], data_in[3]); + data_in += 4; + } + break; + // No reply + // Note: Always sends six bytes + case GCAMCommand::Unknown_22: + { + DEBUG_LOG_FMT( + SERIALINTERFACE_AMBB, + "GC-AM: Command 0x22, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}", + data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5], data_in[6]); + data_in += data_in[0] + 1; + } + break; + case GCAMCommand::Unknown_23: + DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x23, {:02x} {:02x}", data_in[0], + data_in[1]); + + data_out[data_offset++] = gcam_command; + data_out[data_offset++] = 0x00; + + data_in += 2; + break; + case GCAMCommand::Unknown_24: + DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x24, {:02x} {:02x}", data_in[0], + data_in[1]); + + data_out[data_offset++] = gcam_command; + data_out[data_offset++] = 0x00; + + data_in += 2; + break; + case GCAMCommand::SerialA: + { + u32 length = *data_in++; + if (length) + { + INFO_LOG_FMT(SERIALINTERFACE_AMBB, + "GC-AM: Command 0x31, {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} " + "{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", + length, data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], + data_in[5], data_in[6], data_in[7], data_in[8], data_in[9], data_in[10], + data_in[11], data_in[12]); + + // Serial - Wheel + if (AMMediaboard::GetGameType() == MarioKartGP || + AMMediaboard::GetGameType() == MarioKartGP2) + { + INFO_LOG_FMT(SERIALINTERFACE_AMBB, + "GC-AM: Command 0x31, (WHEEL) {:02x}{:02x} {:02x}{:02x} {:02x} {:02x} " + "{:02x} {:02x} {:02x} {:02x}", + data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5], + data_in[6], data_in[7], data_in[8], data_in[9]); + + data_out[data_offset++] = gcam_command; + data_out[data_offset++] = 0x03; + + switch (m_wheel_init) + { + case 0: + data_out[data_offset++] = 'E'; // Error + data_out[data_offset++] = '0'; + data_out[data_offset++] = '0'; + m_wheel_init++; + break; + case 1: + data_out[data_offset++] = 'C'; // Power Off + data_out[data_offset++] = '0'; + data_out[data_offset++] = '6'; + // Only turn on when a wheel is connected + if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING) + { + m_wheel_init++; + } + break; + case 2: + data_out[data_offset++] = 'C'; // Power On + data_out[data_offset++] = '0'; + data_out[data_offset++] = '1'; + break; + default: + break; + } + + // u16 CenteringForce= ptr(6); + // u16 FrictionForce = ptr(8); + // u16 Roll = ptr(10); + + data_in += length; + break; + } + + // Serial - Unknown + if (AMMediaboard::GetGameType() == GekitouProYakyuu) + { + u32 serial_command = *(u32*)(data_in); + + if (serial_command == 0x00001000) + { + data_out[data_offset++] = gcam_command; + data_out[data_offset++] = 0x03; + data_out[data_offset++] = 1; + data_out[data_offset++] = 2; + data_out[data_offset++] = 3; + } + + data_in += length; + break; + } + + // Serial IC-CARD / Serial Deck Reader + if (AMMediaboard::GetGameType() == VirtuaStriker4 || + AMMediaboard::GetGameType() == VirtuaStriker4_2006 || + AMMediaboard::GetGameType() == KeyOfAvalon) + { + u32 serial_command = data_in[1]; + + ICCommand icco; + + // Set default reply + icco.pktcmd = gcam_command; + icco.pktlen = 7; + icco.fixed = 0x10; + icco.command = serial_command; + icco.flag = 0; + icco.length = 2; + icco.status = 0; + icco.extlen = 0; + + // Check for rest of data from the write pages command + if (m_ic_write_size && m_ic_write_offset) + { + const u32 size = data_in[1]; + + char logptr[1024]; + char* log = logptr; + + for (u32 i = 0; i < (u32)(data_in[1] + 2); ++i) + { + log += sprintf(log, "%02X ", data_in[i]); + } + + INFO_LOG_FMT(SERIALINTERFACE_CARD, "Command: {}", logptr); + + INFO_LOG_FMT( + SERIALINTERFACE_CARD, + "GC-AM: Command 25 (IC-CARD) Write Pages: Off:{:x} Size:{:x} PSize:{:x}", + m_ic_write_offset, m_ic_write_size, size); + + memcpy(m_ic_write_buffer + m_ic_write_offset, data_in + 2, size); + + m_ic_write_offset += size; + + if (m_ic_write_offset > m_ic_write_size) + { + m_ic_write_offset = 0; + + const u16 page = m_ic_write_buffer[5]; + const u16 count = m_ic_write_buffer[7]; + + memcpy(m_ic_card_data + page * 8, m_ic_write_buffer + 10, count * 8); + + INFO_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command 25 (IC-CARD) Write Pages:{} Count:{}({:x})", page, + count, size); + + icco.command = WritePages; + + ICCardSendReply(&icco, data_out, &data_offset); + } + data_in += length; + break; + } + + switch (ICCARDCommand(serial_command)) + { + case ICCARDCommand::GetStatus: + icco.status = m_ic_card_state; + + INFO_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command 0x31 (IC-CARD) Get Status:{:02x}", m_ic_card_state); + break; + case ICCARDCommand::SetBaudrate: + INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Set Baudrate"); + break; + case ICCARDCommand::FieldOn: + m_ic_card_state |= 0x10; + INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Field On"); + break; + case ICCARDCommand::InsertCheck: + icco.status = m_ic_card_status; + INFO_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command 0x31 (IC-CARD) Insert Check:{:02x}", m_ic_card_status); + break; + case ICCARDCommand::AntiCollision: + icco.extlen = 8; + icco.length += icco.extlen; + icco.pktlen += icco.extlen; + + // Card ID + icco.extdata[0] = 0x00; + icco.extdata[1] = 0x00; + icco.extdata[2] = 0x54; + icco.extdata[3] = 0x4D; + icco.extdata[4] = 0x50; + icco.extdata[5] = 0x00; + icco.extdata[6] = 0x00; + icco.extdata[7] = 0x00; + + INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Anti Collision"); + break; + case ICCARDCommand::SelectCard: + icco.extlen = 8; + icco.length += icco.extlen; + icco.pktlen += icco.extlen; + + // Session + icco.extdata[0] = 0x00; + icco.extdata[1] = m_ic_card_session; + icco.extdata[2] = 0x00; + icco.extdata[3] = 0x00; + icco.extdata[4] = 0x00; + icco.extdata[5] = 0x00; + icco.extdata[6] = 0x00; + icco.extdata[7] = 0x00; + + INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Select Card:{}", + m_ic_card_session); + break; + case ICCARDCommand::ReadPage: + case ICCARDCommand::ReadUseCount: + { + const u16 page = Common::swap16(*(u16*)(data_in + 6)); + + icco.extlen = 8; + icco.length += icco.extlen; + icco.pktlen += icco.extlen; + + memcpy(icco.extdata, m_ic_card_data + page * 8, 8); + + INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 31 (IC-CARD) Read Page:{}", + page); + break; + } + case ICCARDCommand::WritePage: + { + const u16 page = Common::swap16(*(u16*)(data_in + 8)); + + // Write only one page + if (page == 4) + { + icco.status = 0x80; + } + else + { + memcpy(m_ic_card_data + page * 8, data_in + 10, 8); + } + + INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-CARD) Write Page:{}", + page); + break; + } + case ICCARDCommand::DecreaseUseCount: + { + const u16 page = Common::swap16(*(u16*)(data_in + 6)); + + icco.extlen = 2; + icco.length += icco.extlen; + icco.pktlen += icco.extlen; + + *(u16*)(m_ic_card_data + 0x28) = *(u16*)(m_ic_card_data + 0x28) - 1; + + // Counter + icco.extdata[0] = m_ic_card_data[0x28]; + icco.extdata[1] = m_ic_card_data[0x29]; + + INFO_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command 31 (IC-CARD) Decrease Use Count:{}", page); + break; + } + case ICCARDCommand::ReadPages: + { + const u16 page = Common::swap16(*(u16*)(data_in + 6)); + const u16 count = Common::swap16(*(u16*)(data_in + 8)); + + const u32 offs = page * 8; + u32 cnt = count * 8; + + // Limit read size to not overwrite the reply buffer + if (cnt > (u32)0x50 - data_offset) + { + cnt = 5 * 8; + } + + icco.extlen = cnt; + icco.length += icco.extlen; + icco.pktlen += icco.extlen; + + memcpy(icco.extdata, m_ic_card_data + offs, cnt); + + INFO_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command 31 (IC-CARD) Read Pages:{} Count:{}", page, count); + break; + } + case ICCARDCommand::WritePages: + { + const u16 pksize = length; + const u16 size = Common::swap16(*(u16*)(data_in + 2)); + const u16 page = Common::swap16(*(u16*)(data_in + 6)); + const u16 count = Common::swap16(*(u16*)(data_in + 8)); + + // We got a complete packet + if (pksize - 5 == size) + { + if (page == 4) // Read Only Page, must return error + { + icco.status = 0x80; + } + else + { + memcpy(m_ic_card_data + page * 8, data_in + 13, count * 8); + } + + INFO_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command 0x31 (IC-CARD) Write Pages:{} Count:{}({:x})", page, + count, size); + } + // VirtuaStriker 4 splits the writes over multiple packets + else + { + memcpy(m_ic_write_buffer, data_in + 2, pksize); + m_ic_write_offset += pksize; + m_ic_write_size = size; + } + break; + } + default: + // Handle Deck Reader commands + serial_command = data_in[0]; + icco.command = serial_command; + icco.flag = 0; + switch (CDReaderCommand(serial_command)) + { + case CDReaderCommand::ProgramVersion: + INFO_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command 0x31 (DECK READER) Program Version"); + + icco.extlen = (u32)strlen(s_cdr_program_version); + icco.length += icco.extlen; + icco.pktlen += icco.extlen; + + memcpy(icco.extdata, s_cdr_program_version, icco.extlen); + break; + case CDReaderCommand::BootVersion: + INFO_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command 0x31 (DECK READER) Boot Version"); + + icco.extlen = (u32)strlen(s_cdr_boot_version); + icco.length += icco.extlen; + icco.pktlen += icco.extlen; + + memcpy(icco.extdata, s_cdr_boot_version, icco.extlen); + break; + case CDReaderCommand::ShutterGet: + INFO_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command 0x31 (DECK READER) Shutter Get"); + + icco.extlen = 4; + icco.length += icco.extlen; + icco.pktlen += icco.extlen; + + icco.extdata[0] = 0; + icco.extdata[1] = 0; + icco.extdata[2] = 0; + icco.extdata[3] = 0; + break; + case CDReaderCommand::CameraCheck: + INFO_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command 0x31 (DECK READER) Camera Check"); + + icco.extlen = 6; + icco.length += icco.extlen; + icco.pktlen += icco.extlen; + + icco.extdata[0] = 0x23; + icco.extdata[1] = 0x28; + icco.extdata[2] = 0x45; + icco.extdata[3] = 0x29; + icco.extdata[4] = 0x45; + icco.extdata[5] = 0x29; + break; + case CDReaderCommand::ProgramChecksum: + INFO_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command 0x31 (DECK READER) Program Checksum"); + + icco.extlen = 4; + icco.length += icco.extlen; + icco.pktlen += icco.extlen; + + icco.extdata[0] = 0x23; + icco.extdata[1] = 0x28; + icco.extdata[2] = 0x45; + icco.extdata[3] = 0x29; + break; + case CDReaderCommand::BootChecksum: + INFO_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command 0x31 (DECK READER) Boot Checksum"); + + icco.extlen = 4; + icco.length += icco.extlen; + icco.pktlen += icco.extlen; + + icco.extdata[0] = 0x23; + icco.extdata[1] = 0x28; + icco.extdata[2] = 0x45; + icco.extdata[3] = 0x29; + break; + case CDReaderCommand::SelfTest: + INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Self Test"); + icco.flag = 0x00; + break; + case CDReaderCommand::SensLock: + INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Lock"); + icco.flag = 0x01; + break; + case CDReaderCommand::SensCard: + INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Sens Card"); + break; + case CDReaderCommand::ShutterCard: + INFO_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command 0x31 (DECK READER) Shutter Card"); + break; + case CDReaderCommand::ReadCard: + INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (DECK READER) Read Card"); + + icco.fixed = 0xAA; + icco.flag = 0xAA; + icco.extlen = sizeof(s_cdr_card_data); + icco.length = 0x72; + icco.status = Common::swap16(icco.extlen); + + icco.pktlen += icco.extlen; + + memcpy(icco.extdata, s_cdr_card_data, sizeof(s_cdr_card_data)); + + break; + default: + WARN_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command 0x31 (IC-Card) {:02x} {:02x} {:02x} {:02x} {:02x} " + "{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", + data_in[2], data_in[3], data_in[4], data_in[5], data_in[6], + data_in[7], data_in[8], data_in[9], data_in[10], data_in[11], + data_in[12], data_in[13]); + break; + } + break; + } + + ICCardSendReply(&icco, data_out, &data_offset); + + data_in += length; + break; + } + } + + u32 command_offset = 0; + while (command_offset < length) + { + // All commands are OR'd with 0x80 + // Last byte is checksum which we don't care about + const u32 serial_command = + Common::swap32(*(u32*)(data_in + command_offset)) ^ 0x80000000; + + if (AMMediaboard::GetGameType() == FZeroAX || + AMMediaboard::GetGameType() == FZeroAXMonster) + { + INFO_LOG_FMT(SERIALINTERFACE_AMBB, + "GC-AM: Command 0x31 (MOTOR) Length:{:02x} Command:{:04x}({:02x})", + length, (serial_command >> 8) & 0xFFFF, serial_command >> 24); + } + else + { + INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31 (SERIAL) Command:{:06x}", + serial_command); + + if (/*command == 0xf43200 || */ serial_command == 0x801000) + { + // u32 PC = m_system.GetPowerPC().GetPPCState().pc; + + // INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GCAM: PC:{:08x}", PC); + + // m_system.GetPowerPC().GetBreakPoints().Add(PC + 8, true, true, std::nullopt); + + data_out[data_offset++] = 0x31; + data_out[data_offset++] = 0x02; + data_out[data_offset++] = 0xFF; + data_out[data_offset++] = 0x01; + } + } + + command_offset += 4; + + if (AMMediaboard::GetGameType() == FZeroAX || + AMMediaboard::GetGameType() == FZeroAXMonster) + { + // Status + m_motor_reply[command_offset + 2] = 0; + m_motor_reply[command_offset + 3] = 0; + + // Error + m_motor_reply[command_offset + 4] = 0; + + switch (serial_command >> 24) + { + case 0: + break; + case 1: // Set Maximum? + break; + case 2: + break; + + // 0x00-0x40: left + // 0x40-0x80: right + case 4: // Move Steering Wheel + // Left + if (serial_command & 0x010000) + { + m_motor_force_y = -((s16)serial_command & 0xFF00); + } + else // Right + { + m_motor_force_y = (serial_command - 0x4000) & 0xFF00; + } + + m_motor_force_y *= 2; + + // FFB + if (m_motor_init == 2) + { + if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING) + { + const GCPadStatus PadStatus = Pad::GetStatus(1); + if (PadStatus.isConnected) + { + const ControlState mapped_strength = (double)(m_motor_force_y >> 8) / 127.f; + + Pad::Rumble(1, mapped_strength); + INFO_LOG_FMT(SERIALINTERFACE_AMBB, + "GC-AM: Command 0x31 (MOTOR) mapped_strength:{}", + mapped_strength); + } + } + } + break; + case 6: // nice + case 9: + default: + break; + // Switch back to normal controls + case 7: + m_motor_init = 2; + break; + // Reset + case 0x7F: + m_motor_init = 1; + memset(m_motor_reply, 0, sizeof(m_motor_reply)); + break; + } + + // Checksum + m_motor_reply[command_offset + 5] = m_motor_reply[command_offset + 2] ^ + m_motor_reply[command_offset + 3] ^ + m_motor_reply[command_offset + 4]; + } + } + + if (length == 0) + { + data_out[data_offset++] = gcam_command; + data_out[data_offset++] = 0x00; + } + else + { + if (m_motor_init) + { + // Motor + m_motor_reply[0] = gcam_command; + m_motor_reply[1] = length; // Same out as in size + + memcpy(data_out + data_offset, m_motor_reply, m_motor_reply[1] + 2); + data_offset += m_motor_reply[1] + 2; + } + } + + data_in += length; + break; + } + case GCAMCommand::SerialB: + { + DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 32 (CARD-Interface)"); + const u32 length = *data_in++; + if (length) + { + // Send Card Reply + if (length == 1 && data_in[0] == 0x05) + { + if (m_card_read_length) + { + data_out[data_offset++] = gcam_command; + u32 read_length = m_card_read_length - m_card_read; + + if (AMMediaboard::GetGameType() == FZeroAX) + { + if (read_length > 0x2F) + read_length = 0x2F; + } + + data_out[data_offset++] = read_length; // 0x2F (max size per packet) + + memcpy(data_out + data_offset, m_card_read_packet + m_card_read, read_length); + + data_offset += read_length; + m_card_read += read_length; + + if (m_card_read >= m_card_read_length) + m_card_read_length = 0; + + data_in += length; + break; + } + + data_out[data_offset++] = gcam_command; + const u32 command_length_offset = data_offset; + data_out[data_offset++] = 0x00; // len + + data_out[data_offset++] = 0x02; // + const u32 checksum_start = data_offset; + + data_out[data_offset++] = 0x00; // 0x00 len + + data_out[data_offset++] = m_card_command; // 0x01 command + + switch (CARDCommand(m_card_command)) + { + case CARDCommand::Init: + data_out[data_offset++] = 0x00; // 0x02 + data_out[data_offset++] = 0x30; // 0x03 + break; + case CARDCommand::GetState: + data_out[data_offset++] = 0x20 | m_card_bit; // 0x02 + + // bit 0: Please take your card + // bit 1: Endless waiting causes UNK_E to be called + data_out[data_offset++] = 0x00; // 0x03 + break; + case CARDCommand::Read: + data_out[data_offset++] = 0x02; // 0x02 + data_out[data_offset++] = 0x53; // 0x03 + break; + case CARDCommand::IsPresent: + data_out[data_offset++] = 0x22; // 0x02 + data_out[data_offset++] = 0x30; // 0x03 + break; + case CARDCommand::Write: + data_out[data_offset++] = 0x02; // 0x02 + data_out[data_offset++] = 0x00; // 0x03 + break; + case CARDCommand::SetPrintParam: + data_out[data_offset++] = 0x00; // 0x02 + data_out[data_offset++] = 0x00; // 0x03 + break; + case CARDCommand::RegisterFont: + data_out[data_offset++] = 0x00; // 0x02 + data_out[data_offset++] = 0x00; // 0x03 + break; + case CARDCommand::WriteInfo: + data_out[data_offset++] = 0x02; // 0x02 + data_out[data_offset++] = 0x00; // 0x03 + break; + case CARDCommand::Eject: + if (AMMediaboard::GetGameType() == FZeroAX) + { + data_out[data_offset++] = 0x01; // 0x02 + } + else + { + data_out[data_offset++] = 0x31; // 0x02 + } + data_out[data_offset++] = 0x30; // 0x03 + break; + case CARDCommand::Clean: + data_out[data_offset++] = 0x02; // 0x02 + data_out[data_offset++] = 0x00; // 0x03 + break; + case CARDCommand::Load: + data_out[data_offset++] = 0x02; // 0x02 + data_out[data_offset++] = 0x30; // 0x03 + break; + case CARDCommand::SetShutter: + data_out[data_offset++] = 0x00; // 0x02 + data_out[data_offset++] = 0x00; // 0x03 + break; + } + + data_out[data_offset++] = 0x30; // 0x04 + data_out[data_offset++] = 0x00; // 0x05 + + data_out[data_offset++] = 0x03; // 0x06 + + data_out[checksum_start] = data_offset - checksum_start; // 0x00 len + + data_out[data_offset] = 0; // 0x07 + for (u32 i = 0; i < data_out[checksum_start]; ++i) + data_out[data_offset] ^= data_out[checksum_start + i]; + + data_offset++; + + data_out[command_length_offset] = data_out[checksum_start] + 2; + } + else + { + for (u32 i = 0; i < length; ++i) + m_card_buffer[m_card_offset + i] = data_in[i]; + + m_card_offset += length; + + // Check if we got a complete command + if (m_card_buffer[0] == 0x02) + { + if (m_card_buffer[1] == m_card_offset - 2) + { + if (m_card_buffer[m_card_offset - 2] == 0x03) + { + m_card_command = m_card_buffer[2]; + + switch (CARDCommand(m_card_command)) + { + case CARDCommand::Init: + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Init"); + + m_card_write_length = 0; + m_card_bit = 0; + m_card_memory_size = 0; + m_card_state_call_count = 0; + break; + case CARDCommand::GetState: + { + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD GetState({:02X})", + m_card_bit); + + if (m_card_memory_size == 0) + { + const std::string card_filename( + fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX), + SConfig::GetInstance().GetGameID())); + + if (File::Exists(card_filename)) + { + File::IOFile card(card_filename, "rb+"); + m_card_memory_size = (u32)card.GetSize(); + + card.ReadBytes(m_card_memory, m_card_memory_size); + card.Close(); + + m_card_is_inserted = true; + } + } + + if (AMMediaboard::GetGameType() == FZeroAX && m_card_memory_size) + { + m_card_state_call_count++; + if (m_card_state_call_count > 10) + { + if (m_card_bit & 2) + m_card_bit &= ~2; + else + m_card_bit |= 2; + + m_card_state_call_count = 0; + } + } + + if (m_card_clean == 1) + { + m_card_clean = 2; + } + else if (m_card_clean == 2) + { + const std::string card_filename( + fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX), + SConfig::GetInstance().GetGameID())); + + if (File::Exists(card_filename)) + { + m_card_memory_size = (u32)File::GetSize(card_filename); + if (m_card_memory_size) + { + if (AMMediaboard::GetGameType() == FZeroAX) + { + m_card_bit = 2; + } + else + { + m_card_bit = 1; + } + } + } + m_card_clean = 0; + } + break; + } + case CARDCommand::IsPresent: + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD IsPresent"); + break; + case CARDCommand::RegisterFont: + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD RegisterFont"); + break; + case CARDCommand::Load: + { + u8 mode = m_card_buffer[6]; + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Load({:02X})", + mode); + break; + } + case CARDCommand::Clean: + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Clean"); + m_card_clean = 1; + break; + case CARDCommand::Read: + { + const u8 mode = m_card_buffer[6]; + const u8 bitmode = m_card_buffer[7]; + const u8 track = m_card_buffer[8]; + + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command CARD Read({:02X},{:02X},{:02X})", mode, + bitmode, track); + + // Prepare read packet + memset(m_card_read_packet, 0, 0xDB); + u32 packet_offset = 0; + + const std::string card_filename( + fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX), + SConfig::GetInstance().GetGameID())); + + if (File::Exists(card_filename)) + { + File::IOFile card(card_filename, "rb+"); + if (m_card_memory_size == 0) + { + m_card_memory_size = (u32)card.GetSize(); + } + + card.ReadBytes(m_card_memory, m_card_memory_size); + card.Close(); + + m_card_is_inserted = true; + } + + m_card_read_packet[packet_offset++] = 0x02; // SUB CMD + m_card_read_packet[packet_offset++] = 0x00; // SUB CMDLen + + m_card_read_packet[packet_offset++] = 0x33; // CARD CMD + + if (m_card_is_inserted) // CARD Status + { + m_card_read_packet[packet_offset++] = 0x31; + } + else + { + m_card_read_packet[packet_offset++] = 0x30; + } + + m_card_read_packet[packet_offset++] = 0x30; + m_card_read_packet[packet_offset++] = 0x30; + + // Data reply + memcpy(m_card_read_packet + packet_offset, m_card_memory, m_card_memory_size); + packet_offset += m_card_memory_size; + + m_card_read_packet[packet_offset++] = 0x03; + + m_card_read_packet[1] = packet_offset - 1; // SUB CMDLen + + for (u32 i = 0; i < packet_offset - 1; ++i) + m_card_read_packet[packet_offset] ^= m_card_read_packet[1 + i]; + + packet_offset++; + + m_card_read_length = packet_offset; + m_card_read = 0; + break; + } + case CARDCommand::Write: + { + const u8 mode = m_card_buffer[6]; + const u8 bitmode = m_card_buffer[7]; + const u8 track = m_card_buffer[8]; + + m_card_memory_size = m_card_buffer[1] - 9; + + memcpy(m_card_memory, m_card_buffer + 9, m_card_memory_size); + + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command CARD Write: {:02X} {:02X} {:02X} {}", mode, + bitmode, track, m_card_memory_size); + + const std::string card_filename( + File::GetUserPath(D_TRIUSER_IDX) + "tricard_" + + SConfig::GetInstance().GetGameID().c_str() + ".bin"); + + File::IOFile card(card_filename, "wb+"); + card.WriteBytes(m_card_memory, m_card_memory_size); + card.Close(); + + m_card_bit = 2; + + m_card_state_call_count = 0; + break; + } + case CARDCommand::SetPrintParam: + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD SetPrintParam"); + break; + case CARDCommand::WriteInfo: + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD WriteInfo"); + break; + case CARDCommand::Erase: + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Erase"); + break; + case CARDCommand::Eject: + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Eject"); + if (AMMediaboard::GetGameType() != FZeroAX) + { + m_card_bit = 0; + } + break; + case CARDCommand::SetShutter: + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD SetShutter"); + if (AMMediaboard::GetGameType() != FZeroAX) + { + m_card_bit = 0; + } + // Close + if (m_card_buffer[6] == 0x30) + { + m_card_shutter = false; + } + // Open + else if (m_card_buffer[6] == 0x31) + { + m_card_shutter = true; + } + break; + default: + ERROR_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: CARD:Unhandled command!"); + ERROR_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: CARD:[{:08X}]", m_card_command); + // hexdump( m_card_buffer, m_card_offset ); + break; + } + m_card_offset = 0; + } + } + } + + data_out[data_offset++] = 0x32; + data_out[data_offset++] = 0x01; // len + data_out[data_offset++] = 0x06; // OK + } + } + else + { + data_out[data_offset++] = gcam_command; + data_out[data_offset++] = 0x00; // len + } + data_in += length; + break; + } + case GCAMCommand::JVSIOA: + case GCAMCommand::JVSIOB: + { + DEBUG_LOG_FMT( + SERIALINTERFACE_JVSIO, + "GC-AM: Command {:02x}, {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} (JVS IO)", + gcam_command, data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5], + data_in[6]); + + JVSIOMessage message; + + static int delay = 0; + + const u8* frame = &data_in[0]; + const u8 nr_bytes = frame[3]; // Byte after E0 xx + const u32 frame_len = nr_bytes + 3; // Header(2) + length byte + payload + checksum + + u8 jvs_buf[0x80]; + memcpy(jvs_buf, frame, frame_len); + + // Extract node and payload pointers + u8 node = jvs_buf[2]; + u8* jvs_io = jvs_buf + 4; // First payload byte + const u8* jvs_end = jvs_buf + frame_len; // One byte before checksum + + message.Start(0); + message.AddData(1); + + // Now iterate over the payload + while (jvs_io < jvs_end) + { + int jvsio_command = *jvs_io++; + DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO:node={}, command={:02x}", node, + jvsio_command); + + switch (JVSIOCommand(jvsio_command)) + { + case JVSIOCommand::IOID: + message.AddData(StatusOkay); + switch (AMMediaboard::GetGameType()) + { + case FZeroAX: + // Specific version that enables DX mode on AX machines, all this does is enable the + // motion of a chair + message.AddData("SEGA ENTERPRISES,LTD.;837-13844-01 I/O CNTL BD2 ;"); + break; + case FZeroAXMonster: + case MarioKartGP: + case MarioKartGP2: + default: + message.AddData("namco ltd.;FCA-1;Ver1.01;JPN,Multipurpose + Rotary Encoder"); + break; + case VirtuaStriker3: + case VirtuaStriker4: + case VirtuaStriker4_2006: + message.AddData("SEGA ENTERPRISES,LTD.;I/O BD JVS;837-13551;Ver1.00"); + break; + } + NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x10, BoardID"); + message.AddData((u32)0); + break; + case JVSIOCommand::CommandRevision: + message.AddData(StatusOkay); + message.AddData(0x11); + NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x11, CommandRevision"); + break; + case JVSIOCommand::JVRevision: + message.AddData(StatusOkay); + message.AddData(0x20); + NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x12, JVRevision"); + break; + case JVSIOCommand::CommunicationVersion: + message.AddData(StatusOkay); + message.AddData(0x10); + NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x13, CommunicationVersion"); + break; + + // Slave features: + // + // Inputs: + // 0x01: Switch input: players, buttons + // 0x02: Coin input: slots + // 0x03: Analog input: channels, bits + // 0x04: Rotary input: channels + // 0x05: Keycode input: 0,0,0 ? + // 0x06: Screen position input: X bits, Y bits, channels + // + // Outputs: + // 0x10: Card system: slots + // 0x11: Medal hopper: channels + // 0x12: GPO-out: slots + // 0x13: Analog output: channels + // 0x14: Character output: width, height, type + // 0x15: Backup + case JVSIOCommand::CheckFunctionality: + message.AddData(StatusOkay); + switch (AMMediaboard::GetGameType()) + { + case FZeroAX: + case FZeroAXMonster: + // 2 Player (12bit) (p2=paddles), 1 Coin slot, 6 Analog-in + // message.AddData((void *)"\x01\x02\x0C\x00", 4); + // message.AddData((void *)"\x02\x01\x00\x00", 4); + // message.AddData((void *)"\x03\x06\x00\x00", 4); + // message.AddData((void *)"\x00\x00\x00\x00", 4); + // + // DX Version: 2 Player (22bit) (p2=paddles), 2 Coin slot, 8 Analog-in, + // 22 Driver-out + message.AddData((void*)"\x01\x02\x12\x00", 4); + message.AddData((void*)"\x02\x02\x00\x00", 4); + message.AddData((void*)"\x03\x08\x0A\x00", 4); + message.AddData((void*)"\x12\x16\x00\x00", 4); + message.AddData((void*)"\x00\x00\x00\x00", 4); + break; + case VirtuaStriker3: + // 2 Player (13bit), 2 Coin slot, 4 Analog-in, 1 CARD, 8 Driver-out + message.AddData((void*)"\x01\x02\x0D\x00", 4); + message.AddData((void*)"\x02\x02\x00\x00", 4); + message.AddData((void*)"\x10\x01\x00\x00", 4); + message.AddData((void*)"\x12\x08\x00\x00", 4); + message.AddData((void*)"\x00\x00\x00\x00", 4); + break; + case GekitouProYakyuu: + // 2 Player (13bit), 2 Coin slot, 4 Analog-in, 1 CARD, 8 Driver-out + message.AddData((void*)"\x01\x02\x0D\x00", 4); + message.AddData((void*)"\x02\x02\x00\x00", 4); + message.AddData((void*)"\x03\x04\x00\x00", 4); + message.AddData((void*)"\x10\x01\x00\x00", 4); + message.AddData((void*)"\x12\x08\x00\x00", 4); + message.AddData((void*)"\x00\x00\x00\x00", 4); + break; + case VirtuaStriker4: + case VirtuaStriker4_2006: + // 2 Player (13bit), 1 Coin slot, 4 Analog-in, 1 CARD + message.AddData((void*)"\x01\x02\x0D\x00", 4); + message.AddData((void*)"\x02\x01\x00\x00", 4); + message.AddData((void*)"\x03\x04\x00\x00", 4); + message.AddData((void*)"\x10\x01\x00\x00", 4); + message.AddData((void*)"\x00\x00\x00\x00", 4); + break; + case KeyOfAvalon: + // 1 Player (15bit), 1 Coin slot, 3 Analog-in, Touch, 1 CARD, 1 Driver-out + // (Unconfirmed) + message.AddData((void*)"\x01\x01\x0F\x00", 4); + message.AddData((void*)"\x02\x01\x00\x00", 4); + message.AddData((void*)"\x03\x03\x00\x00", 4); + message.AddData((void*)"\x06\x10\x10\x01", 4); + message.AddData((void*)"\x10\x01\x00\x00", 4); + message.AddData((void*)"\x12\x01\x00\x00", 4); + message.AddData((void*)"\x00\x00\x00\x00", 4); + break; + case MarioKartGP: + case MarioKartGP2: + default: + // 1 Player (15bit), 1 Coin slot, 3 Analog-in, 1 CARD, 1 Driver-out + message.AddData((void*)"\x01\x01\x0F\x00", 4); + message.AddData((void*)"\x02\x01\x00\x00", 4); + message.AddData((void*)"\x03\x03\x00\x00", 4); + message.AddData((void*)"\x10\x01\x00\x00", 4); + message.AddData((void*)"\x12\x01\x00\x00", 4); + message.AddData((void*)"\x00\x00\x00\x00", 4); + break; + } + NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x14, CheckFunctionality"); + break; + case JVSIOCommand::MainID: + while (*jvs_io++) + { + }; + message.AddData(StatusOkay); + break; + case JVSIOCommand::SwitchesInput: + { + u32 player_count = *jvs_io++; + u32 player_byte_count = *jvs_io++; + + DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x20, SwitchInputs: {} {}", + player_count, player_byte_count); + + message.AddData(StatusOkay); + + GCPadStatus PadStatus = Pad::GetStatus(0); + + // Test button + if (PadStatus.switches & SWITCH_TEST) + { + // Trying to access the test menu without SegaBoot present will cause a crash + if (AMMediaboard::GetTestMenu()) + { + message.AddData(0x80); + } + else + { + PanicAlertFmt("Test menu is disabled due missing SegaBoot"); + } + } + else + { + message.AddData((u32)0x00); + } + + for (u32 i = 0; i < player_count; ++i) + { + u8 player_data[3] = {0, 0, 0}; + + // Service button + if (PadStatus.switches & SWITCH_SERVICE) + player_data[0] |= 0x40; + + switch (AMMediaboard::GetGameType()) + { + // Controller configuration for F-Zero AX (DX) + case FZeroAX: + if (i == 0) + { + if (m_fzdx_seatbelt) + { + player_data[0] |= 0x01; + } + + // Start + if (PadStatus.button & PAD_BUTTON_START) + player_data[0] |= 0x80; + // Boost + if (PadStatus.button & PAD_BUTTON_A) + player_data[0] |= 0x02; + // View Change 1 + if (PadStatus.button & PAD_BUTTON_RIGHT) + player_data[0] |= 0x20; + // View Change 2 + if (PadStatus.button & PAD_BUTTON_LEFT) + player_data[0] |= 0x10; + // View Change 3 + if (PadStatus.button & PAD_BUTTON_UP) + player_data[0] |= 0x08; + // View Change 4 + if (PadStatus.button & PAD_BUTTON_DOWN) + player_data[0] |= 0x04; + player_data[1] = m_rx_reply & 0xF0; + } + else if (i == 1) + { + // Paddle left + if (PadStatus.button & PAD_BUTTON_X) + player_data[0] |= 0x20; + // Paddle right + if (PadStatus.button & PAD_BUTTON_Y) + player_data[0] |= 0x10; + + if (m_fzdx_motion_stop) + { + player_data[0] |= 2; + } + if (m_fzdx_sensor_right) + { + player_data[0] |= 4; + } + if (m_fzdx_sensor_left) + { + player_data[0] |= 8; + } + + player_data[1] = m_rx_reply << 4; + } + break; + // Controller configuration for F-Zero AX MonsterRide + case FZeroAXMonster: + if (i == 0) + { + if (m_fzcc_sensor) + { + player_data[0] |= 0x01; + } + + // Start + if (PadStatus.button & PAD_BUTTON_START) + player_data[0] |= 0x80; + // Boost + if (PadStatus.button & PAD_BUTTON_A) + player_data[0] |= 0x02; + // View Change 1 + if (PadStatus.button & PAD_BUTTON_RIGHT) + player_data[0] |= 0x20; + // View Change 2 + if (PadStatus.button & PAD_BUTTON_LEFT) + player_data[0] |= 0x10; + // View Change 3 + if (PadStatus.button & PAD_BUTTON_UP) + player_data[0] |= 0x08; + // View Change 4 + if (PadStatus.button & PAD_BUTTON_DOWN) + player_data[0] |= 0x04; + + player_data[1] = m_rx_reply & 0xF0; + } + else if (i == 1) + { + // Paddle left + if (PadStatus.button & PAD_BUTTON_X) + player_data[0] |= 0x20; + // Paddle right + if (PadStatus.button & PAD_BUTTON_Y) + player_data[0] |= 0x10; + + if (m_fzcc_seatbelt) + { + player_data[0] |= 2; + } + if (m_fzcc_service) + { + player_data[0] |= 4; + } + if (m_fzcc_emergency) + { + player_data[0] |= 8; + } + } + break; + // Controller configuration for Virtua Striker 3 games + case VirtuaStriker3: + PadStatus = Pad::GetStatus(i); + // Start + if (PadStatus.button & PAD_BUTTON_START) + player_data[0] |= 0x80; + // Shoot + if (PadStatus.button & PAD_BUTTON_B) + player_data[0] |= 0x01; + // Short Pass + if (PadStatus.button & PAD_BUTTON_A) + player_data[1] |= 0x80; + // Long Pass + if (PadStatus.button & PAD_BUTTON_X) + player_data[0] |= 0x02; + // Left + if (PadStatus.button & PAD_BUTTON_LEFT) + player_data[0] |= 0x08; + // Up + if (PadStatus.button & PAD_BUTTON_UP) + player_data[0] |= 0x20; + // Right + if (PadStatus.button & PAD_BUTTON_RIGHT) + player_data[0] |= 0x04; + // Down + if (PadStatus.button & PAD_BUTTON_DOWN) + player_data[0] |= 0x10; + break; + // Controller configuration for Virtua Striker 4 games + case VirtuaStriker4: + case VirtuaStriker4_2006: + { + PadStatus = Pad::GetStatus(i); + // Start + if (PadStatus.button & PAD_BUTTON_START) + player_data[0] |= 0x80; + // Long Pass + if (PadStatus.button & PAD_BUTTON_X) + player_data[0] |= 0x01; + // Short Pass + if (PadStatus.button & PAD_BUTTON_A) + player_data[0] |= 0x02; + // Shoot + if (PadStatus.button & PAD_BUTTON_B) + player_data[1] |= 0x80; + // Dash + if (PadStatus.button & PAD_BUTTON_Y) + player_data[1] |= 0x40; + // Tactics (U) + if (PadStatus.button & PAD_BUTTON_LEFT) + player_data[0] |= 0x20; + // Tactics (M) + if (PadStatus.button & PAD_BUTTON_UP) + player_data[0] |= 0x08; + // Tactics (D) + if (PadStatus.button & PAD_BUTTON_RIGHT) + player_data[0] |= 0x04; + + if (i == 0) + { + player_data[0] |= 0x10; // IC-Card Switch ON + + // IC-Card Lock + if (PadStatus.button & PAD_BUTTON_DOWN) + player_data[1] |= 0x20; + } + } + break; + // Controller configuration for Gekitou Pro Yakyuu + case GekitouProYakyuu: + PadStatus = Pad::GetStatus(i); + // Start + if (PadStatus.button & PAD_BUTTON_START) + player_data[0] |= 0x80; + // A + if (PadStatus.button & PAD_BUTTON_B) + player_data[0] |= 0x01; + // B + if (PadStatus.button & PAD_BUTTON_A) + player_data[0] |= 0x02; + // Gekitou + if (PadStatus.button & PAD_TRIGGER_L) + player_data[1] |= 0x80; + // Left + if (PadStatus.button & PAD_BUTTON_LEFT) + player_data[0] |= 0x08; + // Up + if (PadStatus.button & PAD_BUTTON_UP) + player_data[0] |= 0x20; + // Right + if (PadStatus.button & PAD_BUTTON_RIGHT) + player_data[0] |= 0x04; + // Down + if (PadStatus.button & PAD_BUTTON_DOWN) + player_data[0] |= 0x10; + break; + // Controller configuration for Mario Kart and other games + default: + case MarioKartGP: + case MarioKartGP2: + { + // Start + if (PadStatus.button & PAD_BUTTON_START) + player_data[0] |= 0x80; + // Item button + if (PadStatus.button & PAD_BUTTON_A) + player_data[1] |= 0x20; + // VS-Cancel button + if (PadStatus.button & PAD_BUTTON_B) + player_data[1] |= 0x02; + } + break; + case KeyOfAvalon: + { + // Debug On + if (PadStatus.button & PAD_BUTTON_START) + player_data[0] |= 0x80; + // Switch 1 + if (PadStatus.button & PAD_BUTTON_A) + player_data[0] |= 0x04; + // Switch 2 + if (PadStatus.button & PAD_BUTTON_B) + player_data[0] |= 0x08; + // Toggle inserted card + if (PadStatus.button & PAD_TRIGGER_L) + { + m_ic_card_status ^= ICCARDStatus::NoCard; + } + } + break; + } + + for (u32 j = 0; j < player_byte_count; ++j) + message.AddData(player_data[j]); + } + break; + } + case JVSIOCommand::CoinInput: + { + const u32 slots = *jvs_io++; + message.AddData(StatusOkay); + for (u32 i = 0; i < slots; i++) + { + GCPadStatus PadStatus = Pad::GetStatus(i); + if ((PadStatus.switches & SWITCH_COIN) && !m_coin_pressed[i]) + { + m_coin[i]++; + } + m_coin_pressed[i] = PadStatus.switches & SWITCH_COIN; + message.AddData((m_coin[i] >> 8) & 0x3f); + message.AddData(m_coin[i] & 0xff); + } + DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x21, CoinInput: {}", slots); + break; + } + case JVSIOCommand::AnalogInput: + { + message.AddData(StatusOkay); + + const u32 analogs = *jvs_io++; + GCPadStatus PadStatus = Pad::GetStatus(0); + + DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x22, AnalogInput: {}", + analogs); + + switch (AMMediaboard::GetGameType()) + { + case FZeroAX: + case FZeroAXMonster: + // Steering + if (m_motor_init == 1) + { + if (m_motor_force_y > 0) + { + message.AddData(0x80 - (m_motor_force_y >> 8)); + } + else + { + message.AddData((m_motor_force_y >> 8)); + } + message.AddData((u8)0); + + message.AddData(PadStatus.stickY); + message.AddData((u8)0); + } + else + { + // The center for the Y axis is expected to be 78h this adjusts that + message.AddData(PadStatus.stickX - 12); + message.AddData((u8)0); + + message.AddData(PadStatus.stickY); + message.AddData((u8)0); + } + + // Unused + message.AddData((u8)0); + message.AddData((u8)0); + message.AddData((u8)0); + message.AddData((u8)0); + + // Gas + message.AddData(PadStatus.triggerRight); + message.AddData((u8)0); + + // Brake + message.AddData(PadStatus.triggerLeft); + message.AddData((u8)0); + + message.AddData((u8)0x80); // Motion Stop + message.AddData((u8)0); + + message.AddData((u8)0); + message.AddData((u8)0); + + break; + case VirtuaStriker4: + case VirtuaStriker4_2006: + { + message.AddData(-PadStatus.stickY); + message.AddData((u8)0); + message.AddData(PadStatus.stickX); + message.AddData((u8)0); + + PadStatus = Pad::GetStatus(1); + + message.AddData(-PadStatus.stickY); + message.AddData((u8)0); + message.AddData(PadStatus.stickX); + message.AddData((u8)0); + } + break; + default: + case MarioKartGP: + case MarioKartGP2: + // Steering + message.AddData(PadStatus.stickX); + message.AddData((u8)0); + + // Gas + message.AddData(PadStatus.triggerRight); + message.AddData((u8)0); + + // Brake + message.AddData(PadStatus.triggerLeft); + message.AddData((u8)0); + break; + } + break; + } + case JVSIOCommand::PositionInput: + { + const u32 channel = *jvs_io++; + + const GCPadStatus PadStatus = Pad::GetStatus(0); + + if (PadStatus.button & PAD_TRIGGER_R) + { + // Tap at center of screen (~320,240) + message.AddData((void*)"\x01\x00\x8C\x01\x95", + 5); // X=320 (0x0140), Y=240 (0x00F0) + } + else + { + message.AddData((void*)"\x01\xFF\xFF\xFF\xFF", 5); + } + + DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x25, PositionInput:{}", + channel); + break; + } + case JVSIOCommand::CoinSubOutput: + { + const u32 slot = *jvs_io++; + const u8 coinh = *jvs_io++; + const u8 coinl = *jvs_io++; + + m_coin[slot] -= (coinh << 8) | coinl; + + message.AddData(StatusOkay); + DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x30, CoinSubOutput: {}", slot); + break; + } + case JVSIOCommand::GeneralDriverOutput: + { + const u32 bytes = *jvs_io++; + + if (bytes) + { + message.AddData(StatusOkay); + + // The lamps are controlled via this + if (AMMediaboard::GetGameType() == MarioKartGP) + { + const u32 status = *jvs_io++; + if (status & 4) + { + DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 32, Item Button ON"); + } + else + { + DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 32, Item Button OFF"); + } + if (status & 8) + { + DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 32, Cancel Button ON"); + } + else + { + DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 32, Cancel Button OFF"); + } + break; + } + + Common::UniqueBuffer buf(bytes); + + for (u32 i = 0; i < bytes; ++i) + { + buf[i] = *jvs_io++; + } + + INFO_LOG_FMT( + SERIALINTERFACE_JVSIO, + "JVS-IO: Command 0x32, GPO: {:02x} {:02x} {} {:02x}{:02x}{:02x} ({:02x})", + delay, m_rx_reply, bytes, buf[0], buf[1], buf[2], + Common::swap16(*reinterpret_cast(&buf[1])) >> 2); + + // Handling of the motion seat used in F-Zero AXs DX version + switch (Common::swap16(*reinterpret_cast(&buf[1])) >> 2) + { + case 0x70: + delay++; + if ((delay % 10) == 0) + { + m_rx_reply = 0xFB; + } + break; + case 0xF0: + m_rx_reply = 0xF0; + break; + default: + case 0xA0: + case 0x60: + break; + } + } + break; + } + case JVSIOCommand::CoinAddOutput: + { + const u32 slot = *jvs_io++; + const u8 coinh = *jvs_io++; + const u8 coinl = *jvs_io++; + + m_coin[slot] += (coinh << 8) | coinl; + + message.AddData(StatusOkay); + DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x35, CoinAddOutput: {}", slot); + break; + } + case JVSIOCommand::NAMCOCommand: + { + const u32 namco_command = *jvs_io++; + + if (namco_command == 0x18) + { // ID check + jvs_io += 4; + message.AddData(StatusOkay); + message.AddData(0xff); + } + else + { + message.AddData(StatusOkay); + ERROR_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Unknown:{:02x}", namco_command); + } + break; + } + case JVSIOCommand::Reset: + if (*jvs_io++ == 0xD9) + { + NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0xF0, Reset"); + delay = 0; + m_wheel_init = 0; + m_ic_card_state = 0x20; + } + message.AddData(StatusOkay); + + dip_switch_1 |= 1; + break; + case JVSIOCommand::SetAddress: + node = *jvs_io++; + NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0xF1, SetAddress: node={}", + node); + message.AddData(node == 1); + dip_switch_1 &= ~1; + break; + default: + ERROR_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Unhandled: node={}, command={:02x}", + node, jvsio_command); + break; + } + } + + message.End(); + + data_out[data_offset++] = gcam_command; + + const u8* buf = message.m_msg; + const u32 len = message.m_ptr; + data_out[data_offset++] = len; + + for (u32 i = 0; i < len; ++i) + data_out[data_offset++] = buf[i]; + + data_in += frame[0] + 1; + break; + } + case GCAMCommand::Unknown_60: + NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x60, {:02x} {:02x} {:02x}", + data_in[0], data_in[1], data_in[2]); + data_in += data_in[0] + 1; + break; + default: + ERROR_LOG_FMT(SERIALINTERFACE_AMBB, + "GC-AM: Command {:02x} (unknown) {:02x} {:02x} {:02x} {:02x} {:02x}", + gcam_command, data_in[0], data_in[1], data_in[2], data_in[3], data_in[4]); + break; + } + } + + memset(buffer, 0, buffer_length); + + data_in = buffer; + data_out[1] = data_offset - 2; + checksum = 0; + char logptr[1024]; + char* log = logptr; + + for (int i = 0; i < 0x7F; ++i) + { + checksum += data_in[i] = data_out[i]; + log += sprintf(log, "%02X", data_in[i]); + } + data_in[0x7f] = ~checksum; + DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "Command send back: {}", logptr); + + swap_buffers(buffer, &buffer_length); + + buffer_position = buffer_length; + break; + } + default: + { + ERROR_LOG_FMT(SERIALINTERFACE, "Unknown SI command (0x{:08x})", (u32)command); + PanicAlertFmt("SI: Unknown command"); + buffer_position = buffer_length; + } + break; + } + } + + return buffer_position; +} + +u32 CSIDevice_AMBaseboard::MapPadStatus(const GCPadStatus& pad_status) +{ + // Thankfully changing mode does not change the high bits ;) + u32 hi = 0; + hi = pad_status.stickY; + hi |= pad_status.stickX << 8; + hi |= (pad_status.button | PAD_USE_ORIGIN) << 16; + return hi; +} + +CSIDevice_AMBaseboard::EButtonCombo +CSIDevice_AMBaseboard::HandleButtonCombos(const GCPadStatus& pad_status) +{ + // Keep track of the special button combos (embedded in controller hardware... :( ) + EButtonCombo temp_combo; + if ((pad_status.button & 0xff00) == (PAD_BUTTON_Y | PAD_BUTTON_X | PAD_BUTTON_START)) + temp_combo = COMBO_ORIGIN; + else if ((pad_status.button & 0xff00) == (PAD_BUTTON_B | PAD_BUTTON_X | PAD_BUTTON_START)) + temp_combo = COMBO_RESET; + else + temp_combo = COMBO_NONE; + + if (temp_combo != m_last_button_combo) + { + m_last_button_combo = temp_combo; + if (m_last_button_combo != COMBO_NONE) + m_timer_button_combo_start = m_system.GetCoreTiming().GetTicks(); + } + + if (m_last_button_combo != COMBO_NONE) + { + const u64 current_time = m_system.GetCoreTiming().GetTicks(); + const u32 ticks_per_second = m_system.GetSystemTimers().GetTicksPerSecond(); + if (u32(current_time - m_timer_button_combo_start) > ticks_per_second * 3) + { + if (m_last_button_combo == COMBO_RESET) + { + INFO_LOG_FMT(SERIALINTERFACE, "PAD - COMBO_RESET"); + m_system.GetProcessorInterface().ResetButton_Tap(); + } + else if (m_last_button_combo == COMBO_ORIGIN) + { + INFO_LOG_FMT(SERIALINTERFACE, "PAD - COMBO_ORIGIN"); + SetOrigin(pad_status); + } + + m_last_button_combo = COMBO_NONE; + return temp_combo; + } + } + + return COMBO_NONE; +} + +// GetData + +// Return true on new data (max 7 Bytes and 6 bits ;) +// [00?SYXBA] [1LRZUDRL] [x] [y] [cx] [cy] [l] [r] +// |\_ ERR_LATCH (error latched - check SISR) +// |_ ERR_STATUS (error on last GetData or SendCmd?) +DataResponse CSIDevice_AMBaseboard::GetData(u32& hi, u32& low) +{ + GCPadStatus pad_status = GetPadStatus(); + + if (!pad_status.isConnected) + return DataResponse::ErrorNoResponse; + + if (HandleButtonCombos(pad_status) == COMBO_ORIGIN) + pad_status.button |= PAD_GET_ORIGIN; + + hi = MapPadStatus(pad_status); + + // Low bits are packed differently per mode + if (m_mode == 0 || m_mode == 5 || m_mode == 6 || m_mode == 7) + { + low = (pad_status.analogB >> 4); // Top 4 bits + low |= ((pad_status.analogA >> 4) << 4); // Top 4 bits + low |= ((pad_status.triggerRight >> 4) << 8); // Top 4 bits + low |= ((pad_status.triggerLeft >> 4) << 12); // Top 4 bits + low |= ((pad_status.substickY) << 16); // All 8 bits + low |= ((pad_status.substickX) << 24); // All 8 bits + } + else if (m_mode == 1) + { + low = (pad_status.analogB >> 4); // Top 4 bits + low |= ((pad_status.analogA >> 4) << 4); // Top 4 bits + low |= (pad_status.triggerRight << 8); // All 8 bits + low |= (pad_status.triggerLeft << 16); // All 8 bits + low |= ((pad_status.substickY >> 4) << 24); // Top 4 bits + low |= ((pad_status.substickX >> 4) << 28); // Top 4 bits + } + else if (m_mode == 2) + { + low = pad_status.analogB; // All 8 bits + low |= pad_status.analogA << 8; // All 8 bits + low |= ((pad_status.triggerRight >> 4) << 16); // Top 4 bits + low |= ((pad_status.triggerLeft >> 4) << 20); // Top 4 bits + low |= ((pad_status.substickY >> 4) << 24); // Top 4 bits + low |= ((pad_status.substickX >> 4) << 28); // Top 4 bits + } + else if (m_mode == 3) + { + // Analog A/B are always 0 + low = pad_status.triggerRight; // All 8 bits + low |= (pad_status.triggerLeft << 8); // All 8 bits + low |= (pad_status.substickY << 16); // All 8 bits + low |= (pad_status.substickX << 24); // All 8 bits + } + else if (m_mode == 4) + { + low = pad_status.analogB; // All 8 bits + low |= pad_status.analogA << 8; // All 8 bits + // triggerLeft/Right are always 0 + low |= pad_status.substickY << 16; // All 8 bits + low |= pad_status.substickX << 24; // All 8 bits + } + + return DataResponse::Success; +} + +void CSIDevice_AMBaseboard::SendCommand(u32 command, u8 poll) +{ + UCommand controller_command(command); + + if (static_cast(controller_command.command) == EDirectCommands::CMD_WRITE) + { + const u32 type = controller_command.parameter1; // 0 = stop, 1 = rumble, 2 = stop hard + + // get the correct pad number that should rumble locally when using netplay + const int pad_num = NetPlay_InGamePadToLocalPad(m_device_number); + + if (pad_num < 4) + { + const SIDevices device = m_system.GetSerialInterface().GetDeviceType(pad_num); + if (type == 1) + CSIDevice_GCController::Rumble(pad_num, 1.0, device); + else + CSIDevice_GCController::Rumble(pad_num, 0.0, device); + } + + if (poll == 0) + { + m_mode = controller_command.parameter2; + INFO_LOG_FMT(SERIALINTERFACE, "PAD {} set to mode {}", m_device_number, m_mode); + } + } + 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"); + } +} + +void CSIDevice_AMBaseboard::SetOrigin(const GCPadStatus& pad_status) +{ + m_origin.origin_stick_x = pad_status.stickX; + m_origin.origin_stick_y = pad_status.stickY; + m_origin.substick_x = pad_status.substickX; + m_origin.substick_y = pad_status.substickY; + m_origin.trigger_left = pad_status.triggerLeft; + m_origin.trigger_right = pad_status.triggerRight; +} + +void CSIDevice_AMBaseboard::HandleMoviePadStatus(Movie::MovieManager& movie, int device_number, + GCPadStatus* pad_status) +{ + movie.SetPolledDevice(); + if (NetPlay_GetInput(device_number, pad_status)) + { + } + else if (movie.IsPlayingInput()) + { + movie.PlayController(pad_status, device_number); + movie.InputUpdate(); + } + else if (movie.IsRecordingInput()) + { + movie.RecordInput(pad_status, device_number); + movie.InputUpdate(); + } + else + { + movie.CheckPadStatus(pad_status, device_number); + } +} + +GCPadStatus CSIDevice_AMBaseboard::GetPadStatus() +{ + GCPadStatus pad_status = {}; + + // For netplay, the local controllers are polled in GetNetPads(), and + // the remote controllers receive their status there as well + if (!NetPlay::IsNetPlayRunning()) + { + pad_status = Pad::GetStatus(m_device_number); + } + + // Our GCAdapter code sets PAD_GET_ORIGIN when a new device has been connected. + // Watch for this to calibrate real controllers on connection. + if (pad_status.button & PAD_GET_ORIGIN) + SetOrigin(pad_status); + + return pad_status; +} + +} // namespace SerialInterface diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h new file mode 100644 index 0000000000..2473e4280d --- /dev/null +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h @@ -0,0 +1,309 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "Core/HW/GCPad.h" +#include "Core/HW/SI/SI_Device.h" +#include "InputCommon/GCPadStatus.h" + +namespace Movie +{ +class MovieManager; +} + +namespace SerialInterface +{ + +// "JAMMA Video Standard" I/O +class JVSIOMessage +{ +public: + u32 m_ptr; + u32 m_last_start; + u32 m_csum; + u8 m_msg[0x80]; + + JVSIOMessage(); + void Start(int node); + void AddData(const u8* dst, std::size_t len, int sync); + void AddData(const void* data, std::size_t len); + void AddData(const char* data); + void AddData(u32 n); + void End(); +}; + +// Triforce (GC-AM) baseboard +class CSIDevice_AMBaseboard : public ISIDevice +{ +private: + enum BaseBoardCommand + { + GCAM_Reset = 0x00, + GCAM_Command = 0x70, + }; + + enum GCAMCommand + { + StatusSwitches = 0x10, + SerialNumber = 0x11, + Unknown_12 = 0x12, + Unknown_14 = 0x14, + FirmVersion = 0x15, + FPGAVersion = 0x16, + RegionSettings = 0x1F, + + Unknown_21 = 0x21, + Unknown_22 = 0x22, + Unknown_23 = 0x23, + Unknown_24 = 0x24, + + SerialA = 0x31, + SerialB = 0x32, + + JVSIOA = 0x40, + JVSIOB = 0x41, + + Unknown_60 = 0x60, + }; + + enum JVSIOCommand + { + IOID = 0x10, + CommandRevision = 0x11, + JVRevision = 0x12, + CommunicationVersion = 0x13, + CheckFunctionality = 0x14, + MainID = 0x15, + + SwitchesInput = 0x20, + CoinInput = 0x21, + AnalogInput = 0x22, + RotaryInput = 0x23, + KeyCodeInput = 0x24, + PositionInput = 0x25, + GeneralSwitchInput = 0x26, + + PayoutRemain = 0x2E, + Retrans = 0x2F, + CoinSubOutput = 0x30, + PayoutAddOutput = 0x31, + GeneralDriverOutput = 0x32, + AnalogOutput = 0x33, + CharacterOutput = 0x34, + CoinAddOutput = 0x35, + PayoutSubOutput = 0x36, + GeneralDriverOutput2 = 0x37, + GeneralDriverOutput3 = 0x38, + + NAMCOCommand = 0x70, + + Reset = 0xF0, + SetAddress = 0xF1, + ChangeComm = 0xF2, + }; + + enum JVSIOStatusCode + { + StatusOkay = 1, + UnsupportedCommand = 2, + ChecksumError = 3, + AcknowledgeOverflow = 4, + }; + + enum CARDCommand + { + Init = 0x10, + GetState = 0x20, + Read = 0x33, + IsPresent = 0x40, + Write = 0x53, + SetPrintParam = 0x78, + RegisterFont = 0x7A, + WriteInfo = 0x7C, + Erase = 0x7D, + Eject = 0x80, + Clean = 0xA0, + Load = 0xB0, + SetShutter = 0xD0, + }; + + enum ICCARDCommand + { + GetStatus = 0x10, + SetBaudrate = 0x11, + FieldOn = 0x14, + FieldOff = 0x15, + InsertCheck = 0x20, + AntiCollision = 0x21, + SelectCard = 0x22, + ReadPage = 0x24, + WritePage = 0x25, + DecreaseUseCount = 0x26, + ReadUseCount = 0x33, + ReadPages = 0x34, + WritePages = 0x35, + }; + + enum ICCARDStatus + { + Okay = 0, + NoCard = 0x8000, + Unknown = 0x800E, + BadCard = 0xFFFF, + }; + + enum CDReaderCommand + { + ShutterAuto = 0x61, + BootVersion = 0x62, + SensLock = 0x63, + SensCard = 0x65, + FirmwareUpdate = 0x66, + ShutterGet = 0x67, + CameraCheck = 0x68, + ShutterCard = 0x69, + ProgramChecksum = 0x6B, + BootChecksum = 0x6D, + ShutterLoad = 0x6F, + ReadCard = 0x72, + ShutterSave = 0x73, + SelfTest = 0x74, + ProgramVersion = 0x76, + }; + + union ICCommand + { + u8 data[81 + 4 + 4 + 4]; + + struct + { + u32 pktcmd : 8; + u32 pktlen : 8; + u32 fixed : 8; + u32 command : 8; + u32 flag : 8; + u32 length : 8; + u32 status : 16; + + u8 extdata[81]; + u32 extlen; + }; + }; + + u16 m_coin[2]; + u32 m_coin_pressed[2]; + + u8 m_ic_card_data[2048]; + u16 m_ic_card_state; + + u16 m_ic_card_status; + u16 m_ic_card_session; + u8 m_ic_write_buffer[512]; + u32 m_ic_write_offset; + u32 m_ic_write_size; + + u8 m_card_memory[0xD0]; + u8 m_card_read_packet[0xDB]; + u8 m_card_buffer[0x100]; + u32 m_card_memory_size; + bool m_card_is_inserted; + u32 m_card_command; + u32 m_card_clean; + u32 m_card_write_length; + u32 m_card_wrote; + u32 m_card_read_length; + u32 m_card_read; + u32 m_card_bit; + bool m_card_shutter; + u32 m_card_state_call_count; + u8 m_card_offset; + + u32 m_wheel_init; + + u32 m_motor_init; + u8 m_motor_reply[64]; + s16 m_motor_force_y; + + // F-Zero AX (DX) + bool m_fzdx_seatbelt; + bool m_fzdx_motion_stop; + bool m_fzdx_sensor_right; + bool m_fzdx_sensor_left; + u8 m_rx_reply; + + // F-Zero AX (CyCraft) + bool m_fzcc_seatbelt; + bool m_fzcc_sensor; + bool m_fzcc_emergency; + bool m_fzcc_service; + + void ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length); + +protected: + struct SOrigin + { + u16 button; + u8 origin_stick_x; + u8 origin_stick_y; + u8 substick_x; + u8 substick_y; + u8 trigger_left; + u8 trigger_right; + u8 unk_4; + u8 unk_5; + }; + + enum EButtonCombo + { + COMBO_NONE = 0, + COMBO_ORIGIN, + COMBO_RESET + }; + + // struct to compare input against + // Set on connection to perfect neutral values + // (standard pad only) Set on button combo to current input state + SOrigin m_origin = {}; + + // PADAnalogMode + // Dunno if we need to do this, game/lib should set it? + u8 m_mode = 0x3; + + // Timer to track special button combos: + // y, X, start for 3 seconds updates origin with current status + // Technically, the above is only on standard pad, wavebird does not support it for example + // b, x, start for 3 seconds triggers reset (PI reset button interrupt) + u64 m_timer_button_combo_start = 0; + // Type of button combo from the last/current poll + EButtonCombo m_last_button_combo = COMBO_NONE; + +public: + // constructor + CSIDevice_AMBaseboard(Core::System& system, SIDevices device, int device_number); + + // run the SI Buffer + int RunBuffer(u8* buffer, int request_length) override; + + // return true on new data + DataResponse GetData(u32& hi, u32& low) override; + + // send a command directly + void SendCommand(u32 command, u8 poll) override; + + virtual GCPadStatus GetPadStatus(); + virtual u32 MapPadStatus(const GCPadStatus& pad_status); + virtual EButtonCombo HandleButtonCombos(const GCPadStatus& pad_status); + + static void HandleMoviePadStatus(Movie::MovieManager& movie, int device_number, + GCPadStatus* pad_status); + + // Send and Receive pad input from network + static bool NetPlay_GetInput(int pad_num, GCPadStatus* status); + static int NetPlay_InGamePadToLocalPad(int pad_num); + +protected: + void SetOrigin(const GCPadStatus& pad_status); +}; + +} // namespace SerialInterface diff --git a/Source/Core/Core/HW/SI/SI_DeviceGCController.h b/Source/Core/Core/HW/SI/SI_DeviceGCController.h index 19b5bdb14b..f4dc5768c9 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceGCController.h +++ b/Source/Core/Core/HW/SI/SI_DeviceGCController.h @@ -3,8 +3,11 @@ #pragma once +#include #include +#include "Common/CommonTypes.h" +#include "Common/Flag.h" #include "Core/HW/GCPad.h" #include "Core/HW/SI/SI_Device.h" #include "InputCommon/GCPadStatus.h" diff --git a/Source/Core/Core/HW/VideoInterface.cpp b/Source/Core/Core/HW/VideoInterface.cpp index af1ebb7bd1..3d46a345ee 100644 --- a/Source/Core/Core/HW/VideoInterface.cpp +++ b/Source/Core/Core/HW/VideoInterface.cpp @@ -163,7 +163,9 @@ void VideoInterfaceManager::Preset(bool _bNTSC) // Say component cable is plugged m_dtv_status.component_plugged = Config::Get(Config::SYSCONF_PROGRESSIVE_SCAN); - m_dtv_status.ntsc_j = region == DiscIO::Region::NTSC_J; + + // Triforce IPL requires the DTV NTSC-J flag to be set. + m_dtv_status.ntsc_j = m_system.IsTriforce() || region == DiscIO::Region::NTSC_J; m_fb_width.Hex = 0; m_border_hblank.Hex = 0; diff --git a/Source/Core/Core/NetPlayClient.cpp b/Source/Core/Core/NetPlayClient.cpp index cb6ad2293e..028799d56f 100644 --- a/Source/Core/Core/NetPlayClient.cpp +++ b/Source/Core/Core/NetPlayClient.cpp @@ -52,6 +52,7 @@ #include "Core/HW/GCPad.h" #include "Core/HW/SI/SI.h" #include "Core/HW/SI/SI_Device.h" +#include "Core/HW/SI/SI_DeviceAMBaseboard.h" #include "Core/HW/SI/SI_DeviceGCController.h" #include "Core/HW/Sram.h" #include "Core/HW/WiiSave.h" @@ -1883,6 +1884,8 @@ void NetPlayClient::UpdateDevices() auto& si = Core::System::GetInstance().GetSerialInterface(); for (auto player_id : m_pad_map) { + const SerialInterface::SIDevices si_device = Config::Get(Config::GetInfoForSIDevice(local_pad)); + if (m_gba_config[pad].enabled && player_id > 0) { si.ChangeDevice(SerialInterface::SIDEVICE_GC_GBA_EMULATED, pad); @@ -1890,8 +1893,6 @@ void NetPlayClient::UpdateDevices() else if (player_id == m_local_player->pid) { // Use local controller types for local controllers if they are compatible - const SerialInterface::SIDevices si_device = - Config::Get(Config::GetInfoForSIDevice(local_pad)); if (SerialInterface::SIDevice_IsGCController(si_device)) { si.ChangeDevice(si_device, pad); @@ -1909,7 +1910,8 @@ void NetPlayClient::UpdateDevices() } else if (player_id > 0) { - si.ChangeDevice(SerialInterface::SIDEVICE_GC_CONTROLLER, pad); + if (si_device != SerialInterface::SIDEVICE_AM_BASEBOARD) + si.ChangeDevice(SerialInterface::SIDEVICE_GC_CONTROLLER, pad); } else { @@ -2772,6 +2774,16 @@ bool SerialInterface::CSIDevice_GCController::NetPlay_GetInput(int pad_num, GCPa return false; } +bool SerialInterface::CSIDevice_AMBaseboard::NetPlay_GetInput(int pad_num, GCPadStatus* status) +{ + std::lock_guard lk(NetPlay::crit_netplay_client); + + if (NetPlay::netplay_client) + return NetPlay::netplay_client->GetNetPads(pad_num, NetPlay::s_si_poll_batching, status); + + return false; +} + bool NetPlay::NetPlay_GetWiimoteData(const std::span& entries) { std::lock_guard lk(crit_netplay_client); @@ -2835,12 +2847,21 @@ u64 ExpansionInterface::CEXIIPL::NetPlay_GetEmulatedTime() // called from ---CPU--- thread // return the local pad num that should rumble given a ingame pad num -int SerialInterface::CSIDevice_GCController::NetPlay_InGamePadToLocalPad(int numPAD) +int SerialInterface::CSIDevice_GCController::NetPlay_InGamePadToLocalPad(int pad_num) { std::lock_guard lk(NetPlay::crit_netplay_client); if (NetPlay::netplay_client) - return NetPlay::netplay_client->InGamePadToLocalPad(numPAD); + return NetPlay::netplay_client->InGamePadToLocalPad(pad_num); - return numPAD; + return pad_num; +} +int SerialInterface::CSIDevice_AMBaseboard::NetPlay_InGamePadToLocalPad(int pad_num) +{ + std::lock_guard lk(NetPlay::crit_netplay_client); + + if (NetPlay::netplay_client) + return NetPlay::netplay_client->InGamePadToLocalPad(pad_num); + + return pad_num; } diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index fd71ba9df2..98180a8736 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -95,7 +95,7 @@ struct CompressAndDumpStateArgs static Common::WorkQueueThreadSP s_compress_and_dump_thread; // Don't forget to increase this after doing changes on the savestate system -constexpr u32 STATE_VERSION = 176; // Last changed in PR 13768 +constexpr u32 STATE_VERSION = 177; // Last changed in PR 13844 // Increase this if the StateExtendedHeader definition changes constexpr u32 EXTENDED_HEADER_VERSION = 1; // Last changed in PR 12217 diff --git a/Source/Core/Core/TitleDatabase.cpp b/Source/Core/Core/TitleDatabase.cpp index 523b8b500e..41ad0e0e7e 100644 --- a/Source/Core/Core/TitleDatabase.cpp +++ b/Source/Core/Core/TitleDatabase.cpp @@ -91,32 +91,8 @@ TitleDatabase::TitleDatabase() TitleDatabase::~TitleDatabase() = default; const std::string& TitleDatabase::GetTitleName(const std::string& gametdb_id, - const std::string& triforce_id, DiscIO::Language language) const { - if (triforce_id != "") - { - const Map& map = *m_triforce_title_maps.at(DiscIO::Language::English); - auto it = map.find(triforce_id); - if (it != map.end()) - return it->second; - - // This code has been commented out as there is currently only a english title map, and all - // Triforce games are detected as Japanese. - - // if (language != DiscIO::Language::English) - //{ - // const Map& english_triforce_map = *m_triforce_title_maps.at(DiscIO::Language::English); - // it = english_triforce_map.find(triforce_id); - // if (it != english_triforce_map.end()) - // return it->second; - //} - - // it = m_base_map.find(triforce_id); - // if (it != m_base_map.end()) - // return it->second; - } - auto it = m_user_title_map.find(gametdb_id); if (it != m_user_title_map.end()) return it->second; @@ -149,12 +125,12 @@ const std::string& TitleDatabase::GetChannelName(u64 title_id, DiscIO::Language const std::string id{ {static_cast((title_id >> 24) & 0xff), static_cast((title_id >> 16) & 0xff), static_cast((title_id >> 8) & 0xff), static_cast(title_id & 0xff)}}; - return GetTitleName(id, "", language); + return GetTitleName(id, language); } std::string TitleDatabase::Describe(const std::string& gametdb_id, DiscIO::Language language) const { - const std::string& title_name = GetTitleName(gametdb_id, "", language); + const std::string& title_name = GetTitleName(gametdb_id, language); if (title_name.empty()) return gametdb_id; return fmt::format("{} ({})", title_name, gametdb_id); diff --git a/Source/Core/Core/TitleDatabase.h b/Source/Core/Core/TitleDatabase.h index dff9a56dfe..d1163d80a6 100644 --- a/Source/Core/Core/TitleDatabase.h +++ b/Source/Core/Core/TitleDatabase.h @@ -25,8 +25,7 @@ public: // Get a user friendly title name for a GameTDB ID. // This falls back to returning an empty string if none could be found. - const std::string& GetTitleName(const std::string& gametdb_id, const std::string& triforce_id, - DiscIO::Language language) const; + const std::string& GetTitleName(const std::string& gametdb_id, DiscIO::Language language) const; // Same as above, but takes a title ID instead of a GameTDB ID, and only works for channels. const std::string& GetChannelName(u64 title_id, DiscIO::Language language) const; diff --git a/Source/Core/DiscIO/Enums.h b/Source/Core/DiscIO/Enums.h index 9b5e274de9..62ad9e846a 100644 --- a/Source/Core/DiscIO/Enums.h +++ b/Source/Core/DiscIO/Enums.h @@ -19,7 +19,7 @@ enum class Platform WiiDisc, WiiWAD, ELFOrDOL, - NumberOfPlatforms + NumberOfPlatforms, }; enum class Country @@ -38,7 +38,7 @@ enum class Country Taiwan, World, Unknown, - NumberOfCountries + NumberOfCountries, }; // This numbering matches Nintendo's GameCube/Wii region numbering. @@ -48,7 +48,8 @@ enum class Region NTSC_U = 1, // Mainly North America PAL = 2, // Mainly Europe and Oceania Unknown = 3, // Nintendo uses this to mean region free, but we also use it for unknown regions - NTSC_K = 4 // South Korea (Wii only) + NTSC_K = 4, // South Korea (Wii only) + DEV = 5, // Triforce has no regions }; // Languages 0 - 9 match Nintendo's Wii language numbering. @@ -66,7 +67,7 @@ enum class Language SimplifiedChinese = 7, // Not selectable on any unmodded retail Wii TraditionalChinese = 8, // Not selectable on any unmodded retail Wii Korean = 9, - Unknown + Unknown, }; std::string GetName(Country country, bool translate); diff --git a/Source/Core/DiscIO/Volume.h b/Source/Core/DiscIO/Volume.h index 3f80cd2e3d..3d161811e4 100644 --- a/Source/Core/DiscIO/Volume.h +++ b/Source/Core/DiscIO/Volume.h @@ -96,7 +96,6 @@ public: } virtual std::string GetGameID(const Partition& partition = PARTITION_NONE) const = 0; virtual std::string GetGameTDBID(const Partition& partition = PARTITION_NONE) const = 0; - virtual std::string GetTriforceID() const { return ""; } virtual std::string GetMakerID(const Partition& partition = PARTITION_NONE) const = 0; virtual std::optional GetRevision(const Partition& partition = PARTITION_NONE) const = 0; virtual std::string GetInternalName(const Partition& partition = PARTITION_NONE) const = 0; diff --git a/Source/Core/DiscIO/VolumeDisc.cpp b/Source/Core/DiscIO/VolumeDisc.cpp index 882bd3ac96..a915df1224 100644 --- a/Source/Core/DiscIO/VolumeDisc.cpp +++ b/Source/Core/DiscIO/VolumeDisc.cpp @@ -12,6 +12,7 @@ #include "DiscIO/DiscUtils.h" #include "DiscIO/Enums.h" #include "DiscIO/Filesystem.h" +#include "DiscIO/VolumeGC.h" namespace DiscIO { @@ -19,6 +20,55 @@ std::string VolumeDisc::GetGameID(const Partition& partition) const { char id[6]; + if (GetVolumeType() == Platform::Triforce) + { + // Triforce games have their Game ID stored in the boot.id file + const BootID* boot_id = static_cast(this)->GetTriforceBootID(); + + // Construct game ID from the BTID + id[0] = 'G'; + + memcpy(id + 1, boot_id->gameId + 2, 2); + + switch (boot_id->regionFlags) + { + default: + case 0x02: // JAPAN + id[3] = 'J'; + break; + case 0x08: // ASIA + id[3] = 'W'; + break; + case 0x0E: // USA + id[3] = 'E'; + break; + case 0x0C: // EXPORT + id[3] = 'P'; + break; + } + + // There only seem to be three different makers here, + // so we can just check the first char to difference between them. + // + // NAMCO CORPORATION, SEGA CORPORATION and Hitmaker co,ltd. + + switch (boot_id->manufacturer[0]) + { + case 'N': // NAMCO CORPORATION + id[4] = '8'; + id[5] = '2'; + break; + default: + case 'H': // Hitmaker co,ltd. + case 'S': // SEGA CORPORATION + id[4] = '6'; + id[5] = 'E'; + break; + } + + return DecodeString(id); + } + if (!Read(0, sizeof(id), reinterpret_cast(id), partition)) return std::string(); @@ -27,8 +77,35 @@ std::string VolumeDisc::GetGameID(const Partition& partition) const Country VolumeDisc::GetCountry(const Partition& partition) const { - // The 0 that we use as a default value is mapped to Country::Unknown and Region::Unknown - const u8 country_byte = ReadSwapped(3, partition).value_or(0); + u8 country_byte = 0; + + if (GetVolumeType() == Platform::Triforce) + { + const BootID* boot_id = static_cast(this)->GetTriforceBootID(); + + switch (boot_id->regionFlags) + { + default: + case 0x02: // JAPAN + country_byte = 'J'; + break; + case 0x08: // ASIA + country_byte = 'W'; + break; + case 0x0E: // USA + country_byte = 'E'; + break; + case 0x0C: // EXPORT + country_byte = 'P'; + break; + } + } + else + { + // The 0 that we use as a default value is mapped to Country::Unknown and Region::Unknown + country_byte = ReadSwapped(3, partition).value_or(0); + } + const Region region = GetRegion(); const std::optional revision = GetRevision(); @@ -51,6 +128,24 @@ std::string VolumeDisc::GetMakerID(const Partition& partition) const { char maker_id[2]; + // Triforce games have their maker stored in the boot.id file as an actual string + if (GetVolumeType() == Platform::Triforce) + { + const BootID* boot_id = static_cast(this)->GetTriforceBootID(); + + switch (boot_id->manufacturer[0]) + { + case 'S': // SEGA CORPORATION + case 'H': // Hitmaker co,ltd + return DecodeString("6E"); + case 'N': // NAMCO CORPORATION + return DecodeString("82"); + default: + break; + } + // Fall back to normal maker from header + } + if (!Read(0x4, sizeof(maker_id), reinterpret_cast(&maker_id), partition)) return std::string(); @@ -66,6 +161,14 @@ std::optional VolumeDisc::GetRevision(const Partition& partition) const std::string VolumeDisc::GetInternalName(const Partition& partition) const { char name[0x60]; + + // Triforce games have their Title stored in the boot.id file + if (GetVolumeType() == Platform::Triforce) + { + const BootID* boot_id = static_cast(this)->GetTriforceBootID(); + return DecodeString(boot_id->gameName); + } + if (!Read(0x20, sizeof(name), reinterpret_cast(&name), partition)) return std::string(); diff --git a/Source/Core/DiscIO/VolumeDisc.h b/Source/Core/DiscIO/VolumeDisc.h index 17c76ae689..f263c40c16 100644 --- a/Source/Core/DiscIO/VolumeDisc.h +++ b/Source/Core/DiscIO/VolumeDisc.h @@ -15,6 +15,30 @@ namespace DiscIO class VolumeDisc : public Volume { public: + struct BootID + { + u32 magic; // 0x00 (Always BTID) + uint32_t unknown0[3]; + uint32_t unknown1[4]; + char mediaboardType[4]; // 0x20 (GCAM for Triforce) + uint32_t unknown2; + uint16_t year; // 0x28 + uint8_t month; // 0x2A + uint8_t day; // 0x2B + uint8_t videoMode; // 0x2C unknown bitmask, resolutions + horizontal/vertical + uint8_t unknown3; + uint8_t type3Compatible; // 0x2E (Type-3 compatible titles have this set to 1) + uint8_t unknown4; + char gameId[8]; // 0x30 + uint32_t regionFlags; // 0x38 + uint32_t unknown6[9]; + char manufacturer[0x20]; // 0x60 + char gameName[0x20]; // 0x80 + char gameExecutable[0x20]; // 0xA0 + char testExecutable[0x20]; // 0xC0 + char creditTypes[8][0x20]; // 0xE0 + }; + std::string GetGameID(const Partition& partition = PARTITION_NONE) const override; Country GetCountry(const Partition& partition = PARTITION_NONE) const override; std::string GetMakerID(const Partition& partition = PARTITION_NONE) const override; diff --git a/Source/Core/DiscIO/VolumeGC.cpp b/Source/Core/DiscIO/VolumeGC.cpp index ad3d8ec74e..52dacd59a4 100644 --- a/Source/Core/DiscIO/VolumeGC.cpp +++ b/Source/Core/DiscIO/VolumeGC.cpp @@ -45,13 +45,11 @@ VolumeGC::VolumeGC(std::unique_ptr reader) std::unique_ptr file_info = tmp_fs->FindFileInfo("boot.id"); if (!file_info) return; - BootID triforce_header; const u64 file_size = ReadFile(*this, PARTITION_NONE, file_info.get(), - reinterpret_cast(&triforce_header), sizeof(BootID)); - if (file_size >= 4 && triforce_header.magic == BTID_MAGIC) + reinterpret_cast(&m_triforce_header), sizeof(BootID)); + if (file_size >= 4 && m_triforce_header.magic == BTID_MAGIC) { m_is_triforce = true; - m_triforce_id = triforce_header.id; } } } @@ -90,16 +88,23 @@ std::string VolumeGC::GetGameTDBID(const Partition& partition) const return GetGameID(partition); } -std::string VolumeGC::GetTriforceID() const -{ - if (m_is_triforce) - return (std::string(m_triforce_id.data(), m_triforce_id.size())); - else - return ""; -} - Region VolumeGC::GetRegion() const { + if (m_is_triforce) + { + switch (m_triforce_header.regionFlags) + { + default: + case 0x02: // JAPAN + case 0x08: // ASIA + return Region::NTSC_J; + case 0x0E: // USA + return Region::NTSC_U; + case 0x0C: // EXPORT + return Region::PAL; + } + } + return RegionCodeToRegion(m_reader->ReadSwapped(0x458)); } @@ -184,6 +189,12 @@ std::array VolumeGC::GetSyncHash() const VolumeGC::ConvertedGCBanner VolumeGC::LoadBannerFile() const { + // There is at least one Triforce game that has an opening.bnr file but from a different game. + if (m_is_triforce) + { + return {}; + } + GCBanner banner_file; const u64 file_size = ReadFile(*this, PARTITION_NONE, "opening.bnr", reinterpret_cast(&banner_file), sizeof(GCBanner)); diff --git a/Source/Core/DiscIO/VolumeGC.h b/Source/Core/DiscIO/VolumeGC.h index ff768c2e2a..2f85a75535 100644 --- a/Source/Core/DiscIO/VolumeGC.h +++ b/Source/Core/DiscIO/VolumeGC.h @@ -34,7 +34,6 @@ public: const Partition& partition = PARTITION_NONE) const override; const FileSystem* GetFileSystem(const Partition& partition = PARTITION_NONE) const override; std::string GetGameTDBID(const Partition& partition = PARTITION_NONE) const override; - std::string GetTriforceID() const override; std::map GetShortNames() const override; std::map GetLongNames() const override; std::map GetShortMakers() const override; @@ -50,9 +49,10 @@ public: DataSizeType GetDataSizeType() const override; u64 GetRawSize() const override; const BlobReader& GetBlobReader() const override; - std::array GetSyncHash() const override; + const BootID* GetTriforceBootID() const { return &m_triforce_header; } + private: static constexpr u32 GC_BANNER_WIDTH = 96; static constexpr u32 GC_BANNER_HEIGHT = 32; @@ -77,13 +77,6 @@ private: // (only one for BNR1 type) }; - struct BootID - { - u32 magic; // "BTID" - u32 padding[11]; - std::array id; - }; - struct ConvertedGCBanner { ConvertedGCBanner(); @@ -112,8 +105,9 @@ private: std::unique_ptr m_reader; + BootID m_triforce_header; + bool m_is_triforce; - std::array m_triforce_id; }; } // namespace DiscIO diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index d030f8821b..7ed0b580e3 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -279,6 +279,7 @@ + @@ -322,6 +323,7 @@ + @@ -971,6 +973,7 @@ + @@ -1017,6 +1020,7 @@ + diff --git a/Source/Core/DolphinQt/Config/GamecubeControllersWidget.cpp b/Source/Core/DolphinQt/Config/GamecubeControllersWidget.cpp index d23db46eb6..f995a67a1a 100644 --- a/Source/Core/DolphinQt/Config/GamecubeControllersWidget.cpp +++ b/Source/Core/DolphinQt/Config/GamecubeControllersWidget.cpp @@ -42,6 +42,7 @@ static constexpr std::array s_gc_types = { #endif SIDeviceName{SerialInterface::SIDEVICE_GC_GBA, _trans("GBA (TCP)")}, SIDeviceName{SerialInterface::SIDEVICE_GC_KEYBOARD, _trans("Keyboard Controller")}, + SIDeviceName{SerialInterface::SIDEVICE_AM_BASEBOARD, _trans("Triforce Baseboard")}, }; static std::optional ToGCMenuIndex(const SerialInterface::SIDevices sidevice) @@ -132,6 +133,7 @@ void GamecubeControllersWidget::OnGCPadConfigure(size_t index) case SerialInterface::SIDEVICE_NONE: case SerialInterface::SIDEVICE_GC_GBA: return; + case SerialInterface::SIDEVICE_AM_BASEBOARD: case SerialInterface::SIDEVICE_GC_CONTROLLER: type = MappingWindow::Type::MAPPING_GCPAD; break; diff --git a/Source/Core/DolphinQt/Config/InfoWidget.cpp b/Source/Core/DolphinQt/Config/InfoWidget.cpp index fb1e3d0112..4ee3f63bd6 100644 --- a/Source/Core/DolphinQt/Config/InfoWidget.cpp +++ b/Source/Core/DolphinQt/Config/InfoWidget.cpp @@ -122,12 +122,6 @@ QGroupBox* InfoWidget::CreateGameDetails() m_game.GetMakerID() + ")"); layout->addRow(tr("Name:"), internal_name); - if (m_game.GetPlatform() == DiscIO::Platform::Triforce) - { - const auto triforce_id_string = QString::fromStdString(m_game.GetTriforceID()); - auto* const triforce_id = CreateValueDisplay(triforce_id_string); - layout->addRow(tr("Triforce ID:"), triforce_id); - } layout->addRow(tr("Game ID:"), game_id); layout->addRow(tr("Country:"), country); layout->addRow(tr("Maker:"), maker); diff --git a/Source/Core/DolphinQt/Config/Mapping/GCPadEmu.cpp b/Source/Core/DolphinQt/Config/Mapping/GCPadEmu.cpp index e530f495a5..7ae4a68083 100644 --- a/Source/Core/DolphinQt/Config/Mapping/GCPadEmu.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/GCPadEmu.cpp @@ -8,6 +8,10 @@ #include "Core/HW/GCPad.h" #include "Core/HW/GCPadEmu.h" +#include "Core/HW/SI/SI.h" +#include "Core/HW/SI/SI_DeviceAMBaseboard.h" + +#include "Core/Config/MainSettings.h" #include "InputCommon/InputConfig.h" @@ -22,8 +26,15 @@ void GCPadEmu::CreateMainLayout() layout->addWidget(CreateGroupBox(tr("Buttons"), Pad::GetGroup(GetPort(), PadGroup::Buttons)), 0, 0); - layout->addWidget(CreateGroupBox(tr("D-Pad"), Pad::GetGroup(GetPort(), PadGroup::DPad)), 1, 0, -1, - 1); + layout->addWidget(CreateGroupBox(tr("D-Pad"), Pad::GetGroup(GetPort(), PadGroup::DPad)), 1, 0); + + if (Config::Get(Config::GetInfoForSIDevice(0)) == + SerialInterface::SIDevices::SIDEVICE_AM_BASEBOARD) + { + layout->addWidget(CreateGroupBox(tr("Triforce"), Pad::GetGroup(GetPort(), PadGroup::Triforce)), + 2, 0); + } + layout->addWidget( CreateGroupBox(tr("Control Stick"), Pad::GetGroup(GetPort(), PadGroup::MainStick)), 0, 1, -1, 1); diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp index ddb9d39ee6..d2151a0f06 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp @@ -26,6 +26,9 @@ #include "Common/IniFile.h" #include "Common/StringUtil.h" +#include "Core/HW/SI/SI.h" +#include "Core/HW/SI/SI_DeviceAMBaseboard.h" + #include "DolphinQt/Config/Mapping/FreeLookGeneral.h" #include "DolphinQt/Config/Mapping/FreeLookRotation.h" #include "DolphinQt/Config/Mapping/GBAPadEmu.h" @@ -458,8 +461,17 @@ void MappingWindow::SetMappingType(MappingWindow::Type type) case Type::MAPPING_GC_DANCEMAT: case Type::MAPPING_GCPAD: widget = new GCPadEmu(this); - setWindowTitle(tr("GameCube Controller at Port %1").arg(GetPort() + 1)); - AddWidget(tr("GameCube Controller"), widget); + if (Config::Get(Config::GetInfoForSIDevice(GetPort())) == + SerialInterface::SIDevices::SIDEVICE_AM_BASEBOARD) + { + setWindowTitle(tr("Triforce Baseboard at Port %1").arg(GetPort() + 1)); + AddWidget(tr("Triforce Baseboard"), widget); + } + else + { + setWindowTitle(tr("GameCube Controller at Port %1").arg(GetPort() + 1)); + AddWidget(tr("GameCube Controller"), widget); + } break; case Type::MAPPING_GC_MICROPHONE: widget = new GCMicrophone(this); diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index 9f25e66ad4..090b085954 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -250,7 +250,12 @@ MainWindow::MainWindow(Core::System& system, std::unique_ptr boo }); #endif + connect(m_cheats_manager, &CheatsManager::OpenGeneralSettings, this, + &MainWindow::ShowGeneralWindow); + #ifdef USE_RETRO_ACHIEVEMENTS + connect(m_cheats_manager, &CheatsManager::OpenAchievementSettings, this, + &MainWindow::ShowAchievementSettings); connect(m_game_list, &GameList::OpenAchievementSettings, this, &MainWindow::ShowAchievementSettings); #endif // USE_RETRO_ACHIEVEMENTS @@ -511,6 +516,8 @@ void MainWindow::CreateComponents() }); connect(m_breakpoint_widget, &BreakpointWidget::ShowMemory, m_memory_widget, &MemoryWidget::SetAddress); + connect(m_cheats_manager, &CheatsManager::ShowMemory, m_memory_widget, &MemoryWidget::SetAddress); + connect(m_cheats_manager, &CheatsManager::RequestWatch, request_watch); } void MainWindow::ConnectMenuBar() @@ -1151,6 +1158,43 @@ void MainWindow::StartGame(std::unique_ptr&& parameters) if (!NKitWarningDialog::ShowUnlessDisabled()) return; } + + /* + When booting Triforce games, we need to ensure that the hardware is set up correctly. + */ + const auto volume_type = + std::get(parameters->parameters).volume->GetVolumeType(); + + const bool triforce_hardware_sp1 = + Config::Get(Config::MAIN_SERIAL_PORT_1) == ExpansionInterface::EXIDeviceType::Baseboard; + const bool triforce_hardware_port_1 = Config::Get(Config::GetInfoForSIDevice(0)) == + SerialInterface::SIDevices::SIDEVICE_AM_BASEBOARD; + + if (volume_type == DiscIO::Platform::Triforce) + { + if (!triforce_hardware_sp1 || !triforce_hardware_port_1) + { + ModalMessageBox::critical( + this, tr("Error"), + tr("To boot a Triforce game, SP1 and Port 1 must be set to Triforce Baseboard."), + QMessageBox::Ok); + HideRenderWidget(); + return; + } + } + else + { + /* + Some Triforce tools don't include a boot.id file, but they can still be launched. + */ + if (triforce_hardware_sp1 || triforce_hardware_port_1) + { + ModalMessageBox::warning( + this, tr("Warning"), + tr("Non-Triforce games cannot be booted with Triforce hardware attached."), + QMessageBox::Ok); + } + } } // If we're running, only start a new game once we've stopped the last. diff --git a/Source/Core/DolphinQt/MenuBar.cpp b/Source/Core/DolphinQt/MenuBar.cpp index cef4a6a28c..04c1b3eecd 100644 --- a/Source/Core/DolphinQt/MenuBar.cpp +++ b/Source/Core/DolphinQt/MenuBar.cpp @@ -319,6 +319,9 @@ void MenuBar::AddToolsMenu() m_pal_ipl = gc_ipl->addAction(tr("PAL"), this, [this] { emit BootGameCubeIPL(DiscIO::Region::PAL); }); + m_dev_ipl = gc_ipl->addAction(tr("Triforce"), this, + [this] { emit BootGameCubeIPL(DiscIO::Region::Unknown); }); + tools_menu->addAction(tr("Memory Card Manager"), this, [this] { emit ShowMemcardManager(); }); tools_menu->addSeparator(); @@ -1091,6 +1094,7 @@ void MenuBar::UpdateToolsMenu(const Core::State state) m_ntscj_ipl->setEnabled(is_uninitialized && File::Exists(Config::GetBootROMPath(JAP_DIR))); m_ntscu_ipl->setEnabled(is_uninitialized && File::Exists(Config::GetBootROMPath(USA_DIR))); m_pal_ipl->setEnabled(is_uninitialized && File::Exists(Config::GetBootROMPath(EUR_DIR))); + m_dev_ipl->setEnabled(is_uninitialized && File::Exists(Config::GetBootROMPath(DEV_DIR))); m_wad_install_action->setEnabled(is_uninitialized); m_import_backup->setEnabled(is_uninitialized); m_check_nand->setEnabled(is_uninitialized); diff --git a/Source/Core/DolphinQt/MenuBar.h b/Source/Core/DolphinQt/MenuBar.h index 9f51179e2a..2cd56f4e54 100644 --- a/Source/Core/DolphinQt/MenuBar.h +++ b/Source/Core/DolphinQt/MenuBar.h @@ -227,6 +227,7 @@ private: QAction* m_ntscj_ipl; QAction* m_ntscu_ipl; QAction* m_pal_ipl; + QAction* m_dev_ipl; QMenu* m_manage_nand_menu; QAction* m_import_backup; QAction* m_check_nand; diff --git a/Source/Core/DolphinQt/Settings/GameCubePane.cpp b/Source/Core/DolphinQt/Settings/GameCubePane.cpp index 772ade109e..975130531e 100644 --- a/Source/Core/DolphinQt/Settings/GameCubePane.cpp +++ b/Source/Core/DolphinQt/Settings/GameCubePane.cpp @@ -151,6 +151,7 @@ void GameCubePane::CreateWidgets() EXIDeviceType::EthernetIPC, #endif EXIDeviceType::ModemTapServer, + EXIDeviceType::Baseboard, }) { m_slot_combos[ExpansionInterface::Slot::SP1]->addItem(tr(fmt::format("{:n}", device).c_str()), @@ -418,6 +419,10 @@ void GameCubePane::OnConfigPressed(ExpansionInterface::Slot slot) dialog.exec(); return; } + case ExpansionInterface::EXIDeviceType::Baseboard: + { + return; + } default: PanicAlertFmt("Unknown settings pressed for {}", device); return; @@ -452,19 +457,23 @@ bool GameCubePane::SetMemcard(ExpansionInterface::Slot slot, const QString& file const std::string jp_path = Config::GetMemcardPath(raw_path, slot, DiscIO::Region::NTSC_J); const std::string us_path = Config::GetMemcardPath(raw_path, slot, DiscIO::Region::NTSC_U); const std::string eu_path = Config::GetMemcardPath(raw_path, slot, DiscIO::Region::PAL); - const bool raw_path_valid = raw_path == jp_path || raw_path == us_path || raw_path == eu_path; + const std::string dv_path = Config::GetMemcardPath(raw_path, slot, DiscIO::Region::DEV); + const bool raw_path_valid = + raw_path == jp_path || raw_path == us_path || raw_path == eu_path || raw_path == dv_path; if (!raw_path_valid) { // TODO: We could try to autodetect the card region here and offer automatic renaming. - ModalMessageBox::critical(this, tr("Error"), - tr("The filename %1 does not conform to Dolphin's region code format " - "for memory cards. Please rename this file to either %2, %3, or " - "%4, matching the region of the save files that are on it.") - .arg(QString::fromStdString(PathToFileName(raw_path))) - .arg(QString::fromStdString(PathToFileName(us_path))) - .arg(QString::fromStdString(PathToFileName(eu_path))) - .arg(QString::fromStdString(PathToFileName(jp_path)))); + ModalMessageBox::critical( + this, tr("Error"), + tr("The filename %1 does not conform to Dolphin's region code format " + "for memory cards. Please rename this file to either %2, %3, %4, or " + "%5, matching the region of the save files that are on it.") + .arg(QString::fromStdString(PathToFileName(raw_path))) + .arg(QString::fromStdString(PathToFileName(us_path))) + .arg(QString::fromStdString(PathToFileName(eu_path))) + .arg(QString::fromStdString(PathToFileName(jp_path))) + .arg(QString::fromStdString(PathToFileName(dv_path)))); return false; } @@ -556,8 +565,9 @@ bool GameCubePane::SetGCIFolder(ExpansionInterface::Slot slot, const QString& pa const std::string default_jp_path = Config::GetGCIFolderPath("", slot, DiscIO::Region::NTSC_J); const std::string default_us_path = Config::GetGCIFolderPath("", slot, DiscIO::Region::NTSC_U); const std::string default_eu_path = Config::GetGCIFolderPath("", slot, DiscIO::Region::PAL); - const bool is_default_path = - raw_path == default_jp_path || raw_path == default_us_path || raw_path == default_eu_path; + const std::string default_dv_path = Config::GetGCIFolderPath("", slot, DiscIO::Region::DEV); + const bool is_default_path = raw_path == default_jp_path || raw_path == default_us_path || + raw_path == default_eu_path || raw_path == default_dv_path; bool path_changed; if (is_default_path) @@ -574,7 +584,9 @@ bool GameCubePane::SetGCIFolder(ExpansionInterface::Slot slot, const QString& pa const std::string jp_path = Config::GetGCIFolderPath(raw_path, slot, DiscIO::Region::NTSC_J); const std::string us_path = Config::GetGCIFolderPath(raw_path, slot, DiscIO::Region::NTSC_U); const std::string eu_path = Config::GetGCIFolderPath(raw_path, slot, DiscIO::Region::PAL); - const bool raw_path_valid = raw_path == jp_path || raw_path == us_path || raw_path == eu_path; + const std::string dv_path = Config::GetGCIFolderPath(raw_path, slot, DiscIO::Region::DEV); + const bool raw_path_valid = + raw_path == jp_path || raw_path == us_path || raw_path == eu_path || raw_path == dv_path; if (!raw_path_valid) { @@ -582,12 +594,13 @@ bool GameCubePane::SetGCIFolder(ExpansionInterface::Slot slot, const QString& pa ModalMessageBox::critical( this, tr("Error"), tr("The folder %1 does not conform to Dolphin's region code format " - "for GCI folders. Please rename this folder to either %2, %3, or " - "%4, matching the region of the save files that are in it.") + "for GCI folders. Please rename this folder to either %2, %3, %4, or " + "%5, matching the region of the save files that are in it.") .arg(QString::fromStdString(PathToFileName(raw_path))) .arg(QString::fromStdString(PathToFileName(us_path))) .arg(QString::fromStdString(PathToFileName(eu_path))) - .arg(QString::fromStdString(PathToFileName(jp_path)))); + .arg(QString::fromStdString(PathToFileName(jp_path))) + .arg(QString::fromStdString(PathToFileName(dv_path)))); return false; } diff --git a/Source/Core/InputCommon/GCPadStatus.h b/Source/Core/InputCommon/GCPadStatus.h index 74849e5594..b6bbf804b4 100644 --- a/Source/Core/InputCommon/GCPadStatus.h +++ b/Source/Core/InputCommon/GCPadStatus.h @@ -28,6 +28,13 @@ enum PadButton PAD_BUTTON_START = 0x1000, }; +enum SwitchButton +{ + SWITCH_TEST = 0x0001, + SWITCH_SERVICE = 0x0002, + SWITCH_COIN = 0x0004, +}; + struct GCPadStatus { u16 button = 0; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits @@ -39,6 +46,8 @@ struct GCPadStatus u8 triggerRight = 0; // 0 <= triggerRight <= 255 u8 analogA = 0; // 0 <= analogA <= 255 u8 analogB = 0; // 0 <= analogB <= 255 + // Triforce + u8 switches = 0; bool isConnected = true; static const u8 MAIN_STICK_CENTER_X = 0x80; diff --git a/Source/Core/UICommon/GameFile.cpp b/Source/Core/UICommon/GameFile.cpp index 94ddf1f59c..4acab314d1 100644 --- a/Source/Core/UICommon/GameFile.cpp +++ b/Source/Core/UICommon/GameFile.cpp @@ -128,7 +128,6 @@ GameFile::GameFile(std::string path) : m_file_path(std::move(path)) m_internal_name = volume->GetInternalName(); m_game_id = volume->GetGameID(); m_gametdb_id = volume->GetGameTDBID(); - m_triforce_id = volume->GetTriforceID(); m_title_id = volume->GetTitleID().value_or(0); m_maker_id = volume->GetMakerID(); m_revision = volume->GetRevision().value_or(0); @@ -309,7 +308,6 @@ void GameFile::DoState(PointerWrap& p) p.Do(m_internal_name); p.Do(m_game_id); p.Do(m_gametdb_id); - p.Do(m_triforce_id); p.Do(m_title_id); p.Do(m_maker_id); @@ -498,8 +496,7 @@ const std::string& GameFile::GetName(const Core::TitleDatabase& title_database) if (IsModDescriptor()) return GetName(Variant::LongAndPossiblyCustom); - const std::string& database_name = - title_database.GetTitleName(m_gametdb_id, m_triforce_id, GetConfigLanguage()); + const std::string& database_name = title_database.GetTitleName(m_gametdb_id, GetConfigLanguage()); return database_name.empty() ? GetName(Variant::LongAndPossiblyCustom) : database_name; } diff --git a/Source/Core/UICommon/GameFile.h b/Source/Core/UICommon/GameFile.h index f015fa9725..e43950143b 100644 --- a/Source/Core/UICommon/GameFile.h +++ b/Source/Core/UICommon/GameFile.h @@ -76,7 +76,6 @@ public: const std::string& GetInternalName() const { return m_internal_name; } const std::string& GetGameID() const { return m_game_id; } const std::string& GetGameTDBID() const { return m_gametdb_id; } - std::string GetTriforceID() const { return m_triforce_id; } u64 GetTitleID() const { return m_title_id; } const std::string& GetMakerID() const { return m_maker_id; } u16 GetRevision() const { return m_revision; } @@ -160,7 +159,6 @@ private: std::string m_internal_name; std::string m_game_id; std::string m_gametdb_id; - std::string m_triforce_id; u64 m_title_id{}; std::string m_maker_id; diff --git a/Source/Core/UICommon/GameFileCache.cpp b/Source/Core/UICommon/GameFileCache.cpp index 37a130599d..898e818852 100644 --- a/Source/Core/UICommon/GameFileCache.cpp +++ b/Source/Core/UICommon/GameFileCache.cpp @@ -23,7 +23,7 @@ namespace UICommon { -static constexpr u32 CACHE_REVISION = 26; // Last changed in PR 10084 +static constexpr u32 CACHE_REVISION = 27; // Last changed in PR 13844 std::vector FindAllGamePaths(std::span directories_to_scan, bool recursive_scan) diff --git a/Source/Core/UICommon/UICommon.cpp b/Source/Core/UICommon/UICommon.cpp index 43973a02e0..c56d1e1d09 100644 --- a/Source/Core/UICommon/UICommon.cpp +++ b/Source/Core/UICommon/UICommon.cpp @@ -280,6 +280,7 @@ void CreateDirectories() File::CreateFullPath(File::GetUserPath(D_GCUSER_IDX) + USA_DIR DIR_SEP); File::CreateFullPath(File::GetUserPath(D_GCUSER_IDX) + EUR_DIR DIR_SEP); File::CreateFullPath(File::GetUserPath(D_GCUSER_IDX) + JAP_DIR DIR_SEP); + File::CreateFullPath(File::GetUserPath(D_TRIUSER_IDX)); File::CreateFullPath(File::GetUserPath(D_HIRESTEXTURES_IDX)); File::CreateFullPath(File::GetUserPath(D_GRAPHICSMOD_IDX)); File::CreateFullPath(File::GetUserPath(D_MAPS_IDX)); From 083faa8b218d4ef7bdb1e9a4e609da0f790fe5c0 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 26 Oct 2025 22:40:40 -0500 Subject: [PATCH 02/71] DiscIO: Make Volume::DecodeString take a std::span so it can work with std::array. --- Source/Core/DiscIO/Volume.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Core/DiscIO/Volume.h b/Source/Core/DiscIO/Volume.h index 3d161811e4..32f8e8d71f 100644 --- a/Source/Core/DiscIO/Volume.h +++ b/Source/Core/DiscIO/Volume.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -143,16 +144,15 @@ public: virtual std::array GetSyncHash() const = 0; protected: - template - std::string DecodeString(const char (&data)[N]) const + std::string DecodeString(std::span data) const { // strnlen to trim NULLs - std::string string(data, strnlen(data, sizeof(data))); + std::string string(data.data(), strnlen(data.data(), data.size())); if (GetRegion() == Region::NTSC_J) return SHIFTJISToUTF8(string); - else - return CP1252ToUTF8(string); + + return CP1252ToUTF8(string); } void ReadAndAddToSyncHash(Common::SHA1::Context* context, u64 offset, u64 length, From be2da8dc7b79a4bee3d4ee5736e8619ed66a8c4d Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 26 Oct 2025 21:53:53 -0500 Subject: [PATCH 03/71] Triforce: Code cleanups. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 392 +++++++++--------- Source/Core/Core/HW/DVD/AMMediaboard.h | 23 +- .../Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp | 45 +- Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.h | 14 +- .../Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp | 318 +++++++------- Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h | 9 +- Source/Core/DiscIO/VolumeDisc.cpp | 8 +- Source/Core/DiscIO/VolumeDisc.h | 35 +- Source/Core/DiscIO/VolumeGC.cpp | 2 +- 9 files changed, 390 insertions(+), 456 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 5bd3e2c036..416625b609 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -38,17 +38,20 @@ #include -#define closesocket close -#define ioctlsocket ioctl +static constexpr auto* closesocket = close; +static auto ioctlsocket(auto... args) +{ + return ioctl(args...); +} -#define WSAEWOULDBLOCK 10035L -#define SOCKET_ERROR (-1) +static constexpr int WSAEWOULDBLOCK = 10035; +static constexpr int SOCKET_ERROR = -1; -typedef int SOCKET; +using SOCKET = int; -#define INVALID_SOCKET (SOCKET)(~0) +static constexpr auto INVALID_SOCKET = SOCKET(~0); -static int WSAGetLastError(void) +static int WSAGetLastError() { switch (errno) { @@ -69,12 +72,12 @@ namespace AMMediaboard static bool s_firmware_map = false; static bool s_test_menu = false; static SOCKET s_namco_cam = 0; -static u32 s_timeouts[3] = {20000, 20000, 20000}; +static std::array s_timeouts = {20000, 20000, 20000}; static u32 s_last_error = SSC_SUCCESS; -static u32 s_GCAM_key_a = 0; -static u32 s_GCAM_key_b = 0; -static u32 s_GCAM_key_c = 0; +static u32 s_gcam_key_a = 0; +static u32 s_gcam_key_b = 0; +static u32 s_gcam_key_c = 0; static File::IOFile s_netcfg; static File::IOFile s_netctrl; @@ -82,7 +85,7 @@ static File::IOFile s_extra; static File::IOFile s_backup; static File::IOFile s_dimm; -Common::UniqueBuffer s_dimm_disc; +static Common::UniqueBuffer s_dimm_disc; static u8 s_firmware[2 * 1024 * 1024]; static u32 s_media_buffer_32[192]; @@ -103,6 +106,15 @@ constexpr char s_allnet_reply[] = { static SOCKET s_sockets[64]; +static constexpr u32 SocketCheck(u32 x) +{ + if (x < std::size(s_sockets)) + return x; + + WARN_LOG_FMT(AMMEDIABOARD, "Bad SOCKET value: {}", x); + return 0; +} + static SOCKET socket_(int af, int type, int protocol) { for (u32 i = 1; i < 64; ++i) @@ -118,13 +130,13 @@ static SOCKET socket_(int af, int type, int protocol) return SOCKET_ERROR; } -static SOCKET accept_(int fd, sockaddr* addr, int* len) +static SOCKET accept_(int fd, sockaddr* addr, socklen_t* len) { for (u32 i = 1; i < 64; ++i) { if (s_sockets[i] == SOCKET_ERROR) { - s_sockets[i] = accept(fd, addr, (socklen_t*)len); + s_sockets[i] = accept(fd, addr, len); if (s_sockets[i] == SOCKET_ERROR) return SOCKET_ERROR; return i; @@ -155,9 +167,9 @@ void FirmwareMap(bool on) void InitKeys(u32 key_a, u32 key_b, u32 key_c) { - s_GCAM_key_a = key_a; - s_GCAM_key_b = key_b; - s_GCAM_key_c = key_c; + s_gcam_key_a = key_a; + s_gcam_key_b = key_b; + s_gcam_key_c = key_c; } static File::IOFile OpenOrCreateFile(const std::string& filename) @@ -185,9 +197,9 @@ void Init() s_last_error = SSC_SUCCESS; - s_GCAM_key_a = 0; - s_GCAM_key_b = 0; - s_GCAM_key_c = 0; + s_gcam_key_a = 0; + s_gcam_key_b = 0; + s_gcam_key_c = 0; const std::string base_path = File::GetUserPath(D_TRIUSER_IDX); @@ -236,48 +248,48 @@ u8* InitDIMM(u32 size) if (size == 0) return nullptr; - if (!s_dimm_disc.size()) + if (s_dimm_disc.empty()) { s_dimm_disc.reset(size); - if (!s_dimm_disc.size()) + if (s_dimm_disc.empty()) { PanicAlertFmt("Failed to allocate DIMM memory."); return nullptr; } } - s_firmware_map = 0; + s_firmware_map = false; return s_dimm_disc.data(); } -static s32 NetDIMMAccept(int fd, sockaddr* addr, int* len) +static s32 NetDIMMAccept(int fd, sockaddr* addr, socklen_t* len) { - SOCKET clientSock = INVALID_SOCKET; + SOCKET client_sock = INVALID_SOCKET; fd_set readfds; FD_ZERO(&readfds); FD_SET(fd, &readfds); - timeval timeout; - timeout.tv_sec = 0; - timeout.tv_usec = 10000; // 10 milliseconds + timeval timeout{ + .tv_sec = 0, + .tv_usec = 10000, // 10 milliseconds + }; const int result = select(fd + 1, &readfds, nullptr, nullptr, &timeout); if (result > 0 && FD_ISSET(fd, &readfds)) { - clientSock = accept_(fd, addr, len); - if (clientSock != INVALID_SOCKET) + client_sock = accept_(fd, addr, len); + if (client_sock != INVALID_SOCKET) { s_last_error = SSC_SUCCESS; - return clientSock; - } - else - { - s_last_error = SOCKET_ERROR; - return SOCKET_ERROR; + return client_sock; } + + s_last_error = SOCKET_ERROR; + return SOCKET_ERROR; } - else if (result == 0) + + if (result == 0) { // Timeout s_last_error = SSC_EWOULDBLOCK; @@ -336,7 +348,7 @@ static s32 NetDIMMConnect(int fd, sockaddr_in* addr, int len) FD_ZERO(&writefds); FD_SET(fd, &writefds); - timeval timeout; + timeval timeout{}; timeout.tv_sec = 0; timeout.tv_usec = s_timeouts[0]; @@ -394,26 +406,26 @@ static void FileWriteData(File::IOFile* file, u32 seek_pos, const u8* data, std: file->Flush(); } -u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 length) +u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u32 length) { auto& system = Core::System::GetInstance(); auto& memory = system.GetMemory(); - DICMDBUF[0] ^= s_GCAM_key_a; - DICMDBUF[1] ^= s_GCAM_key_b; - DICMDBUF[2] ^= s_GCAM_key_c; + dicmd_buf[0] ^= s_gcam_key_a; + dicmd_buf[1] ^= s_gcam_key_b; + dicmd_buf[2] ^= s_gcam_key_c; - const u32 seed = DICMDBUF[0] >> 16; + const u32 seed = dicmd_buf[0] >> 16; - s_GCAM_key_a *= seed; - s_GCAM_key_b *= seed; - s_GCAM_key_c *= seed; + s_gcam_key_a *= seed; + s_gcam_key_b *= seed; + s_gcam_key_c *= seed; // Key setup for Triforce IPL: // These RAM offsets always hold the key for the next command. Since two dummy commands are sent // before any real ones, you can just use the key from RAM without missing a real command. - if (s_GCAM_key_a == 0) + if (s_gcam_key_a == 0) { if (memory.Read_U32(0)) { @@ -424,12 +436,12 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 } } - const u32 command = DICMDBUF[0] << 24; - const u32 offset = DICMDBUF[1] << 2; + const u32 command = dicmd_buf[0] << 24; + const u32 offset = dicmd_buf[1] << 2; DEBUG_LOG_FMT(AMMEDIABOARD, "GC-AM: {:08x} {:08x} DMA=addr:{:08x},len:{:08x} Keys: {:08x} {:08x} {:08x}", - command, offset, address, length, s_GCAM_key_a, s_GCAM_key_b, s_GCAM_key_c); + command, offset, address, length, s_gcam_key_a, s_gcam_key_b, s_gcam_key_c); // Test mode if (offset == 0x0002440) @@ -440,7 +452,7 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 // Don't map firmware while in SegaBoot if (memory.Read_U32(0x8006BF70) != 0x0A536567) { - s_firmware_map = 1; + s_firmware_map = true; } } } @@ -457,14 +469,14 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 switch (GetGameType()) { default: - *DIIMMBUF = Version1; + *diimm_buf = Version1; return 0; case VirtuaStriker4_2006: case KeyOfAvalon: case MarioKartGP: case MarioKartGP2: case FirmwareUpdate: - *DIIMMBUF = Version2; + *diimm_buf = Version2; return 0; } break; @@ -629,8 +641,7 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 if (offset == DIMMCommandExecute2) { - const AMMBCommand ammb_command = - static_cast(*reinterpret_cast(s_media_buffer + 0x202)); + const AMMBCommand ammb_command = Common::BitCastPtr(s_media_buffer + 0x202); INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Execute command: (2){0:04X}", static_cast(ammb_command)); @@ -661,10 +672,10 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 break; case AMMBCommand::Accept: { - const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const auto fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; int ret = -1; sockaddr addr; - int len = 0; + socklen_t len = 0; // Handle optional parameters if (s_media_buffer_32[3] == 0 || s_media_buffer_32[4] == 0) @@ -691,13 +702,12 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 } case AMMBCommand::Bind: { - sockaddr_in addr; - - const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const auto fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; const u32 off = s_media_buffer_32[3] - NetworkCommandAddress2; const u32 len = s_media_buffer_32[4]; - memcpy((void*)&addr, s_network_command_buffer + off, sizeof(sockaddr_in)); + sockaddr_in addr; + memcpy(&addr, s_network_command_buffer + off, sizeof(sockaddr_in)); addr.sin_family = Common::swap16(addr.sin_family); @@ -708,7 +718,7 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 addr.sin_addr.s_addr = INADDR_ANY; - const int ret = bind(fd, (const sockaddr*)&addr, len); + const int ret = bind(fd, reinterpret_cast(&addr), len); const int err = WSAGetLastError(); if (ret < 0) @@ -724,7 +734,7 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 } case AMMBCommand::Closesocket: { - const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const auto fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; const int ret = closesocket(fd); @@ -739,13 +749,12 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 } case AMMBCommand::Connect: { - sockaddr_in addr; - - const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const auto fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; const u32 off = s_media_buffer_32[3] - NetworkCommandAddress2; const u32 len = s_media_buffer_32[4]; - memcpy((void*)&addr, s_network_command_buffer + off, sizeof(sockaddr_in)); + sockaddr_in addr; + memcpy(&addr, s_network_command_buffer + off, sizeof(sockaddr_in)); const int ret = NetDIMMConnect(fd, &addr, len); @@ -759,9 +768,10 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 } case AMMBCommand::InetAddr: { - const u32 ip = inet_addr((char*)s_network_command_buffer); + const u32 ip = inet_addr(reinterpret_cast(s_network_command_buffer)); + // TODO: This expects null termination? NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: InetAddr( {} )\n", - (char*)s_network_command_buffer); + reinterpret_cast(s_network_command_buffer)); s_media_buffer[1] = s_media_buffer[8]; s_media_buffer_32[1] = Common::swap32(ip); @@ -769,7 +779,7 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 } case AMMBCommand::Listen: { - const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const auto fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; const u32 backlog = s_media_buffer_32[3]; const int ret = listen(fd, backlog); @@ -782,20 +792,15 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 } case AMMBCommand::Recv: { - const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const auto fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; const u32 off = s_media_buffer_32[3]; - u32 len = s_media_buffer_32[4]; + const auto len = std::min(s_media_buffer_32[4], sizeof(s_network_buffer)); - if (len >= sizeof(s_network_buffer)) - { - len = sizeof(s_network_buffer); - } - - u8* buffer = reinterpret_cast(s_network_buffer + off); + u8* buffer = s_network_buffer + off; if (off >= NetworkBufferAddress4 && off < NetworkBufferAddress4 + sizeof(s_network_buffer)) { - buffer = reinterpret_cast(s_network_buffer + off - NetworkBufferAddress4); + buffer = s_network_buffer + off - NetworkBufferAddress4; } else { @@ -814,7 +819,7 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 } case AMMBCommand::Send: { - const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const auto fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; u32 off = s_media_buffer_32[3]; const u32 len = s_media_buffer_32[4]; @@ -853,7 +858,7 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 } case AMMBCommand::Select: { - u32 fd = s_sockets[SocketCheck(s_media_buffer_32[2] - 1)]; + auto fd = s_sockets[SocketCheck(s_media_buffer_32[2] - 1)]; // BUG: NAMCAM is hardcoded to call this with socket ID 0x100 which might be some magic // thing? A valid value is needed so we use the socket from the connect. @@ -869,43 +874,41 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 fd_set* readfds = nullptr; fd_set* writefds = nullptr; fd_set* exceptfds = nullptr; + timeval timeout = {}; - timeval* timeout_src = nullptr; + u8* timeout_src = nullptr; + + fd_set fds; + FD_ZERO(&fds); + FD_SET(fd, &fds); // Only one of 3, 4, 5 is ever set alongside 6 - if (s_media_buffer_32[3] && s_media_buffer_32[6]) + if (s_media_buffer_32[6] != 0) { - const u32 ROffset = s_media_buffer_32[6] - NetworkCommandAddress2; - readfds = reinterpret_cast(s_network_command_buffer + ROffset); - FD_ZERO(readfds); - FD_SET(fd, readfds); + const u32 fd_offset = s_media_buffer_32[6] - NetworkCommandAddress2; - timeout_src = reinterpret_cast(s_network_command_buffer + s_media_buffer_32[3] - - NetworkCommandAddress2); - } - else if (s_media_buffer_32[4] && s_media_buffer_32[6]) - { - const u32 WOffset = s_media_buffer_32[6] - NetworkCommandAddress2; - writefds = reinterpret_cast(s_network_command_buffer + WOffset); - FD_ZERO(writefds); - FD_SET(fd, writefds); + // TODO: Check that fd_offset is in bounds of of s_network_command_buffer. + Common::BitCastPtr(s_network_command_buffer + fd_offset) = fds; - timeout_src = reinterpret_cast(s_network_command_buffer + s_media_buffer_32[4] - - NetworkCommandAddress2); - } - else if (s_media_buffer_32[5] && s_media_buffer_32[6]) - { - const u32 EOffset = s_media_buffer_32[6] - NetworkCommandAddress2; - exceptfds = reinterpret_cast(s_network_command_buffer + EOffset); - FD_ZERO(exceptfds); - FD_SET(fd, exceptfds); - - timeout_src = reinterpret_cast(s_network_command_buffer + s_media_buffer_32[5] - - NetworkCommandAddress2); + if (s_media_buffer_32[3] != 0) + { + readfds = &fds; + timeout_src = s_network_command_buffer + s_media_buffer_32[3] - NetworkCommandAddress2; + } + else if (s_media_buffer_32[4] != 0) + { + writefds = &fds; + timeout_src = s_network_command_buffer + s_media_buffer_32[4] - NetworkCommandAddress2; + } + else if (s_media_buffer_32[5] != 0) + { + exceptfds = &fds; + timeout_src = s_network_command_buffer + s_media_buffer_32[5] - NetworkCommandAddress2; + } } // Copy timeout if set - if (timeout_src) + if (timeout_src != nullptr) { std::memcpy(&timeout, timeout_src, sizeof(timeval)); } @@ -935,11 +938,11 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 } case AMMBCommand::SetSockOpt: { - const SOCKET fd = (SOCKET)(s_sockets[SocketCheck(s_media_buffer_32[2])]); + const auto fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; const int level = (int)(s_media_buffer_32[3]); const int optname = (int)(s_media_buffer_32[4]); - const char* optval = - (char*)(s_network_command_buffer + s_media_buffer_32[5] - NetworkCommandAddress2); + const char* optval = reinterpret_cast(s_network_command_buffer + + s_media_buffer_32[5] - NetworkCommandAddress2); const int optlen = (int)(s_media_buffer_32[6]); const int ret = setsockopt(fd, level, optname, optval, optlen); @@ -956,34 +959,36 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 } case AMMBCommand::SetTimeOuts: { - const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; - const u32 timeoutA = s_media_buffer_32[3]; - const u32 timeoutB = s_media_buffer_32[4]; - const u32 timeoutC = s_media_buffer_32[5]; + const auto fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const u32 timeout_a = s_media_buffer_32[3]; + const u32 timeout_b = s_media_buffer_32[4]; + const u32 timeout_c = s_media_buffer_32[5]; - s_timeouts[0] = timeoutA; - s_timeouts[1] = timeoutB; - s_timeouts[2] = timeoutC; + s_timeouts[0] = timeout_a; + s_timeouts[1] = timeout_b; + s_timeouts[2] = timeout_c; int ret = 0; if (fd != INVALID_SOCKET) { - ret = setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeoutB, sizeof(int)); + ret = setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast(&timeout_b), + sizeof(int)); if (ret < 0) { ret = WSAGetLastError(); } else { - ret = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeoutC, sizeof(int)); + ret = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&timeout_c), + sizeof(int)); if (ret < 0) ret = WSAGetLastError(); } } - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: SetTimeOuts( {}, {}, {}, {} ):{}\n", fd, timeoutA, - timeoutB, timeoutC, ret); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: SetTimeOuts( {}, {}, {}, {} ):{}\n", fd, timeout_a, + timeout_b, timeout_c, ret); s_media_buffer[1] = s_media_buffer[8]; s_media_buffer_32[1] = ret; @@ -1001,16 +1006,18 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 } case AMMBCommand::ModifyMyIPaddr: { - const u32 NetBufferOffset = *(u32*)(s_media_buffer + 8) - NetworkCommandAddress2; + const u32 net_buffer_offset = s_media_buffer_32[2] - NetworkCommandAddress2; - const char* IP = (char*)(s_network_command_buffer + NetBufferOffset); + // TODO: Not checked for null termination. + const char* ip_address = + reinterpret_cast(s_network_command_buffer + net_buffer_offset); - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: modifyMyIPaddr({})\n", IP); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: modifyMyIPaddr({})\n", ip_address); break; } case AMMBCommand::GetLastError: { - const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const auto fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: GetLastError( {}({}) ):{}\n", fd, s_media_buffer_32[2], s_last_error); @@ -1239,8 +1246,7 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 if (dimm_offset == 0x40 && cmd_flag == 1) { - const AMMBCommand ammb_command = - static_cast(*reinterpret_cast(s_media_buffer + 0x22)); + const AMMBCommand ammb_command = Common::BitCastPtr(s_media_buffer + 0x22); INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Execute command (2):{0:04X}", static_cast(ammb_command)); @@ -1297,10 +1303,8 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 ExpansionInterface::GenerateInterrupt(0x04); return 0; } - else - { - memcpy(s_media_buffer + dimm_offset, memory.GetSpanForAddress(address).data(), length); - } + + memcpy(s_media_buffer + dimm_offset, memory.GetSpanForAddress(address).data(), length); return 0; } @@ -1311,7 +1315,6 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 PrintMBBuffer(address, length); memcpy(s_media_buffer + dimm_offset, memory.GetSpanForAddress(address).data(), length); - return 0; } @@ -1347,7 +1350,7 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 break; case AMMBDICommand::Execute: { - const AMMBCommand ammb_command = static_cast(s_media_buffer_32[8] >> 16); + const auto ammb_command = static_cast(s_media_buffer_32[8] >> 16); if (ammb_command == AMMBCommand::Unknown_000) break; @@ -1432,7 +1435,7 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 break; case AMMBCommand::Closesocket: { - const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; + const auto fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; const int ret = closesocket(fd); @@ -1447,13 +1450,12 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 break; case AMMBCommand::Connect: { - sockaddr_in addr; - - const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; + const auto fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; const u32 off = s_media_buffer_32[11] - NetworkCommandAddress; const u32 len = s_media_buffer_32[12]; - memcpy((void*)&addr, s_network_command_buffer + off, sizeof(sockaddr_in)); + sockaddr_in addr; + memcpy(&addr, s_network_command_buffer + off, sizeof(sockaddr_in)); const int ret = NetDIMMConnect(fd, &addr, len); @@ -1467,27 +1469,22 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 break; case AMMBCommand::Recv: { - const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; + const auto fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; const u32 off = s_media_buffer_32[11]; - u32 len = s_media_buffer_32[12]; + const auto len = std::min(s_media_buffer_32[12], sizeof(s_network_buffer)); - if (len >= sizeof(s_network_buffer)) - { - len = sizeof(s_network_buffer); - } - - char* buffer = (char*)(s_network_buffer + off); + auto* buffer = s_network_buffer + off; if (off >= NetworkBufferAddress5 && off < NetworkBufferAddress5 + sizeof(s_network_buffer)) { - buffer = (char*)(s_network_buffer + off - NetworkBufferAddress5); + buffer = s_network_buffer + off - NetworkBufferAddress5; } else { PanicAlertFmt("RECV: Buffer overrun:{0} {1} ", off, len); } - const int ret = recv(fd, buffer, len, 0); + const int ret = recv(fd, reinterpret_cast(buffer), len, 0); const int err = WSAGetLastError(); NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: recv( {}, 0x{:08x}, {} ):{} {}\n", fd, off, len, @@ -1499,7 +1496,7 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 break; case AMMBCommand::Send: { - const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; + const auto fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; u32 off = s_media_buffer_32[11]; const u32 len = s_media_buffer_32[12]; @@ -1512,7 +1509,7 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: send(error) unhandled destination:{:08x}\n", off); } - const int ret = send(fd, (char*)(s_network_buffer + off), len, 0); + const int ret = send(fd, reinterpret_cast(s_network_buffer + off), len, 0); const int err = WSAGetLastError(); NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: send( {}({}), 0x{:08x}, {} ): {} {}\n", fd, @@ -1538,48 +1535,46 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 break; case AMMBCommand::Select: { - const u32 fd = s_sockets[SocketCheck(s_media_buffer_32[10] - 1)]; + const auto fd = s_sockets[SocketCheck(s_media_buffer_32[10] - 1)]; fd_set* readfds = nullptr; fd_set* writefds = nullptr; fd_set* exceptfds = nullptr; + timeval timeout = {}; - timeval* timeout_src = nullptr; + u8* timeout_src = nullptr; + + fd_set fds; + FD_ZERO(&fds); + FD_SET(fd, &fds); // Only one of 11, 12, 13 is ever set alongside 14 - if (s_media_buffer_32[11] && s_media_buffer_32[14]) + if (s_media_buffer_32[14] != 0) { - const u32 ROffset = s_media_buffer_32[14] - NetworkCommandAddress; - readfds = reinterpret_cast(s_network_command_buffer + ROffset); - FD_ZERO(readfds); - FD_SET(fd, readfds); + const u32 fd_offset = s_media_buffer_32[14] - NetworkCommandAddress; - timeout_src = reinterpret_cast(s_network_command_buffer + - s_media_buffer_32[11] - NetworkCommandAddress); - } - else if (s_media_buffer_32[12] && s_media_buffer_32[14]) - { - const u32 WOffset = s_media_buffer_32[14] - NetworkCommandAddress; - writefds = reinterpret_cast(s_network_command_buffer + WOffset); - FD_ZERO(writefds); - FD_SET(fd, writefds); + // TODO: Check that fd_offset is in bounds of of s_network_command_buffer. + Common::BitCastPtr(s_network_command_buffer + fd_offset) = fds; - timeout_src = reinterpret_cast(s_network_command_buffer + - s_media_buffer_32[12] - NetworkCommandAddress); - } - else if (s_media_buffer_32[13] && s_media_buffer_32[14]) - { - const u32 EOffset = s_media_buffer_32[14] - NetworkCommandAddress; - exceptfds = reinterpret_cast(s_network_command_buffer + EOffset); - FD_ZERO(exceptfds); - FD_SET(fd, exceptfds); - - timeout_src = reinterpret_cast(s_network_command_buffer + - s_media_buffer_32[13] - NetworkCommandAddress); + if (s_media_buffer_32[11] != 0) + { + readfds = &fds; + timeout_src = s_network_command_buffer + s_media_buffer_32[11] - NetworkCommandAddress; + } + else if (s_media_buffer_32[12] != 0) + { + writefds = &fds; + timeout_src = s_network_command_buffer + s_media_buffer_32[12] - NetworkCommandAddress; + } + else if (s_media_buffer_32[13] != 0) + { + exceptfds = &fds; + timeout_src = s_network_command_buffer + s_media_buffer_32[13] - NetworkCommandAddress; + } } // Copy timeout if set - if (timeout_src) + if (timeout_src != nullptr) { std::memcpy(&timeout, timeout_src, sizeof(timeval)); } @@ -1608,11 +1603,11 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 break; case AMMBCommand::SetSockOpt: { - const SOCKET fd = (SOCKET)(s_sockets[SocketCheck(s_media_buffer_32[10])]); + const auto fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; const int level = (int)(s_media_buffer_32[11]); const int optname = (int)(s_media_buffer_32[12]); - const char* optval = - (char*)(s_network_command_buffer + s_media_buffer_32[13] - NetworkCommandAddress); + const char* optval = reinterpret_cast(s_network_command_buffer + + s_media_buffer_32[13] - NetworkCommandAddress); const int optlen = (int)(s_media_buffer_32[14]); const int ret = setsockopt(fd, level, optname, optval, optlen); @@ -1628,11 +1623,13 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 break; case AMMBCommand::ModifyMyIPaddr: { - const u32 NetBufferOffset = s_media_buffer_32[10] - NetworkCommandAddress; + const u32 net_buffer_offset = s_media_buffer_32[10] - NetworkCommandAddress; - const char* IP = (char*)(s_network_command_buffer + NetBufferOffset); + // TODO: Not checked for null termination. + const char* ip_address = + reinterpret_cast(s_network_command_buffer + net_buffer_offset); - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: modifyMyIPaddr({})\n", IP); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: modifyMyIPaddr({})\n", ip_address); } break; // Empty reply @@ -1650,9 +1647,9 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 addra.sin_addr.s_addr = s_media_buffer_32[12]; addrb.sin_addr.s_addr = s_media_buffer_32[13]; - u16 size = s_media_buffer[0x24] | s_media_buffer[0x25] << 8; - u16 port = Common::swap16(s_media_buffer[0x27] | s_media_buffer[0x26] << 8); - u16 unknown = s_media_buffer[0x2D] | s_media_buffer[0x2C] << 8; + const u16 size = s_media_buffer[0x24] | s_media_buffer[0x25] << 8; + const u16 port = Common::swap16(s_media_buffer[0x27] | s_media_buffer[0x26] << 8); + const u16 unknown = s_media_buffer[0x2D] | s_media_buffer[0x2C] << 8; NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: SetupLink:"); NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: Size: ({}) ", size); @@ -1682,12 +1679,13 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 address, u32 NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: Offset: ({:04x})", off); NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: ({:08x})", addr); - const u8* data = (u8*)(s_network_buffer + off + addr - NetworkBufferAddress2); + const u8* data = s_network_buffer + off + addr - NetworkBufferAddress2; for (u32 i = 0; i < 0x20; i += 0x10) { - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: {:08x} {:08x} {:08x} {:08x}", *(u32*)(data + i), - *(u32*)(data + i + 4), *(u32*)(data + i + 8), *(u32*)(data + i + 12)); + const std::array data_u32 = Common::BitCastPtr>(data + i); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: {:08x} {:08x} {:08x} {:08x}", data_u32[0], + data_u32[1], data_u32[2], data_u32[3]); } s_media_buffer_32[1] = 0; @@ -1760,7 +1758,7 @@ u32 GetGameType() else { // Convert game ID into hex - sscanf(SConfig::GetInstance().GetGameID().c_str(), "%s", (char*)&game_id); + sscanf(SConfig::GetInstance().GetGameID().c_str(), "%s", reinterpret_cast(&game_id)); } // This is checking for the real game IDs (See boot.id within the game) @@ -1831,11 +1829,7 @@ void Shutdown() s_extra.Close(); s_backup.Close(); s_dimm.Close(); - - if (s_dimm_disc.size()) - { - s_dimm_disc.clear(); - } + s_dimm_disc.clear(); // Close all sockets for (u32 i = 1; i < 64; ++i) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.h b/Source/Core/Core/HW/DVD/AMMediaboard.h index 3cfa79fef6..aab8888a89 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.h +++ b/Source/Core/Core/HW/DVD/AMMediaboard.h @@ -3,25 +3,10 @@ #pragma once -#include -#include -#include -#include +#include #include "Common/CommonTypes.h" -class PointerWrap; - -namespace Core -{ -class System; -} - -namespace File -{ -class IOFile; -} - enum GameType { FZeroAX = 1, @@ -67,8 +52,6 @@ enum InquiryType Version2 = 0x29484100, }; -#define SocketCheck(x) (x <= 0x3F ? x : 0) - namespace AMMediaboard { @@ -228,8 +211,8 @@ enum SocketStatusCodes void Init(); void FirmwareMap(bool on); u8* InitDIMM(u32 size); -void InitKeys(u32 KeyA, u32 KeyB, u32 KeyC); -u32 ExecuteCommand(std::array& DICMDBUF, u32* DIIMMBUF, u32 Address, u32 Length); +void InitKeys(u32 key_a, u32 key_b, u32 key_c); +u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u32 length); u32 GetGameType(); u32 GetMediaType(); bool GetTestMenu(); diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp index a489d97a72..6731c156f5 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp @@ -3,28 +3,19 @@ #include "Core/HW/EXI/EXI_DeviceBaseboard.h" -#include -#include -#include +#include #include -#include #include #include "Common/Buffer.h" #include "Common/CommonTypes.h" -#include "Common/Config/Config.h" #include "Common/FileUtil.h" #include "Common/IOFile.h" -#include "Common/IniFile.h" #include "Common/Logging/Log.h" #include "Core/Boot/Boot.h" #include "Core/BootManager.h" -#include "Core/Config/MainSettings.h" -#include "Core/Config/SYSCONFSettings.h" -#include "Core/ConfigLoaders/BaseConfigLoader.h" -#include "Core/ConfigLoaders/NetPlayConfigLoader.h" #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/HW/DVD/AMMediaboard.h" @@ -35,30 +26,18 @@ #include "Core/HW/Memmap.h" #include "Core/HW/SI/SI.h" #include "Core/HW/SI/SI_Device.h" -#include "Core/HW/Sram.h" #include "Core/HW/WiimoteReal/WiimoteReal.h" #include "Core/Movie.h" -#include "Core/NetPlayProto.h" #include "Core/PowerPC/PowerPC.h" #include "Core/System.h" -#include "Core/WiiRoot.h" - -#include "DiscIO/Enums.h" static bool s_interrupt_set = false; static u32 s_irq_timer = 0; static u32 s_irq_status = 0; -static u16 CheckSum(u8* data, u32 length) +static u16 CheckSum(const u8* data, u32 length) { - u16 check = 0; - - for (u32 i = 0; i < length; i++) - { - check += data[i]; - } - - return check; + return std::accumulate(data, data + length, 0); } namespace ExpansionInterface @@ -75,7 +54,7 @@ void GenerateInterrupt(int flag) system.GetExpansionInterface().UpdateInterrupts(); } -CEXIBaseboard::CEXIBaseboard(Core::System& system) : IEXIDevice(system), m_position(0) +CEXIBaseboard::CEXIBaseboard(Core::System& system) : IEXIDevice(system) { std::string backup_filename(fmt::format("{}tribackup_{}.bin", File::GetUserPath(D_TRIUSER_IDX), SConfig::GetInstance().GetGameID())); @@ -110,12 +89,12 @@ CEXIBaseboard::CEXIBaseboard(Core::System& system) : IEXIDevice(system), m_posit m_backup.ReadBytes(data.data(), data.size()); // Set FIRM version - reinterpret_cast(data[0x12]) = 0x1703; - reinterpret_cast(data[0x212]) = 0x1703; + Common::BitCastPtr(data.data() + 0x12) = 0x1703; + Common::BitCastPtr(data.data() + 0x212) = 0x1703; // Update checksum - reinterpret_cast(data[0x0A]) = Common::swap16(CheckSum(&data[0xC], 0x1F4)); - reinterpret_cast(data[0x20A]) = Common::swap16(CheckSum(&data[0x20C], 0x1F4)); + Common::BitCastPtr(data.data() + 0x0A) = Common::swap16(CheckSum(&data[0xC], 0x1F4)); + Common::BitCastPtr(data.data() + 0x20A) = Common::swap16(CheckSum(&data[0x20C], 0x1F4)); m_backup.Seek(0, File::SeekOrigin::Begin); m_backup.WriteBytes(data.data(), data.size()); @@ -148,12 +127,10 @@ bool CEXIBaseboard::IsInterruptSet() DEBUG_LOG_FMT(SP1, "AM-BB: IRQ"); if (++s_irq_timer > 12) s_interrupt_set = false; - return 1; - } - else - { - return 0; + return true; } + + return false; } void CEXIBaseboard::DMAWrite(u32 addr, u32 size) diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.h b/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.h index 6b32da191c..8fa9d449a1 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.h @@ -16,11 +16,11 @@ namespace ExpansionInterface { void GenerateInterrupt(int flag); -class CEXIBaseboard : public IEXIDevice +class CEXIBaseboard final : public IEXIDevice { public: explicit CEXIBaseboard(Core::System& system); - virtual ~CEXIBaseboard(); + ~CEXIBaseboard() override; void SetCS(int cs) override; bool IsInterruptSet() override; @@ -46,11 +46,11 @@ private: WriteLANCNT = 0xFF, }; - u32 m_position; - u32 m_backup_dma_offset; - u32 m_backup_dma_length; - u8 m_command[4]; - u16 m_backup_offset; + u32 m_position = 0; + u32 m_backup_dma_offset = 0; + u32 m_backup_dma_length = 0; + std::array m_command{}; + u16 m_backup_offset = 0; File::IOFile m_backup; protected: diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp index 971c15e87a..0d9c99beb1 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp @@ -4,21 +4,14 @@ #include "Core/HW/SI/SI_DeviceAMBaseboard.h" #include -#include -#include #include -#include #include #include "Common/Buffer.h" -#include "Common/ChunkFile.h" #include "Common/CommonTypes.h" -#include "Common/Config/Config.h" #include "Common/FileUtil.h" -#include "Common/Hash.h" #include "Common/IOFile.h" -#include "Common/IniFile.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" #include "Common/Swap.h" @@ -26,9 +19,6 @@ #include "Core/Boot/Boot.h" #include "Core/BootManager.h" #include "Core/Config/MainSettings.h" -#include "Core/Config/SYSCONFSettings.h" -#include "Core/ConfigLoaders/BaseConfigLoader.h" -#include "Core/ConfigLoaders/NetPlayConfigLoader.h" #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/CoreTiming.h" @@ -42,28 +32,16 @@ #include "Core/HW/SI/SI.h" #include "Core/HW/SI/SI_Device.h" #include "Core/HW/SI/SI_DeviceGCController.h" -#include "Core/HW/Sram.h" #include "Core/HW/SystemTimers.h" -#include "Core/HW/WiimoteReal/WiimoteReal.h" #include "Core/Movie.h" #include "Core/NetPlayProto.h" -#include "Core/PowerPC/PowerPC.h" #include "Core/System.h" -#include "Core/WiiRoot.h" - -#include "DiscIO/Enums.h" #include "InputCommon/GCPadStatus.h" namespace SerialInterface { -JVSIOMessage::JVSIOMessage() -{ - m_ptr = 0; - m_last_start = 0; -} - void JVSIOMessage::Start(int node) { m_last_start = m_ptr; @@ -287,7 +265,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) for (u32 i = 0; i < buffer_length; ++i) checksum += buffer[i]; - u8 data_out[0x80]{}; + std::array data_out{}; u32 data_offset = 0; static u32 dip_switch_1 = 0xFE; @@ -315,11 +293,11 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) // We read Test/Service from the JVS-I/O SwitchesInput instead // - // const GCPadStatus PadStatus = Pad::GetStatus(ISIDevice::m_device_number); + // const GCPadStatus pad_status = Pad::GetStatus(ISIDevice::m_device_number); // baseboard test/service switches - // if (PadStatus.button & PAD_BUTTON_Y) // Test + // if (pad_status.button & PAD_BUTTON_Y) // Test // dip_switch_0 &= ~0x80; - // if (PadStatus.button & PAD_BUTTON_X) // Service + // if (pad_status.button & PAD_BUTTON_X) // Service // dip_switch_0 &= ~0x40; // Horizontal Scanning Frequency switch @@ -348,7 +326,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) data_out[data_offset++] = gcam_command; data_out[data_offset++] = 16; - memcpy(data_out + data_offset, "AADE-01B98394904", 16); + memcpy(data_out.data() + data_offset, "AADE-01B98394904", 16); data_offset += 16; break; @@ -509,7 +487,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) // Serial - Unknown if (AMMediaboard::GetGameType() == GekitouProYakyuu) { - u32 serial_command = *(u32*)(data_in); + const u32 serial_command = Common::BitCastPtr(data_in); if (serial_command == 0x00001000) { @@ -582,7 +560,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) icco.command = WritePages; - ICCardSendReply(&icco, data_out, &data_offset); + ICCardSendReply(&icco, data_out.data(), &data_offset); } data_in += length; break; @@ -646,7 +624,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) case ICCARDCommand::ReadPage: case ICCARDCommand::ReadUseCount: { - const u16 page = Common::swap16(*(u16*)(data_in + 6)); + const u16 page = Common::swap16(data_in + 6); icco.extlen = 8; icco.length += icco.extlen; @@ -660,7 +638,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } case ICCARDCommand::WritePage: { - const u16 page = Common::swap16(*(u16*)(data_in + 8)); + const u16 page = Common::swap16(data_in + 8); // Write only one page if (page == 4) @@ -678,13 +656,14 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } case ICCARDCommand::DecreaseUseCount: { - const u16 page = Common::swap16(*(u16*)(data_in + 6)); + const u16 page = Common::swap16(data_in + 6); icco.extlen = 2; icco.length += icco.extlen; icco.pktlen += icco.extlen; - *(u16*)(m_ic_card_data + 0x28) = *(u16*)(m_ic_card_data + 0x28) - 1; + auto ic_card_data = Common::BitCastPtr(m_ic_card_data + 0x28); + ic_card_data = ic_card_data - 1; // Counter icco.extdata[0] = m_ic_card_data[0x28]; @@ -696,8 +675,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } case ICCARDCommand::ReadPages: { - const u16 page = Common::swap16(*(u16*)(data_in + 6)); - const u16 count = Common::swap16(*(u16*)(data_in + 8)); + const u16 page = Common::swap16(data_in + 6); + const u16 count = Common::swap16(data_in + 8); const u32 offs = page * 8; u32 cnt = count * 8; @@ -721,9 +700,9 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) case ICCARDCommand::WritePages: { const u16 pksize = length; - const u16 size = Common::swap16(*(u16*)(data_in + 2)); - const u16 page = Common::swap16(*(u16*)(data_in + 6)); - const u16 count = Common::swap16(*(u16*)(data_in + 8)); + const u16 size = Common::swap16(data_in + 2); + const u16 page = Common::swap16(data_in + 6); + const u16 count = Common::swap16(data_in + 8); // We got a complete packet if (pksize - 5 == size) @@ -872,7 +851,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) break; } - ICCardSendReply(&icco, data_out, &data_offset); + ICCardSendReply(&icco, data_out.data(), &data_offset); data_in += length; break; @@ -884,8 +863,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) { // All commands are OR'd with 0x80 // Last byte is checksum which we don't care about - const u32 serial_command = - Common::swap32(*(u32*)(data_in + command_offset)) ^ 0x80000000; + const u32 serial_command = Common::swap32(data_in + command_offset) ^ 0x80000000; if (AMMediaboard::GetGameType() == FZeroAX || AMMediaboard::GetGameType() == FZeroAXMonster) @@ -914,7 +892,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } } - command_offset += 4; + command_offset += sizeof(u32); if (AMMediaboard::GetGameType() == FZeroAX || AMMediaboard::GetGameType() == FZeroAXMonster) @@ -929,9 +907,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) switch (serial_command >> 24) { case 0: - break; case 1: // Set Maximum? - break; case 2: break; @@ -955,8 +931,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) { if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING) { - const GCPadStatus PadStatus = Pad::GetStatus(1); - if (PadStatus.isConnected) + const GCPadStatus pad_status = Pad::GetStatus(1); + if (pad_status.isConnected) { const ControlState mapped_strength = (double)(m_motor_force_y >> 8) / 127.f; @@ -1003,7 +979,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) m_motor_reply[0] = gcam_command; m_motor_reply[1] = length; // Same out as in size - memcpy(data_out + data_offset, m_motor_reply, m_motor_reply[1] + 2); + memcpy(data_out.data() + data_offset, m_motor_reply, m_motor_reply[1] + 2); data_offset += m_motor_reply[1] + 2; } } @@ -1027,13 +1003,13 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) if (AMMediaboard::GetGameType() == FZeroAX) { - if (read_length > 0x2F) - read_length = 0x2F; + read_length = std::min(read_length, 0x2F); } data_out[data_offset++] = read_length; // 0x2F (max size per packet) - memcpy(data_out + data_offset, m_card_read_packet + m_card_read, read_length); + memcpy(data_out.data() + data_offset, m_card_read_packet + m_card_read, + read_length); data_offset += read_length; m_card_read += read_length; @@ -1082,9 +1058,6 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) data_out[data_offset++] = 0x00; // 0x03 break; case CARDCommand::SetPrintParam: - data_out[data_offset++] = 0x00; // 0x02 - data_out[data_offset++] = 0x00; // 0x03 - break; case CARDCommand::RegisterFont: data_out[data_offset++] = 0x00; // 0x02 data_out[data_offset++] = 0x00; // 0x03 @@ -1093,6 +1066,9 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) data_out[data_offset++] = 0x02; // 0x02 data_out[data_offset++] = 0x00; // 0x03 break; + // TODO: CARDCommand::Erase is not handled. + ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "CARDCommand::Erase is not handled."); + break; case CARDCommand::Eject: if (AMMediaboard::GetGameType() == FZeroAX) { @@ -1322,9 +1298,9 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) "GC-AM: Command CARD Write: {:02X} {:02X} {:02X} {}", mode, bitmode, track, m_card_memory_size); - const std::string card_filename( - File::GetUserPath(D_TRIUSER_IDX) + "tricard_" + - SConfig::GetInstance().GetGameID().c_str() + ".bin"); + const std::string card_filename(File::GetUserPath(D_TRIUSER_IDX) + + "tricard_" + + SConfig::GetInstance().GetGameID() + ".bin"); File::IOFile card(card_filename, "wb+"); card.WriteBytes(m_card_memory, m_card_memory_size); @@ -1409,6 +1385,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) const u8 nr_bytes = frame[3]; // Byte after E0 xx const u32 frame_len = nr_bytes + 3; // Header(2) + length byte + payload + checksum + // TODO: frame_len isn't checked for buffer overflow. + u8 jvs_buf[0x80]; memcpy(jvs_buf, frame, frame_len); @@ -1500,59 +1478,59 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) // // DX Version: 2 Player (22bit) (p2=paddles), 2 Coin slot, 8 Analog-in, // 22 Driver-out - message.AddData((void*)"\x01\x02\x12\x00", 4); - message.AddData((void*)"\x02\x02\x00\x00", 4); - message.AddData((void*)"\x03\x08\x0A\x00", 4); - message.AddData((void*)"\x12\x16\x00\x00", 4); - message.AddData((void*)"\x00\x00\x00\x00", 4); + message.AddData("\x01\x02\x12\x00", 4); + message.AddData("\x02\x02\x00\x00", 4); + message.AddData("\x03\x08\x0A\x00", 4); + message.AddData("\x12\x16\x00\x00", 4); + message.AddData("\x00\x00\x00\x00", 4); break; case VirtuaStriker3: // 2 Player (13bit), 2 Coin slot, 4 Analog-in, 1 CARD, 8 Driver-out - message.AddData((void*)"\x01\x02\x0D\x00", 4); - message.AddData((void*)"\x02\x02\x00\x00", 4); - message.AddData((void*)"\x10\x01\x00\x00", 4); - message.AddData((void*)"\x12\x08\x00\x00", 4); - message.AddData((void*)"\x00\x00\x00\x00", 4); + message.AddData("\x01\x02\x0D\x00", 4); + message.AddData("\x02\x02\x00\x00", 4); + message.AddData("\x10\x01\x00\x00", 4); + message.AddData("\x12\x08\x00\x00", 4); + message.AddData("\x00\x00\x00\x00", 4); break; case GekitouProYakyuu: // 2 Player (13bit), 2 Coin slot, 4 Analog-in, 1 CARD, 8 Driver-out - message.AddData((void*)"\x01\x02\x0D\x00", 4); - message.AddData((void*)"\x02\x02\x00\x00", 4); - message.AddData((void*)"\x03\x04\x00\x00", 4); - message.AddData((void*)"\x10\x01\x00\x00", 4); - message.AddData((void*)"\x12\x08\x00\x00", 4); - message.AddData((void*)"\x00\x00\x00\x00", 4); + message.AddData("\x01\x02\x0D\x00", 4); + message.AddData("\x02\x02\x00\x00", 4); + message.AddData("\x03\x04\x00\x00", 4); + message.AddData("\x10\x01\x00\x00", 4); + message.AddData("\x12\x08\x00\x00", 4); + message.AddData("\x00\x00\x00\x00", 4); break; case VirtuaStriker4: case VirtuaStriker4_2006: // 2 Player (13bit), 1 Coin slot, 4 Analog-in, 1 CARD - message.AddData((void*)"\x01\x02\x0D\x00", 4); - message.AddData((void*)"\x02\x01\x00\x00", 4); - message.AddData((void*)"\x03\x04\x00\x00", 4); - message.AddData((void*)"\x10\x01\x00\x00", 4); - message.AddData((void*)"\x00\x00\x00\x00", 4); + message.AddData("\x01\x02\x0D\x00", 4); + message.AddData("\x02\x01\x00\x00", 4); + message.AddData("\x03\x04\x00\x00", 4); + message.AddData("\x10\x01\x00\x00", 4); + message.AddData("\x00\x00\x00\x00", 4); break; case KeyOfAvalon: // 1 Player (15bit), 1 Coin slot, 3 Analog-in, Touch, 1 CARD, 1 Driver-out // (Unconfirmed) - message.AddData((void*)"\x01\x01\x0F\x00", 4); - message.AddData((void*)"\x02\x01\x00\x00", 4); - message.AddData((void*)"\x03\x03\x00\x00", 4); - message.AddData((void*)"\x06\x10\x10\x01", 4); - message.AddData((void*)"\x10\x01\x00\x00", 4); - message.AddData((void*)"\x12\x01\x00\x00", 4); - message.AddData((void*)"\x00\x00\x00\x00", 4); + message.AddData("\x01\x01\x0F\x00", 4); + message.AddData("\x02\x01\x00\x00", 4); + message.AddData("\x03\x03\x00\x00", 4); + message.AddData("\x06\x10\x10\x01", 4); + message.AddData("\x10\x01\x00\x00", 4); + message.AddData("\x12\x01\x00\x00", 4); + message.AddData("\x00\x00\x00\x00", 4); break; case MarioKartGP: case MarioKartGP2: default: // 1 Player (15bit), 1 Coin slot, 3 Analog-in, 1 CARD, 1 Driver-out - message.AddData((void*)"\x01\x01\x0F\x00", 4); - message.AddData((void*)"\x02\x01\x00\x00", 4); - message.AddData((void*)"\x03\x03\x00\x00", 4); - message.AddData((void*)"\x10\x01\x00\x00", 4); - message.AddData((void*)"\x12\x01\x00\x00", 4); - message.AddData((void*)"\x00\x00\x00\x00", 4); + message.AddData("\x01\x01\x0F\x00", 4); + message.AddData("\x02\x01\x00\x00", 4); + message.AddData("\x03\x03\x00\x00", 4); + message.AddData("\x10\x01\x00\x00", 4); + message.AddData("\x12\x01\x00\x00", 4); + message.AddData("\x00\x00\x00\x00", 4); break; } NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x14, CheckFunctionality"); @@ -1573,10 +1551,10 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) message.AddData(StatusOkay); - GCPadStatus PadStatus = Pad::GetStatus(0); + GCPadStatus pad_status = Pad::GetStatus(0); // Test button - if (PadStatus.switches & SWITCH_TEST) + if (pad_status.switches & SWITCH_TEST) { // Trying to access the test menu without SegaBoot present will cause a crash if (AMMediaboard::GetTestMenu()) @@ -1585,7 +1563,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } else { - PanicAlertFmt("Test menu is disabled due missing SegaBoot"); + PanicAlertFmt("Test menu is disabled due to missing SegaBoot"); } } else @@ -1595,10 +1573,10 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) for (u32 i = 0; i < player_count; ++i) { - u8 player_data[3] = {0, 0, 0}; + u8 player_data[3]{}; // Service button - if (PadStatus.switches & SWITCH_SERVICE) + if (pad_status.switches & SWITCH_SERVICE) player_data[0] |= 0x40; switch (AMMediaboard::GetGameType()) @@ -1613,32 +1591,32 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } // Start - if (PadStatus.button & PAD_BUTTON_START) + if (pad_status.button & PAD_BUTTON_START) player_data[0] |= 0x80; // Boost - if (PadStatus.button & PAD_BUTTON_A) + if (pad_status.button & PAD_BUTTON_A) player_data[0] |= 0x02; // View Change 1 - if (PadStatus.button & PAD_BUTTON_RIGHT) + if (pad_status.button & PAD_BUTTON_RIGHT) player_data[0] |= 0x20; // View Change 2 - if (PadStatus.button & PAD_BUTTON_LEFT) + if (pad_status.button & PAD_BUTTON_LEFT) player_data[0] |= 0x10; // View Change 3 - if (PadStatus.button & PAD_BUTTON_UP) + if (pad_status.button & PAD_BUTTON_UP) player_data[0] |= 0x08; // View Change 4 - if (PadStatus.button & PAD_BUTTON_DOWN) + if (pad_status.button & PAD_BUTTON_DOWN) player_data[0] |= 0x04; player_data[1] = m_rx_reply & 0xF0; } else if (i == 1) { // Paddle left - if (PadStatus.button & PAD_BUTTON_X) + if (pad_status.button & PAD_BUTTON_X) player_data[0] |= 0x20; // Paddle right - if (PadStatus.button & PAD_BUTTON_Y) + if (pad_status.button & PAD_BUTTON_Y) player_data[0] |= 0x10; if (m_fzdx_motion_stop) @@ -1667,22 +1645,22 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } // Start - if (PadStatus.button & PAD_BUTTON_START) + if (pad_status.button & PAD_BUTTON_START) player_data[0] |= 0x80; // Boost - if (PadStatus.button & PAD_BUTTON_A) + if (pad_status.button & PAD_BUTTON_A) player_data[0] |= 0x02; // View Change 1 - if (PadStatus.button & PAD_BUTTON_RIGHT) + if (pad_status.button & PAD_BUTTON_RIGHT) player_data[0] |= 0x20; // View Change 2 - if (PadStatus.button & PAD_BUTTON_LEFT) + if (pad_status.button & PAD_BUTTON_LEFT) player_data[0] |= 0x10; // View Change 3 - if (PadStatus.button & PAD_BUTTON_UP) + if (pad_status.button & PAD_BUTTON_UP) player_data[0] |= 0x08; // View Change 4 - if (PadStatus.button & PAD_BUTTON_DOWN) + if (pad_status.button & PAD_BUTTON_DOWN) player_data[0] |= 0x04; player_data[1] = m_rx_reply & 0xF0; @@ -1690,10 +1668,10 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) else if (i == 1) { // Paddle left - if (PadStatus.button & PAD_BUTTON_X) + if (pad_status.button & PAD_BUTTON_X) player_data[0] |= 0x20; // Paddle right - if (PadStatus.button & PAD_BUTTON_Y) + if (pad_status.button & PAD_BUTTON_Y) player_data[0] |= 0x10; if (m_fzcc_seatbelt) @@ -1712,60 +1690,60 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) break; // Controller configuration for Virtua Striker 3 games case VirtuaStriker3: - PadStatus = Pad::GetStatus(i); + pad_status = Pad::GetStatus(i); // Start - if (PadStatus.button & PAD_BUTTON_START) + if (pad_status.button & PAD_BUTTON_START) player_data[0] |= 0x80; // Shoot - if (PadStatus.button & PAD_BUTTON_B) + if (pad_status.button & PAD_BUTTON_B) player_data[0] |= 0x01; // Short Pass - if (PadStatus.button & PAD_BUTTON_A) + if (pad_status.button & PAD_BUTTON_A) player_data[1] |= 0x80; // Long Pass - if (PadStatus.button & PAD_BUTTON_X) + if (pad_status.button & PAD_BUTTON_X) player_data[0] |= 0x02; // Left - if (PadStatus.button & PAD_BUTTON_LEFT) + if (pad_status.button & PAD_BUTTON_LEFT) player_data[0] |= 0x08; // Up - if (PadStatus.button & PAD_BUTTON_UP) + if (pad_status.button & PAD_BUTTON_UP) player_data[0] |= 0x20; // Right - if (PadStatus.button & PAD_BUTTON_RIGHT) + if (pad_status.button & PAD_BUTTON_RIGHT) player_data[0] |= 0x04; // Down - if (PadStatus.button & PAD_BUTTON_DOWN) + if (pad_status.button & PAD_BUTTON_DOWN) player_data[0] |= 0x10; break; // Controller configuration for Virtua Striker 4 games case VirtuaStriker4: case VirtuaStriker4_2006: { - PadStatus = Pad::GetStatus(i); + pad_status = Pad::GetStatus(i); // Start - if (PadStatus.button & PAD_BUTTON_START) + if (pad_status.button & PAD_BUTTON_START) player_data[0] |= 0x80; // Long Pass - if (PadStatus.button & PAD_BUTTON_X) + if (pad_status.button & PAD_BUTTON_X) player_data[0] |= 0x01; // Short Pass - if (PadStatus.button & PAD_BUTTON_A) + if (pad_status.button & PAD_BUTTON_A) player_data[0] |= 0x02; // Shoot - if (PadStatus.button & PAD_BUTTON_B) + if (pad_status.button & PAD_BUTTON_B) player_data[1] |= 0x80; // Dash - if (PadStatus.button & PAD_BUTTON_Y) + if (pad_status.button & PAD_BUTTON_Y) player_data[1] |= 0x40; // Tactics (U) - if (PadStatus.button & PAD_BUTTON_LEFT) + if (pad_status.button & PAD_BUTTON_LEFT) player_data[0] |= 0x20; // Tactics (M) - if (PadStatus.button & PAD_BUTTON_UP) + if (pad_status.button & PAD_BUTTON_UP) player_data[0] |= 0x08; // Tactics (D) - if (PadStatus.button & PAD_BUTTON_RIGHT) + if (pad_status.button & PAD_BUTTON_RIGHT) player_data[0] |= 0x04; if (i == 0) @@ -1773,37 +1751,37 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) player_data[0] |= 0x10; // IC-Card Switch ON // IC-Card Lock - if (PadStatus.button & PAD_BUTTON_DOWN) + if (pad_status.button & PAD_BUTTON_DOWN) player_data[1] |= 0x20; } } break; // Controller configuration for Gekitou Pro Yakyuu case GekitouProYakyuu: - PadStatus = Pad::GetStatus(i); + pad_status = Pad::GetStatus(i); // Start - if (PadStatus.button & PAD_BUTTON_START) + if (pad_status.button & PAD_BUTTON_START) player_data[0] |= 0x80; // A - if (PadStatus.button & PAD_BUTTON_B) + if (pad_status.button & PAD_BUTTON_B) player_data[0] |= 0x01; // B - if (PadStatus.button & PAD_BUTTON_A) + if (pad_status.button & PAD_BUTTON_A) player_data[0] |= 0x02; // Gekitou - if (PadStatus.button & PAD_TRIGGER_L) + if (pad_status.button & PAD_TRIGGER_L) player_data[1] |= 0x80; // Left - if (PadStatus.button & PAD_BUTTON_LEFT) + if (pad_status.button & PAD_BUTTON_LEFT) player_data[0] |= 0x08; // Up - if (PadStatus.button & PAD_BUTTON_UP) + if (pad_status.button & PAD_BUTTON_UP) player_data[0] |= 0x20; // Right - if (PadStatus.button & PAD_BUTTON_RIGHT) + if (pad_status.button & PAD_BUTTON_RIGHT) player_data[0] |= 0x04; // Down - if (PadStatus.button & PAD_BUTTON_DOWN) + if (pad_status.button & PAD_BUTTON_DOWN) player_data[0] |= 0x10; break; // Controller configuration for Mario Kart and other games @@ -1812,29 +1790,29 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) case MarioKartGP2: { // Start - if (PadStatus.button & PAD_BUTTON_START) + if (pad_status.button & PAD_BUTTON_START) player_data[0] |= 0x80; // Item button - if (PadStatus.button & PAD_BUTTON_A) + if (pad_status.button & PAD_BUTTON_A) player_data[1] |= 0x20; // VS-Cancel button - if (PadStatus.button & PAD_BUTTON_B) + if (pad_status.button & PAD_BUTTON_B) player_data[1] |= 0x02; } break; case KeyOfAvalon: { // Debug On - if (PadStatus.button & PAD_BUTTON_START) + if (pad_status.button & PAD_BUTTON_START) player_data[0] |= 0x80; // Switch 1 - if (PadStatus.button & PAD_BUTTON_A) + if (pad_status.button & PAD_BUTTON_A) player_data[0] |= 0x04; // Switch 2 - if (PadStatus.button & PAD_BUTTON_B) + if (pad_status.button & PAD_BUTTON_B) player_data[0] |= 0x08; // Toggle inserted card - if (PadStatus.button & PAD_TRIGGER_L) + if (pad_status.button & PAD_TRIGGER_L) { m_ic_card_status ^= ICCARDStatus::NoCard; } @@ -1853,12 +1831,12 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) message.AddData(StatusOkay); for (u32 i = 0; i < slots; i++) { - GCPadStatus PadStatus = Pad::GetStatus(i); - if ((PadStatus.switches & SWITCH_COIN) && !m_coin_pressed[i]) + GCPadStatus pad_status = Pad::GetStatus(i); + if ((pad_status.switches & SWITCH_COIN) && !m_coin_pressed[i]) { m_coin[i]++; } - m_coin_pressed[i] = PadStatus.switches & SWITCH_COIN; + m_coin_pressed[i] = pad_status.switches & SWITCH_COIN; message.AddData((m_coin[i] >> 8) & 0x3f); message.AddData(m_coin[i] & 0xff); } @@ -1870,7 +1848,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) message.AddData(StatusOkay); const u32 analogs = *jvs_io++; - GCPadStatus PadStatus = Pad::GetStatus(0); + GCPadStatus pad_status = Pad::GetStatus(0); DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x22, AnalogInput: {}", analogs); @@ -1892,16 +1870,16 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } message.AddData((u8)0); - message.AddData(PadStatus.stickY); + message.AddData(pad_status.stickY); message.AddData((u8)0); } else { // The center for the Y axis is expected to be 78h this adjusts that - message.AddData(PadStatus.stickX - 12); + message.AddData(pad_status.stickX - 12); message.AddData((u8)0); - message.AddData(PadStatus.stickY); + message.AddData(pad_status.stickY); message.AddData((u8)0); } @@ -1912,11 +1890,11 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) message.AddData((u8)0); // Gas - message.AddData(PadStatus.triggerRight); + message.AddData(pad_status.triggerRight); message.AddData((u8)0); // Brake - message.AddData(PadStatus.triggerLeft); + message.AddData(pad_status.triggerLeft); message.AddData((u8)0); message.AddData((u8)0x80); // Motion Stop @@ -1929,16 +1907,16 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) case VirtuaStriker4: case VirtuaStriker4_2006: { - message.AddData(-PadStatus.stickY); + message.AddData(-pad_status.stickY); message.AddData((u8)0); - message.AddData(PadStatus.stickX); + message.AddData(pad_status.stickX); message.AddData((u8)0); - PadStatus = Pad::GetStatus(1); + pad_status = Pad::GetStatus(1); - message.AddData(-PadStatus.stickY); + message.AddData(-pad_status.stickY); message.AddData((u8)0); - message.AddData(PadStatus.stickX); + message.AddData(pad_status.stickX); message.AddData((u8)0); } break; @@ -1946,15 +1924,15 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) case MarioKartGP: case MarioKartGP2: // Steering - message.AddData(PadStatus.stickX); + message.AddData(pad_status.stickX); message.AddData((u8)0); // Gas - message.AddData(PadStatus.triggerRight); + message.AddData(pad_status.triggerRight); message.AddData((u8)0); // Brake - message.AddData(PadStatus.triggerLeft); + message.AddData(pad_status.triggerLeft); message.AddData((u8)0); break; } @@ -1964,17 +1942,17 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) { const u32 channel = *jvs_io++; - const GCPadStatus PadStatus = Pad::GetStatus(0); + const GCPadStatus pad_status = Pad::GetStatus(0); - if (PadStatus.button & PAD_TRIGGER_R) + if (pad_status.button & PAD_TRIGGER_R) { // Tap at center of screen (~320,240) - message.AddData((void*)"\x01\x00\x8C\x01\x95", + message.AddData("\x01\x00\x8C\x01\x95", 5); // X=320 (0x0140), Y=240 (0x00F0) } else { - message.AddData((void*)"\x01\xFF\xFF\xFF\xFF", 5); + message.AddData("\x01\xFF\xFF\xFF\xFF", 5); } DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x25, PositionInput:{}", @@ -2035,10 +2013,10 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x32, GPO: {:02x} {:02x} {} {:02x}{:02x}{:02x} ({:02x})", delay, m_rx_reply, bytes, buf[0], buf[1], buf[2], - Common::swap16(*reinterpret_cast(&buf[1])) >> 2); + Common::swap16(buf.data() + 1) >> 2); // Handling of the motion seat used in F-Zero AXs DX version - switch (Common::swap16(*reinterpret_cast(&buf[1])) >> 2) + switch (Common::swap16(buf.data() + 1) >> 2) { case 0x70: delay++; @@ -2117,7 +2095,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) data_out[data_offset++] = gcam_command; - const u8* buf = message.m_msg; + const u8* buf = message.m_msg.data(); const u32 len = message.m_ptr; data_out[data_offset++] = len; diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h index 2473e4280d..a6b1c5f5a2 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h @@ -19,12 +19,11 @@ namespace SerialInterface class JVSIOMessage { public: - u32 m_ptr; - u32 m_last_start; - u32 m_csum; - u8 m_msg[0x80]; + u32 m_ptr = 0; + u32 m_last_start = 0; + u32 m_csum = 0; + std::array m_msg; - JVSIOMessage(); void Start(int node); void AddData(const u8* dst, std::size_t len, int sync); void AddData(const void* data, std::size_t len); diff --git a/Source/Core/DiscIO/VolumeDisc.cpp b/Source/Core/DiscIO/VolumeDisc.cpp index a915df1224..12e92eb0f8 100644 --- a/Source/Core/DiscIO/VolumeDisc.cpp +++ b/Source/Core/DiscIO/VolumeDisc.cpp @@ -28,9 +28,9 @@ std::string VolumeDisc::GetGameID(const Partition& partition) const // Construct game ID from the BTID id[0] = 'G'; - memcpy(id + 1, boot_id->gameId + 2, 2); + memcpy(id + 1, boot_id->game_id.data() + 2, 2); - switch (boot_id->regionFlags) + switch (boot_id->region_flags) { default: case 0x02: // JAPAN @@ -83,7 +83,7 @@ Country VolumeDisc::GetCountry(const Partition& partition) const { const BootID* boot_id = static_cast(this)->GetTriforceBootID(); - switch (boot_id->regionFlags) + switch (boot_id->region_flags) { default: case 0x02: // JAPAN @@ -166,7 +166,7 @@ std::string VolumeDisc::GetInternalName(const Partition& partition) const if (GetVolumeType() == Platform::Triforce) { const BootID* boot_id = static_cast(this)->GetTriforceBootID(); - return DecodeString(boot_id->gameName); + return DecodeString(boot_id->game_name); } if (!Read(0x20, sizeof(name), reinterpret_cast(&name), partition)) diff --git a/Source/Core/DiscIO/VolumeDisc.h b/Source/Core/DiscIO/VolumeDisc.h index f263c40c16..f630522e68 100644 --- a/Source/Core/DiscIO/VolumeDisc.h +++ b/Source/Core/DiscIO/VolumeDisc.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include @@ -18,25 +19,25 @@ public: struct BootID { u32 magic; // 0x00 (Always BTID) - uint32_t unknown0[3]; - uint32_t unknown1[4]; - char mediaboardType[4]; // 0x20 (GCAM for Triforce) + std::array unknown0; + std::array unknown1; + std::array mediaboard_type; // 0x20 (GCAM for Triforce) uint32_t unknown2; - uint16_t year; // 0x28 - uint8_t month; // 0x2A - uint8_t day; // 0x2B - uint8_t videoMode; // 0x2C unknown bitmask, resolutions + horizontal/vertical + uint16_t year; // 0x28 + uint8_t month; // 0x2A + uint8_t day; // 0x2B + uint8_t video_mode; // 0x2C unknown bitmask, resolutions + horizontal/vertical uint8_t unknown3; - uint8_t type3Compatible; // 0x2E (Type-3 compatible titles have this set to 1) + uint8_t type_3_compatible; // 0x2E (Type-3 compatible titles have this set to 1) uint8_t unknown4; - char gameId[8]; // 0x30 - uint32_t regionFlags; // 0x38 - uint32_t unknown6[9]; - char manufacturer[0x20]; // 0x60 - char gameName[0x20]; // 0x80 - char gameExecutable[0x20]; // 0xA0 - char testExecutable[0x20]; // 0xC0 - char creditTypes[8][0x20]; // 0xE0 + std::array game_id; // 0x30 + uint32_t region_flags; // 0x38 + std::array unknown6; + std::array manufacturer; // 0x60 + std::array game_name; // 0x80 + std::array game_executable; // 0xA0 + std::array test_executable; // 0xC0 + std::array, 8> credit_types; // 0xE0 }; std::string GetGameID(const Partition& partition = PARTITION_NONE) const override; @@ -53,4 +54,6 @@ protected: void AddGamePartitionToSyncHash(Common::SHA1::Context* context) const; }; +static_assert(sizeof(VolumeDisc::BootID) == 480); + } // namespace DiscIO diff --git a/Source/Core/DiscIO/VolumeGC.cpp b/Source/Core/DiscIO/VolumeGC.cpp index 52dacd59a4..5d3d372dcf 100644 --- a/Source/Core/DiscIO/VolumeGC.cpp +++ b/Source/Core/DiscIO/VolumeGC.cpp @@ -92,7 +92,7 @@ Region VolumeGC::GetRegion() const { if (m_is_triforce) { - switch (m_triforce_header.regionFlags) + switch (m_triforce_header.region_flags) { default: case 0x02: // JAPAN From ce802f1e62d5448cc802d85885fabc46ce643550 Mon Sep 17 00:00:00 2001 From: crediar Date: Thu, 6 Nov 2025 23:44:17 +0100 Subject: [PATCH 04/71] Triforce: Optimized code Removed unneeded code Removed unused code Added EINPROGRESS to fix networking on Linux systems Added function NetworkBufferCheck and NetworkCMDBufferCheck to verify buffer sizes Added NetworkCMDBufferRead/NetworkCMDBufferWrite/NetworkBufferRead/NetworkBufferWrite Added multiply buffer size checks Rewrote GetGameType() Moved AMMediaboard::Shutdown to DVDInterface::Shutdown() Changed JVSIOMessage overrun check Changed SwapBuffers function to be a member function Added sanity checks for buffer sizes Added translation prefix for Triforce buttons Updated text for misconfigurations when trying to boot normal games with Triforce hardware connected Fixed NAMCAM for Mario Kart GP --- Source/Core/Core/Core.cpp | 7 - Source/Core/Core/HW/DVD/AMMediaboard.cpp | 641 ++++++++++-------- Source/Core/Core/HW/DVD/AMMediaboard.h | 12 +- Source/Core/Core/HW/DVD/DVDInterface.cpp | 5 + .../Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp | 1 - Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.h | 2 +- Source/Core/Core/HW/GCPadEmu.h | 6 +- .../Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp | 101 ++- Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h | 146 ++-- Source/Core/DolphinQt/MainWindow.cpp | 27 +- 10 files changed, 512 insertions(+), 436 deletions(-) diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index 0ddf5b11d1..67ad22351c 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -289,13 +289,6 @@ void Stop(Core::System& system) // - Hammertime! // Stop the CPU INFO_LOG_FMT(CONSOLE, "{}", StopMessage(true, "Stop CPU")); system.GetCPU().Stop(); - - const ExpansionInterface::EXIDeviceType type = Config::Get(Config::MAIN_SERIAL_PORT_1); - - if ((type == ExpansionInterface::EXIDeviceType::Baseboard)) - { - AMMediaboard::Shutdown(); - } } void DeclareAsCPUThread() diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 416625b609..be7ed82572 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -4,7 +4,9 @@ #include "Core/HW/DVD/AMMediaboard.h" #include +#include #include +#include #include @@ -49,12 +51,13 @@ static constexpr int SOCKET_ERROR = -1; using SOCKET = int; -static constexpr auto INVALID_SOCKET = SOCKET(~0); +static constexpr SOCKET INVALID_SOCKET = SOCKET(~0); static int WSAGetLastError() { switch (errno) { + case EINPROGRESS: case EWOULDBLOCK: return WSAEWOULDBLOCK; default: @@ -71,7 +74,7 @@ namespace AMMediaboard static bool s_firmware_map = false; static bool s_test_menu = false; -static SOCKET s_namco_cam = 0; +static SOCKET s_fd_namco_cam = 0; static std::array s_timeouts = {20000, 20000, 20000}; static u32 s_last_error = SSC_SUCCESS; @@ -87,6 +90,9 @@ static File::IOFile s_dimm; static Common::UniqueBuffer s_dimm_disc; +auto& s_system = Core::System::GetInstance(); +auto& s_memory = s_system.GetMemory(); + static u8 s_firmware[2 * 1024 * 1024]; static u32 s_media_buffer_32[192]; static u8* const s_media_buffer = reinterpret_cast(s_media_buffer_32); @@ -106,15 +112,69 @@ constexpr char s_allnet_reply[] = { static SOCKET s_sockets[64]; -static constexpr u32 SocketCheck(u32 x) +static u32 SocketCheck(u32 x) { if (x < std::size(s_sockets)) return x; - WARN_LOG_FMT(AMMEDIABOARD, "Bad SOCKET value: {}", x); + WARN_LOG_FMT(AMMEDIABOARD, "GC-AM: Bad SOCKET value: {}", x); return 0; } +static bool NetworkCMDBufferCheck(u32 x) +{ + if (x < std::size(s_network_command_buffer)) + return true; + + ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: Invalid command network buffer offset:{}", x); + return false; +} + +static bool NetworkBufferCheck(u32 x) +{ + if (x < std::size(s_network_buffer)) + return true; + + ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: Invalid network buffer offset:{}", x); + return false; +} + +static void NetworkCMDBufferRead(u32 offset, u32 address, u32 length) +{ + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read NETWORK COMMAND BUFFER ({:08x},{})", offset, length); + if (NetworkCMDBufferCheck(offset)) + { + s_memory.CopyToEmu(address, s_network_command_buffer + offset, length); + } +} + +static void NetworkBufferRead(u32 offset, u32 address, u32 length) +{ + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read NETWORK BUFFER ({:08x},{})", offset, length); + if (NetworkBufferCheck(offset)) + { + s_memory.CopyToEmu(address, s_network_buffer + offset, length); + } +} + +static void NetworkBufferWrite(u32 offset, u32 address, u32 length) +{ + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write NETWORK BUFFER ({:08x},{})", offset, length); + if (NetworkBufferCheck(offset)) + { + s_memory.CopyFromEmu(s_network_buffer + offset, address, length); + } +} + +static void NetworkCMDBufferWrite(u32 offset, u32 address, u32 length) +{ + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write NETWORK COMMAND BUFFER ({:08x},{})", offset, length); + if (NetworkCMDBufferCheck(offset)) + { + s_memory.CopyFromEmu(s_network_command_buffer + offset, address, length); + } +} + static SOCKET socket_(int af, int type, int protocol) { for (u32 i = 1; i < 64; ++i) @@ -149,14 +209,11 @@ static SOCKET accept_(int fd, sockaddr* addr, socklen_t* len) static inline void PrintMBBuffer(u32 address, u32 length) { - const auto& system = Core::System::GetInstance(); - const auto& memory = system.GetMemory(); - for (u32 i = 0; i < length; i += 0x10) { - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: {:08x} {:08x} {:08x} {:08x}", memory.Read_U32(address + i), - memory.Read_U32(address + i + 4), memory.Read_U32(address + i + 8), - memory.Read_U32(address + i + 12)); + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: {:08x} {:08x} {:08x} {:08x}", s_memory.Read_U32(address + i), + s_memory.Read_U32(address + i + 4), s_memory.Read_U32(address + i + 8), + s_memory.Read_U32(address + i + 12)); } } @@ -253,7 +310,7 @@ u8* InitDIMM(u32 size) s_dimm_disc.reset(size); if (s_dimm_disc.empty()) { - PanicAlertFmt("Failed to allocate DIMM memory."); + PanicAlertFmt("Failed to allocate DIMM s_memory."); return nullptr; } } @@ -324,7 +381,7 @@ static s32 NetDIMMConnect(int fd, sockaddr_in* addr, int len) // BUG: An invalid family value is being used addr->sin_family = htons(AF_INET); - s_namco_cam = fd; + s_fd_namco_cam = fd; } // Key of Avalon Client @@ -408,9 +465,6 @@ static void FileWriteData(File::IOFile* file, u32 seek_pos, const u8* data, std: u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u32 length) { - auto& system = Core::System::GetInstance(); - auto& memory = system.GetMemory(); - dicmd_buf[0] ^= s_gcam_key_a; dicmd_buf[1] ^= s_gcam_key_b; dicmd_buf[2] ^= s_gcam_key_c; @@ -427,12 +481,12 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u if (s_gcam_key_a == 0) { - if (memory.Read_U32(0)) + if (s_memory.Read_U32(0)) { - HLE::Patch(system, 0x813048B8, "OSReport"); - HLE::Patch(system, 0x8130095C, "OSReport"); // Apploader + HLE::Patch(s_system, 0x813048B8, "OSReport"); + HLE::Patch(s_system, 0x8130095C, "OSReport"); // Apploader - InitKeys(memory.Read_U32(0), memory.Read_U32(4), memory.Read_U32(8)); + InitKeys(s_memory.Read_U32(0), s_memory.Read_U32(4), s_memory.Read_U32(8)); } } @@ -447,10 +501,10 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u if (offset == 0x0002440) { // Set by OSResetSystem - if (memory.Read_U32(0x811FFF00) == 1) + if (s_memory.Read_U32(0x811FFF00) == 1) { // Don't map firmware while in SegaBoot - if (memory.Read_U32(0x8006BF70) != 0x0A536567) + if (s_memory.Read_U32(0x8006BF70) != 0x0A536567) { s_firmware_map = true; } @@ -486,35 +540,35 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u switch (offset) { case MediaBoardStatus1: - memory.Write_U16(Common::swap16(0x0100), address); + s_memory.Write_U16(1, address); break; case MediaBoardStatus2: - memset(memory.GetSpanForAddress(address).data(), 0, length); + s_memory.Memset(address, 0, length); break; case MediaBoardStatus3: - memset(memory.GetSpanForAddress(address).data(), 0xFF, length); + s_memory.Memset(address, 0xFF, length); // DIMM size (512MB) - memory.Write_U32(Common::swap32(0x20000000), address); + s_memory.Write_U32_Swap(0x20000000, address); // GCAM signature - memory.Write_U32(0x4743414D, address + 4); + s_memory.Write_U32(0x4743414D, address + 4); break; case 0x80000100: - memory.Write_U32(Common::swap32((u32)0x001F1F1F), address); + s_memory.Write_U32_Swap(0x001F1F1F, address); break; case FirmwareStatus1: - memory.Write_U32(Common::swap32((u32)0x01FA), address); + s_memory.Write_U32_Swap(0x01FA, address); break; case FirmwareStatus2: - memory.Write_U32(Common::swap32((u32)1), address); + s_memory.Write_U32_Swap(1, address); break; case 0x80000160: - memory.Write_U32(0x00001E00, address); + s_memory.Write_U32(0x00001E00, address); break; case 0x80000180: - memory.Write_U32(0, address); + s_memory.Write_U32(0, address); break; case 0x800001A0: - memory.Write_U32(0xFFFFFFFF, address); + s_memory.Write_U32(0xFFFFFFFF, address); break; default: PrintMBBuffer(address, length); @@ -528,7 +582,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u if (offset == 0x00000000 && length == 0x80) { s_netcfg.Seek(0, File::SeekOrigin::Begin); - s_netcfg.ReadBytes(memory.GetSpanForAddress(address).data(), length); + s_netcfg.ReadBytes(s_memory.GetSpanForAddress(address).data(), length); return 0; } @@ -536,16 +590,16 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u if (offset == DIMMExtraSettings && length == 0x20) { s_extra.Seek(0, File::SeekOrigin::Begin); - s_extra.ReadBytes(memory.GetSpanForAddress(address).data(), length); + s_extra.ReadBytes(s_memory.GetSpanForAddress(address).data(), length); return 0; } - // DIMM memory (8MB) - if (offset >= DIMMMemory && offset <= 0x1F800000) + // DIMM s_memory (8MB) + if (offset >= DIMMMemory && offset < 0x1F800000) { const u32 dimm_offset = offset - DIMMMemory; s_dimm.Seek(dimm_offset, File::SeekOrigin::Begin); - s_dimm.ReadBytes(memory.GetSpanForAddress(address).data(), length); + s_dimm.ReadBytes(s_memory.GetSpanForAddress(address).data(), length); return 0; } @@ -554,14 +608,15 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read All.Net Buffer ({:08x},{})", offset, length); // Fake reply - memcpy(memory.GetSpanForAddress(address).data(), s_allnet_reply, sizeof(s_allnet_reply)); + s_memory.CopyToEmu(address, s_allnet_reply, sizeof(s_allnet_reply)); return 0; } - if (offset >= DIMMCommandVersion1 && offset < 0x1F900040) + if (offset >= DIMMCommandVersion1 && offset < 0x1F900040 && length == 0x20) { const u32 dimm_offset = offset - DIMMCommandVersion1; - memcpy(memory.GetSpanForAddress(address).data(), s_media_buffer + dimm_offset, length); + + s_memory.CopyToEmu(address, s_media_buffer + dimm_offset, length); INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read MEDIA BOARD COMM AREA (1) ({:08x},{})", offset, length); @@ -569,73 +624,65 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u return 0; } + if (offset == DIMMCommandVersion2 || offset == DIMMCommandVersion2_1 && length == 0x20) + { + const u32 dimm_offset = offset - DIMMCommandVersion2; + s_memory.CopyToEmu(address, s_media_buffer + dimm_offset, length); + + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read MEDIA BOARD COMM AREA (2) ({:08x},{})", offset, + length); + PrintMBBuffer(address, length); + return 0; + } + + if (offset == DIMMCommandVersion2_2 || offset == DIMMCommandVersion2_2_1 && length == 0x20) + { + const u32 dimm_offset = offset - DIMMCommandVersion2_2; + s_memory.CopyToEmu(address, s_media_buffer + dimm_offset, length); + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read MEDIA BOARD COMM AREA (3) ({:08x})", dimm_offset); + PrintMBBuffer(address, length); + return 0; + } + if (offset >= NetworkBufferAddress4 && offset < 0x89240000) { - const u32 dimm_offset = offset - NetworkBufferAddress4; - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read NETWORK BUFFER (4) ({:08x},{})", offset, length); - memcpy(memory.GetSpanForAddress(address).data(), s_network_buffer + dimm_offset, length); + NetworkBufferRead(offset - NetworkBufferAddress4, address, length); return 0; } if (offset >= NetworkBufferAddress5 && offset < 0x1FB10000) { - const u32 dimm_offset = offset - NetworkBufferAddress5; - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read NETWORK BUFFER (5) ({:08x},{})", offset, length); - memcpy(memory.GetSpanForAddress(address).data(), s_network_buffer + dimm_offset, length); + NetworkBufferRead(offset - NetworkBufferAddress5, address, length); return 0; } - if (offset >= NetworkCommandAddress && offset < NetworkBufferAddress2) + if (offset >= NetworkCommandAddress1 && offset < NetworkBufferAddress2) { - const u32 dimm_offset = offset - NetworkCommandAddress; - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read NETWORK COMMAND BUFFER ({:08x},{})", offset, length); - memcpy(memory.GetSpanForAddress(address).data(), s_network_command_buffer + dimm_offset, - length); + NetworkCMDBufferRead(offset - NetworkCommandAddress1, address, length); return 0; } if (offset >= NetworkCommandAddress2 && offset < 0x89060200) { - const u32 dimm_offset = offset - NetworkCommandAddress2; - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read NETWORK COMMAND BUFFER (2) ({:08x},{})", offset, - length); - memcpy(memory.GetSpanForAddress(address).data(), s_network_command_buffer + dimm_offset, - length); + NetworkCMDBufferRead(offset - NetworkCommandAddress2, address, length); return 0; } if (offset >= NetworkBufferAddress1 && offset < 0x1FA10000) { - const u32 dimm_offset = offset - NetworkBufferAddress1; - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read NETWORK BUFFER (1) ({:08x},{})", offset, length); - memcpy(memory.GetSpanForAddress(address).data(), s_network_buffer + dimm_offset, length); + NetworkBufferRead(offset - NetworkBufferAddress1, address, length); return 0; } if (offset >= NetworkBufferAddress2 && offset < 0x1FD10000) { - const u32 dimm_offset = offset - NetworkBufferAddress2; - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read NETWORK BUFFER (2) ({:08x},{})", offset, length); - memcpy(memory.GetSpanForAddress(address).data(), s_network_buffer + dimm_offset, length); + NetworkBufferRead(offset - NetworkBufferAddress2, address, length); return 0; } if (offset >= NetworkBufferAddress3 && offset < 0x89110000) { - const u32 dimm_offset = offset - NetworkBufferAddress3; - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read NETWORK BUFFER (3) ({:08x},{})", offset, length); - memcpy(memory.GetSpanForAddress(address).data(), s_network_buffer + dimm_offset, length); - return 0; - } - - if (offset >= DIMMCommandVersion2 && offset < 0x84000060) - { - const u32 dimm_offset = offset - DIMMCommandVersion2; - memcpy(memory.GetSpanForAddress(address).data(), s_media_buffer + dimm_offset, length); - - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read MEDIA BOARD COMM AREA (2) ({:08x},{})", offset, - length); - PrintMBBuffer(address, length); + NetworkBufferRead(offset - NetworkBufferAddress3, address, length); return 0; } @@ -672,7 +719,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; case AMMBCommand::Accept: { - const auto fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; int ret = -1; sockaddr addr; socklen_t len = 0; @@ -687,6 +734,11 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u const u32 addr_off = s_media_buffer_32[3] - NetworkCommandAddress2; const u32 len_off = s_media_buffer_32[4] - NetworkCommandAddress2; + if (!NetworkCMDBufferCheck(addr_off) || !NetworkCMDBufferCheck(len_off)) + { + break; + } + ret = NetDIMMAccept(fd, &addr, &len); if (len) { @@ -702,10 +754,15 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u } case AMMBCommand::Bind: { - const auto fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; const u32 off = s_media_buffer_32[3] - NetworkCommandAddress2; const u32 len = s_media_buffer_32[4]; + if (!NetworkCMDBufferCheck(off)) + { + break; + } + sockaddr_in addr; memcpy(&addr, s_network_command_buffer + off, sizeof(sockaddr_in)); @@ -734,7 +791,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u } case AMMBCommand::Closesocket: { - const auto fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; const int ret = closesocket(fd); @@ -749,10 +806,15 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u } case AMMBCommand::Connect: { - const auto fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; const u32 off = s_media_buffer_32[3] - NetworkCommandAddress2; const u32 len = s_media_buffer_32[4]; + if (!NetworkCMDBufferCheck(off)) + { + break; + } + sockaddr_in addr; memcpy(&addr, s_network_command_buffer + off, sizeof(sockaddr_in)); @@ -768,10 +830,18 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u } case AMMBCommand::InetAddr: { - const u32 ip = inet_addr(reinterpret_cast(s_network_command_buffer)); - // TODO: This expects null termination? - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: InetAddr( {} )\n", - reinterpret_cast(s_network_command_buffer)); + const char* ip_address = reinterpret_cast(s_network_command_buffer); + + // IP address shouldn't be longer than 15 + if (strnlen(ip_address, 15) > 15) + { + ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: Invalid size for address: InetAddr():{}\n", + strlen(ip_address)); + break; + } + + const u32 ip = inet_addr(ip_address); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: InetAddr( {} )\n", ip_address); s_media_buffer[1] = s_media_buffer[8]; s_media_buffer_32[1] = Common::swap32(ip); @@ -779,7 +849,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u } case AMMBCommand::Listen: { - const auto fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; const u32 backlog = s_media_buffer_32[3]; const int ret = listen(fd, backlog); @@ -792,7 +862,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u } case AMMBCommand::Recv: { - const auto fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; const u32 off = s_media_buffer_32[3]; const auto len = std::min(s_media_buffer_32[4], sizeof(s_network_buffer)); @@ -804,7 +874,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u } else { - PanicAlertFmt("RECV: Buffer overrun:{0} {1} ", off, len); + ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: recv(error) unhandled destination:{:08x}\n", off); + buffer = s_network_buffer; } const int ret = recv(fd, reinterpret_cast(buffer), len, 0); @@ -819,7 +890,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u } case AMMBCommand::Send: { - const auto fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; u32 off = s_media_buffer_32[3]; const u32 len = s_media_buffer_32[4]; @@ -830,6 +901,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u else { ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: send(error) unhandled destination:{:08x}\n", off); + off = 0; } const int ret = send(fd, reinterpret_cast(s_network_buffer + off), len, 0); @@ -858,16 +930,17 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u } case AMMBCommand::Select: { - auto fd = s_sockets[SocketCheck(s_media_buffer_32[2] - 1)]; + SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2] - 1)]; // BUG: NAMCAM is hardcoded to call this with socket ID 0x100 which might be some magic // thing? A valid value is needed so we use the socket from the connect. - if (AMMediaboard::GetGameType() == MarioKartGP2) + if (AMMediaboard::GetGameType() == MarioKartGP || + AMMediaboard::GetGameType() == MarioKartGP2) { if (s_media_buffer_32[2] == 256) { - fd = s_namco_cam; + fd = s_fd_namco_cam; } } @@ -885,10 +958,15 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u // Only one of 3, 4, 5 is ever set alongside 6 if (s_media_buffer_32[6] != 0) { - const u32 fd_offset = s_media_buffer_32[6] - NetworkCommandAddress2; + const u32 fd_set_offset = s_media_buffer_32[6] - NetworkCommandAddress2; + if (!NetworkCMDBufferCheck(fd_set_offset + sizeof(fd_set))) + { + ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: Select(error) unhandled destination:{:08x}\n", + s_media_buffer_32[6]); + break; + } - // TODO: Check that fd_offset is in bounds of of s_network_command_buffer. - Common::BitCastPtr(s_network_command_buffer + fd_offset) = fds; + Common::BitCastPtr(s_network_command_buffer + fd_set_offset) = fds; if (s_media_buffer_32[3] != 0) { @@ -938,12 +1016,18 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u } case AMMBCommand::SetSockOpt: { - const auto fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; const int level = (int)(s_media_buffer_32[3]); const int optname = (int)(s_media_buffer_32[4]); + const int optlen = (int)(s_media_buffer_32[6]); + + if (!NetworkCMDBufferCheck(s_media_buffer_32[5] - NetworkCommandAddress2 + optlen)) + { + break; + } + const char* optval = reinterpret_cast(s_network_command_buffer + s_media_buffer_32[5] - NetworkCommandAddress2); - const int optlen = (int)(s_media_buffer_32[6]); const int ret = setsockopt(fd, level, optname, optval, optlen); @@ -959,7 +1043,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u } case AMMBCommand::SetTimeOuts: { - const auto fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; const u32 timeout_a = s_media_buffer_32[3]; const u32 timeout_b = s_media_buffer_32[4]; const u32 timeout_c = s_media_buffer_32[5]; @@ -968,7 +1052,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u s_timeouts[1] = timeout_b; s_timeouts[2] = timeout_c; - int ret = 0; + int ret = SOCKET_ERROR; if (fd != INVALID_SOCKET) { @@ -985,10 +1069,15 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u if (ret < 0) ret = WSAGetLastError(); } - } - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: SetTimeOuts( {}, {}, {}, {} ):{}\n", fd, timeout_a, - timeout_b, timeout_c, ret); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: SetTimeOuts( {:d}, {}, {}, {} ):{}\n", fd, + timeout_a, timeout_b, timeout_c, ret); + } + else + { + ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: Invalid Socket: SetTimeOuts( {}, {}, {} ):{}\n", + timeout_a, timeout_b, timeout_c, ret); + } s_media_buffer[1] = s_media_buffer[8]; s_media_buffer_32[1] = ret; @@ -1008,16 +1097,21 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u { const u32 net_buffer_offset = s_media_buffer_32[2] - NetworkCommandAddress2; - // TODO: Not checked for null termination. + if (!NetworkCMDBufferCheck(net_buffer_offset)) + { + break; + } + const char* ip_address = reinterpret_cast(s_network_command_buffer + net_buffer_offset); - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: modifyMyIPaddr({})\n", ip_address); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: modifyMyIPaddr({})\n", + fmt::string_view(ip_address, 15)); break; } case AMMBCommand::GetLastError: { - const auto fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: GetLastError( {}({}) ):{}\n", fd, s_media_buffer_32[2], s_last_error); @@ -1040,42 +1134,35 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u s_media_buffer[3] |= 0x80; // Command complete flag - memset(memory.GetSpanForAddress(address).data(), 0, length); + s_memory.Memset(address, 0, length); ExpansionInterface::GenerateInterrupt(0x10); return 0; } - if (offset >= DIMMCommandVersion2_2 && offset <= 0x89000200) - { - const u32 dimm_offset = offset - DIMMCommandVersion2_2; - memcpy(memory.GetSpanForAddress(address).data(), s_media_buffer + dimm_offset, length); - - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read MEDIA BOARD COMM AREA (3) ({:08x})", dimm_offset); - PrintMBBuffer(address, length); - return 0; - } - - // DIMM memory (8MB) - if (offset >= DIMMMemory2 && offset <= 0xFF800000) + // DIMM s_memory (8MB) + if (offset >= DIMMMemory2 && offset < 0xFF800000) { const u32 dimm_offset = offset - DIMMMemory2; s_dimm.Seek(dimm_offset, File::SeekOrigin::Begin); - s_dimm.ReadBytes(memory.GetSpanForAddress(address).data(), length); + s_dimm.ReadBytes(s_memory.GetSpanForAddress(address).data(), length); return 0; } - if (offset >= AllNetSettings && offset <= 0x1F000000) + if (offset >= AllNetSettings && offset < 0x1F000000) { const u32 allnet_offset = offset - AllNetSettings; - memcpy(memory.GetSpanForAddress(address).data(), s_allnet_settings + allnet_offset, length); + if (allnet_offset + length < sizeof(s_allnet_settings)) + { + s_memory.CopyToEmu(address, s_allnet_settings + allnet_offset, length); + } return 0; } if (offset == NetworkControl && length == 0x20) { s_netctrl.Seek(0, File::SeekOrigin::Begin); - s_netctrl.ReadBytes(memory.GetSpanForAddress(address).data(), length); + s_netctrl.ReadBytes(s_memory.GetSpanForAddress(address).data(), length); return 0; } @@ -1088,13 +1175,13 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u if (s_firmware_map) { - memcpy(memory.GetSpanForAddress(address).data(), s_firmware + offset, length); + s_memory.CopyToEmu(address, s_firmware + offset, length); return 0; } if (s_dimm_disc.size()) { - memcpy(memory.GetSpanForAddress(address).data(), s_dimm_disc.data() + offset, length); + s_memory.CopyToEmu(address, s_dimm_disc.data() + offset, length); return 0; } @@ -1103,13 +1190,13 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u case AMMBDICommand::Write: // These two magic writes allow a new firmware to be programmed - if ((offset == FirmwareMagicWrite1) && (length == 0x20)) + if (offset == FirmwareMagicWrite1 && length == 0x20) { s_firmware_map = true; return 0; } - if ((offset == FirmwareMagicWrite2) && (length == 0x20)) + if (offset == FirmwareMagicWrite2 && length == 0x20) { s_firmware_map = true; return 0; @@ -1117,134 +1204,109 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u if (s_firmware_map) { - // Firmware memory (2MB) + // Firmware s_memory (2MB) if ((offset >= 0x00400000) && (offset <= 0x600000)) { const u32 fwoffset = offset - 0x00400000; - memcpy(s_firmware + fwoffset, memory.GetSpanForAddress(address).data(), length); + s_memory.CopyFromEmu(s_firmware + fwoffset, address, length); return 0; } } // Network configuration - if ((offset == 0x00000000) && (length == 0x80)) + if (offset == 0x00000000 && length == 0x80) { - FileWriteData(&s_netcfg, 0, memory.GetSpanForAddress(address).data(), length); + FileWriteData(&s_netcfg, 0, s_memory.GetSpanForAddress(address).data(), length); return 0; } // media crc check on/off - if ((offset == DIMMExtraSettings) && (length == 0x20)) + if (offset == DIMMExtraSettings && length == 0x20) { - FileWriteData(&s_extra, 0, memory.GetSpanForAddress(address).data(), length); + FileWriteData(&s_extra, 0, s_memory.GetSpanForAddress(address).data(), length); return 0; } - // Backup memory (8MB) - if ((offset >= BackupMemory) && (offset <= 0x00800000)) + // Backup s_memory (8MB) + if (offset >= BackupMemory && offset < 0x00800000) { - FileWriteData(&s_backup, 0, memory.GetSpanForAddress(address).data(), length); + FileWriteData(&s_backup, 0, s_memory.GetSpanForAddress(address).data(), length); return 0; } - // DIMM memory (8MB) - if ((offset >= DIMMMemory) && (offset <= 0x1F800000)) + // DIMM s_memory (8MB) + if (offset >= DIMMMemory && offset < 0x1F800000) { const u32 dimm_offset = offset - DIMMMemory; - FileWriteData(&s_dimm, dimm_offset, memory.GetSpanForAddress(address).data(), length); + FileWriteData(&s_dimm, dimm_offset, s_memory.GetSpanForAddress(address).data(), length); return 0; } - if ((offset >= AllNetBuffer) && (offset <= 0x89011000)) + if (offset >= AllNetBuffer && offset < 0x89011000) { const u32 allnet_offset = offset - AllNetBuffer; - memcpy(s_allnet_buffer + allnet_offset, memory.GetSpanForAddress(address).data(), length); + if (allnet_offset + length < sizeof(s_allnet_settings)) + { + s_memory.CopyFromEmu(s_allnet_buffer + allnet_offset, address, length); + } return 0; } - if ((offset >= NetworkCommandAddress) && (offset < 0x1F801240)) + if (offset >= NetworkCommandAddress1 && offset < 0x1F801240) { - const u32 dimm_offset = offset - NetworkCommandAddress; - - memcpy(s_network_command_buffer + dimm_offset, memory.GetSpanForAddress(address).data(), - length); - - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write NETWORK COMMAND BUFFER ({:08x},{})", dimm_offset, - length); - PrintMBBuffer(address, length); + NetworkCMDBufferWrite(offset - NetworkCommandAddress1, address, length); return 0; } - if ((offset >= NetworkCommandAddress2) && (offset <= 0x890601FF)) + if (offset >= NetworkCommandAddress2 && offset < 0x89060200 && length <= 0x60) { - const u32 dimm_offset = offset - NetworkCommandAddress2; - - memcpy(s_network_command_buffer + dimm_offset, memory.GetSpanForAddress(address).data(), - length); - - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write NETWORK COMMAND BUFFER (2) ({:08x},{})", dimm_offset, - length); - PrintMBBuffer(address, length); + NetworkCMDBufferWrite(offset - NetworkCommandAddress2, address, length); return 0; } - if ((offset >= NetworkBufferAddress1) && (offset <= 0x1FA1FFFF)) + if (offset >= NetworkBufferAddress1 && offset < 0x1FA20000) { - const u32 dimm_offset = offset - NetworkBufferAddress1; - - memcpy(s_network_buffer + dimm_offset, memory.GetSpanForAddress(address).data(), length); - - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write NETWORK BUFFER (1) ({:08x},{})", dimm_offset, - length); - PrintMBBuffer(address, length); + NetworkBufferWrite(offset - NetworkBufferAddress1, address, length); return 0; } - if ((offset >= NetworkBufferAddress2) && (offset <= 0x1FD0FFFF)) + if (offset >= NetworkBufferAddress2 && offset < 0x1FD10000) { - const u32 dimm_offset = offset - NetworkBufferAddress2; - - memcpy(s_network_buffer + dimm_offset, memory.GetSpanForAddress(address).data(), length); - - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write NETWORK BUFFER (2) ({:08x},{})", dimm_offset, - length); - PrintMBBuffer(address, length); + NetworkBufferWrite(offset - NetworkBufferAddress2, address, length); return 0; } - if ((offset >= NetworkBufferAddress3) && (offset <= 0x8910FFFF)) + if (offset >= NetworkBufferAddress3 && offset < 0x89110000) { - const u32 dimm_offset = offset - NetworkBufferAddress3; - - memcpy(s_network_buffer + dimm_offset, memory.GetSpanForAddress(address).data(), length); - - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write NETWORK BUFFER (3) ({:08x},{})", dimm_offset, - length); - PrintMBBuffer(address, length); + NetworkBufferWrite(offset - NetworkBufferAddress3, address, length); return 0; } - if ((offset >= DIMMCommandVersion1) && (offset <= 0x1F90003F)) + if (offset >= DIMMCommandVersion1 && offset < 0x1F900040 && length == 0x20) { const u32 dimm_offset = offset - DIMMCommandVersion1; - memcpy(s_media_buffer + dimm_offset, memory.GetSpanForAddress(address).data(), length); + s_memory.CopyFromEmu(s_media_buffer + dimm_offset, address, length); INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write MEDIA BOARD COMM AREA (1) ({:08x},{})", offset, length); PrintMBBuffer(address, length); return 0; } - if ((offset >= DIMMCommandVersion2) && (offset <= 0x8400005F)) + if (offset == DIMMCommandVersion2 || offset == DIMMCommandVersion2_1 && length == 0x20) { const u32 dimm_offset = offset - DIMMCommandVersion2; + + s_memory.CopyFromEmu(s_media_buffer + dimm_offset, address, length); INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write MEDIA BOARD COMM AREA (2) ({:08x},{})", offset, length); PrintMBBuffer(address, length); + return 0; + } - const u8 cmd_flag = memory.Read_U8(address); - - if (dimm_offset == 0x40 && cmd_flag == 1) + if (offset == DIMMCommandExecute1 && length == 0x20) + { + if (s_memory.Read_U8(address) == 1) { const AMMBCommand ammb_command = Common::BitCastPtr(s_media_buffer + 0x22); @@ -1303,41 +1365,40 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u ExpansionInterface::GenerateInterrupt(0x04); return 0; } - - memcpy(s_media_buffer + dimm_offset, memory.GetSpanForAddress(address).data(), length); return 0; } - if ((offset >= DIMMCommandVersion2_2) && (offset <= 0x89000200)) + if (offset == DIMMCommandVersion2_2 || offset == DIMMCommandVersion2_2_1 && length == 0x20) { const u32 dimm_offset = offset - DIMMCommandVersion2_2; - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write MEDIA BOARD COMM AREA (3) ({:08x})", dimm_offset); + + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write MEDIA BOARD COMM AREA (3) ({:08x},{})", offset, + length); PrintMBBuffer(address, length); - memcpy(s_media_buffer + dimm_offset, memory.GetSpanForAddress(address).data(), length); + s_memory.CopyFromEmu(s_media_buffer + dimm_offset, address, length); return 0; } // Firmware Write - if ((offset >= FirmwareAddress) && (offset <= 0x84818000)) + if (offset >= FirmwareAddress && offset < 0x84818000) { - const u32 dimm_offset = offset - FirmwareAddress; - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write Firmware ({:08x})", dimm_offset); + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write Firmware ({:08x})", offset); PrintMBBuffer(address, length); return 0; } - // DIMM memory (8MB) - if ((offset >= DIMMMemory2) && (offset <= 0xFF800000)) + // DIMM s_memory (8MB) + if (offset >= DIMMMemory2 && offset < 0xFF800000) { const u32 dimm_offset = offset - DIMMMemory2; - FileWriteData(&s_dimm, dimm_offset, memory.GetSpanForAddress(address).data(), length); + FileWriteData(&s_dimm, dimm_offset, s_memory.GetSpanForAddress(address).data(), length); return 0; } - if ((offset == NetworkControl) && (length == 0x20)) + if (offset == NetworkControl && length == 0x20) { - FileWriteData(&s_netctrl, 0, memory.GetSpanForAddress(address).data(), length); + FileWriteData(&s_netctrl, 0, s_memory.GetSpanForAddress(address).data(), length); return 0; } @@ -1419,23 +1480,20 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u // 0x01: Media board // 0x04: Network - // DEBUG_LOG_FMT(AMMEDIABOARD, "GC-AM: 0x301: ({:08x})", s_media_buffer_32[9] ) - // ); + DEBUG_LOG_FMT(AMMEDIABOARD, "GC-AM: TestHardware: ({:08x})", s_media_buffer_32[11]); + // Pointer to a s_memory address that is directly displayed on screen as a string + DEBUG_LOG_FMT(AMMEDIABOARD, "GC-AM: ({:08x})", s_media_buffer_32[12]); - // Pointer to a memory address that is directly displayed on screen as a string - // DEBUG_LOG_FMT(AMMEDIABOARD, "GC-AM: ({:08x})", s_media_buffer_32[10]) - // ); - - // On real system it shows the status about the DIMM/GD-ROM here + // On real s_system it shows the status about the DIMM/GD-ROM here // We just show "TEST OK" - memory.Write_U32(0x54534554, s_media_buffer_32[12]); - memory.Write_U32(0x004B4F20, s_media_buffer_32[12] + 4); + s_memory.Write_U32(0x54534554, s_media_buffer_32[12]); + s_memory.Write_U32(0x004B4F20, s_media_buffer_32[12] + 4); s_media_buffer_32[1] = s_media_buffer_32[9]; break; case AMMBCommand::Closesocket: { - const auto fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; + const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; const int ret = closesocket(fd); @@ -1450,10 +1508,15 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; case AMMBCommand::Connect: { - const auto fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; - const u32 off = s_media_buffer_32[11] - NetworkCommandAddress; + const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; + const u32 off = s_media_buffer_32[11] - NetworkCommandAddress1; const u32 len = s_media_buffer_32[12]; + if (NetworkCMDBufferCheck(off + sizeof(sockaddr_in))) + { + break; + } + sockaddr_in addr; memcpy(&addr, s_network_command_buffer + off, sizeof(sockaddr_in)); @@ -1469,7 +1532,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; case AMMBCommand::Recv: { - const auto fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; + const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; const u32 off = s_media_buffer_32[11]; const auto len = std::min(s_media_buffer_32[12], sizeof(s_network_buffer)); @@ -1481,7 +1544,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u } else { - PanicAlertFmt("RECV: Buffer overrun:{0} {1} ", off, len); + ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: recv(error) unhandled destination:{:08x}\n", off); + buffer = s_network_buffer; } const int ret = recv(fd, reinterpret_cast(buffer), len, 0); @@ -1496,7 +1560,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; case AMMBCommand::Send: { - const auto fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; + const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; u32 off = s_media_buffer_32[11]; const u32 len = s_media_buffer_32[12]; @@ -1507,6 +1571,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u else { ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: send(error) unhandled destination:{:08x}\n", off); + off = 0; } const int ret = send(fd, reinterpret_cast(s_network_buffer + off), len, 0); @@ -1527,7 +1592,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u const SOCKET fd = socket_(af, type, IPPROTO_TCP); - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: socket( {}, {}, 6 ):{}\n", af, type, fd); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: socket( {}, {}, IPPROTO_TCP ):{}\n", af, type, fd); s_media_buffer[1] = 0; s_media_buffer_32[1] = fd; @@ -1535,7 +1600,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; case AMMBCommand::Select: { - const auto fd = s_sockets[SocketCheck(s_media_buffer_32[10] - 1)]; + const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[10] - 1)]; fd_set* readfds = nullptr; fd_set* writefds = nullptr; @@ -1551,25 +1616,30 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u // Only one of 11, 12, 13 is ever set alongside 14 if (s_media_buffer_32[14] != 0) { - const u32 fd_offset = s_media_buffer_32[14] - NetworkCommandAddress; + const u32 fd_set_offset = s_media_buffer_32[14] - NetworkCommandAddress1; + if (!NetworkCMDBufferCheck(fd_set_offset + sizeof(fd_set))) + { + ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: Select(error) unhandled destination:{:08x}\n", + s_media_buffer_32[14]); + break; + } - // TODO: Check that fd_offset is in bounds of of s_network_command_buffer. - Common::BitCastPtr(s_network_command_buffer + fd_offset) = fds; + Common::BitCastPtr(s_network_command_buffer + fd_set_offset) = fds; if (s_media_buffer_32[11] != 0) { readfds = &fds; - timeout_src = s_network_command_buffer + s_media_buffer_32[11] - NetworkCommandAddress; + timeout_src = s_network_command_buffer + s_media_buffer_32[11] - NetworkCommandAddress1; } else if (s_media_buffer_32[12] != 0) { writefds = &fds; - timeout_src = s_network_command_buffer + s_media_buffer_32[12] - NetworkCommandAddress; + timeout_src = s_network_command_buffer + s_media_buffer_32[12] - NetworkCommandAddress1; } else if (s_media_buffer_32[13] != 0) { exceptfds = &fds; - timeout_src = s_network_command_buffer + s_media_buffer_32[13] - NetworkCommandAddress; + timeout_src = s_network_command_buffer + s_media_buffer_32[13] - NetworkCommandAddress1; } } @@ -1603,13 +1673,19 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; case AMMBCommand::SetSockOpt: { - const auto fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; + const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; const int level = (int)(s_media_buffer_32[11]); const int optname = (int)(s_media_buffer_32[12]); - const char* optval = reinterpret_cast(s_network_command_buffer + - s_media_buffer_32[13] - NetworkCommandAddress); const int optlen = (int)(s_media_buffer_32[14]); + if (!NetworkCMDBufferCheck(s_media_buffer_32[13] - NetworkCommandAddress1 + optlen)) + { + break; + } + + const char* optval = reinterpret_cast( + s_network_command_buffer + s_media_buffer_32[13] - NetworkCommandAddress1); + const int ret = setsockopt(fd, level, optname, optval, optlen); const int err = WSAGetLastError(); @@ -1623,18 +1699,23 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; case AMMBCommand::ModifyMyIPaddr: { - const u32 net_buffer_offset = s_media_buffer_32[10] - NetworkCommandAddress; + const u32 net_buffer_offset = s_media_buffer_32[10] - NetworkCommandAddress1; + + if (!NetworkCMDBufferCheck(net_buffer_offset)) + { + break; + } - // TODO: Not checked for null termination. const char* ip_address = reinterpret_cast(s_network_command_buffer + net_buffer_offset); - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: modifyMyIPaddr({})\n", ip_address); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: modifyMyIPaddr({})\n", + fmt::string_view(ip_address, 15)); } break; // Empty reply case AMMBCommand::InitLink: - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: 0x601"); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: InitLink"); break; case AMMBCommand::Unknown_605: NOTICE_LOG_FMT(AMMEDIABOARD, "GC-AM: 0x605"); @@ -1679,6 +1760,11 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: Offset: ({:04x})", off); NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: ({:08x})", addr); + if (!NetworkBufferCheck(off + addr - NetworkBufferAddress2)) + { + break; + } + const u8* data = s_network_buffer + off + addr - NetworkBufferAddress2; for (u32 i = 0; i < 0x20; i += 0x10) @@ -1704,7 +1790,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: 0x614"); break; default: - ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: execute buffer UNKNOWN:{0:04x}", + ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: Execute buffer UNKNOWN:{0:04x}", static_cast(ammb_command)); break; } @@ -1747,74 +1833,53 @@ u32 GetMediaType() // Never reached } +// This is checking for the real game IDs (See boot.id within the game) u32 GetGameType() { - u64 game_id = 0; + u16 triforce_id = 0; + const std::string& game_id = SConfig::GetInstance().GetGameID(); - if (SConfig::GetInstance().GetGameID().length() > 6) + if (game_id.length() > 6) { - game_id = 0x32323232; + triforce_id = 0x3232; } else { - // Convert game ID into hex - sscanf(SConfig::GetInstance().GetGameID().c_str(), "%s", reinterpret_cast(&game_id)); + triforce_id = game_id[1] << 8 | game_id[2]; } - // This is checking for the real game IDs (See boot.id within the game) - switch ((Common::swap32((u32)game_id) >> 8) & 0xFFFF) + static const std::unordered_map game_map = {{0x4747, FZeroAX}, + {0x4841, FZeroAXMonster}, + {0x4B50, MarioKartGP}, + {0x4B5A, MarioKartGP}, + {0x4E4A, MarioKartGP2}, + {0x4E4C, MarioKartGP2}, + {0x454A, VirtuaStriker3}, + {0x4559, VirtuaStriker3}, + {0x4C4A, VirtuaStriker4_2006}, + {0x4C4B, VirtuaStriker4_2006}, + {0x4C4C, VirtuaStriker4_2006}, + {0x484A, VirtuaStriker4}, + {0x484E, VirtuaStriker4}, + {0x485A, VirtuaStriker4}, + {0x4A41, VirtuaStriker4}, + {0x4A4A, VirtuaStriker4}, + {0x4658, KeyOfAvalon}, + {0x4A4E, KeyOfAvalon}, + {0x4758, GekitouProYakyuu}, + {0x5342, VirtuaStriker3}, + {0x3132, VirtuaStriker3}, + {0x454C, VirtuaStriker3}, + {0x3030, FirmwareUpdate}}; + + auto it = game_map.find(triforce_id); + if (it != game_map.end()) { - // SBGG - F-ZERO AX - case 0x4747: - return FZeroAX; - // SBHA - F-ZERO AX (Monster) - case 0x4841: - return FZeroAXMonster; - // SBKJ/SBKP - MARIOKART ARCADE GP - case 0x4B50: - case 0x4B5A: - return MarioKartGP; - // SBNJ/SBNL - MARIOKART ARCADE GP2 - case 0x4E4A: - case 0x4E4C: - return MarioKartGP2; - // SBEJ/SBEY - Virtua Striker 2002 - case 0x454A: - case 0x4559: - return VirtuaStriker3; - // SBLJ/SBLK/SBLL - VIRTUA STRIKER 4 Ver.2006 - case 0x4C4A: - case 0x4C4B: - case 0x4C4C: - return VirtuaStriker4_2006; - // SBHJ/SBHN/SBHZ - VIRTUA STRIKER 4 VER.A - case 0x484A: - case 0x484E: - case 0x485A: - // SBJA/SBJJ - VIRTUA STRIKER 4 - case 0x4A41: - case 0x4A4A: - return VirtuaStriker4; - // SBFX/SBJN - Key of Avalon - case 0x4658: - case 0x4A4E: - return KeyOfAvalon; - // SBGX - Gekitou Pro Yakyuu (DIMM Upgrade 3.17) - case 0x4758: - return GekitouProYakyuu; - default: - PanicAlertFmtT("Unknown game ID:{0:08x}, using default controls.", game_id); - // GSBJ/G12U - VIRTUA STRIKER 3 - // RELS/RELJ - SegaBoot (does not have a boot.id) - case 0x5342: - case 0x3132: - case 0x454C: - return VirtuaStriker3; - // S000 - Firmware update - case 0x3030: - return FirmwareUpdate; + return it->second; } - // Never reached + + PanicAlertFmtT("Unknown game ID:{0:08x}, using default controls.", triforce_id); + return VirtuaStriker3; // Fallback } bool GetTestMenu() diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.h b/Source/Core/Core/HW/DVD/AMMediaboard.h index aab8888a89..4bc24dc6a6 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.h +++ b/Source/Core/Core/HW/DVD/AMMediaboard.h @@ -52,6 +52,12 @@ enum InquiryType Version2 = 0x29484100, }; +namespace Core +{ +class CPUThreadGuard; +class System; +} // namespace Core + namespace AMMediaboard { @@ -148,10 +154,13 @@ enum MediaBoardAddress : u32 DIMMCommandVersion1 = 0x1F900000, DIMMCommandVersion2 = 0x84000000, + DIMMCommandVersion2_1 = 0x84000020, DIMMCommandVersion2_2 = 0x89000000, + DIMMCommandVersion2_2_1 = 0x89000200, + DIMMCommandExecute1 = 0x84000040, DIMMCommandExecute2 = 0x88000000, - NetworkCommandAddress = 0x1F800200, + NetworkCommandAddress1 = 0x1F800200, NetworkCommandAddress2 = 0x89040200, NetworkBufferAddress1 = 0x1FA00000, @@ -217,4 +226,5 @@ u32 GetGameType(); u32 GetMediaType(); bool GetTestMenu(); void Shutdown(); + }; // namespace AMMediaboard diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index e88a61084b..768870f185 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -341,6 +341,11 @@ void DVDInterface::ResetDrive(bool spinup) void DVDInterface::Shutdown() { m_system.GetDVDThread().Stop(); + + if (m_system.IsTriforce()) + { + AMMediaboard::Shutdown(); + } } static u64 GetDiscEndOffset(const DiscIO::VolumeDisc& disc) diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp index 6731c156f5..bc46afe65c 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp @@ -105,7 +105,6 @@ CEXIBaseboard::CEXIBaseboard(Core::System& system) : IEXIDevice(system) CEXIBaseboard::~CEXIBaseboard() { - m_backup.Close(); } void CEXIBaseboard::SetCS(int cs) diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.h b/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.h index 8fa9d449a1..be929026e3 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.h @@ -54,6 +54,6 @@ private: File::IOFile m_backup; protected: - void TransferByte(u8& _uByte) override; + void TransferByte(u8& byte) override; }; } // namespace ExpansionInterface diff --git a/Source/Core/Core/HW/GCPadEmu.h b/Source/Core/Core/HW/GCPadEmu.h index 6e244d139f..906f7750f3 100644 --- a/Source/Core/Core/HW/GCPadEmu.h +++ b/Source/Core/Core/HW/GCPadEmu.h @@ -72,9 +72,9 @@ public: static constexpr const char* START_BUTTON = "Start"; // Special Triforce buttons - static constexpr const char* TEST_BUTTON = "Test"; - static constexpr const char* SERVICE_BUTTON = "Service"; - static constexpr const char* COIN_BUTTON = "Coin"; + static constexpr const char* TEST_BUTTON = _trans("Test"); + static constexpr const char* SERVICE_BUTTON = _trans("Service"); + static constexpr const char* COIN_BUTTON = _trans("Coin"); // i18n: The left trigger button (labeled L on real controllers) static constexpr const char* L_DIGITAL = _trans("L"); diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp index 0d9c99beb1..22392442ac 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp @@ -52,6 +52,12 @@ void JVSIOMessage::Start(int node) void JVSIOMessage::AddData(const u8* dst, std::size_t len, int sync = 0) { + if (m_ptr + len >= 0x80) + { + PanicAlertFmt("JVSIOMessage overrun!"); + return; + } + while (len--) { const u8 c = *dst++; @@ -68,8 +74,6 @@ void JVSIOMessage::AddData(const u8* dst, std::size_t len, int sync = 0) if (!sync) m_csum += c; sync = 0; - if (m_ptr >= 0x80) - PanicAlertFmt("JVSIOMessage overrun!"); } } @@ -96,33 +100,18 @@ void JVSIOMessage::End() AddData(m_csum + len - 2); } -static u8 CheckSumXOR(u8* Data, u32 Length) +static u8 CheckSumXOR(u8* data, u32 length) { u8 check = 0; - for (u32 i = 0; i < Length; i++) + for (u32 i = 0; i < length; i++) { - check ^= Data[i]; + check ^= data[i]; } return check; } -// Reply has to be delayed due a bug in the parser -static void swap_buffers(u8* buffer, u32* buffer_length) -{ - static u8 last[2][0x80]; - static u32 lastptr[2]; - - memcpy(last[1], buffer, 0x80); // Save current buffer - memcpy(buffer, last[0], 0x80); // Load previous buffer - memcpy(last[0], last[1], 0x80); // Update history - - lastptr[1] = *buffer_length; // Swap lengths - *buffer_length = lastptr[0]; - lastptr[0] = lastptr[1]; -} - static const char s_cdr_program_version[] = {" Version 1.22,2003/09/19,171-8213B"}; static const char s_cdr_boot_version[] = {" Version 1.04,2003/06/17,171-8213B"}; static const u8 s_cdr_card_data[] = { @@ -236,6 +225,17 @@ void CSIDevice_AMBaseboard::ICCardSendReply(ICCommand* iccommand, u8* buffer, u3 buffer[(*length)++] = crc; } +void CSIDevice_AMBaseboard::SwapBuffers(u8* buffer, u32* buffer_length) +{ + memcpy(m_last[1], buffer, 0x80); // Save current buffer + memcpy(buffer, m_last[0], 0x80); // Load previous buffer + memcpy(m_last[0], m_last[1], 0x80); // Update history + + m_lastptr[1] = *buffer_length; // Swap lengths + *buffer_length = m_lastptr[0]; + m_lastptr[0] = m_lastptr[1]; +} + int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) { const auto& serial_interface = m_system.GetSerialInterface(); @@ -526,15 +526,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) { const u32 size = data_in[1]; - char logptr[1024]; - char* log = logptr; - - for (u32 i = 0; i < (u32)(data_in[1] + 2); ++i) - { - log += sprintf(log, "%02X ", data_in[i]); - } - - INFO_LOG_FMT(SERIALINTERFACE_CARD, "Command: {}", logptr); + DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "Command: {}", HexDump(data_in, size + 2)); INFO_LOG_FMT( SERIALINTERFACE_CARD, @@ -624,7 +616,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) case ICCARDCommand::ReadPage: case ICCARDCommand::ReadUseCount: { - const u16 page = Common::swap16(data_in + 6); + const u16 page = Common::swap16(data_in + 6) & 0xFF; // 255 is max page icco.extlen = 8; icco.length += icco.extlen; @@ -638,7 +630,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } case ICCARDCommand::WritePage: { - const u16 page = Common::swap16(data_in + 8); + const u16 page = Common::swap16(data_in + 8) & 0xFF; // 255 is max page // Write only one page if (page == 4) @@ -656,7 +648,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } case ICCARDCommand::DecreaseUseCount: { - const u16 page = Common::swap16(data_in + 6); + const u16 page = Common::swap16(data_in + 6) & 0xFF; // 255 is max page icco.extlen = 2; icco.length += icco.extlen; @@ -675,7 +667,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } case ICCARDCommand::ReadPages: { - const u16 page = Common::swap16(data_in + 6); + const u16 page = Common::swap16(data_in + 6) & 0xFF; // 255 is max page const u16 count = Common::swap16(data_in + 8); const u32 offs = page * 8; @@ -701,7 +693,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) { const u16 pksize = length; const u16 size = Common::swap16(data_in + 2); - const u16 page = Common::swap16(data_in + 6); + const u16 page = Common::swap16(data_in + 6) & 0xFF; // 255 is max page const u16 count = Common::swap16(data_in + 8); // We got a complete packet @@ -713,7 +705,17 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } else { - memcpy(m_ic_card_data + page * 8, data_in + 13, count * 8); + if (page * 8 + count * 8 > sizeof(m_ic_card_data)) + { + ERROR_LOG_FMT( + SERIALINTERFACE_CARD, + "GC-AM: Command 0x31 (IC-CARD) Data overflow: Pages:{} Count:{}({:x})", + page, count, size); + } + else + { + memcpy(m_ic_card_data + page * 8, data_in + 13, count * 8); + } } INFO_LOG_FMT(SERIALINTERFACE_CARD, @@ -877,14 +879,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31 (SERIAL) Command:{:06x}", serial_command); - if (/*command == 0xf43200 || */ serial_command == 0x801000) + if (serial_command == 0x801000) { - // u32 PC = m_system.GetPowerPC().GetPPCState().pc; - - // INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GCAM: PC:{:08x}", PC); - - // m_system.GetPowerPC().GetBreakPoints().Add(PC + 8, true, true, std::nullopt); - data_out[data_offset++] = 0x31; data_out[data_offset++] = 0x02; data_out[data_offset++] = 0xFF; @@ -1025,7 +1021,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) const u32 command_length_offset = data_offset; data_out[data_offset++] = 0x00; // len - data_out[data_offset++] = 0x02; // + data_out[data_offset++] = 0x02; const u32 checksum_start = data_offset; data_out[data_offset++] = 0x00; // 0x00 len @@ -1382,12 +1378,16 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) static int delay = 0; const u8* frame = &data_in[0]; - const u8 nr_bytes = frame[3]; // Byte after E0 xx - const u32 frame_len = nr_bytes + 3; // Header(2) + length byte + payload + checksum - - // TODO: frame_len isn't checked for buffer overflow. + const u8 nr_bytes = frame[3]; // Byte after E0 xx + u32 frame_len = nr_bytes + 3; // Header(2) + length byte + payload + checksum u8 jvs_buf[0x80]; + + if (frame_len > sizeof(jvs_buf)) + { + frame_len = sizeof(jvs_buf); + } + memcpy(jvs_buf, frame, frame_len); // Extract node and payload pointers @@ -2123,18 +2123,15 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) data_in = buffer; data_out[1] = data_offset - 2; checksum = 0; - char logptr[1024]; - char* log = logptr; for (int i = 0; i < 0x7F; ++i) { checksum += data_in[i] = data_out[i]; - log += sprintf(log, "%02X", data_in[i]); } data_in[0x7f] = ~checksum; - DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "Command send back: {}", logptr); + DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "Command send back: {}", HexDump(data_out.data(), 0x7F)); - swap_buffers(buffer, &buffer_length); + SwapBuffers(buffer, &buffer_length); buffer_position = buffer_length; break; diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h index a6b1c5f5a2..c199426dd9 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h @@ -19,22 +19,90 @@ namespace SerialInterface class JVSIOMessage { public: - u32 m_ptr = 0; - u32 m_last_start = 0; - u32 m_csum = 0; - std::array m_msg; - void Start(int node); void AddData(const u8* dst, std::size_t len, int sync); void AddData(const void* data, std::size_t len); void AddData(const char* data); void AddData(u32 n); void End(); + + u32 m_ptr = 0; + std::array m_msg; + +private: + u32 m_last_start = 0; + u32 m_csum = 0; }; // Triforce (GC-AM) baseboard class CSIDevice_AMBaseboard : public ISIDevice { + enum EButtonCombo + { + COMBO_NONE = 0, + COMBO_ORIGIN, + COMBO_RESET + }; + +public: + // constructor + CSIDevice_AMBaseboard(Core::System& system, SIDevices device, int device_number); + + // run the SI Buffer + int RunBuffer(u8* buffer, int request_length) override; + + // Reply has to be delayed due a bug in the parser + void SwapBuffers(u8* buffer, u32* buffer_length); + + // return true on new data + DataResponse GetData(u32& hi, u32& low) override; + + // send a command directly + void SendCommand(u32 command, u8 poll) override; + + virtual GCPadStatus GetPadStatus(); + virtual u32 MapPadStatus(const GCPadStatus& pad_status); + virtual EButtonCombo HandleButtonCombos(const GCPadStatus& pad_status); + + static void HandleMoviePadStatus(Movie::MovieManager& movie, int device_number, + GCPadStatus* pad_status); + + // Send and Receive pad input from network + static bool NetPlay_GetInput(int pad_num, GCPadStatus* status); + static int NetPlay_InGamePadToLocalPad(int pad_num); + +protected: + struct SOrigin + { + u16 button; + u8 origin_stick_x; + u8 origin_stick_y; + u8 substick_x; + u8 substick_y; + u8 trigger_left; + u8 trigger_right; + u8 unk_4; + u8 unk_5; + }; + + // struct to compare input against + // Set on connection to perfect neutral values + // (standard pad only) Set on button combo to current input state + SOrigin m_origin = {}; + + // PADAnalogMode + // Dunno if we need to do this, game/lib should set it? + u8 m_mode = 0x3; + + // Timer to track special button combos: + // y, X, start for 3 seconds updates origin with current status + // Technically, the above is only on standard pad, wavebird does not support it for example + // b, x, start for 3 seconds triggers reset (PI reset button interrupt) + u64 m_timer_button_combo_start = 0; + // Type of button combo from the last/current poll + EButtonCombo m_last_button_combo = COMBO_NONE; + void SetOrigin(const GCPadStatus& pad_status); + private: enum BaseBoardCommand { @@ -190,6 +258,9 @@ private: }; }; + u8 m_last[2][0x80]; + u32 m_lastptr[2]; + u16 m_coin[2]; u32 m_coin_pressed[2]; @@ -238,71 +309,6 @@ private: bool m_fzcc_service; void ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length); - -protected: - struct SOrigin - { - u16 button; - u8 origin_stick_x; - u8 origin_stick_y; - u8 substick_x; - u8 substick_y; - u8 trigger_left; - u8 trigger_right; - u8 unk_4; - u8 unk_5; - }; - - enum EButtonCombo - { - COMBO_NONE = 0, - COMBO_ORIGIN, - COMBO_RESET - }; - - // struct to compare input against - // Set on connection to perfect neutral values - // (standard pad only) Set on button combo to current input state - SOrigin m_origin = {}; - - // PADAnalogMode - // Dunno if we need to do this, game/lib should set it? - u8 m_mode = 0x3; - - // Timer to track special button combos: - // y, X, start for 3 seconds updates origin with current status - // Technically, the above is only on standard pad, wavebird does not support it for example - // b, x, start for 3 seconds triggers reset (PI reset button interrupt) - u64 m_timer_button_combo_start = 0; - // Type of button combo from the last/current poll - EButtonCombo m_last_button_combo = COMBO_NONE; - -public: - // constructor - CSIDevice_AMBaseboard(Core::System& system, SIDevices device, int device_number); - - // run the SI Buffer - int RunBuffer(u8* buffer, int request_length) override; - - // return true on new data - DataResponse GetData(u32& hi, u32& low) override; - - // send a command directly - void SendCommand(u32 command, u8 poll) override; - - virtual GCPadStatus GetPadStatus(); - virtual u32 MapPadStatus(const GCPadStatus& pad_status); - virtual EButtonCombo HandleButtonCombos(const GCPadStatus& pad_status); - - static void HandleMoviePadStatus(Movie::MovieManager& movie, int device_number, - GCPadStatus* pad_status); - - // Send and Receive pad input from network - static bool NetPlay_GetInput(int pad_num, GCPadStatus* status); - static int NetPlay_InGamePadToLocalPad(int pad_num); - -protected: - void SetOrigin(const GCPadStatus& pad_status); }; } // namespace SerialInterface diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index 090b085954..b14885b947 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -516,8 +516,6 @@ void MainWindow::CreateComponents() }); connect(m_breakpoint_widget, &BreakpointWidget::ShowMemory, m_memory_widget, &MemoryWidget::SetAddress); - connect(m_cheats_manager, &CheatsManager::ShowMemory, m_memory_widget, &MemoryWidget::SetAddress); - connect(m_cheats_manager, &CheatsManager::RequestWatch, request_watch); } void MainWindow::ConnectMenuBar() @@ -1159,9 +1157,7 @@ void MainWindow::StartGame(std::unique_ptr&& parameters) return; } - /* - When booting Triforce games, we need to ensure that the hardware is set up correctly. - */ + // When booting Triforce games, we need to ensure that the hardware is set up correctly. const auto volume_type = std::get(parameters->parameters).volume->GetVolumeType(); @@ -1184,15 +1180,20 @@ void MainWindow::StartGame(std::unique_ptr&& parameters) } else { - /* - Some Triforce tools don't include a boot.id file, but they can still be launched. - */ - if (triforce_hardware_sp1 || triforce_hardware_port_1) + // Some Triforce tools don't include a boot.id file, but they can still be launched. + if (triforce_hardware_sp1) { - ModalMessageBox::warning( - this, tr("Warning"), - tr("Non-Triforce games cannot be booted with Triforce hardware attached."), - QMessageBox::Ok); + ModalMessageBox::warning(this, tr("Warning"), + tr("Non-Triforce games cannot be booted with Triforce hardware " + "attached.\nPlease remove the Triforce Baseboard from SP1."), + QMessageBox::Ok); + } + if (triforce_hardware_port_1) + { + ModalMessageBox::warning(this, tr("Warning"), + tr("Non-Triforce games cannot be booted with Triforce hardware " + "attached.\nPlease remove the Triforce Baseboard from Port 1."), + QMessageBox::Ok); } } } From 56ed7817de87736dc62d3c6725eb0779a9ee0c2e Mon Sep 17 00:00:00 2001 From: crediar Date: Wed, 12 Nov 2025 19:11:21 +0100 Subject: [PATCH 05/71] Triforce: Code fixes. Fixed a bug caused by static usage of Core::System::GetInstance() Removed unused headers Removed unneeded code Optimised code Added sanity checks Added SafeCopyFromEmu/SafeCopyToEmu Set Triforce buttons to be translatable --- Source/Core/Core/Core.cpp | 1 - Source/Core/Core/HW/DVD/AMMediaboard.cpp | 640 ++++++++---------- Source/Core/Core/HW/DVD/AMMediaboard.h | 11 +- .../Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp | 9 - Source/Core/Core/HW/GCPadEmu.cpp | 2 +- Source/Core/Core/HW/SI/SI.h | 2 +- .../Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp | 45 +- Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h | 6 +- Source/Core/DiscIO/VolumeDisc.cpp | 34 +- 9 files changed, 349 insertions(+), 401 deletions(-) diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index 67ad22351c..bdae112865 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -51,7 +51,6 @@ #include "Core/HLE/HLE.h" #include "Core/HW/CPU.h" #include "Core/HW/DSP.h" -#include "Core/HW/DVD/AMMediaboard.h" #include "Core/HW/EXI/EXI.h" #include "Core/HW/GBAPad.h" #include "Core/HW/GCKeyboard.h" diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index be7ed82572..d94f86e404 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -90,9 +90,6 @@ static File::IOFile s_dimm; static Common::UniqueBuffer s_dimm_disc; -auto& s_system = Core::System::GetInstance(); -auto& s_memory = s_system.GetMemory(); - static u8 s_firmware[2 * 1024 * 1024]; static u32 s_media_buffer_32[192]; static u8* const s_media_buffer = reinterpret_cast(s_media_buffer_32); @@ -107,6 +104,56 @@ constexpr char s_allnet_reply[] = { "second=12&place_id=1234&setting=0x123®ion0=jap®ion_name0=japan®ion_name1=usa®ion_" "name2=asia®ion_name3=export&end"}; +static const MediaBoardRanges s_mediaboard_ranges[] = { + {DIMMCommandVersion1, 0x1F900040, s_media_buffer, sizeof(s_media_buffer_32), + DIMMCommandVersion1}, + {DIMMCommandVersion2, 0x84000060, s_media_buffer, sizeof(s_media_buffer_32), + DIMMCommandVersion2}, + {DIMMCommandVersion2_2, 0x89000220, s_media_buffer, sizeof(s_media_buffer_32), + DIMMCommandVersion2_2}, + {NetworkCommandAddress1, NetworkBufferAddress2, s_network_command_buffer, + sizeof(s_network_command_buffer), NetworkCommandAddress1}, + {NetworkCommandAddress2, 0x89060200, s_network_command_buffer, sizeof(s_network_command_buffer), + NetworkCommandAddress2}, + {NetworkBufferAddress1, 0x1FA10000, s_network_buffer, sizeof(s_network_buffer), + NetworkBufferAddress1}, + {NetworkBufferAddress2, 0x1FD10000, s_network_buffer, sizeof(s_network_buffer), + NetworkBufferAddress2}, + {NetworkBufferAddress3, 0x89110000, s_network_buffer, sizeof(s_network_buffer), + NetworkBufferAddress3}, + {NetworkBufferAddress4, 0x89240000, s_network_buffer, sizeof(s_network_buffer), + NetworkBufferAddress4}, + {NetworkBufferAddress5, 0x1FB10000, s_network_buffer, sizeof(s_network_buffer), + NetworkBufferAddress5}, + {AllNetSettings, 0x1F000000, s_allnet_settings, sizeof(s_allnet_settings), AllNetSettings}, + {AllNetBuffer, 0x89011000, s_allnet_buffer, sizeof(s_allnet_buffer), AllNetBuffer}, +}; + +static const std::unordered_map s_game_map = { + {0x4747, FZeroAX}, + {0x4841, FZeroAXMonster}, + {0x4B50, MarioKartGP}, + {0x4B5A, MarioKartGP}, + {0x4E4A, MarioKartGP2}, + {0x4E4C, MarioKartGP2}, + {0x454A, VirtuaStriker3}, + {0x4559, VirtuaStriker3}, + {0x4C4A, VirtuaStriker4_2006}, + {0x4C4B, VirtuaStriker4_2006}, + {0x4C4C, VirtuaStriker4_2006}, + {0x484A, VirtuaStriker4}, + {0x484E, VirtuaStriker4}, + {0x485A, VirtuaStriker4}, + {0x4A41, VirtuaStriker4}, + {0x4A4A, VirtuaStriker4}, + {0x4658, KeyOfAvalon}, + {0x4A4E, KeyOfAvalon}, + {0x4758, GekitouProYakyuu}, + {0x5342, VirtuaStriker3}, + {0x3132, VirtuaStriker3}, + {0x454C, VirtuaStriker3}, + {0x3030, FirmwareUpdate}, +}; // Sockets FDs are required to go from 0 to 63. // Games use the FD as indexes so we have to workaround it. @@ -121,58 +168,75 @@ static u32 SocketCheck(u32 x) return 0; } -static bool NetworkCMDBufferCheck(u32 x) +static bool NetworkCMDBufferCheck(u32 offset, u32 length) { - if (x < std::size(s_network_command_buffer)) + if (offset <= std::size(s_network_command_buffer) && + length <= std::size(s_network_command_buffer) - offset) + { return true; + } - ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: Invalid command network buffer offset:{}", x); + ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: Invalid command buffer range: offset={}, length={}", offset, + length); return false; } -static bool NetworkBufferCheck(u32 x) +static bool NetworkBufferCheck(u32 offset, u32 length) { - if (x < std::size(s_network_buffer)) + if (offset <= std::size(s_network_buffer) && length <= std::size(s_network_buffer) - offset) + { return true; + } - ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: Invalid network buffer offset:{}", x); + ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: Invalid network buffer range: offset={}, length={}", offset, + length); return false; } -static void NetworkCMDBufferRead(u32 offset, u32 address, u32 length) +static bool SafeCopyToEmu(Memory::MemoryManager& memory, u32 address, const u8* source, + u64 source_size, u32 offset, u32 length) { - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read NETWORK COMMAND BUFFER ({:08x},{})", offset, length); - if (NetworkCMDBufferCheck(offset)) + if (offset > source_size || length > source_size - offset) { - s_memory.CopyToEmu(address, s_network_command_buffer + offset, length); + ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: Read overflow: offset=0x{:08x}, length={}, source_size={}", + offset, length, source_size); + return false; } -} -static void NetworkBufferRead(u32 offset, u32 address, u32 length) -{ - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read NETWORK BUFFER ({:08x},{})", offset, length); - if (NetworkBufferCheck(offset)) + auto span = memory.GetSpanForAddress(address); + if (length > span.size()) { - s_memory.CopyToEmu(address, s_network_buffer + offset, length); + ERROR_LOG_FMT(AMMEDIABOARD, + "GC-AM: Memory buffer too small: address=0x{:08x}, length={}, span={}", address, + length, span.size()); + return false; } -} -static void NetworkBufferWrite(u32 offset, u32 address, u32 length) -{ - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write NETWORK BUFFER ({:08x},{})", offset, length); - if (NetworkBufferCheck(offset)) - { - s_memory.CopyFromEmu(s_network_buffer + offset, address, length); - } + memory.CopyToEmu(address, source + offset, length); + return true; } - -static void NetworkCMDBufferWrite(u32 offset, u32 address, u32 length) +static bool SafeCopyFromEmu(Memory::MemoryManager& memory, u8* destionation, u32 address, + u64 destionation_size, u32 offset, u32 length) { - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write NETWORK COMMAND BUFFER ({:08x},{})", offset, length); - if (NetworkCMDBufferCheck(offset)) + if (offset > destionation_size || length > destionation_size - offset) { - s_memory.CopyFromEmu(s_network_command_buffer + offset, address, length); + ERROR_LOG_FMT(AMMEDIABOARD, + "GC-AM: Write overflow: offset=0x{:08x}, length={}, destionation_size={}", offset, + length, destionation_size); + return false; } + + auto span = memory.GetSpanForAddress(address); + if (length > span.size()) + { + ERROR_LOG_FMT(AMMEDIABOARD, + "GC-AM: Memory buffer too small: address=0x{:08x}, length={}, span={}", address, + length, span.size()); + return false; + } + + memory.CopyFromEmu(destionation + offset, address, length); + return true; } static SOCKET socket_(int af, int type, int protocol) @@ -209,11 +273,14 @@ static SOCKET accept_(int fd, sockaddr* addr, socklen_t* len) static inline void PrintMBBuffer(u32 address, u32 length) { + const auto& system = Core::System::GetInstance(); + auto& memory = system.GetMemory(); + for (u32 i = 0; i < length; i += 0x10) { - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: {:08x} {:08x} {:08x} {:08x}", s_memory.Read_U32(address + i), - s_memory.Read_U32(address + i + 4), s_memory.Read_U32(address + i + 8), - s_memory.Read_U32(address + i + 12)); + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: {:08x} {:08x} {:08x} {:08x}", memory.Read_U32(address + i), + memory.Read_U32(address + i + 4), memory.Read_U32(address + i + 8), + memory.Read_U32(address + i + 12)); } } @@ -310,7 +377,7 @@ u8* InitDIMM(u32 size) s_dimm_disc.reset(size); if (s_dimm_disc.empty()) { - PanicAlertFmt("Failed to allocate DIMM s_memory."); + PanicAlertFmt("Failed to allocate DIMM memory."); return nullptr; } } @@ -456,15 +523,43 @@ static s32 NetDIMMConnect(int fd, sockaddr_in* addr, int len) return ret; } -static void FileWriteData(File::IOFile* file, u32 seek_pos, const u8* data, std::size_t length) +static void FileWriteData(Memory::MemoryManager& memory, File::IOFile* file, u32 seek_pos, + u32 address, std::size_t length) { - file->Seek(seek_pos, File::SeekOrigin::Begin); - file->WriteBytes(data, length); - file->Flush(); + auto span = memory.GetSpanForAddress(address); + if (length <= span.size()) + { + file->Seek(seek_pos, File::SeekOrigin::Begin); + file->WriteBytes(span.data(), length); + file->Flush(); + } + else + { + ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: Write overflow: address=0x{:08x}, length={}, span={}", + address, length, span.size()); + } +} +static void FileReadData(Memory::MemoryManager& memory, File::IOFile* file, u32 seek_pos, + u32 address, std::size_t length) +{ + auto span = memory.GetSpanForAddress(address); + if (length <= span.size()) + { + file->Seek(seek_pos, File::SeekOrigin::Begin); + file->ReadBytes(span.data(), length); + } + else + { + ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: Read overflow: address=0x{:08x}, length={}, span={}", + address, length, span.size()); + } } u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u32 length) { + auto& system = Core::System::GetInstance(); + auto& memory = system.GetMemory(); + dicmd_buf[0] ^= s_gcam_key_a; dicmd_buf[1] ^= s_gcam_key_b; dicmd_buf[2] ^= s_gcam_key_c; @@ -481,12 +576,12 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u if (s_gcam_key_a == 0) { - if (s_memory.Read_U32(0)) + if (memory.Read_U32(0)) { - HLE::Patch(s_system, 0x813048B8, "OSReport"); - HLE::Patch(s_system, 0x8130095C, "OSReport"); // Apploader + HLE::Patch(system, 0x813048B8, "OSReport"); + HLE::Patch(system, 0x8130095C, "OSReport"); // Apploader - InitKeys(s_memory.Read_U32(0), s_memory.Read_U32(4), s_memory.Read_U32(8)); + InitKeys(memory.Read_U32(0), memory.Read_U32(4), memory.Read_U32(8)); } } @@ -501,10 +596,10 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u if (offset == 0x0002440) { // Set by OSResetSystem - if (s_memory.Read_U32(0x811FFF00) == 1) + if (memory.Read_U32(0x811FFF00) == 1) { // Don't map firmware while in SegaBoot - if (s_memory.Read_U32(0x8006BF70) != 0x0A536567) + if (memory.Read_U32(0x8006BF70) != 0x0A536567) { s_firmware_map = true; } @@ -540,35 +635,35 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u switch (offset) { case MediaBoardStatus1: - s_memory.Write_U16(1, address); + memory.Write_U16(1, address); break; case MediaBoardStatus2: - s_memory.Memset(address, 0, length); + memory.Memset(address, 0, length); break; case MediaBoardStatus3: - s_memory.Memset(address, 0xFF, length); + memory.Memset(address, 0xFF, length); // DIMM size (512MB) - s_memory.Write_U32_Swap(0x20000000, address); + memory.Write_U32_Swap(0x20000000, address); // GCAM signature - s_memory.Write_U32(0x4743414D, address + 4); + memory.Write_U32(0x4743414D, address + 4); break; case 0x80000100: - s_memory.Write_U32_Swap(0x001F1F1F, address); + memory.Write_U32_Swap(0x001F1F1F, address); break; case FirmwareStatus1: - s_memory.Write_U32_Swap(0x01FA, address); + memory.Write_U32_Swap(0x01FA, address); break; case FirmwareStatus2: - s_memory.Write_U32_Swap(1, address); + memory.Write_U32_Swap(1, address); break; case 0x80000160: - s_memory.Write_U32(0x00001E00, address); + memory.Write_U32(0x00001E00, address); break; case 0x80000180: - s_memory.Write_U32(0, address); + memory.Write_U32(0, address); break; case 0x800001A0: - s_memory.Write_U32(0xFFFFFFFF, address); + memory.Write_U32(0xFFFFFFFF, address); break; default: PrintMBBuffer(address, length); @@ -581,109 +676,57 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u // Network configuration if (offset == 0x00000000 && length == 0x80) { - s_netcfg.Seek(0, File::SeekOrigin::Begin); - s_netcfg.ReadBytes(s_memory.GetSpanForAddress(address).data(), length); + FileReadData(memory, &s_netcfg, 0, address, length); return 0; } - // media crc check on/off + // Media crc check on/off if (offset == DIMMExtraSettings && length == 0x20) { - s_extra.Seek(0, File::SeekOrigin::Begin); - s_extra.ReadBytes(s_memory.GetSpanForAddress(address).data(), length); + FileReadData(memory, &s_extra, 0, address, length); return 0; } - // DIMM s_memory (8MB) + // DIMM memory (8MB) if (offset >= DIMMMemory && offset < 0x1F800000) { - const u32 dimm_offset = offset - DIMMMemory; - s_dimm.Seek(dimm_offset, File::SeekOrigin::Begin); - s_dimm.ReadBytes(s_memory.GetSpanForAddress(address).data(), length); + FileReadData(memory, &s_dimm, offset - DIMMMemory, address, length); + return 0; + } + + // DIMM memory (8MB) + if (offset >= DIMMMemory2 && offset < 0xFF800000) + { + FileReadData(memory, &s_dimm, offset - DIMMMemory2, address, length); + return 0; + } + + if (offset == NetworkControl && length == 0x20) + { + FileReadData(memory, &s_netctrl, 0, address, length); return 0; } if (offset >= AllNetBuffer && offset < 0x89011000) { INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read All.Net Buffer ({:08x},{})", offset, length); - // Fake reply - s_memory.CopyToEmu(address, s_allnet_reply, sizeof(s_allnet_reply)); + SafeCopyToEmu(memory, address, (u8*)s_allnet_reply, sizeof(s_allnet_reply), + offset - AllNetBuffer, sizeof(s_allnet_reply)); return 0; } - if (offset >= DIMMCommandVersion1 && offset < 0x1F900040 && length == 0x20) + for (const auto& range : s_mediaboard_ranges) { - const u32 dimm_offset = offset - DIMMCommandVersion1; - - s_memory.CopyToEmu(address, s_media_buffer + dimm_offset, length); - - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read MEDIA BOARD COMM AREA (1) ({:08x},{})", offset, - length); - PrintMBBuffer(address, length); - return 0; - } - - if (offset == DIMMCommandVersion2 || offset == DIMMCommandVersion2_1 && length == 0x20) - { - const u32 dimm_offset = offset - DIMMCommandVersion2; - s_memory.CopyToEmu(address, s_media_buffer + dimm_offset, length); - - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read MEDIA BOARD COMM AREA (2) ({:08x},{})", offset, - length); - PrintMBBuffer(address, length); - return 0; - } - - if (offset == DIMMCommandVersion2_2 || offset == DIMMCommandVersion2_2_1 && length == 0x20) - { - const u32 dimm_offset = offset - DIMMCommandVersion2_2; - s_memory.CopyToEmu(address, s_media_buffer + dimm_offset, length); - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read MEDIA BOARD COMM AREA (3) ({:08x})", dimm_offset); - PrintMBBuffer(address, length); - return 0; - } - - if (offset >= NetworkBufferAddress4 && offset < 0x89240000) - { - NetworkBufferRead(offset - NetworkBufferAddress4, address, length); - return 0; - } - - if (offset >= NetworkBufferAddress5 && offset < 0x1FB10000) - { - NetworkBufferRead(offset - NetworkBufferAddress5, address, length); - return 0; - } - - if (offset >= NetworkCommandAddress1 && offset < NetworkBufferAddress2) - { - NetworkCMDBufferRead(offset - NetworkCommandAddress1, address, length); - return 0; - } - - if (offset >= NetworkCommandAddress2 && offset < 0x89060200) - { - NetworkCMDBufferRead(offset - NetworkCommandAddress2, address, length); - return 0; - } - - if (offset >= NetworkBufferAddress1 && offset < 0x1FA10000) - { - NetworkBufferRead(offset - NetworkBufferAddress1, address, length); - return 0; - } - - if (offset >= NetworkBufferAddress2 && offset < 0x1FD10000) - { - NetworkBufferRead(offset - NetworkBufferAddress2, address, length); - return 0; - } - - if (offset >= NetworkBufferAddress3 && offset < 0x89110000) - { - NetworkBufferRead(offset - NetworkBufferAddress3, address, length); - return 0; + if (offset >= range.start && offset < range.end) + { + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read MediaBoard ({:08x},{:08x},{:08x})", offset, + range.base_offset, length); + SafeCopyToEmu(memory, address, range.buffer, range.buffer_size, offset - range.base_offset, + length); + PrintMBBuffer(address, length); + return 0; + } } if (offset == DIMMCommandExecute2) @@ -734,7 +777,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u const u32 addr_off = s_media_buffer_32[3] - NetworkCommandAddress2; const u32 len_off = s_media_buffer_32[4] - NetworkCommandAddress2; - if (!NetworkCMDBufferCheck(addr_off) || !NetworkCMDBufferCheck(len_off)) + if (!NetworkCMDBufferCheck(addr_off, sizeof(sockaddr)) || + !NetworkCMDBufferCheck(len_off, sizeof(u32))) { break; } @@ -758,7 +802,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u const u32 off = s_media_buffer_32[3] - NetworkCommandAddress2; const u32 len = s_media_buffer_32[4]; - if (!NetworkCMDBufferCheck(off)) + if (!NetworkCMDBufferCheck(off, len)) { break; } @@ -810,7 +854,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u const u32 off = s_media_buffer_32[3] - NetworkCommandAddress2; const u32 len = s_media_buffer_32[4]; - if (!NetworkCMDBufferCheck(off)) + if (!NetworkCMDBufferCheck(off, len)) { break; } @@ -863,22 +907,24 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u case AMMBCommand::Recv: { const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; - const u32 off = s_media_buffer_32[3]; - const auto len = std::min(s_media_buffer_32[4], sizeof(s_network_buffer)); + u32 off = s_media_buffer_32[3]; + auto len = std::min(s_media_buffer_32[4], sizeof(s_network_buffer)); - u8* buffer = s_network_buffer + off; - - if (off >= NetworkBufferAddress4 && off < NetworkBufferAddress4 + sizeof(s_network_buffer)) + if (off >= NetworkBufferAddress4 && + off + len <= NetworkBufferAddress4 + sizeof(s_network_buffer)) { - buffer = s_network_buffer + off - NetworkBufferAddress4; + off -= NetworkBufferAddress4; } - else + else if (off + len > sizeof(s_network_buffer)) { - ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: recv(error) unhandled destination:{:08x}\n", off); - buffer = s_network_buffer; + ERROR_LOG_FMT(AMMEDIABOARD_NET, + "GC-AM: recv(error) invalid destination or length: off={:08x}, len={}\n", + off, len); + off = 0; + len = 0; } - const int ret = recv(fd, reinterpret_cast(buffer), len, 0); + int ret = recv(fd, reinterpret_cast(s_network_buffer + off), len, 0); const int err = WSAGetLastError(); NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: recv( {}, 0x{:08x}, {} ):{} {}\n", fd, off, len, @@ -892,16 +938,20 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u { const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; u32 off = s_media_buffer_32[3]; - const u32 len = s_media_buffer_32[4]; + auto len = std::min(s_media_buffer_32[4], sizeof(s_network_buffer)); - if (off >= NetworkBufferAddress3 && off < NetworkBufferAddress3 + sizeof(s_network_buffer)) + if (off >= NetworkBufferAddress3 && + off + len <= NetworkBufferAddress3 + sizeof(s_network_buffer)) { off -= NetworkBufferAddress3; } - else + else if (off + len > sizeof(s_network_buffer)) { - ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: send(error) unhandled destination:{:08x}\n", off); + ERROR_LOG_FMT(AMMEDIABOARD_NET, + "GC-AM: send(error) unhandled destination or length: {:08x}, len={}", off, + len); off = 0; + len = 0; } const int ret = send(fd, reinterpret_cast(s_network_buffer + off), len, 0); @@ -959,7 +1009,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u if (s_media_buffer_32[6] != 0) { const u32 fd_set_offset = s_media_buffer_32[6] - NetworkCommandAddress2; - if (!NetworkCMDBufferCheck(fd_set_offset + sizeof(fd_set))) + if (!NetworkCMDBufferCheck(fd_set_offset, sizeof(fd_set))) { ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: Select(error) unhandled destination:{:08x}\n", s_media_buffer_32[6]); @@ -1017,11 +1067,11 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u case AMMBCommand::SetSockOpt: { const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; - const int level = (int)(s_media_buffer_32[3]); - const int optname = (int)(s_media_buffer_32[4]); - const int optlen = (int)(s_media_buffer_32[6]); + const int level = static_cast(s_media_buffer_32[3]); + const int optname = static_cast(s_media_buffer_32[4]); + const int optlen = static_cast(s_media_buffer_32[6]); - if (!NetworkCMDBufferCheck(s_media_buffer_32[5] - NetworkCommandAddress2 + optlen)) + if (!NetworkCMDBufferCheck(s_media_buffer_32[5] - NetworkCommandAddress2, optlen)) { break; } @@ -1097,7 +1147,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u { const u32 net_buffer_offset = s_media_buffer_32[2] - NetworkCommandAddress2; - if (!NetworkCMDBufferCheck(net_buffer_offset)) + if (!NetworkCMDBufferCheck(net_buffer_offset, 15)) { break; } @@ -1134,38 +1184,12 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u s_media_buffer[3] |= 0x80; // Command complete flag - s_memory.Memset(address, 0, length); + memory.Memset(address, 0, length); ExpansionInterface::GenerateInterrupt(0x10); return 0; } - // DIMM s_memory (8MB) - if (offset >= DIMMMemory2 && offset < 0xFF800000) - { - const u32 dimm_offset = offset - DIMMMemory2; - s_dimm.Seek(dimm_offset, File::SeekOrigin::Begin); - s_dimm.ReadBytes(s_memory.GetSpanForAddress(address).data(), length); - return 0; - } - - if (offset >= AllNetSettings && offset < 0x1F000000) - { - const u32 allnet_offset = offset - AllNetSettings; - if (allnet_offset + length < sizeof(s_allnet_settings)) - { - s_memory.CopyToEmu(address, s_allnet_settings + allnet_offset, length); - } - return 0; - } - - if (offset == NetworkControl && length == 0x20) - { - s_netctrl.Seek(0, File::SeekOrigin::Begin); - s_netctrl.ReadBytes(s_memory.GetSpanForAddress(address).data(), length); - return 0; - } - // Max GC disc offset if (offset >= 0x57058000) { @@ -1175,13 +1199,21 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u if (s_firmware_map) { - s_memory.CopyToEmu(address, s_firmware + offset, length); + if (!SafeCopyToEmu(memory, address, s_firmware, sizeof(s_firmware), offset, length)) + { + ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: Invalid firmware buffer range: offset={}, length={}", + offset, length); + } return 0; } if (s_dimm_disc.size()) { - s_memory.CopyToEmu(address, s_dimm_disc.data() + offset, length); + if (!SafeCopyToEmu(memory, address, s_dimm_disc.data(), s_dimm_disc.size(), offset, length)) + { + ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: Invalid DIMM Disc read from: offset={}, length={}", + offset, length); + } return 0; } @@ -1204,11 +1236,11 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u if (s_firmware_map) { - // Firmware s_memory (2MB) + // Firmware memory (2MB) if ((offset >= 0x00400000) && (offset <= 0x600000)) { const u32 fwoffset = offset - 0x00400000; - s_memory.CopyFromEmu(s_firmware + fwoffset, address, length); + memory.CopyFromEmu(s_firmware + fwoffset, address, length); return 0; } } @@ -1216,101 +1248,59 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u // Network configuration if (offset == 0x00000000 && length == 0x80) { - FileWriteData(&s_netcfg, 0, s_memory.GetSpanForAddress(address).data(), length); + FileWriteData(memory, &s_netcfg, 0, address, length); return 0; } // media crc check on/off if (offset == DIMMExtraSettings && length == 0x20) { - FileWriteData(&s_extra, 0, s_memory.GetSpanForAddress(address).data(), length); + FileWriteData(memory, &s_extra, 0, address, length); return 0; } - // Backup s_memory (8MB) + // Backup memory (8MB) if (offset >= BackupMemory && offset < 0x00800000) { - FileWriteData(&s_backup, 0, s_memory.GetSpanForAddress(address).data(), length); + FileWriteData(memory, &s_backup, 0, address, length); return 0; } - // DIMM s_memory (8MB) + // DIMM memory (8MB) if (offset >= DIMMMemory && offset < 0x1F800000) { - const u32 dimm_offset = offset - DIMMMemory; - FileWriteData(&s_dimm, dimm_offset, s_memory.GetSpanForAddress(address).data(), length); + FileWriteData(memory, &s_dimm, offset - DIMMMemory, address, length); return 0; } - if (offset >= AllNetBuffer && offset < 0x89011000) + // Firmware Write + if (offset >= FirmwareAddress && offset < 0x84818000) { - const u32 allnet_offset = offset - AllNetBuffer; - if (allnet_offset + length < sizeof(s_allnet_settings)) - { - s_memory.CopyFromEmu(s_allnet_buffer + allnet_offset, address, length); - } - return 0; - } - - if (offset >= NetworkCommandAddress1 && offset < 0x1F801240) - { - NetworkCMDBufferWrite(offset - NetworkCommandAddress1, address, length); - return 0; - } - - if (offset >= NetworkCommandAddress2 && offset < 0x89060200 && length <= 0x60) - { - NetworkCMDBufferWrite(offset - NetworkCommandAddress2, address, length); - return 0; - } - - if (offset >= NetworkBufferAddress1 && offset < 0x1FA20000) - { - NetworkBufferWrite(offset - NetworkBufferAddress1, address, length); - return 0; - } - - if (offset >= NetworkBufferAddress2 && offset < 0x1FD10000) - { - NetworkBufferWrite(offset - NetworkBufferAddress2, address, length); - return 0; - } - - if (offset >= NetworkBufferAddress3 && offset < 0x89110000) - { - NetworkBufferWrite(offset - NetworkBufferAddress3, address, length); - return 0; - } - - if (offset >= DIMMCommandVersion1 && offset < 0x1F900040 && length == 0x20) - { - const u32 dimm_offset = offset - DIMMCommandVersion1; - - s_memory.CopyFromEmu(s_media_buffer + dimm_offset, address, length); - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write MEDIA BOARD COMM AREA (1) ({:08x},{})", offset, - length); + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write Firmware ({:08x})", offset); PrintMBBuffer(address, length); return 0; } - if (offset == DIMMCommandVersion2 || offset == DIMMCommandVersion2_1 && length == 0x20) + // DIMM memory (8MB) + if (offset >= DIMMMemory2 && offset < 0xFF800000) { - const u32 dimm_offset = offset - DIMMCommandVersion2; + FileWriteData(memory, &s_dimm, offset - DIMMMemory2, address, length); + return 0; + } - s_memory.CopyFromEmu(s_media_buffer + dimm_offset, address, length); - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write MEDIA BOARD COMM AREA (2) ({:08x},{})", offset, - length); - PrintMBBuffer(address, length); + if (offset == NetworkControl && length == 0x20) + { + FileWriteData(memory, &s_netctrl, 0, address, length); return 0; } if (offset == DIMMCommandExecute1 && length == 0x20) { - if (s_memory.Read_U8(address) == 1) + if (memory.Read_U8(address) == 1) { const AMMBCommand ammb_command = Common::BitCastPtr(s_media_buffer + 0x22); - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Execute command (2):{0:04X}", + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Execute command: (1):{0:04X}", static_cast(ammb_command)); memset(s_media_buffer, 0, 0x20); @@ -1365,41 +1355,19 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u ExpansionInterface::GenerateInterrupt(0x04); return 0; } - return 0; } - if (offset == DIMMCommandVersion2_2 || offset == DIMMCommandVersion2_2_1 && length == 0x20) + for (const auto& range : s_mediaboard_ranges) { - const u32 dimm_offset = offset - DIMMCommandVersion2_2; - - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write MEDIA BOARD COMM AREA (3) ({:08x},{})", offset, - length); - PrintMBBuffer(address, length); - - s_memory.CopyFromEmu(s_media_buffer + dimm_offset, address, length); - return 0; - } - - // Firmware Write - if (offset >= FirmwareAddress && offset < 0x84818000) - { - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write Firmware ({:08x})", offset); - PrintMBBuffer(address, length); - return 0; - } - - // DIMM s_memory (8MB) - if (offset >= DIMMMemory2 && offset < 0xFF800000) - { - const u32 dimm_offset = offset - DIMMMemory2; - FileWriteData(&s_dimm, dimm_offset, s_memory.GetSpanForAddress(address).data(), length); - return 0; - } - - if (offset == NetworkControl && length == 0x20) - { - FileWriteData(&s_netctrl, 0, s_memory.GetSpanForAddress(address).data(), length); - return 0; + if (offset >= range.start && offset < range.end) + { + INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write MediaBoard ({:08x},{:08x},{:08x})", offset, + range.base_offset, length); + SafeCopyFromEmu(memory, range.buffer, address, range.buffer_size, + offset - range.base_offset, length); + PrintMBBuffer(address, length); + return 0; + } } // Max GC disc offset @@ -1481,13 +1449,13 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u // 0x04: Network DEBUG_LOG_FMT(AMMEDIABOARD, "GC-AM: TestHardware: ({:08x})", s_media_buffer_32[11]); - // Pointer to a s_memory address that is directly displayed on screen as a string + // Pointer to a memory address that is directly displayed on screen as a string DEBUG_LOG_FMT(AMMEDIABOARD, "GC-AM: ({:08x})", s_media_buffer_32[12]); - // On real s_system it shows the status about the DIMM/GD-ROM here + // On real systems it shows the status about the DIMM/GD-ROM here // We just show "TEST OK" - s_memory.Write_U32(0x54534554, s_media_buffer_32[12]); - s_memory.Write_U32(0x004B4F20, s_media_buffer_32[12] + 4); + memory.Write_U32(0x54534554, s_media_buffer_32[12]); + memory.Write_U32(0x004B4F20, s_media_buffer_32[12] + 4); s_media_buffer_32[1] = s_media_buffer_32[9]; break; @@ -1512,7 +1480,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u const u32 off = s_media_buffer_32[11] - NetworkCommandAddress1; const u32 len = s_media_buffer_32[12]; - if (NetworkCMDBufferCheck(off + sizeof(sockaddr_in))) + if (NetworkCMDBufferCheck(off, sizeof(sockaddr_in))) { break; } @@ -1533,22 +1501,24 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u case AMMBCommand::Recv: { const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; - const u32 off = s_media_buffer_32[11]; - const auto len = std::min(s_media_buffer_32[12], sizeof(s_network_buffer)); + u32 off = s_media_buffer_32[11]; + auto len = std::min(s_media_buffer_32[12], sizeof(s_network_buffer)); - auto* buffer = s_network_buffer + off; - - if (off >= NetworkBufferAddress5 && off < NetworkBufferAddress5 + sizeof(s_network_buffer)) + if (off >= NetworkBufferAddress5 && + off + len <= NetworkBufferAddress5 + sizeof(s_network_buffer)) { - buffer = s_network_buffer + off - NetworkBufferAddress5; + off -= NetworkBufferAddress5; } - else + else if (off + len > sizeof(s_network_buffer)) { - ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: recv(error) unhandled destination:{:08x}\n", off); - buffer = s_network_buffer; + ERROR_LOG_FMT(AMMEDIABOARD_NET, + "GC-AM: recv(error) invalid destination or length: off={:08x}, len={}\n", + off, len); + off = 0; + len = 0; } - const int ret = recv(fd, reinterpret_cast(buffer), len, 0); + int ret = recv(fd, reinterpret_cast(s_network_buffer + off), len, 0); const int err = WSAGetLastError(); NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: recv( {}, 0x{:08x}, {} ):{} {}\n", fd, off, len, @@ -1562,16 +1532,20 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u { const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; u32 off = s_media_buffer_32[11]; - const u32 len = s_media_buffer_32[12]; + auto len = std::min(s_media_buffer_32[12], sizeof(s_network_buffer)); - if (off >= NetworkBufferAddress1 && off < NetworkBufferAddress1 + sizeof(s_network_buffer)) + if (off >= NetworkBufferAddress1 && + off + len <= NetworkBufferAddress1 + sizeof(s_network_buffer)) { off -= NetworkBufferAddress1; } - else + else if (off + len > sizeof(s_network_buffer)) { - ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: send(error) unhandled destination:{:08x}\n", off); + ERROR_LOG_FMT(AMMEDIABOARD_NET, + "GC-AM: send(error) unhandled destination or length: {:08x}, len={}", off, + len); off = 0; + len = 0; } const int ret = send(fd, reinterpret_cast(s_network_buffer + off), len, 0); @@ -1617,7 +1591,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u if (s_media_buffer_32[14] != 0) { const u32 fd_set_offset = s_media_buffer_32[14] - NetworkCommandAddress1; - if (!NetworkCMDBufferCheck(fd_set_offset + sizeof(fd_set))) + if (!NetworkCMDBufferCheck(fd_set_offset, sizeof(fd_set))) { ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: Select(error) unhandled destination:{:08x}\n", s_media_buffer_32[14]); @@ -1665,7 +1639,6 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u "GC-AM: select( {}({}), 0x{:08x} 0x{:08x} 0x{:08x} 0x{:08x} ):{} {} \n", fd, s_media_buffer_32[10], s_media_buffer_32[11], s_media_buffer_32[14], s_media_buffer_32[15], s_media_buffer_32[16], ret, err); - // hexdump( NetworkCMDBuffer, 0x40 ); s_media_buffer[1] = 0; s_media_buffer_32[1] = ret; @@ -1674,11 +1647,11 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u case AMMBCommand::SetSockOpt: { const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; - const int level = (int)(s_media_buffer_32[11]); - const int optname = (int)(s_media_buffer_32[12]); - const int optlen = (int)(s_media_buffer_32[14]); + const int level = static_cast(s_media_buffer_32[11]); + const int optname = static_cast(s_media_buffer_32[12]); + const int optlen = static_cast(s_media_buffer_32[14]); - if (!NetworkCMDBufferCheck(s_media_buffer_32[13] - NetworkCommandAddress1 + optlen)) + if (!NetworkCMDBufferCheck(s_media_buffer_32[13] - NetworkCommandAddress1, optlen)) { break; } @@ -1701,7 +1674,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u { const u32 net_buffer_offset = s_media_buffer_32[10] - NetworkCommandAddress1; - if (!NetworkCMDBufferCheck(net_buffer_offset)) + if (!NetworkCMDBufferCheck(net_buffer_offset, 15)) { break; } @@ -1760,7 +1733,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: Offset: ({:04x})", off); NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: ({:08x})", addr); - if (!NetworkBufferCheck(off + addr - NetworkBufferAddress2)) + if (!NetworkBufferCheck(off + addr - NetworkBufferAddress2, 0x20)) { break; } @@ -1822,15 +1795,12 @@ u32 GetMediaType() case GekitouProYakyuu: case KeyOfAvalon: return GDROM; - break; case MarioKartGP: case MarioKartGP2: case FZeroAXMonster: return NAND; - break; } - // Never reached } // This is checking for the real game IDs (See boot.id within the game) @@ -1839,41 +1809,17 @@ u32 GetGameType() u16 triforce_id = 0; const std::string& game_id = SConfig::GetInstance().GetGameID(); - if (game_id.length() > 6) - { - triforce_id = 0x3232; - } - else + if (game_id.length() == 6) { triforce_id = game_id[1] << 8 | game_id[2]; } + else + { + triforce_id = 0x454A; // Fallback (VirtuaStriker3) + } - static const std::unordered_map game_map = {{0x4747, FZeroAX}, - {0x4841, FZeroAXMonster}, - {0x4B50, MarioKartGP}, - {0x4B5A, MarioKartGP}, - {0x4E4A, MarioKartGP2}, - {0x4E4C, MarioKartGP2}, - {0x454A, VirtuaStriker3}, - {0x4559, VirtuaStriker3}, - {0x4C4A, VirtuaStriker4_2006}, - {0x4C4B, VirtuaStriker4_2006}, - {0x4C4C, VirtuaStriker4_2006}, - {0x484A, VirtuaStriker4}, - {0x484E, VirtuaStriker4}, - {0x485A, VirtuaStriker4}, - {0x4A41, VirtuaStriker4}, - {0x4A4A, VirtuaStriker4}, - {0x4658, KeyOfAvalon}, - {0x4A4E, KeyOfAvalon}, - {0x4758, GekitouProYakyuu}, - {0x5342, VirtuaStriker3}, - {0x3132, VirtuaStriker3}, - {0x454C, VirtuaStriker3}, - {0x3030, FirmwareUpdate}}; - - auto it = game_map.find(triforce_id); - if (it != game_map.end()) + auto it = s_game_map.find(triforce_id); + if (it != s_game_map.end()) { return it->second; } diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.h b/Source/Core/Core/HW/DVD/AMMediaboard.h index 4bc24dc6a6..d57a800107 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.h +++ b/Source/Core/Core/HW/DVD/AMMediaboard.h @@ -58,6 +58,15 @@ class CPUThreadGuard; class System; } // namespace Core +struct MediaBoardRanges +{ + u32 start; + u32 end; + u8* buffer; + size_t buffer_size; + u32 base_offset; +}; + namespace AMMediaboard { @@ -154,9 +163,7 @@ enum MediaBoardAddress : u32 DIMMCommandVersion1 = 0x1F900000, DIMMCommandVersion2 = 0x84000000, - DIMMCommandVersion2_1 = 0x84000020, DIMMCommandVersion2_2 = 0x89000000, - DIMMCommandVersion2_2_1 = 0x89000200, DIMMCommandExecute1 = 0x84000040, DIMMCommandExecute2 = 0x88000000, diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp index bc46afe65c..90577ec8cc 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp @@ -24,9 +24,6 @@ #include "Core/HW/EXI/EXI_Device.h" #include "Core/HW/MMIO.h" #include "Core/HW/Memmap.h" -#include "Core/HW/SI/SI.h" -#include "Core/HW/SI/SI_Device.h" -#include "Core/HW/WiimoteReal/WiimoteReal.h" #include "Core/Movie.h" #include "Core/PowerPC/PowerPC.h" #include "Core/System.h" @@ -140,9 +137,7 @@ void CEXIBaseboard::DMAWrite(u32 addr, u32 size) NOTICE_LOG_FMT(SP1, "AM-BB: COMMAND: Backup DMA Write: {:08x} {:x}", addr, size); m_backup.Seek(m_backup_offset, File::SeekOrigin::Begin); - m_backup.WriteBytes(memory.GetSpanForAddress(addr).data(), size); - m_backup.Flush(); } @@ -154,9 +149,6 @@ void CEXIBaseboard::DMARead(u32 addr, u32 size) NOTICE_LOG_FMT(SP1, "AM-BB: COMMAND: Backup DMA Read: {:08x} {:x}", addr, size); m_backup.Seek(m_backup_offset, File::SeekOrigin::Begin); - - m_backup.Flush(); - m_backup.ReadBytes(memory.GetSpanForAddress(addr).data(), size); } @@ -264,7 +256,6 @@ void CEXIBaseboard::TransferByte(u8& byte) { // 1 byte out case BackupRead: - m_backup.Flush(); m_backup.ReadBytes(&byte, 1); break; case DMAOffsetLengthSet: diff --git a/Source/Core/Core/HW/GCPadEmu.cpp b/Source/Core/Core/HW/GCPadEmu.cpp index 35cfeda2a8..4c6bb432f5 100644 --- a/Source/Core/Core/HW/GCPadEmu.cpp +++ b/Source/Core/Core/HW/GCPadEmu.cpp @@ -73,7 +73,7 @@ GCPad::GCPad(const unsigned int index) : m_index(index) groups.emplace_back(m_triforce = new ControllerEmu::Buttons(TRIFORCE_GROUP)); for (const char* named_button : {TEST_BUTTON, SERVICE_BUTTON, COIN_BUTTON}) { - m_triforce->AddInput(Translatability::DoNotTranslate, named_button); + m_triforce->AddInput(Translatability::Translate, named_button); } // Microphone diff --git a/Source/Core/Core/HW/SI/SI.h b/Source/Core/Core/HW/SI/SI.h index 0a10b54a6a..5f8a47afd7 100644 --- a/Source/Core/Core/HW/SI/SI.h +++ b/Source/Core/Core/HW/SI/SI.h @@ -56,7 +56,7 @@ public: void RemoveEvent(int device_number); void UpdateDevices(); - u32 GetInLength(void) const { return m_com_csr.INLNGTH; } + u32 GetInLength() const { return m_com_csr.INLNGTH; } void RemoveDevice(int device_number); void AddDevice(SIDevices device, int device_number); diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp index 22392442ac..92cec36b30 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp @@ -44,15 +44,15 @@ namespace SerialInterface void JVSIOMessage::Start(int node) { - m_last_start = m_ptr; - const u8 hdr[3] = {0xE0, (u8)node, 0}; - m_csum = 0; - AddData(hdr, 3, 1); + m_last_start = m_pointer; + const u8 header[3] = {0xE0, (u8)node, 0}; + m_checksum = 0; + AddData(header, 3, 1); } void JVSIOMessage::AddData(const u8* dst, std::size_t len, int sync = 0) { - if (m_ptr + len >= 0x80) + if (m_pointer + len >= sizeof(m_message)) { PanicAlertFmt("JVSIOMessage overrun!"); return; @@ -63,16 +63,26 @@ void JVSIOMessage::AddData(const u8* dst, std::size_t len, int sync = 0) const u8 c = *dst++; if (!sync && ((c == 0xE0) || (c == 0xD0))) { - m_msg[m_ptr++] = 0xD0; - m_msg[m_ptr++] = c - 1; + if (m_pointer + 2 > sizeof(m_message)) + { + PanicAlertFmt("JVSIOMessage overrun!"); + break; + } + m_message[m_pointer++] = 0xD0; + m_message[m_pointer++] = c - 1; } else { - m_msg[m_ptr++] = c; + if (m_pointer >= sizeof(m_message)) + { + PanicAlertFmt("JVSIOMessage overrun!"); + break; + } + m_message[m_pointer++] = c; } if (!sync) - m_csum += c; + m_checksum += c; sync = 0; } } @@ -95,9 +105,16 @@ void JVSIOMessage::AddData(u32 n) void JVSIOMessage::End() { - const u32 len = m_ptr - m_last_start; - m_msg[m_last_start + 2] = len - 2; // assuming len <0xD0 - AddData(m_csum + len - 2); + const u32 len = m_pointer - m_last_start; + if (m_last_start + 2 < sizeof(m_message) && len >= 3) + { + m_message[m_last_start + 2] = len - 2; // assuming len <0xD0 + AddData(m_checksum + len - 2); + } + else + { + PanicAlertFmt("JVSIOMessage: Not enough space for checksum!"); + } } static u8 CheckSumXOR(u8* data, u32 length) @@ -2095,8 +2112,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) data_out[data_offset++] = gcam_command; - const u8* buf = message.m_msg.data(); - const u32 len = message.m_ptr; + const u8* buf = message.m_message.data(); + const u32 len = message.m_pointer; data_out[data_offset++] = len; for (u32 i = 0; i < len; ++i) diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h index c199426dd9..0870412f60 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h @@ -26,12 +26,12 @@ public: void AddData(u32 n); void End(); - u32 m_ptr = 0; - std::array m_msg; + u32 m_pointer = 0; + std::array m_message; private: u32 m_last_start = 0; - u32 m_csum = 0; + u32 m_checksum = 0; }; // Triforce (GC-AM) baseboard diff --git a/Source/Core/DiscIO/VolumeDisc.cpp b/Source/Core/DiscIO/VolumeDisc.cpp index 12e92eb0f8..5913f63c11 100644 --- a/Source/Core/DiscIO/VolumeDisc.cpp +++ b/Source/Core/DiscIO/VolumeDisc.cpp @@ -30,41 +30,24 @@ std::string VolumeDisc::GetGameID(const Partition& partition) const memcpy(id + 1, boot_id->game_id.data() + 2, 2); - switch (boot_id->region_flags) + switch (GetCountry()) { default: - case 0x02: // JAPAN + case Country::Japan: id[3] = 'J'; break; - case 0x08: // ASIA + case Country::Taiwan: id[3] = 'W'; break; - case 0x0E: // USA + case Country::USA: id[3] = 'E'; break; - case 0x0C: // EXPORT + case Country::Europe: id[3] = 'P'; break; } - // There only seem to be three different makers here, - // so we can just check the first char to difference between them. - // - // NAMCO CORPORATION, SEGA CORPORATION and Hitmaker co,ltd. - - switch (boot_id->manufacturer[0]) - { - case 'N': // NAMCO CORPORATION - id[4] = '8'; - id[5] = '2'; - break; - default: - case 'H': // Hitmaker co,ltd. - case 'S': // SEGA CORPORATION - id[4] = '6'; - id[5] = 'E'; - break; - } + memcpy(id + 4, GetMakerID().c_str(), 2); return DecodeString(id); } @@ -133,6 +116,11 @@ std::string VolumeDisc::GetMakerID(const Partition& partition) const { const BootID* boot_id = static_cast(this)->GetTriforceBootID(); + // There only seem to be three different makers here, + // so we can just check the first char to difference between them. + // + // NAMCO CORPORATION, SEGA CORPORATION and Hitmaker co,ltd. + switch (boot_id->manufacturer[0]) { case 'S': // SEGA CORPORATION From 95442a2b6a83d1b3155d9166bba7216113a42e8e Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 18 Jan 2026 19:00:40 -0600 Subject: [PATCH 06/71] Triforce: Warning silencing and other minor cleanups. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 29 +++--- Source/Core/Core/HW/DVD/DVDInterface.cpp | 2 +- .../Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp | 97 ++++--------------- Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h | 86 ++++++++-------- Source/Core/DolphinQt/MainWindow.cpp | 5 - 5 files changed, 77 insertions(+), 142 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index d94f86e404..7effc677c3 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -4,7 +4,6 @@ #include "Core/HW/DVD/AMMediaboard.h" #include -#include #include #include @@ -33,8 +32,6 @@ #include "Core/Movie.h" #include "Core/System.h" -#include "DiscIO/DirectoryBlob.h" - #if defined(__linux__) or defined(__APPLE__) or defined(__FreeBSD__) or defined(__NetBSD__) or \ defined(__HAIKU__) @@ -215,14 +212,14 @@ static bool SafeCopyToEmu(Memory::MemoryManager& memory, u32 address, const u8* memory.CopyToEmu(address, source + offset, length); return true; } -static bool SafeCopyFromEmu(Memory::MemoryManager& memory, u8* destionation, u32 address, - u64 destionation_size, u32 offset, u32 length) +static bool SafeCopyFromEmu(Memory::MemoryManager& memory, u8* destination, u32 address, + u64 destination_size, u32 offset, u32 length) { - if (offset > destionation_size || length > destionation_size - offset) + if (offset > destination_size || length > destination_size - offset) { ERROR_LOG_FMT(AMMEDIABOARD, - "GC-AM: Write overflow: offset=0x{:08x}, length={}, destionation_size={}", offset, - length, destionation_size); + "GC-AM: Write overflow: offset=0x{:08x}, length={}, destination_size={}", offset, + length, destination_size); return false; } @@ -235,13 +232,13 @@ static bool SafeCopyFromEmu(Memory::MemoryManager& memory, u8* destionation, u32 return false; } - memory.CopyFromEmu(destionation + offset, address, length); + memory.CopyFromEmu(destination + offset, address, length); return true; } static SOCKET socket_(int af, int type, int protocol) { - for (u32 i = 1; i < 64; ++i) + for (u32 i = 1; i < std::size(s_sockets); ++i) { if (s_sockets[i] == SOCKET_ERROR) { @@ -256,7 +253,7 @@ static SOCKET socket_(int af, int type, int protocol) static SOCKET accept_(int fd, sockaddr* addr, socklen_t* len) { - for (u32 i = 1; i < 64; ++i) + for (u32 i = 1; i < std::size(s_sockets); ++i) { if (s_sockets[i] == SOCKET_ERROR) { @@ -711,8 +708,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u { INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read All.Net Buffer ({:08x},{})", offset, length); // Fake reply - SafeCopyToEmu(memory, address, (u8*)s_allnet_reply, sizeof(s_allnet_reply), - offset - AllNetBuffer, sizeof(s_allnet_reply)); + SafeCopyToEmu(memory, address, reinterpret_cast(s_allnet_reply), + sizeof(s_allnet_reply), offset - AllNetBuffer, sizeof(s_allnet_reply)); return 0; } @@ -1239,8 +1236,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u // Firmware memory (2MB) if ((offset >= 0x00400000) && (offset <= 0x600000)) { - const u32 fwoffset = offset - 0x00400000; - memory.CopyFromEmu(s_firmware + fwoffset, address, length); + const u32 fw_offset = offset - 0x00400000; + memory.CopyFromEmu(s_firmware + fw_offset, address, length); return 0; } } @@ -1843,7 +1840,7 @@ void Shutdown() s_dimm_disc.clear(); // Close all sockets - for (u32 i = 1; i < 64; ++i) + for (u32 i = 1; i < std::size(s_sockets); ++i) { if (s_sockets[i] != SOCKET_ERROR) { diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index 768870f185..edbd9bdc6d 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -321,7 +321,7 @@ void DVDInterface::ResetDrive(bool spinup) else if (!spinup) { // Wii hardware tests indicate that this is used when ejecting and inserting a new disc, or - // performing a reset without spin up. + // performing a reset without spin-up. SetDriveState(DriveState::DiscChangeDetected); } else diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp index 92cec36b30..fce4ed9f63 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp @@ -4,6 +4,7 @@ #include "Core/HW/SI/SI_DeviceAMBaseboard.h" #include +#include #include #include @@ -41,7 +42,6 @@ namespace SerialInterface { - void JVSIOMessage::Start(int node) { m_last_start = m_pointer; @@ -89,7 +89,7 @@ void JVSIOMessage::AddData(const u8* dst, std::size_t len, int sync = 0) void JVSIOMessage::AddData(const void* data, std::size_t len) { - AddData((const u8*)data, len); + AddData(static_cast(data), len); } void JVSIOMessage::AddData(const char* data) @@ -117,21 +117,14 @@ void JVSIOMessage::End() } } -static u8 CheckSumXOR(u8* data, u32 length) +static constexpr u8 CheckSumXOR(const u8* data, u32 length) { - u8 check = 0; - - for (u32 i = 0; i < length; i++) - { - check ^= data[i]; - } - - return check; + return std::accumulate(data, data + length, u8{}, std::bit_xor()); } -static const char s_cdr_program_version[] = {" Version 1.22,2003/09/19,171-8213B"}; -static const char s_cdr_boot_version[] = {" Version 1.04,2003/06/17,171-8213B"}; -static const u8 s_cdr_card_data[] = { +static constexpr char s_cdr_program_version[] = {" Version 1.22,2003/09/19,171-8213B"}; +static constexpr char s_cdr_boot_version[] = {" Version 1.04,2003/06/17,171-8213B"}; +static constexpr u8 s_cdr_card_data[] = { 0x00, 0x6E, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x07, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x00, 0x00, 0x19, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x20, 0x00, @@ -139,29 +132,16 @@ static const u8 s_cdr_card_data[] = { 0x00, 0x00, 0x2C, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x34, 0x00, 0x00, 0x35, 0x00, 0x00, 0x37, 0x00, 0x00, 0x38, 0x00, 0x00, 0x39, 0x00, 0x00, 0x3D, 0x00}; -const static u8 s_region_flags[] = "\x00\x00\x30\x00" - // "\x01\xfe\x00\x00" // JAPAN - "\x02\xfd\x00\x00" // USA - //"\x03\xfc\x00\x00" // export - "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"; +const constexpr u8 s_region_flags[] = "\x00\x00\x30\x00" + // "\x01\xfe\x00\x00" // JAPAN + "\x02\xfd\x00\x00" // USA + //"\x03\xfc\x00\x00" // export + "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"; // AM-Baseboard device on SI CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices device, int device_number) : ISIDevice(system, device, device_number) { - std::ranges::fill(m_coin, 0); - - // Setup IC-card - m_ic_card_state = 0x20; - m_ic_card_status = ICCARDStatus::Okay; - m_ic_card_session = 0x23; - - m_ic_write_size = 0; - m_ic_write_offset = 0; - - memset(m_ic_write_buffer, 0, sizeof(m_ic_write_buffer)); - memset(m_ic_card_data, 0, sizeof(m_ic_card_data)); - // Card ID m_ic_card_data[0x20] = 0x95; m_ic_card_data[0x21] = 0x71; @@ -180,44 +160,6 @@ CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices dev // Use count m_ic_card_data[0x28] = 0xFF; m_ic_card_data[0x29] = 0xFF; - - // Setup CARD - m_card_memory_size = 0; - m_card_is_inserted = false; - - m_card_offset = 0; - m_card_command = 0; - m_card_clean = 0; - - m_card_write_length = 0; - m_card_wrote = 0; - - m_card_read_length = 0; - m_card_read = 0; - - m_card_bit = 0; - m_card_shutter = true; // Open - m_card_state_call_count = 0; - - // Serial - m_wheel_init = 0; - - m_motor_init = 0; - m_motor_force_y = 0; - - m_fzdx_seatbelt = true; - m_fzdx_motion_stop = false; - m_fzdx_sensor_right = false; - m_fzdx_sensor_left = false; - - m_rx_reply = 0xF0; - - m_fzcc_seatbelt = true; - m_fzcc_sensor = false; - m_fzcc_emergency = false; - m_fzcc_service = false; - - memset(m_motor_reply, 0, sizeof(m_motor_reply)); } constexpr u32 SI_XFER_LENGTH_MASK = 0x7f; @@ -1177,7 +1119,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) if (m_card_state_call_count > 10) { if (m_card_bit & 2) - m_card_bit &= ~2; + m_card_bit &= ~2u; else m_card_bit |= 2; @@ -1400,10 +1342,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) u8 jvs_buf[0x80]; - if (frame_len > sizeof(jvs_buf)) - { - frame_len = sizeof(jvs_buf); - } + frame_len = std::min(frame_len, sizeof(jvs_buf)); memcpy(jvs_buf, frame, frame_len); @@ -1555,7 +1494,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) case JVSIOCommand::MainID: while (*jvs_io++) { - }; + } message.AddData(StatusOkay); break; case JVSIOCommand::SwitchesInput: @@ -2099,7 +2038,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0xF1, SetAddress: node={}", node); message.AddData(node == 1); - dip_switch_1 &= ~1; + dip_switch_1 &= ~1u; break; default: ERROR_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Unhandled: node={}, command={:02x}", @@ -2180,13 +2119,11 @@ CSIDevice_AMBaseboard::EButtonCombo CSIDevice_AMBaseboard::HandleButtonCombos(const GCPadStatus& pad_status) { // Keep track of the special button combos (embedded in controller hardware... :( ) - EButtonCombo temp_combo; + EButtonCombo temp_combo = COMBO_NONE; if ((pad_status.button & 0xff00) == (PAD_BUTTON_Y | PAD_BUTTON_X | PAD_BUTTON_START)) temp_combo = COMBO_ORIGIN; else if ((pad_status.button & 0xff00) == (PAD_BUTTON_B | PAD_BUTTON_X | PAD_BUTTON_START)) temp_combo = COMBO_RESET; - else - temp_combo = COMBO_NONE; if (temp_combo != m_last_button_combo) { diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h index 0870412f60..35ec5c4448 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h @@ -241,7 +241,7 @@ private: union ICCommand { - u8 data[81 + 4 + 4 + 4]; + u8 data[81 + 4 + 4 + 4] = {}; struct { @@ -258,55 +258,61 @@ private: }; }; - u8 m_last[2][0x80]; - u32 m_lastptr[2]; + u8 m_last[2][0x80] = {}; + u32 m_lastptr[2] = {}; - u16 m_coin[2]; - u32 m_coin_pressed[2]; + u16 m_coin[2] = {}; + u32 m_coin_pressed[2] = {}; - u8 m_ic_card_data[2048]; - u16 m_ic_card_state; + u8 m_ic_card_data[2048] = {}; - u16 m_ic_card_status; - u16 m_ic_card_session; - u8 m_ic_write_buffer[512]; - u32 m_ic_write_offset; - u32 m_ic_write_size; + // Setup IC-card + u16 m_ic_card_state = 0x20; + u16 m_ic_card_status = ICCARDStatus::Okay; + u16 m_ic_card_session = 0x23; - u8 m_card_memory[0xD0]; - u8 m_card_read_packet[0xDB]; - u8 m_card_buffer[0x100]; - u32 m_card_memory_size; - bool m_card_is_inserted; - u32 m_card_command; - u32 m_card_clean; - u32 m_card_write_length; - u32 m_card_wrote; - u32 m_card_read_length; - u32 m_card_read; - u32 m_card_bit; - bool m_card_shutter; - u32 m_card_state_call_count; - u8 m_card_offset; + u8 m_ic_write_buffer[512] = {}; + u32 m_ic_write_offset = 0; + u32 m_ic_write_size = 0; - u32 m_wheel_init; + u8 m_card_memory[0xD0] = {}; + u8 m_card_read_packet[0xDB] = {}; + u8 m_card_buffer[0x100] = {}; - u32 m_motor_init; - u8 m_motor_reply[64]; - s16 m_motor_force_y; + // Setup CARD + u32 m_card_memory_size = 0; + bool m_card_is_inserted = false; + + u32 m_card_command = 0; + u32 m_card_clean = 0; + u32 m_card_write_length = 0; + u32 m_card_wrote = 0; + u32 m_card_read_length = 0; + u32 m_card_read = 0; + u32 m_card_bit = 0; + bool m_card_shutter = true; // Open + u32 m_card_state_call_count = 0; + u8 m_card_offset = 0; + + // Serial + u32 m_wheel_init = 0; + + u32 m_motor_init = 0; + u8 m_motor_reply[64] = {}; + s16 m_motor_force_y = 0; // F-Zero AX (DX) - bool m_fzdx_seatbelt; - bool m_fzdx_motion_stop; - bool m_fzdx_sensor_right; - bool m_fzdx_sensor_left; - u8 m_rx_reply; + bool m_fzdx_seatbelt = true; + bool m_fzdx_motion_stop = false; + bool m_fzdx_sensor_right = false; + bool m_fzdx_sensor_left = false; + u8 m_rx_reply = 0xF0; // F-Zero AX (CyCraft) - bool m_fzcc_seatbelt; - bool m_fzcc_sensor; - bool m_fzcc_emergency; - bool m_fzcc_service; + bool m_fzcc_seatbelt = true; + bool m_fzcc_sensor = false; + bool m_fzcc_emergency = false; + bool m_fzcc_service = false; void ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length); }; diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index b14885b947..f0ffbec687 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -250,12 +250,7 @@ MainWindow::MainWindow(Core::System& system, std::unique_ptr boo }); #endif - connect(m_cheats_manager, &CheatsManager::OpenGeneralSettings, this, - &MainWindow::ShowGeneralWindow); - #ifdef USE_RETRO_ACHIEVEMENTS - connect(m_cheats_manager, &CheatsManager::OpenAchievementSettings, this, - &MainWindow::ShowAchievementSettings); connect(m_game_list, &GameList::OpenAchievementSettings, this, &MainWindow::ShowAchievementSettings); #endif // USE_RETRO_ACHIEVEMENTS From a5eca0996783dab05ded5a8a01b28733493840d6 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 18 Jan 2026 21:29:30 -0600 Subject: [PATCH 07/71] AMMediaboard: Add NetworkCMDBufferCheck calls for reading of timeout in Select commands. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 33 ++++++++++++++---------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 7effc677c3..d5312c5da1 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -996,7 +996,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u fd_set* exceptfds = nullptr; timeval timeout = {}; - u8* timeout_src = nullptr; + std::optional timeout_offset; fd_set fds; FD_ZERO(&fds); @@ -1018,24 +1018,27 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u if (s_media_buffer_32[3] != 0) { readfds = &fds; - timeout_src = s_network_command_buffer + s_media_buffer_32[3] - NetworkCommandAddress2; + timeout_offset = s_media_buffer_32[3] - NetworkCommandAddress2; } else if (s_media_buffer_32[4] != 0) { writefds = &fds; - timeout_src = s_network_command_buffer + s_media_buffer_32[4] - NetworkCommandAddress2; + timeout_offset = s_media_buffer_32[4] - NetworkCommandAddress2; } else if (s_media_buffer_32[5] != 0) { exceptfds = &fds; - timeout_src = s_network_command_buffer + s_media_buffer_32[5] - NetworkCommandAddress2; + timeout_offset = s_media_buffer_32[5] - NetworkCommandAddress2; } } // Copy timeout if set - if (timeout_src != nullptr) + if (timeout_offset.has_value()) { - std::memcpy(&timeout, timeout_src, sizeof(timeval)); + if (!NetworkCMDBufferCheck(*timeout_offset, sizeof(timeval))) + break; + + std::memcpy(&timeout, s_network_command_buffer + *timeout_offset, sizeof(timeval)); } // BUG: The game sets timeout to two seconds @@ -1237,6 +1240,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u if ((offset >= 0x00400000) && (offset <= 0x600000)) { const u32 fw_offset = offset - 0x00400000; + // TODO: Bounds checking for s_firmware memory.CopyFromEmu(s_firmware + fw_offset, address, length); return 0; } @@ -1578,7 +1582,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u fd_set* exceptfds = nullptr; timeval timeout = {}; - u8* timeout_src = nullptr; + std::optional timeout_offset; fd_set fds; FD_ZERO(&fds); @@ -1600,24 +1604,27 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u if (s_media_buffer_32[11] != 0) { readfds = &fds; - timeout_src = s_network_command_buffer + s_media_buffer_32[11] - NetworkCommandAddress1; + timeout_offset = s_media_buffer_32[11] - NetworkCommandAddress1; } else if (s_media_buffer_32[12] != 0) { writefds = &fds; - timeout_src = s_network_command_buffer + s_media_buffer_32[12] - NetworkCommandAddress1; + timeout_offset = s_media_buffer_32[12] - NetworkCommandAddress1; } else if (s_media_buffer_32[13] != 0) { exceptfds = &fds; - timeout_src = s_network_command_buffer + s_media_buffer_32[13] - NetworkCommandAddress1; + timeout_offset = s_media_buffer_32[13] - NetworkCommandAddress1; } } // Copy timeout if set - if (timeout_src != nullptr) + if (timeout_offset.has_value()) { - std::memcpy(&timeout, timeout_src, sizeof(timeval)); + if (!NetworkCMDBufferCheck(*timeout_offset, sizeof(timeval))) + break; + + std::memcpy(&timeout, s_network_command_buffer + *timeout_offset, sizeof(timeval)); } // BUG?: F-Zero AX Monster calls select with a two second timeout @@ -1629,7 +1636,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u } const int ret = - select(fd + 1, readfds, writefds, exceptfds, timeout_src ? &timeout : nullptr); + select(fd + 1, readfds, writefds, exceptfds, timeout_offset ? &timeout : nullptr); const int err = WSAGetLastError(); NOTICE_LOG_FMT(AMMEDIABOARD_NET, From 2d9d7e4d89465e750c412f8a5641077b23b4b8be Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 18 Jan 2026 21:30:01 -0600 Subject: [PATCH 08/71] AMMediaboard: Replace magic number 15 with a MAX_IPV4_STRING_LENGTH constant. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index d5312c5da1..4ae35aa4f2 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -95,6 +95,8 @@ static u8 s_network_buffer[512 * 1024]; static u8 s_allnet_buffer[4096]; static u8 s_allnet_settings[0x8500]; +static constexpr std::size_t MAX_IPV4_STRING_LENGTH = 15; + constexpr char s_allnet_reply[] = { "uri=http://" "sega.com&host=sega.com&nickname=sega&name=sega&year=2025&month=08&day=16&hour=21&minute=10&" @@ -874,7 +876,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u const char* ip_address = reinterpret_cast(s_network_command_buffer); // IP address shouldn't be longer than 15 - if (strnlen(ip_address, 15) > 15) + // TODO: Shouldn't this look at 16 characters for lack of null-termination? + if (strnlen(ip_address, MAX_IPV4_STRING_LENGTH) > MAX_IPV4_STRING_LENGTH) { ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: Invalid size for address: InetAddr():{}\n", strlen(ip_address)); @@ -1049,7 +1052,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u } const int ret = - select(fd + 1, readfds, writefds, exceptfds, timeout_src ? &timeout : nullptr); + select(fd + 1, readfds, writefds, exceptfds, timeout_offset ? &timeout : nullptr); const int err = WSAGetLastError(); @@ -1147,7 +1150,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u { const u32 net_buffer_offset = s_media_buffer_32[2] - NetworkCommandAddress2; - if (!NetworkCMDBufferCheck(net_buffer_offset, 15)) + if (!NetworkCMDBufferCheck(net_buffer_offset, MAX_IPV4_STRING_LENGTH)) { break; } @@ -1156,7 +1159,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u reinterpret_cast(s_network_command_buffer + net_buffer_offset); NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: modifyMyIPaddr({})\n", - fmt::string_view(ip_address, 15)); + fmt::string_view(ip_address, MAX_IPV4_STRING_LENGTH)); break; } case AMMBCommand::GetLastError: @@ -1678,7 +1681,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u { const u32 net_buffer_offset = s_media_buffer_32[10] - NetworkCommandAddress1; - if (!NetworkCMDBufferCheck(net_buffer_offset, 15)) + if (!NetworkCMDBufferCheck(net_buffer_offset, MAX_IPV4_STRING_LENGTH)) { break; } @@ -1687,7 +1690,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u reinterpret_cast(s_network_command_buffer + net_buffer_offset); NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: modifyMyIPaddr({})\n", - fmt::string_view(ip_address, 15)); + fmt::string_view(ip_address, MAX_IPV4_STRING_LENGTH)); } break; // Empty reply From 71ef512516d66c24109cd9b4d0ad5d97940a389c Mon Sep 17 00:00:00 2001 From: Sepalani Date: Wed, 21 Jan 2026 17:07:08 +0400 Subject: [PATCH 09/71] Triforce: Minor cleanups related to coding style and documentation --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 39 +++++++++++++------ Source/Core/Core/HW/DVD/DVDInterface.cpp | 5 ++- .../Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp | 10 +++-- .../Core/Core/HW/SI/SI_DeviceGCController.h | 3 -- 4 files changed, 38 insertions(+), 19 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 4ae35aa4f2..18d0b220c1 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -214,6 +214,7 @@ static bool SafeCopyToEmu(Memory::MemoryManager& memory, u32 address, const u8* memory.CopyToEmu(address, source + offset, length); return true; } + static bool SafeCopyFromEmu(Memory::MemoryManager& memory, u8* destination, u32 address, u64 destination_size, u32 offset, u32 length) { @@ -453,6 +454,11 @@ static s32 NetDIMMConnect(int fd, sockaddr_in* addr, int len) // Key of Avalon Client if (addr->sin_addr.s_addr == inet_addr("192.168.13.1")) { + // Unlike the other addresses, this one isn't converted to loopback + // because the server and client can't run on the same system. + // + // NOTE: Due to lack of touch-screen support, it's not in a playable state. + // TODO: Make the IP configurable. addr->sin_addr.s_addr = inet_addr("10.0.0.45"); } @@ -538,6 +544,7 @@ static void FileWriteData(Memory::MemoryManager& memory, File::IOFile* file, u32 address, length, span.size()); } } + static void FileReadData(Memory::MemoryManager& memory, File::IOFile* file, u32 seek_pos, u32 address, std::size_t length) { @@ -782,11 +789,18 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; } + // TODO: Check that the current implementation is correct. Currently, `len=0`, + // so `accept()` might not write to `addr` properly. It might be missing the + // following (assuming the code, address and endianness are correct): + // + // // socklen_t might be larger than u32 + // const u32 addr_len = Common::BitCastPtr(s_network_command_buffer + len_off); + // len = addr_len; ret = NetDIMMAccept(fd, &addr, &len); if (len) { - memcpy((s_network_command_buffer + addr_off), &addr, len); - memcpy((s_network_command_buffer + len_off), &len, sizeof(int)); + memcpy(s_network_command_buffer + addr_off, &addr, len); + memcpy(s_network_command_buffer + len_off, &len, sizeof(int)); } } @@ -1016,6 +1030,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; } + // TODO: Ensure host's fd_set is compatible with Triforce's Common::BitCastPtr(s_network_command_buffer + fd_set_offset) = fds; if (s_media_buffer_32[3] != 0) @@ -1073,17 +1088,17 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u const int level = static_cast(s_media_buffer_32[3]); const int optname = static_cast(s_media_buffer_32[4]); const int optlen = static_cast(s_media_buffer_32[6]); + const u32 optval_offset = s_media_buffer_32[5] - NetworkCommandAddress2; - if (!NetworkCMDBufferCheck(s_media_buffer_32[5] - NetworkCommandAddress2, optlen)) + if (!NetworkCMDBufferCheck(optval_offset, optlen)) { break; } - const char* optval = reinterpret_cast(s_network_command_buffer + - s_media_buffer_32[5] - NetworkCommandAddress2); + const char* optval = reinterpret_cast(s_network_command_buffer + optval_offset); + // TODO: Ensure parameters are compatible with host's setsockopt const int ret = setsockopt(fd, level, optname, optval, optlen); - const int err = WSAGetLastError(); NOTICE_LOG_FMT(AMMEDIABOARD_NET, @@ -1240,7 +1255,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u if (s_firmware_map) { // Firmware memory (2MB) - if ((offset >= 0x00400000) && (offset <= 0x600000)) + if (offset >= 0x00400000 && offset <= 0x600000) { const u32 fw_offset = offset - 0x00400000; // TODO: Bounds checking for s_firmware @@ -1602,6 +1617,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; } + // TODO: Ensure host's fd_set is compatible with Triforce's Common::BitCastPtr(s_network_command_buffer + fd_set_offset) = fds; if (s_media_buffer_32[11] != 0) @@ -1657,15 +1673,16 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u const int level = static_cast(s_media_buffer_32[11]); const int optname = static_cast(s_media_buffer_32[12]); const int optlen = static_cast(s_media_buffer_32[14]); + const u32 optval_offset = s_media_buffer_32[13] - NetworkCommandAddress1; - if (!NetworkCMDBufferCheck(s_media_buffer_32[13] - NetworkCommandAddress1, optlen)) + if (!NetworkCMDBufferCheck(optval_offset, optlen)) { break; } - const char* optval = reinterpret_cast( - s_network_command_buffer + s_media_buffer_32[13] - NetworkCommandAddress1); + const char* optval = reinterpret_cast(s_network_command_buffer + optval_offset); + // TODO: Ensure parameters are compatible with host's setsockopt const int ret = setsockopt(fd, level, optname, optval, optlen); const int err = WSAGetLastError(); @@ -1745,7 +1762,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; } - const u8* data = s_network_buffer + off + addr - NetworkBufferAddress2; + const u8* data = s_network_buffer + (off + addr - NetworkBufferAddress2); for (u32 i = 0; i < 0x20; i += 0x10) { diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index edbd9bdc6d..0801b8d26a 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -289,8 +289,9 @@ void DVDInterface::Init() // The Triforce IPL expects the cover to be closed m_DICVR.Hex = 0; - m_DICFG.Hex |= 8; /* The Triforce IPL checks this bit - to set the physical memory to either 50MB(unset) or 24MB(set) */ + // The Triforce IPL checks this bit to set the physical memory to + // either 50MB(unset) or 24MB(set) + m_DICFG.Hex |= 8; } } diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp index fce4ed9f63..4fb5c40d2a 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp @@ -281,7 +281,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) case GCAMCommand::SerialNumber: { NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x11, {:02x} (READ SERIAL NR)", - *data_in++); + *data_in); + data_in++; data_out[data_offset++] = gcam_command; data_out[data_offset++] = 16; @@ -310,7 +311,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) break; case GCAMCommand::FirmVersion: NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x15, {:02x} (READ FIRM VERSION)", - *data_in++); + *data_in); + data_in++; data_out[data_offset++] = gcam_command; data_out[data_offset++] = 0x02; @@ -320,7 +322,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) break; case GCAMCommand::FPGAVersion: NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x16, {:02x} (READ FPGA VERSION)", - *data_in++); + *data_in); + data_in++; data_out[data_offset++] = gcam_command; data_out[data_offset++] = 0x02; @@ -1021,6 +1024,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) data_out[data_offset++] = 0x02; // 0x02 data_out[data_offset++] = 0x00; // 0x03 break; + case CARDCommand::Erase: // TODO: CARDCommand::Erase is not handled. ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "CARDCommand::Erase is not handled."); break; diff --git a/Source/Core/Core/HW/SI/SI_DeviceGCController.h b/Source/Core/Core/HW/SI/SI_DeviceGCController.h index f4dc5768c9..19b5bdb14b 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceGCController.h +++ b/Source/Core/Core/HW/SI/SI_DeviceGCController.h @@ -3,11 +3,8 @@ #pragma once -#include #include -#include "Common/CommonTypes.h" -#include "Common/Flag.h" #include "Core/HW/GCPad.h" #include "Core/HW/SI/SI_Device.h" #include "InputCommon/GCPadStatus.h" From 85707891cfcbb680c04780d7a5b7b6a60910d980 Mon Sep 17 00:00:00 2001 From: Sepalani Date: Wed, 21 Jan 2026 18:28:50 +0400 Subject: [PATCH 10/71] Triforce: Address some logging and error checks --- Source/Core/Core/Boot/Boot_BS2Emu.cpp | 5 ++ Source/Core/Core/HW/DVD/AMMediaboard.cpp | 46 +++++++++++++------ .../Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp | 20 ++++++-- 3 files changed, 54 insertions(+), 17 deletions(-) diff --git a/Source/Core/Core/Boot/Boot_BS2Emu.cpp b/Source/Core/Core/Boot/Boot_BS2Emu.cpp index 31ce5c8c8b..b462e89edc 100644 --- a/Source/Core/Core/Boot/Boot_BS2Emu.cpp +++ b/Source/Core/Core/Boot/Boot_BS2Emu.cpp @@ -237,6 +237,11 @@ bool CBoot::RunApploader(Core::System& system, const Core::CPUThreadGuard& guard // Load game into RAM, like on the actual Triforce u8* dimm = AMMediaboard::InitDIMM(disc_size); + if (!dimm) + { + ERROR_LOG_FMT(BOOT, "AMMediaboard::InitDIMM failed"); + return false; + } volume.Read(0, disc_size, dimm, DiscIO::PARTITION_NONE); diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 18d0b220c1..8167d49f19 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -14,6 +14,7 @@ #include "Common/FileUtil.h" #include "Common/IOFile.h" #include "Common/Logging/Log.h" +#include "Common/Network.h" #include "Core/Boot/Boot.h" #include "Core/BootManager.h" @@ -245,7 +246,14 @@ static SOCKET socket_(int af, int type, int protocol) { if (s_sockets[i] == SOCKET_ERROR) { - s_sockets[i] = socket(af, type, protocol); + const s32 host_fd = socket(af, type, protocol); + if (host_fd < 0) + { + ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: failed to create socket ({})", + Common::StrNetworkError()); + return SOCKET_ERROR; + } + s_sockets[i] = host_fd; return i; } } @@ -372,14 +380,11 @@ u8* InitDIMM(u32 size) if (size == 0) return nullptr; + s_dimm_disc.reset(size); if (s_dimm_disc.empty()) { - s_dimm_disc.reset(size); - if (s_dimm_disc.empty()) - { - PanicAlertFmt("Failed to allocate DIMM memory."); - return nullptr; - } + PanicAlertFmt("Failed to allocate DIMM memory."); + return nullptr; } s_firmware_map = false; @@ -409,6 +414,8 @@ static s32 NetDIMMAccept(int fd, sockaddr* addr, socklen_t* len) return client_sock; } + ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: accept() failed in NetDIMMAccept ({})", + Common::StrNetworkError()); s_last_error = SOCKET_ERROR; return SOCKET_ERROR; } @@ -420,7 +427,8 @@ static s32 NetDIMMAccept(int fd, sockaddr* addr, socklen_t* len) } else { - // select() failed + ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: select() failed in NetDIMMAccept ({})", + Common::StrNetworkError()); s_last_error = SOCKET_ERROR; } @@ -494,6 +502,8 @@ static s32 NetDIMMConnect(int fd, sockaddr_in* addr, int len) } else { + ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: getsockopt() failed in NetDIMMConnect ({})", + Common::StrNetworkError()); s_last_error = SOCKET_ERROR; ret = SOCKET_ERROR; } @@ -506,7 +516,8 @@ static s32 NetDIMMConnect(int fd, sockaddr_in* addr, int len) } else { - // select() failed + ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: select() failed in NetDIMMConnect ({})", + Common::StrNetworkError()); s_last_error = SOCKET_ERROR; ret = SOCKET_ERROR; } @@ -514,6 +525,8 @@ static s32 NetDIMMConnect(int fd, sockaddr_in* addr, int len) else if (ret == SOCKET_ERROR) { // Immediate failure (e.g. WSAECONNREFUSED) + ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: NetDIMMConnect failed, connect() = {} ({})", err, + Common::DecodeNetworkError(err)); s_last_error = ret; } else @@ -799,8 +812,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u ret = NetDIMMAccept(fd, &addr, &len); if (len) { - memcpy(s_network_command_buffer + addr_off, &addr, len); - memcpy(s_network_command_buffer + len_off, &len, sizeof(int)); + memcpy(s_network_command_buffer + addr_off, &addr, sizeof(sockaddr)); + memcpy(s_network_command_buffer + len_off, &len, sizeof(u32)); } } @@ -815,7 +828,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u const u32 off = s_media_buffer_32[3] - NetworkCommandAddress2; const u32 len = s_media_buffer_32[4]; - if (!NetworkCMDBufferCheck(off, len)) + if (!NetworkCMDBufferCheck(off, std::max(sizeof(sockaddr_in), len))) { break; } @@ -836,7 +849,12 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u const int err = WSAGetLastError(); if (ret < 0) - PanicAlertFmt("Socket Bind Failed with{0}", err); + { + const auto err_msg = Common::DecodeNetworkError(err); + PanicAlertFmt("Failed to bind socket (error {}: {})", err, err_msg); + ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: AMMBCommand::Bind failed, bind() = {} ({})", err, + err_msg); + } NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: bind( {}, ({},{:08x}:{}), {} ):{} ({})\n", fd, addr.sin_family, addr.sin_addr.s_addr, Common::swap16(addr.sin_port), len, @@ -867,7 +885,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u const u32 off = s_media_buffer_32[3] - NetworkCommandAddress2; const u32 len = s_media_buffer_32[4]; - if (!NetworkCMDBufferCheck(off, len)) + if (!NetworkCMDBufferCheck(off, std::max(sizeof(sockaddr_in), len))) { break; } diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp index 90577ec8cc..e5566ce2ba 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp @@ -80,7 +80,7 @@ CEXIBaseboard::CEXIBaseboard(Core::System& system) : IEXIDevice(system) if (AMMediaboard::GetGameType() == VirtuaStriker4 || AMMediaboard::GetGameType() == GekitouProYakyuu) { - if (m_backup.GetSize()) + if (m_backup.GetSize() >= 0x20C + 0x1F4) { Common::UniqueBuffer data(m_backup.GetSize()); m_backup.ReadBytes(data.data(), data.size()); @@ -133,11 +133,18 @@ void CEXIBaseboard::DMAWrite(u32 addr, u32 size) { const auto& system = Core::System::GetInstance(); const auto& memory = system.GetMemory(); + auto span = memory.GetSpanForAddress(addr); + if (span.size() < size) + { + ERROR_LOG_FMT(SP1, "AM-BB: Backup DMA Write overflow: address=0x{:08x}, length={}, span={}", + addr, size, span.size()); + return; + } NOTICE_LOG_FMT(SP1, "AM-BB: COMMAND: Backup DMA Write: {:08x} {:x}", addr, size); m_backup.Seek(m_backup_offset, File::SeekOrigin::Begin); - m_backup.WriteBytes(memory.GetSpanForAddress(addr).data(), size); + m_backup.WriteBytes(span.data(), size); m_backup.Flush(); } @@ -145,11 +152,18 @@ void CEXIBaseboard::DMARead(u32 addr, u32 size) { const auto& system = Core::System::GetInstance(); const auto& memory = system.GetMemory(); + auto span = memory.GetSpanForAddress(addr); + if (span.size() < size) + { + ERROR_LOG_FMT(SP1, "AM-BB: Backup DMA Read overflow: address=0x{:08x}, length={}, span={}", + addr, size, span.size()); + return; + } NOTICE_LOG_FMT(SP1, "AM-BB: COMMAND: Backup DMA Read: {:08x} {:x}", addr, size); m_backup.Seek(m_backup_offset, File::SeekOrigin::Begin); - m_backup.ReadBytes(memory.GetSpanForAddress(addr).data(), size); + m_backup.ReadBytes(span.data(), size); } void CEXIBaseboard::TransferByte(u8& byte) From fcb4d27f1ad94299429d9f409c660888969636cd Mon Sep 17 00:00:00 2001 From: Sepalani Date: Wed, 21 Jan 2026 19:04:43 +0400 Subject: [PATCH 11/71] AMBaseboard: Convert union ICCommand into struct --- .../Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp | 5 ++-- Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h | 27 +++++++++---------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp index 4fb5c40d2a..2c5beca98e 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp @@ -174,11 +174,12 @@ void CSIDevice_AMBaseboard::ICCardSendReply(ICCommand* iccommand, u8* buffer, u3 { iccommand->status = Common::swap16(iccommand->status); - const u8 crc = CheckSumXOR(iccommand->data + 2, iccommand->pktlen - 1); + const auto iccommand_data = reinterpret_cast(iccommand); + const u8 crc = CheckSumXOR(iccommand_data + 2, iccommand->pktlen - 1); for (u32 i = 0; i < iccommand->pktlen + 1; ++i) { - buffer[(*length)++] = iccommand->data[i]; + buffer[(*length)++] = iccommand_data[i]; } buffer[(*length)++] = crc; diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h index 35ec5c4448..acbf57e21b 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h @@ -239,23 +239,20 @@ private: ProgramVersion = 0x76, }; - union ICCommand + // NOTE: Used to be an union with `u8 data[81 + 4 + 4 + 4]` + // TODO: Should the struct be packed? + struct ICCommand { - u8 data[81 + 4 + 4 + 4] = {}; + u32 pktcmd : 8; + u32 pktlen : 8; + u32 fixed : 8; + u32 command : 8; + u32 flag : 8; + u32 length : 8; + u32 status : 16; - struct - { - u32 pktcmd : 8; - u32 pktlen : 8; - u32 fixed : 8; - u32 command : 8; - u32 flag : 8; - u32 length : 8; - u32 status : 16; - - u8 extdata[81]; - u32 extlen; - }; + u8 extdata[81]; + u32 extlen; }; u8 m_last[2][0x80] = {}; From 77cb65313da62726816d4f7fe3f42e8e3c85bdf6 Mon Sep 17 00:00:00 2001 From: Sepalani Date: Wed, 21 Jan 2026 23:37:43 +0400 Subject: [PATCH 12/71] SI_DeviceAMBaseboard: Add validate_data_in_out helper --- .../Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp | 204 +++++++++++++++++- 1 file changed, 193 insertions(+), 11 deletions(-) diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp index 2c5beca98e..4f820bbf1e 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp @@ -235,7 +235,20 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) data_out[data_offset++] = 1; u8* data_in = buffer + 2; - const u8* data_in_end = buffer + buffer[buffer_position] + 2; + u8* const data_in_end = buffer + buffer[buffer_position] + 2; + + // Helper to check that iterating over data n times is safe, + // i.e. *data++ at most lead to data.end() + auto validate_data_in_out = [&](u32 n_in, u32 n_out, std::string_view command) -> bool { + if (data_in + n_in > data_in_end) + ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_in overflow in {}", command); + else if (data_offset + n_out > data_out.size()) + ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_out overflow in {}", command); + else + return true; + data_in = data_in_end; + return false; + }; while (data_in < data_in_end) { @@ -244,6 +257,9 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) { case GCAMCommand::StatusSwitches: { + if (!validate_data_in_out(1, 4, "StatusSwitches")) + break; + const u8 status = *data_in++; DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x10, {:02x} (READ STATUS&SWITCHES)", status); @@ -281,6 +297,9 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } case GCAMCommand::SerialNumber: { + if (!validate_data_in_out(1, 18, "SerialNumber")) + break; + NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x11, {:02x} (READ SERIAL NR)", *data_in); data_in++; @@ -293,6 +312,9 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) break; } case GCAMCommand::Unknown_12: + if (!validate_data_in_out(2, 2, "Unknown_12")) + break; + NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x12, {:02x} {:02x}", data_in[0], data_in[1]); @@ -302,6 +324,9 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) data_in += 2; break; case GCAMCommand::Unknown_14: + if (!validate_data_in_out(2, 2, "Unknown_14")) + break; + NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x14, {:02x} {:02x}", data_in[0], data_in[1]); @@ -311,6 +336,9 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) data_in += 2; break; case GCAMCommand::FirmVersion: + if (!validate_data_in_out(1, 4, "FirmVersion")) + break; + NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x15, {:02x} (READ FIRM VERSION)", *data_in); data_in++; @@ -322,6 +350,9 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) data_out[data_offset++] = 0x26; break; case GCAMCommand::FPGAVersion: + if (!validate_data_in_out(1, 4, "FPGAVersion")) + break; + NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x16, {:02x} (READ FPGA VERSION)", *data_in); data_in++; @@ -334,6 +365,9 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) break; case GCAMCommand::RegionSettings: { + if (!validate_data_in_out(5, 0x16, "RegionSettings")) + break; + // Used by SegaBoot for region checks (dev mode skips this check) // In some games this also controls the displayed language NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, @@ -353,6 +387,9 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) // Note: Always sends three bytes even though size is set to two case GCAMCommand::Unknown_21: { + if (!validate_data_in_out(4, 0, "Unknown_21")) + break; + DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x21, {:02x}, {:02x}, {:02x}, {:02x}", data_in[0], data_in[1], data_in[2], data_in[3]); data_in += 4; @@ -362,14 +399,24 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) // Note: Always sends six bytes case GCAMCommand::Unknown_22: { + if (!validate_data_in_out(7, 0, "Unknown_22")) + break; + DEBUG_LOG_FMT( SERIALINTERFACE_AMBB, "GC-AM: Command 0x22, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}, {:02x}", data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5], data_in[6]); - data_in += data_in[0] + 1; + + const u32 in_size = data_in[0] + 1; + if (!validate_data_in_out(in_size, 0, "Unknown_22")) + break; + data_in += in_size; } break; case GCAMCommand::Unknown_23: + if (!validate_data_in_out(2, 2, "Unknown_23")) + break; + DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x23, {:02x} {:02x}", data_in[0], data_in[1]); @@ -379,6 +426,9 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) data_in += 2; break; case GCAMCommand::Unknown_24: + if (!validate_data_in_out(2, 2, "Unknown_24")) + break; + DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x24, {:02x} {:02x}", data_in[0], data_in[1]); @@ -389,9 +439,15 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) break; case GCAMCommand::SerialA: { + if (!validate_data_in_out(1, 0, "SerialA")) + break; + u32 length = *data_in++; if (length) { + if (!validate_data_in_out(13, 0, "SerialA")) + break; + INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31, {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} " "{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", @@ -403,6 +459,9 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) if (AMMediaboard::GetGameType() == MarioKartGP || AMMediaboard::GetGameType() == MarioKartGP2) { + if (!validate_data_in_out(10, 2, "SerialA (Wheel)")) + break; + INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31, (WHEEL) {:02x}{:02x} {:02x}{:02x} {:02x} {:02x} " "{:02x} {:02x} {:02x} {:02x}", @@ -415,12 +474,16 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) switch (m_wheel_init) { case 0: + if (!validate_data_in_out(0, 3, "SerialA (Wheel)")) + break; data_out[data_offset++] = 'E'; // Error data_out[data_offset++] = '0'; data_out[data_offset++] = '0'; m_wheel_init++; break; case 1: + if (!validate_data_in_out(0, 3, "SerialA (Wheel)")) + break; data_out[data_offset++] = 'C'; // Power Off data_out[data_offset++] = '0'; data_out[data_offset++] = '6'; @@ -431,6 +494,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } break; case 2: + if (!validate_data_in_out(0, 3, "SerialA (Wheel)")) + break; data_out[data_offset++] = 'C'; // Power On data_out[data_offset++] = '0'; data_out[data_offset++] = '1'; @@ -442,7 +507,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) // u16 CenteringForce= ptr(6); // u16 FrictionForce = ptr(8); // u16 Roll = ptr(10); - + if (!validate_data_in_out(length, 0, "SerialA (Wheel)")) + break; data_in += length; break; } @@ -450,10 +516,14 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) // Serial - Unknown if (AMMediaboard::GetGameType() == GekitouProYakyuu) { + if (!validate_data_in_out(sizeof(u32), 0, "SerialA (Unknown)")) + break; const u32 serial_command = Common::BitCastPtr(data_in); if (serial_command == 0x00001000) { + if (!validate_data_in_out(0, 5, "SerialA (Unknown)")) + break; data_out[data_offset++] = gcam_command; data_out[data_offset++] = 0x03; data_out[data_offset++] = 1; @@ -461,6 +531,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) data_out[data_offset++] = 3; } + if (!validate_data_in_out(length, 0, "SerialA (Unknown)")) + break; data_in += length; break; } @@ -470,6 +542,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) AMMediaboard::GetGameType() == VirtuaStriker4_2006 || AMMediaboard::GetGameType() == KeyOfAvalon) { + if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)")) + break; u32 serial_command = data_in[1]; ICCommand icco; @@ -496,6 +570,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) "GC-AM: Command 25 (IC-CARD) Write Pages: Off:{:x} Size:{:x} PSize:{:x}", m_ic_write_offset, m_ic_write_size, size); + if (!validate_data_in_out(2 + size, 0, "SerialA (IC-CARD)")) + break; memcpy(m_ic_write_buffer + m_ic_write_offset, data_in + 2, size); m_ic_write_offset += size; @@ -517,6 +593,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) ICCardSendReply(&icco, data_out.data(), &data_offset); } + if (!validate_data_in_out(length, 0, "SerialA (IC-CARD)")) + break; data_in += length; break; } @@ -579,6 +657,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) case ICCARDCommand::ReadPage: case ICCARDCommand::ReadUseCount: { + if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)")) + break; const u16 page = Common::swap16(data_in + 6) & 0xFF; // 255 is max page icco.extlen = 8; @@ -593,6 +673,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } case ICCARDCommand::WritePage: { + if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)")) + break; const u16 page = Common::swap16(data_in + 8) & 0xFF; // 255 is max page // Write only one page @@ -602,6 +684,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } else { + if (!validate_data_in_out(18, 0, "SerialA (IC-CARD)")) + break; memcpy(m_ic_card_data + page * 8, data_in + 10, 8); } @@ -611,6 +695,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } case ICCARDCommand::DecreaseUseCount: { + if (!validate_data_in_out(8, 0, "SerialA (IC-CARD)")) + break; const u16 page = Common::swap16(data_in + 6) & 0xFF; // 255 is max page icco.extlen = 2; @@ -630,6 +716,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } case ICCARDCommand::ReadPages: { + if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)")) + break; const u16 page = Common::swap16(data_in + 6) & 0xFF; // 255 is max page const u16 count = Common::swap16(data_in + 8); @@ -654,6 +742,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } case ICCARDCommand::WritePages: { + if (!validate_data_in_out(10, 0, "SerialA (IC-CARD)")) + break; const u16 pksize = length; const u16 size = Common::swap16(data_in + 2); const u16 page = Common::swap16(data_in + 6) & 0xFF; // 255 is max page @@ -677,6 +767,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } else { + if (!validate_data_in_out(13 + count * 8, 0, "SerialA (IC-CARD)")) + break; memcpy(m_ic_card_data + page * 8, data_in + 13, count * 8); } } @@ -688,6 +780,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) // VirtuaStriker 4 splits the writes over multiple packets else { + if (!validate_data_in_out(2 + pksize, 0, "SerialA (IC-CARD)")) + break; memcpy(m_ic_write_buffer, data_in + 2, pksize); m_ic_write_offset += pksize; m_ic_write_size = size; @@ -696,6 +790,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } default: // Handle Deck Reader commands + if (!validate_data_in_out(1, 0, "SerialA (DECK READER)")) + break; serial_command = data_in[0]; icco.command = serial_command; icco.flag = 0; @@ -805,6 +901,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) break; default: + if (!validate_data_in_out(14, 0, "SerialA (DECK READER)")) + break; WARN_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 0x31 (IC-Card) {:02x} {:02x} {:02x} {:02x} {:02x} " "{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", @@ -816,8 +914,12 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) break; } + if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)")) + break; ICCardSendReply(&icco, data_out.data(), &data_offset); + if (!validate_data_in_out(2, 0, "SerialA (IC-CARD)")) + break; data_in += length; break; } @@ -828,6 +930,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) { // All commands are OR'd with 0x80 // Last byte is checksum which we don't care about + if (!validate_data_in_out(command_offset + 4, 0, "SerialA")) + break; const u32 serial_command = Common::swap32(data_in + command_offset) ^ 0x80000000; if (AMMediaboard::GetGameType() == FZeroAX || @@ -844,6 +948,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) if (serial_command == 0x801000) { + if (!validate_data_in_out(0, 4, "SerialA")) + break; data_out[data_offset++] = 0x31; data_out[data_offset++] = 0x02; data_out[data_offset++] = 0xFF; @@ -927,6 +1033,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) if (length == 0) { + if (!validate_data_in_out(length, 2, "SerialA")) + break; data_out[data_offset++] = gcam_command; data_out[data_offset++] = 0x00; } @@ -938,18 +1046,30 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) m_motor_reply[0] = gcam_command; m_motor_reply[1] = length; // Same out as in size - memcpy(data_out.data() + data_offset, m_motor_reply, m_motor_reply[1] + 2); - data_offset += m_motor_reply[1] + 2; + const u32 reply_size = m_motor_reply[1] + 2; + if (!validate_data_in_out(length, reply_size, "SerialA")) + break; + + memcpy(data_out.data() + data_offset, m_motor_reply, reply_size); + data_offset += reply_size; } } + if (!validate_data_in_out(length, 0, "SerialA")) + { + break; + } data_in += length; break; } case GCAMCommand::SerialB: { DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 32 (CARD-Interface)"); + if (!validate_data_in_out(1, 0, "SerialB")) + break; const u32 length = *data_in++; + if (!validate_data_in_out(length, 0, "SerialB")) + break; if (length) { // Send Card Reply @@ -957,6 +1077,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) { if (m_card_read_length) { + if (!validate_data_in_out(0, 1, "SerialB")) + break; data_out[data_offset++] = gcam_command; u32 read_length = m_card_read_length - m_card_read; @@ -965,8 +1087,12 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) read_length = std::min(read_length, 0x2F); } + if (!validate_data_in_out(0, 1, "SerialB")) + break; data_out[data_offset++] = read_length; // 0x2F (max size per packet) + if (!validate_data_in_out(0, read_length, "SerialB")) + break; memcpy(data_out.data() + data_offset, m_card_read_packet + m_card_read, read_length); @@ -980,6 +1106,9 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) break; } + if (!validate_data_in_out(0, 5, "SerialB")) + break; + data_out[data_offset++] = gcam_command; const u32 command_length_offset = data_offset; data_out[data_offset++] = 0x00; // len @@ -994,10 +1123,14 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) switch (CARDCommand(m_card_command)) { case CARDCommand::Init: + if (!validate_data_in_out(0, 2, "SerialB")) + break; data_out[data_offset++] = 0x00; // 0x02 data_out[data_offset++] = 0x30; // 0x03 break; case CARDCommand::GetState: + if (!validate_data_in_out(0, 2, "SerialB")) + break; data_out[data_offset++] = 0x20 | m_card_bit; // 0x02 // bit 0: Please take your card @@ -1005,23 +1138,33 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) data_out[data_offset++] = 0x00; // 0x03 break; case CARDCommand::Read: + if (!validate_data_in_out(0, 2, "SerialB")) + break; data_out[data_offset++] = 0x02; // 0x02 data_out[data_offset++] = 0x53; // 0x03 break; case CARDCommand::IsPresent: + if (!validate_data_in_out(0, 2, "SerialB")) + break; data_out[data_offset++] = 0x22; // 0x02 data_out[data_offset++] = 0x30; // 0x03 break; case CARDCommand::Write: + if (!validate_data_in_out(0, 2, "SerialB")) + break; data_out[data_offset++] = 0x02; // 0x02 data_out[data_offset++] = 0x00; // 0x03 break; case CARDCommand::SetPrintParam: case CARDCommand::RegisterFont: + if (!validate_data_in_out(0, 2, "SerialB")) + break; data_out[data_offset++] = 0x00; // 0x02 data_out[data_offset++] = 0x00; // 0x03 break; case CARDCommand::WriteInfo: + if (!validate_data_in_out(0, 2, "SerialB")) + break; data_out[data_offset++] = 0x02; // 0x02 data_out[data_offset++] = 0x00; // 0x03 break; @@ -1030,6 +1173,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "CARDCommand::Erase is not handled."); break; case CARDCommand::Eject: + if (!validate_data_in_out(0, 2, "SerialB")) + break; if (AMMediaboard::GetGameType() == FZeroAX) { data_out[data_offset++] = 0x01; // 0x02 @@ -1041,19 +1186,27 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) data_out[data_offset++] = 0x30; // 0x03 break; case CARDCommand::Clean: + if (!validate_data_in_out(0, 2, "SerialB")) + break; data_out[data_offset++] = 0x02; // 0x02 data_out[data_offset++] = 0x00; // 0x03 break; case CARDCommand::Load: + if (!validate_data_in_out(0, 2, "SerialB")) + break; data_out[data_offset++] = 0x02; // 0x02 data_out[data_offset++] = 0x30; // 0x03 break; case CARDCommand::SetShutter: + if (!validate_data_in_out(0, 2, "SerialB")) + break; data_out[data_offset++] = 0x00; // 0x02 data_out[data_offset++] = 0x00; // 0x03 break; } + if (!validate_data_in_out(0, 3, "SerialB")) + break; data_out[data_offset++] = 0x30; // 0x04 data_out[data_offset++] = 0x00; // 0x05 @@ -1061,10 +1214,14 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) data_out[checksum_start] = data_offset - checksum_start; // 0x00 len + if (!validate_data_in_out(0, 1, "SerialB")) + break; data_out[data_offset] = 0; // 0x07 for (u32 i = 0; i < data_out[checksum_start]; ++i) data_out[data_offset] ^= data_out[checksum_start + i]; + if (!validate_data_in_out(0, 2, "SerialB")) + break; data_offset++; data_out[command_length_offset] = data_out[checksum_start] + 2; @@ -1315,6 +1472,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } } + if (!validate_data_in_out(0, 3, "SerialB")) + break; data_out[data_offset++] = 0x32; data_out[data_offset++] = 0x01; // len data_out[data_offset++] = 0x06; // OK @@ -1322,6 +1481,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } else { + if (!validate_data_in_out(0, 2, "SerialB")) + break; data_out[data_offset++] = gcam_command; data_out[data_offset++] = 0x00; // len } @@ -1331,6 +1492,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) case GCAMCommand::JVSIOA: case GCAMCommand::JVSIOB: { + if (!validate_data_in_out(7, 0, "JVSIO")) + break; DEBUG_LOG_FMT( SERIALINTERFACE_JVSIO, "GC-AM: Command {:02x}, {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} (JVS IO)", @@ -2067,11 +2230,22 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) break; } case GCAMCommand::Unknown_60: + { + if (!validate_data_in_out(3, 0, "Unknown_60")) + break; + NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x60, {:02x} {:02x} {:02x}", data_in[0], data_in[1], data_in[2]); - data_in += data_in[0] + 1; - break; + + const u32 in_size = data_in[0] + 1; + if (!validate_data_in_out(in_size, 0, "Unknown_60")) + break; + data_in += in_size; + } + break; default: + if (!validate_data_in_out(5, 0, fmt::format("Unknown_{}", gcam_command))) + break; ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command {:02x} (unknown) {:02x} {:02x} {:02x} {:02x} {:02x}", gcam_command, data_in[0], data_in[1], data_in[2], data_in[3], data_in[4]); @@ -2085,12 +2259,20 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) data_out[1] = data_offset - 2; checksum = 0; - for (int i = 0; i < 0x7F; ++i) + if (buffer_length >= 0x80) { - checksum += data_in[i] = data_out[i]; + for (int i = 0; i < 0x7F; ++i) + { + checksum += data_in[i] = data_out[i]; + } + data_in[0x7f] = ~checksum; + DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "Command send back: {}", + HexDump(data_out.data(), 0x7F)); + } + else + { + ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: overflow in GCAM_Command's checksum"); } - data_in[0x7f] = ~checksum; - DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "Command send back: {}", HexDump(data_out.data(), 0x7F)); SwapBuffers(buffer, &buffer_length); From 1c54e41a60ba0a894118c2f6f803c39b4d50f882 Mon Sep 17 00:00:00 2001 From: Sepalani Date: Fri, 23 Jan 2026 03:17:19 +0400 Subject: [PATCH 13/71] SI_DeviceAMBaseboard: Improve logging and validation edge cases --- .../Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp index 4f820bbf1e..9b9d620b23 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp @@ -246,6 +246,13 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_out overflow in {}", command); else return true; + ERROR_LOG_FMT(SERIALINTERFACE_AMBB, + "Overflow details:\n" + " - data_in(begin={}, current={}, end={}, n_in={})\n" + " - data_out(offset={}, size={}, n_out={})\n" + " - buffer(position={}, length={})", + fmt::ptr(buffer + 2), fmt::ptr(data_in), fmt::ptr(data_in_end), n_in, + data_offset, data_out.size(), n_out, buffer_position, buffer_length); data_in = data_in_end; return false; }; @@ -445,15 +452,11 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) u32 length = *data_in++; if (length) { - if (!validate_data_in_out(13, 0, "SerialA")) + if (!validate_data_in_out(length, 0, "SerialA")) break; - INFO_LOG_FMT(SERIALINTERFACE_AMBB, - "GC-AM: Command 0x31, {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} " - "{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", - length, data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], - data_in[5], data_in[6], data_in[7], data_in[8], data_in[9], data_in[10], - data_in[11], data_in[12]); + INFO_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x31, length=0x{:02x}, hexdump:\n{}", + length, HexDump(data_in, length)); // Serial - Wheel if (AMMediaboard::GetGameType() == MarioKartGP || @@ -563,6 +566,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) { const u32 size = data_in[1]; + if (!validate_data_in_out(size + 2, 0, "SerialA (IC-CARD)")) + break; DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "Command: {}", HexDump(data_in, size + 2)); INFO_LOG_FMT( @@ -570,8 +575,6 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) "GC-AM: Command 25 (IC-CARD) Write Pages: Off:{:x} Size:{:x} PSize:{:x}", m_ic_write_offset, m_ic_write_size, size); - if (!validate_data_in_out(2 + size, 0, "SerialA (IC-CARD)")) - break; memcpy(m_ic_write_buffer + m_ic_write_offset, data_in + 2, size); m_ic_write_offset += size; @@ -1492,13 +1495,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) case GCAMCommand::JVSIOA: case GCAMCommand::JVSIOB: { - if (!validate_data_in_out(7, 0, "JVSIO")) + if (!validate_data_in_out(4, 0, "JVSIO")) break; - DEBUG_LOG_FMT( - SERIALINTERFACE_JVSIO, - "GC-AM: Command {:02x}, {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} (JVS IO)", - gcam_command, data_in[0], data_in[1], data_in[2], data_in[3], data_in[4], data_in[5], - data_in[6]); JVSIOMessage message; @@ -1512,6 +1510,11 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) frame_len = std::min(frame_len, sizeof(jvs_buf)); + if (!validate_data_in_out(frame_len, 0, "JVSIO")) + break; + DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "GC-AM: Command {:02x} (JVS IO), hexdump:\n{}", + gcam_command, HexDump(data_in, frame_len)); + memcpy(jvs_buf, frame, frame_len); // Extract node and payload pointers From bfd83f3a7794b96fcb8aa8b6f3226f2d9563bd26 Mon Sep 17 00:00:00 2001 From: Sepalani Date: Sun, 25 Jan 2026 21:07:23 +0400 Subject: [PATCH 14/71] SI_DeviceAMBaseboard: Add validate_jvs_io helper --- .../Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp | 75 +++++++++++++++++-- 1 file changed, 67 insertions(+), 8 deletions(-) diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp index 9b9d620b23..6b9d8c992f 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp @@ -1502,7 +1502,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) static int delay = 0; - const u8* frame = &data_in[0]; + const u8* const frame = &data_in[0]; const u8 nr_bytes = frame[3]; // Byte after E0 xx u32 frame_len = nr_bytes + 3; // Header(2) + length byte + payload + checksum @@ -1520,15 +1520,34 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) // Extract node and payload pointers u8 node = jvs_buf[2]; u8* jvs_io = jvs_buf + 4; // First payload byte - const u8* jvs_end = jvs_buf + frame_len; // One byte before checksum + u8* const jvs_end = jvs_buf + frame_len; // One byte before checksum + u8* const jvs_begin = jvs_io; message.Start(0); message.AddData(1); + // Helper to check that iterating over jvs_io n times is safe, + // i.e. *jvs_io++ at most lead to jvs_end + auto validate_jvs_io = [&](u32 n, std::string_view command) -> bool { + if (jvs_io + n > jvs_end) + ERROR_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: overflow in {}", command); + else + return true; + ERROR_LOG_FMT(SERIALINTERFACE_JVSIO, + "Overflow details:\n" + " - jvs_io(begin={}, current={}, end={}, n={})\n" + " - delay={}, node={}\n" + " - frame(begin={}, len={})", + fmt::ptr(jvs_begin), fmt::ptr(jvs_io), fmt::ptr(jvs_end), n, delay, node, + fmt::ptr(frame), frame_len); + jvs_io = jvs_end; + return false; + }; + // Now iterate over the payload while (jvs_io < jvs_end) { - int jvsio_command = *jvs_io++; + const u8 jvsio_command = *jvs_io++; DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO:node={}, command={:02x}", node, jvsio_command); @@ -1663,15 +1682,25 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x14, CheckFunctionality"); break; case JVSIOCommand::MainID: - while (*jvs_io++) + { + const u8* const main_id = jvs_io; + while (jvs_io < jvs_end && *jvs_io++) { } + if (main_id < jvs_io) + { + DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command MainId:\n{}", + HexDump(main_id, jvs_io - main_id)); + } message.AddData(StatusOkay); break; + } case JVSIOCommand::SwitchesInput: { - u32 player_count = *jvs_io++; - u32 player_byte_count = *jvs_io++; + if (!validate_jvs_io(2, "SwitchesInput")) + break; + const u32 player_count = *jvs_io++; + const u32 player_byte_count = *jvs_io++; DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x20, SwitchInputs: {} {}", player_count, player_byte_count); @@ -1954,6 +1983,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } case JVSIOCommand::CoinInput: { + if (!validate_jvs_io(1, "CoinInput")) + break; const u32 slots = *jvs_io++; message.AddData(StatusOkay); for (u32 i = 0; i < slots; i++) @@ -1972,6 +2003,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } case JVSIOCommand::AnalogInput: { + if (!validate_jvs_io(1, "AnalogInput")) + break; message.AddData(StatusOkay); const u32 analogs = *jvs_io++; @@ -2067,6 +2100,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } case JVSIOCommand::PositionInput: { + if (!validate_jvs_io(1, "PositionInput")) + break; const u32 channel = *jvs_io++; const GCPadStatus pad_status = Pad::GetStatus(0); @@ -2088,6 +2123,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } case JVSIOCommand::CoinSubOutput: { + if (!validate_jvs_io(3, "CoinSubOutput")) + break; const u32 slot = *jvs_io++; const u8 coinh = *jvs_io++; const u8 coinl = *jvs_io++; @@ -2100,6 +2137,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } case JVSIOCommand::GeneralDriverOutput: { + if (!validate_jvs_io(1, "GeneralDriverOutput")) + break; const u32 bytes = *jvs_io++; if (bytes) @@ -2109,6 +2148,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) // The lamps are controlled via this if (AMMediaboard::GetGameType() == MarioKartGP) { + if (!validate_jvs_io(1, "GeneralDriverOutput (MarioKartGP)")) + break; const u32 status = *jvs_io++; if (status & 4) { @@ -2129,6 +2170,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) break; } + if (!validate_jvs_io(bytes, "GeneralDriverOutput")) + break; Common::UniqueBuffer buf(bytes); for (u32 i = 0; i < bytes; ++i) @@ -2165,6 +2208,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } case JVSIOCommand::CoinAddOutput: { + if (!validate_jvs_io(3, "CoinAddOutput")) + break; const u32 slot = *jvs_io++; const u8 coinh = *jvs_io++; const u8 coinl = *jvs_io++; @@ -2177,10 +2222,15 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } case JVSIOCommand::NAMCOCommand: { + if (!validate_jvs_io(1, "NAMCOCommand")) + break; const u32 namco_command = *jvs_io++; if (namco_command == 0x18) - { // ID check + { + if (!validate_jvs_io(4, "NAMCOCommand(0x18) / ID check")) + break; + // ID check jvs_io += 4; message.AddData(StatusOkay); message.AddData(0xff); @@ -2193,6 +2243,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) break; } case JVSIOCommand::Reset: + if (!validate_jvs_io(1, "Reset")) + break; if (*jvs_io++ == 0xD9) { NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0xF0, Reset"); @@ -2205,6 +2257,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) dip_switch_1 |= 1; break; case JVSIOCommand::SetAddress: + if (!validate_jvs_io(1, "SetAddress")) + break; node = *jvs_io++; NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0xF1, SetAddress: node={}", node); @@ -2220,16 +2274,21 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) message.End(); + if (!validate_data_in_out(0, 2, "JVSIO")) + break; data_out[data_offset++] = gcam_command; const u8* buf = message.m_message.data(); const u32 len = message.m_pointer; data_out[data_offset++] = len; + const u32 in_size = frame[0] + 1; + if (!validate_data_in_out(in_size, len, "JVSIO")) + break; for (u32 i = 0; i < len; ++i) data_out[data_offset++] = buf[i]; - data_in += frame[0] + 1; + data_in += in_size; break; } case GCAMCommand::Unknown_60: From f3321b2893902a634767c1ac775d0d541c8de594 Mon Sep 17 00:00:00 2001 From: Sepalani Date: Mon, 26 Jan 2026 16:32:03 +0400 Subject: [PATCH 15/71] SI_DeviceAMBaseboard: Improve bounds checking --- .../Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp | 156 ++++++++++++++++-- Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h | 2 +- 2 files changed, 140 insertions(+), 18 deletions(-) diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp index 6b9d8c992f..f62e9d3fbd 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp @@ -242,7 +242,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) auto validate_data_in_out = [&](u32 n_in, u32 n_out, std::string_view command) -> bool { if (data_in + n_in > data_in_end) ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_in overflow in {}", command); - else if (data_offset + n_out > data_out.size()) + else if (std::size_t{data_offset} + n_out > data_out.size()) ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_out overflow in {}", command); else return true; @@ -449,7 +449,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) if (!validate_data_in_out(1, 0, "SerialA")) break; - u32 length = *data_in++; + const u32 length = *data_in++; if (length) { if (!validate_data_in_out(length, 0, "SerialA")) @@ -575,6 +575,16 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) "GC-AM: Command 25 (IC-CARD) Write Pages: Off:{:x} Size:{:x} PSize:{:x}", m_ic_write_offset, m_ic_write_size, size); + if (std::size_t{m_ic_write_offset} + size > sizeof(m_ic_write_buffer)) + { + ERROR_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command 25 (IC-CARD) m_ic_write_buffer overflow:\n" + " - m_ic_write_buffer(offset={}, size={})\n" + " - size={}\n", + m_ic_write_offset, sizeof(m_ic_write_buffer), size); + data_in = data_in_end; + break; + } memcpy(m_ic_write_buffer + m_ic_write_offset, data_in + 2, size); m_ic_write_offset += size; @@ -586,6 +596,19 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) const u16 page = m_ic_write_buffer[5]; const u16 count = m_ic_write_buffer[7]; + if ((page * 8 + count * 8) > sizeof(m_ic_card_data) || + (10 + count * 8) > sizeof(m_ic_write_buffer)) + { + ERROR_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command 25 (IC-CARD) Write Pages overflow:\n" + " - m_ic_card_data(offset={}, size={})\n" + " - m_ic_write_buffer(offset={}, size={})\n" + " - size={}, page={}, count={}\n", + page * 8, sizeof(m_ic_card_data), 10, sizeof(m_ic_write_buffer), + count * 8, page, count); + data_in = data_in_end; + break; + } memcpy(m_ic_card_data + page * 8, m_ic_write_buffer + 10, count * 8); INFO_LOG_FMT(SERIALINTERFACE_CARD, @@ -594,6 +617,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) icco.command = WritePages; + if (!validate_data_in_out(0, icco.pktlen + 2, "SerialA (IC-CARD)")) + break; ICCardSendReply(&icco, data_out.data(), &data_offset); } if (!validate_data_in_out(length, 0, "SerialA (IC-CARD)")) @@ -728,7 +753,17 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) u32 cnt = count * 8; // Limit read size to not overwrite the reply buffer - if (cnt > (u32)0x50 - data_offset) + const std::size_t reply_buffer_size = sizeof(icco.extdata) - 1; + if (data_offset > reply_buffer_size) + { + ERROR_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command 31 (IC-CARD) Read Pages overflow:" + " offset={} > buffer_size={}", + data_offset, reply_buffer_size); + data_in = data_in_end; + break; + } + if (cnt > reply_buffer_size - data_offset) { cnt = 5 * 8; } @@ -737,6 +772,15 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) icco.length += icco.extlen; icco.pktlen += icco.extlen; + if (offs + cnt > sizeof(icco.extdata)) + { + ERROR_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command 31 (IC-CARD) Read Pages overflow:" + " offset={} + count={} > buffer_size={}", + offs, cnt, sizeof(icco.extdata)); + data_in = data_in_end; + break; + } memcpy(icco.extdata, m_ic_card_data + offs, cnt); INFO_LOG_FMT(SERIALINTERFACE_CARD, @@ -965,6 +1009,16 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) if (AMMediaboard::GetGameType() == FZeroAX || AMMediaboard::GetGameType() == FZeroAXMonster) { + if (command_offset + 5 >= std::size(m_motor_reply)) + { + ERROR_LOG_FMT( + SERIALINTERFACE_AMBB, + "GC-AM: Command 0x31 (MOTOR) overflow: offset={} >= motor_reply_size={}", + command_offset + 5, std::size(m_motor_reply)); + data_in = data_in_end; + break; + } + // Status m_motor_reply[command_offset + 2] = 0; m_motor_reply[command_offset + 3] = 0; @@ -1053,6 +1107,14 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) if (!validate_data_in_out(length, reply_size, "SerialA")) break; + if (reply_size > sizeof(m_motor_reply)) + { + ERROR_LOG_FMT(SERIALINTERFACE_AMBB, + "GC-AM: Command SerialA, reply_size={} too big for m_motor_reply", + reply_size); + data_in = data_in_end; + break; + } memcpy(data_out.data() + data_offset, m_motor_reply, reply_size); data_offset += reply_size; } @@ -1096,6 +1158,17 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) if (!validate_data_in_out(0, read_length, "SerialB")) break; + if (std::size_t{m_card_read} + read_length > sizeof(m_card_read_packet)) + { + ERROR_LOG_FMT(SERIALINTERFACE_AMBB, + "GC-AM: Command SerialB, m_card_read_packet overflow:\n" + " - m_card_read_packet = {}\n" + " - m_card_read = {}\n" + " - read_length = {}", + fmt::ptr(m_card_read_packet), m_card_read, read_length); + data_in = data_in_end; + break; + } memcpy(data_out.data() + data_offset, m_card_read_packet + m_card_read, read_length); @@ -1223,7 +1296,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) for (u32 i = 0; i < data_out[checksum_start]; ++i) data_out[data_offset] ^= data_out[checksum_start + i]; - if (!validate_data_in_out(0, 2, "SerialB")) + if (!validate_data_in_out(0, 1, "SerialB")) break; data_offset++; @@ -1231,6 +1304,19 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } else { + if (!validate_data_in_out(length, 0, "SerialB")) + break; + if (std::size_t{m_card_offset} + length > std::size(m_card_buffer)) + { + ERROR_LOG_FMT(SERIALINTERFACE_AMBB, + "GC-AM: Command SerialB, m_card_buffer overflow:\n" + " - m_card_buffer = {}\n" + " - m_card_offset = {}\n" + " - length = {}", + fmt::ptr(m_card_buffer), m_card_offset, length); + data_in = data_in_end; + break; + } for (u32 i = 0; i < length; ++i) m_card_buffer[m_card_offset + i] = data_in[i]; @@ -1269,8 +1355,18 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) if (File::Exists(card_filename)) { File::IOFile card(card_filename, "rb+"); - m_card_memory_size = (u32)card.GetSize(); - + m_card_memory_size = static_cast(card.GetSize()); + if (m_card_memory_size > sizeof(m_card_memory)) + { + ERROR_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command CARD GetState overflow:\n" + " - file name = {}\n" + " - file size = {}\n" + " - card size = {}", + card_filename, m_card_memory_size, sizeof(m_card_memory)); + data_in = data_in_end; + break; + } card.ReadBytes(m_card_memory, m_card_memory_size); card.Close(); @@ -1304,7 +1400,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) if (File::Exists(card_filename)) { - m_card_memory_size = (u32)File::GetSize(card_filename); + m_card_memory_size = static_cast(File::GetSize(card_filename)); if (m_card_memory_size) { if (AMMediaboard::GetGameType() == FZeroAX) @@ -1329,7 +1425,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) break; case CARDCommand::Load: { - u8 mode = m_card_buffer[6]; + const u8 mode = m_card_buffer[6]; NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Load({:02X})", mode); break; @@ -1350,7 +1446,6 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) // Prepare read packet memset(m_card_read_packet, 0, 0xDB); - u32 packet_offset = 0; const std::string card_filename( fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX), @@ -1361,43 +1456,59 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) File::IOFile card(card_filename, "rb+"); if (m_card_memory_size == 0) { - m_card_memory_size = (u32)card.GetSize(); + m_card_memory_size = static_cast(card.GetSize()); } + if (m_card_memory_size > sizeof(m_card_memory)) + { + ERROR_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command CARD Read overflow:\n" + " - file name = {}\n" + " - file size = {}\n" + " - card size = {}", + card_filename, m_card_memory_size, sizeof(m_card_memory)); + data_in = data_in_end; + break; + } card.ReadBytes(m_card_memory, m_card_memory_size); card.Close(); m_card_is_inserted = true; } - m_card_read_packet[packet_offset++] = 0x02; // SUB CMD - m_card_read_packet[packet_offset++] = 0x00; // SUB CMDLen + m_card_read_packet[0] = 0x02; // SUB CMD + m_card_read_packet[1] = 0x00; // SUB CMDLen - m_card_read_packet[packet_offset++] = 0x33; // CARD CMD + m_card_read_packet[2] = 0x33; // CARD CMD if (m_card_is_inserted) // CARD Status { - m_card_read_packet[packet_offset++] = 0x31; + m_card_read_packet[3] = 0x31; } else { - m_card_read_packet[packet_offset++] = 0x30; + m_card_read_packet[3] = 0x30; } - m_card_read_packet[packet_offset++] = 0x30; - m_card_read_packet[packet_offset++] = 0x30; + m_card_read_packet[4] = 0x30; + m_card_read_packet[5] = 0x30; + u32 packet_offset = 6; // Data reply + static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 6); memcpy(m_card_read_packet + packet_offset, m_card_memory, m_card_memory_size); packet_offset += m_card_memory_size; + static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 7); m_card_read_packet[packet_offset++] = 0x03; m_card_read_packet[1] = packet_offset - 1; // SUB CMDLen + static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 8); for (u32 i = 0; i < packet_offset - 1; ++i) m_card_read_packet[packet_offset] ^= m_card_read_packet[1 + i]; + static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 9); packet_offset++; m_card_read_length = packet_offset; @@ -1411,7 +1522,18 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) const u8 track = m_card_buffer[8]; m_card_memory_size = m_card_buffer[1] - 9; + if (m_card_memory_size > sizeof(m_card_memory)) + { + ERROR_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command CARD Write overflow:\n" + " - write size = {}\n" + " - card size = {}", + m_card_memory_size, sizeof(m_card_memory)); + data_in = data_in_end; + break; + } + static_assert(sizeof(m_card_buffer) >= sizeof(m_card_memory) + 9); memcpy(m_card_memory, m_card_buffer + 9, m_card_memory_size); NOTICE_LOG_FMT(SERIALINTERFACE_CARD, diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h index acbf57e21b..ab5d6bab4c 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h @@ -251,7 +251,7 @@ private: u32 length : 8; u32 status : 16; - u8 extdata[81]; + u8 extdata[81] = {}; u32 extlen; }; From 355a4d8188556efd53db3a98a87eaa3c0a4ea254 Mon Sep 17 00:00:00 2001 From: Sepalani Date: Mon, 26 Jan 2026 20:22:30 +0400 Subject: [PATCH 16/71] AMMediaboard: Fix several warnings --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 39 ++++++++++++++---------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 8167d49f19..c8374787b1 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -394,7 +394,7 @@ u8* InitDIMM(u32 size) static s32 NetDIMMAccept(int fd, sockaddr* addr, socklen_t* len) { SOCKET client_sock = INVALID_SOCKET; - fd_set readfds; + fd_set readfds{}; FD_ZERO(&readfds); FD_SET(fd, &readfds); @@ -481,7 +481,7 @@ static s32 NetDIMMConnect(int fd, sockaddr_in* addr, int len) if (ret == SOCKET_ERROR && err == WSAEWOULDBLOCK) { - fd_set writefds; + fd_set writefds{}; FD_ZERO(&writefds); FD_SET(fd, &writefds); @@ -941,13 +941,14 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; u32 off = s_media_buffer_32[3]; auto len = std::min(s_media_buffer_32[4], sizeof(s_network_buffer)); + const u64 off_len = u64(off) + len; if (off >= NetworkBufferAddress4 && - off + len <= NetworkBufferAddress4 + sizeof(s_network_buffer)) + off_len <= NetworkBufferAddress4 + sizeof(s_network_buffer)) { off -= NetworkBufferAddress4; } - else if (off + len > sizeof(s_network_buffer)) + else if (off_len > sizeof(s_network_buffer)) { ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: recv(error) invalid destination or length: off={:08x}, len={}\n", @@ -971,13 +972,14 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; u32 off = s_media_buffer_32[3]; auto len = std::min(s_media_buffer_32[4], sizeof(s_network_buffer)); + const u64 off_len = u64(off) + len; if (off >= NetworkBufferAddress3 && - off + len <= NetworkBufferAddress3 + sizeof(s_network_buffer)) + off_len <= NetworkBufferAddress3 + sizeof(s_network_buffer)) { off -= NetworkBufferAddress3; } - else if (off + len > sizeof(s_network_buffer)) + else if (off_len > sizeof(s_network_buffer)) { ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: send(error) unhandled destination or length: {:08x}, len={}", off, @@ -1033,7 +1035,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u timeval timeout = {}; std::optional timeout_offset; - fd_set fds; + fd_set fds{}; FD_ZERO(&fds); FD_SET(fd, &fds); @@ -1276,8 +1278,11 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u if (offset >= 0x00400000 && offset <= 0x600000) { const u32 fw_offset = offset - 0x00400000; - // TODO: Bounds checking for s_firmware - memory.CopyFromEmu(s_firmware + fw_offset, address, length); + if (!SafeCopyFromEmu(memory, s_firmware, address, sizeof(s_firmware), fw_offset, length)) + { + ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: Invalid firmware write: offset={}, length={}", + fw_offset, length); + } return 0; } } @@ -1540,13 +1545,14 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; u32 off = s_media_buffer_32[11]; auto len = std::min(s_media_buffer_32[12], sizeof(s_network_buffer)); + const u64 off_len = u64(off) + len; if (off >= NetworkBufferAddress5 && - off + len <= NetworkBufferAddress5 + sizeof(s_network_buffer)) + off_len <= NetworkBufferAddress5 + sizeof(s_network_buffer)) { off -= NetworkBufferAddress5; } - else if (off + len > sizeof(s_network_buffer)) + else if (off_len > sizeof(s_network_buffer)) { ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: recv(error) invalid destination or length: off={:08x}, len={}\n", @@ -1570,13 +1576,14 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; u32 off = s_media_buffer_32[11]; auto len = std::min(s_media_buffer_32[12], sizeof(s_network_buffer)); + const u64 off_len = u64(off) + len; if (off >= NetworkBufferAddress1 && - off + len <= NetworkBufferAddress1 + sizeof(s_network_buffer)) + off_len <= NetworkBufferAddress1 + sizeof(s_network_buffer)) { off -= NetworkBufferAddress1; } - else if (off + len > sizeof(s_network_buffer)) + else if (off_len > sizeof(s_network_buffer)) { ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: send(error) unhandled destination or length: {:08x}, len={}", off, @@ -1620,7 +1627,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u timeval timeout = {}; std::optional timeout_offset; - fd_set fds; + fd_set fds{}; FD_ZERO(&fds); FD_SET(fd, &fds); @@ -1737,8 +1744,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; case AMMBCommand::SetupLink: { - sockaddr_in addra; - sockaddr_in addrb; + sockaddr_in addra{}; + sockaddr_in addrb{}; addra.sin_addr.s_addr = s_media_buffer_32[12]; addrb.sin_addr.s_addr = s_media_buffer_32[13]; From 0acb6145f72826af8d1d73f7fadba1e1ec72fca7 Mon Sep 17 00:00:00 2001 From: Sepalani Date: Sun, 1 Feb 2026 00:18:10 +0400 Subject: [PATCH 17/71] Triforce: Fix remaining issues --- .../Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp | 5 +-- .../Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp | 36 ++++++++++++++++--- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp index e5566ce2ba..fb449860ad 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp @@ -80,9 +80,10 @@ CEXIBaseboard::CEXIBaseboard(Core::System& system) : IEXIDevice(system) if (AMMediaboard::GetGameType() == VirtuaStriker4 || AMMediaboard::GetGameType() == GekitouProYakyuu) { - if (m_backup.GetSize() >= 0x20C + 0x1F4) + const u64 backup_size = m_backup.GetSize(); + if (backup_size >= 0x20C + 0x1F4) { - Common::UniqueBuffer data(m_backup.GetSize()); + Common::UniqueBuffer data(backup_size); m_backup.ReadBytes(data.data(), data.size()); // Set FIRM version diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp index f62e9d3fbd..d02faf2eef 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp @@ -235,14 +235,30 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) data_out[data_offset++] = 1; u8* data_in = buffer + 2; - u8* const data_in_end = buffer + buffer[buffer_position] + 2; + if (buffer_position >= buffer_length) + { + ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: buffer overflow (position={}, length={})", + buffer_position, buffer_length); + buffer_position = buffer_length; + break; + } + + const u32 requested_size = buffer[buffer_position] + 2; + if (requested_size > buffer_length) + { + ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: requested size ({}) bigger than buffer's ({})", + requested_size, buffer_length); + buffer_position = buffer_length; + break; + } + u8* const data_in_end = buffer + requested_size; // Helper to check that iterating over data n times is safe, // i.e. *data++ at most lead to data.end() auto validate_data_in_out = [&](u32 n_in, u32 n_out, std::string_view command) -> bool { if (data_in + n_in > data_in_end) ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_in overflow in {}", command); - else if (std::size_t{data_offset} + n_out > data_out.size()) + else if (u64{data_offset} + n_out > data_out.size()) ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: data_out overflow in {}", command); else return true; @@ -575,7 +591,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) "GC-AM: Command 25 (IC-CARD) Write Pages: Off:{:x} Size:{:x} PSize:{:x}", m_ic_write_offset, m_ic_write_size, size); - if (std::size_t{m_ic_write_offset} + size > sizeof(m_ic_write_buffer)) + if (u64{m_ic_write_offset} + size > sizeof(m_ic_write_buffer)) { ERROR_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 25 (IC-CARD) m_ic_write_buffer overflow:\n" @@ -1158,7 +1174,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) if (!validate_data_in_out(0, read_length, "SerialB")) break; - if (std::size_t{m_card_read} + read_length > sizeof(m_card_read_packet)) + if (u64{m_card_read} + read_length > sizeof(m_card_read_packet)) { ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command SerialB, m_card_read_packet overflow:\n" @@ -1306,7 +1322,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) { if (!validate_data_in_out(length, 0, "SerialB")) break; - if (std::size_t{m_card_offset} + length > std::size(m_card_buffer)) + if (u64{m_card_offset} + length > std::size(m_card_buffer)) { ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command SerialB, m_card_buffer overflow:\n" @@ -1475,6 +1491,16 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) m_card_is_inserted = true; } + else if (m_card_memory_size > sizeof(m_card_memory)) + { + ERROR_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command CARD Read overflow:\n" + " - requested size = {}\n" + " - card size = {}", + m_card_memory_size, sizeof(m_card_memory)); + data_in = data_in_end; + break; + } m_card_read_packet[0] = 0x02; // SUB CMD m_card_read_packet[1] = 0x00; // SUB CMDLen From cbc7c2d3858aab404974a16be2b3cf54e98c923a Mon Sep 17 00:00:00 2001 From: Sepalani Date: Mon, 2 Feb 2026 22:35:59 +0400 Subject: [PATCH 18/71] SI_DeviceAMBaseboard: Add missing bounds checks VolumeDisc: Fix Triforce's GetGameID memcpy by checking MakerID's size --- .../Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp | 536 +++++++++--------- Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h | 4 +- Source/Core/DiscIO/VolumeDisc.cpp | 5 +- 3 files changed, 286 insertions(+), 259 deletions(-) diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp index d02faf2eef..326362bacf 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp @@ -1341,128 +1341,40 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) // Check if we got a complete command if (m_card_buffer[0] == 0x02) { - if (m_card_buffer[1] == m_card_offset - 2) + if (m_card_offset < 2) { - if (m_card_buffer[m_card_offset - 2] == 0x03) + ERROR_LOG_FMT( + SERIALINTERFACE_AMBB, + "GC-AM: Command SerialB, m_card_buffer overflow (m_card_offset < 2):\n" + " - m_card_buffer = {}\n" + " - m_card_offset = {}\n" + " - length = {}", + fmt::ptr(m_card_buffer), m_card_offset, length); + data_in = data_in_end; + break; + } + if (const u32 offset = m_card_offset - 2; + m_card_buffer[1] == offset && m_card_buffer[offset] == 0x03) + { + m_card_command = m_card_buffer[2]; + + switch (CARDCommand(m_card_command)) { - m_card_command = m_card_buffer[2]; + case CARDCommand::Init: + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Init"); - switch (CARDCommand(m_card_command)) + m_card_write_length = 0; + m_card_bit = 0; + m_card_memory_size = 0; + m_card_state_call_count = 0; + break; + case CARDCommand::GetState: + { + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD GetState({:02X})", + m_card_bit); + + if (m_card_memory_size == 0) { - case CARDCommand::Init: - NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Init"); - - m_card_write_length = 0; - m_card_bit = 0; - m_card_memory_size = 0; - m_card_state_call_count = 0; - break; - case CARDCommand::GetState: - { - NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD GetState({:02X})", - m_card_bit); - - if (m_card_memory_size == 0) - { - const std::string card_filename( - fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX), - SConfig::GetInstance().GetGameID())); - - if (File::Exists(card_filename)) - { - File::IOFile card(card_filename, "rb+"); - m_card_memory_size = static_cast(card.GetSize()); - if (m_card_memory_size > sizeof(m_card_memory)) - { - ERROR_LOG_FMT(SERIALINTERFACE_CARD, - "GC-AM: Command CARD GetState overflow:\n" - " - file name = {}\n" - " - file size = {}\n" - " - card size = {}", - card_filename, m_card_memory_size, sizeof(m_card_memory)); - data_in = data_in_end; - break; - } - card.ReadBytes(m_card_memory, m_card_memory_size); - card.Close(); - - m_card_is_inserted = true; - } - } - - if (AMMediaboard::GetGameType() == FZeroAX && m_card_memory_size) - { - m_card_state_call_count++; - if (m_card_state_call_count > 10) - { - if (m_card_bit & 2) - m_card_bit &= ~2u; - else - m_card_bit |= 2; - - m_card_state_call_count = 0; - } - } - - if (m_card_clean == 1) - { - m_card_clean = 2; - } - else if (m_card_clean == 2) - { - const std::string card_filename( - fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX), - SConfig::GetInstance().GetGameID())); - - if (File::Exists(card_filename)) - { - m_card_memory_size = static_cast(File::GetSize(card_filename)); - if (m_card_memory_size) - { - if (AMMediaboard::GetGameType() == FZeroAX) - { - m_card_bit = 2; - } - else - { - m_card_bit = 1; - } - } - } - m_card_clean = 0; - } - break; - } - case CARDCommand::IsPresent: - NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD IsPresent"); - break; - case CARDCommand::RegisterFont: - NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD RegisterFont"); - break; - case CARDCommand::Load: - { - const u8 mode = m_card_buffer[6]; - NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Load({:02X})", - mode); - break; - } - case CARDCommand::Clean: - NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Clean"); - m_card_clean = 1; - break; - case CARDCommand::Read: - { - const u8 mode = m_card_buffer[6]; - const u8 bitmode = m_card_buffer[7]; - const u8 track = m_card_buffer[8]; - - NOTICE_LOG_FMT(SERIALINTERFACE_CARD, - "GC-AM: Command CARD Read({:02X},{:02X},{:02X})", mode, - bitmode, track); - - // Prepare read packet - memset(m_card_read_packet, 0, 0xDB); - const std::string card_filename( fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX), SConfig::GetInstance().GetGameID())); @@ -1470,15 +1382,11 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) if (File::Exists(card_filename)) { File::IOFile card(card_filename, "rb+"); - if (m_card_memory_size == 0) - { - m_card_memory_size = static_cast(card.GetSize()); - } - + m_card_memory_size = static_cast(card.GetSize()); if (m_card_memory_size > sizeof(m_card_memory)) { ERROR_LOG_FMT(SERIALINTERFACE_CARD, - "GC-AM: Command CARD Read overflow:\n" + "GC-AM: Command CARD GetState overflow:\n" " - file name = {}\n" " - file size = {}\n" " - card size = {}", @@ -1491,135 +1399,235 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) m_card_is_inserted = true; } - else if (m_card_memory_size > sizeof(m_card_memory)) - { - ERROR_LOG_FMT(SERIALINTERFACE_CARD, - "GC-AM: Command CARD Read overflow:\n" - " - requested size = {}\n" - " - card size = {}", - m_card_memory_size, sizeof(m_card_memory)); - data_in = data_in_end; - break; - } - - m_card_read_packet[0] = 0x02; // SUB CMD - m_card_read_packet[1] = 0x00; // SUB CMDLen - - m_card_read_packet[2] = 0x33; // CARD CMD - - if (m_card_is_inserted) // CARD Status - { - m_card_read_packet[3] = 0x31; - } - else - { - m_card_read_packet[3] = 0x30; - } - - m_card_read_packet[4] = 0x30; - m_card_read_packet[5] = 0x30; - - u32 packet_offset = 6; - // Data reply - static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 6); - memcpy(m_card_read_packet + packet_offset, m_card_memory, m_card_memory_size); - packet_offset += m_card_memory_size; - - static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 7); - m_card_read_packet[packet_offset++] = 0x03; - - m_card_read_packet[1] = packet_offset - 1; // SUB CMDLen - - static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 8); - for (u32 i = 0; i < packet_offset - 1; ++i) - m_card_read_packet[packet_offset] ^= m_card_read_packet[1 + i]; - - static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 9); - packet_offset++; - - m_card_read_length = packet_offset; - m_card_read = 0; - break; } - case CARDCommand::Write: - { - const u8 mode = m_card_buffer[6]; - const u8 bitmode = m_card_buffer[7]; - const u8 track = m_card_buffer[8]; - m_card_memory_size = m_card_buffer[1] - 9; + if (AMMediaboard::GetGameType() == FZeroAX && m_card_memory_size) + { + m_card_state_call_count++; + if (m_card_state_call_count > 10) + { + if (m_card_bit & 2) + m_card_bit &= ~2u; + else + m_card_bit |= 2; + + m_card_state_call_count = 0; + } + } + + if (m_card_clean == 1) + { + m_card_clean = 2; + } + else if (m_card_clean == 2) + { + const std::string card_filename( + fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX), + SConfig::GetInstance().GetGameID())); + + if (File::Exists(card_filename)) + { + m_card_memory_size = static_cast(File::GetSize(card_filename)); + if (m_card_memory_size) + { + if (AMMediaboard::GetGameType() == FZeroAX) + { + m_card_bit = 2; + } + else + { + m_card_bit = 1; + } + } + } + m_card_clean = 0; + } + break; + } + case CARDCommand::IsPresent: + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD IsPresent"); + break; + case CARDCommand::RegisterFont: + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD RegisterFont"); + break; + case CARDCommand::Load: + { + const u8 mode = m_card_buffer[6]; + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Load({:02X})", mode); + break; + } + case CARDCommand::Clean: + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Clean"); + m_card_clean = 1; + break; + case CARDCommand::Read: + { + const u8 mode = m_card_buffer[6]; + const u8 bitmode = m_card_buffer[7]; + const u8 track = m_card_buffer[8]; + + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command CARD Read({:02X},{:02X},{:02X})", mode, bitmode, + track); + + // Prepare read packet + memset(m_card_read_packet, 0, 0xDB); + + const std::string card_filename( + fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX), + SConfig::GetInstance().GetGameID())); + + if (File::Exists(card_filename)) + { + File::IOFile card(card_filename, "rb+"); + if (m_card_memory_size == 0) + { + m_card_memory_size = static_cast(card.GetSize()); + } + if (m_card_memory_size > sizeof(m_card_memory)) { ERROR_LOG_FMT(SERIALINTERFACE_CARD, - "GC-AM: Command CARD Write overflow:\n" - " - write size = {}\n" + "GC-AM: Command CARD Read overflow:\n" + " - file name = {}\n" + " - file size = {}\n" " - card size = {}", - m_card_memory_size, sizeof(m_card_memory)); + card_filename, m_card_memory_size, sizeof(m_card_memory)); data_in = data_in_end; break; } - - static_assert(sizeof(m_card_buffer) >= sizeof(m_card_memory) + 9); - memcpy(m_card_memory, m_card_buffer + 9, m_card_memory_size); - - NOTICE_LOG_FMT(SERIALINTERFACE_CARD, - "GC-AM: Command CARD Write: {:02X} {:02X} {:02X} {}", mode, - bitmode, track, m_card_memory_size); - - const std::string card_filename(File::GetUserPath(D_TRIUSER_IDX) + - "tricard_" + - SConfig::GetInstance().GetGameID() + ".bin"); - - File::IOFile card(card_filename, "wb+"); - card.WriteBytes(m_card_memory, m_card_memory_size); + card.ReadBytes(m_card_memory, m_card_memory_size); card.Close(); - m_card_bit = 2; + m_card_is_inserted = true; + } + else if (m_card_memory_size > sizeof(m_card_memory)) + { + ERROR_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command CARD Read overflow:\n" + " - requested size = {}\n" + " - card size = {}", + m_card_memory_size, sizeof(m_card_memory)); + data_in = data_in_end; + break; + } - m_card_state_call_count = 0; - break; + m_card_read_packet[0] = 0x02; // SUB CMD + m_card_read_packet[1] = 0x00; // SUB CMDLen + + m_card_read_packet[2] = 0x33; // CARD CMD + + if (m_card_is_inserted) // CARD Status + { + m_card_read_packet[3] = 0x31; } - case CARDCommand::SetPrintParam: - NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD SetPrintParam"); - break; - case CARDCommand::WriteInfo: - NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD WriteInfo"); - break; - case CARDCommand::Erase: - NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Erase"); - break; - case CARDCommand::Eject: - NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Eject"); - if (AMMediaboard::GetGameType() != FZeroAX) - { - m_card_bit = 0; - } - break; - case CARDCommand::SetShutter: - NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD SetShutter"); - if (AMMediaboard::GetGameType() != FZeroAX) - { - m_card_bit = 0; - } - // Close - if (m_card_buffer[6] == 0x30) - { - m_card_shutter = false; - } - // Open - else if (m_card_buffer[6] == 0x31) - { - m_card_shutter = true; - } - break; - default: - ERROR_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: CARD:Unhandled command!"); - ERROR_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: CARD:[{:08X}]", m_card_command); - // hexdump( m_card_buffer, m_card_offset ); - break; + else + { + m_card_read_packet[3] = 0x30; } - m_card_offset = 0; + + m_card_read_packet[4] = 0x30; + m_card_read_packet[5] = 0x30; + + u32 packet_offset = 6; + // Data reply + static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 6); + memcpy(m_card_read_packet + packet_offset, m_card_memory, m_card_memory_size); + packet_offset += m_card_memory_size; + + static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 7); + m_card_read_packet[packet_offset++] = 0x03; + + m_card_read_packet[1] = packet_offset - 1; // SUB CMDLen + + static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 8); + for (u32 i = 0; i < packet_offset - 1; ++i) + m_card_read_packet[packet_offset] ^= m_card_read_packet[1 + i]; + + static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 9); + packet_offset++; + + m_card_read_length = packet_offset; + m_card_read = 0; + break; } + case CARDCommand::Write: + { + const u8 mode = m_card_buffer[6]; + const u8 bitmode = m_card_buffer[7]; + const u8 track = m_card_buffer[8]; + + m_card_memory_size = m_card_buffer[1] - 9; + if (m_card_memory_size > sizeof(m_card_memory)) + { + ERROR_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command CARD Write overflow:\n" + " - write size = {}\n" + " - card size = {}", + m_card_memory_size, sizeof(m_card_memory)); + data_in = data_in_end; + break; + } + + static_assert(sizeof(m_card_buffer) >= sizeof(m_card_memory) + 9); + memcpy(m_card_memory, m_card_buffer + 9, m_card_memory_size); + + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, + "GC-AM: Command CARD Write: {:02X} {:02X} {:02X} {}", mode, + bitmode, track, m_card_memory_size); + + const std::string card_filename(File::GetUserPath(D_TRIUSER_IDX) + "tricard_" + + SConfig::GetInstance().GetGameID() + ".bin"); + + File::IOFile card(card_filename, "wb+"); + card.WriteBytes(m_card_memory, m_card_memory_size); + card.Close(); + + m_card_bit = 2; + + m_card_state_call_count = 0; + break; + } + case CARDCommand::SetPrintParam: + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD SetPrintParam"); + break; + case CARDCommand::WriteInfo: + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD WriteInfo"); + break; + case CARDCommand::Erase: + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Erase"); + break; + case CARDCommand::Eject: + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Eject"); + if (AMMediaboard::GetGameType() != FZeroAX) + { + m_card_bit = 0; + } + break; + case CARDCommand::SetShutter: + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD SetShutter"); + if (AMMediaboard::GetGameType() != FZeroAX) + { + m_card_bit = 0; + } + // Close + if (m_card_buffer[6] == 0x30) + { + m_card_shutter = false; + } + // Open + else if (m_card_buffer[6] == 0x31) + { + m_card_shutter = true; + } + break; + default: + ERROR_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: CARD:Unhandled command!"); + ERROR_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: CARD:[{:08X}]", m_card_command); + // hexdump( m_card_buffer, m_card_offset ); + break; + } + m_card_offset = 0; } } @@ -1877,7 +1885,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) for (u32 i = 0; i < player_count; ++i) { - u8 player_data[3]{}; + std::array player_data{}; // Service button if (pad_status.switches & SWITCH_SERVICE) @@ -2124,7 +2132,14 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) break; } - for (u32 j = 0; j < player_byte_count; ++j) + if (player_byte_count > player_data.size()) + { + WARN_LOG_FMT(SERIALINTERFACE_JVSIO, + "JVS-IO: Command 0x20, SwitchInputs: invalid player_byte_count={}", + player_byte_count); + } + const u32 data_size = std::min(player_byte_count, u32(player_data.size())); + for (u32 j = 0; j < data_size; ++j) message.AddData(player_data[j]); } break; @@ -2135,7 +2150,15 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) break; const u32 slots = *jvs_io++; message.AddData(StatusOkay); - for (u32 i = 0; i < slots; i++) + static_assert(std::tuple_size{} == 2 && + std::tuple_size{} == 2); + if (slots > 2) + { + WARN_LOG_FMT(SERIALINTERFACE_JVSIO, + "JVS-IO: Command 0x21, CoinInput: invalid slots {}", slots); + } + const u32 max_slots = std::min(slots, 2u); + for (u32 i = 0; i < max_slots; i++) { GCPadStatus pad_status = Pad::GetStatus(i); if ((pad_status.switches & SWITCH_COIN) && !m_coin_pressed[i]) @@ -2277,7 +2300,15 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) const u8 coinh = *jvs_io++; const u8 coinl = *jvs_io++; - m_coin[slot] -= (coinh << 8) | coinl; + if (slot < m_coin.size()) + { + m_coin[slot] -= (coinh << 8) | coinl; + } + else + { + WARN_LOG_FMT(SERIALINTERFACE_JVSIO, + "JVS-IO: Command 0x30, CoinSubOutput: invalid slot {}", slot); + } message.AddData(StatusOkay); DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x30, CoinSubOutput: {}", slot); @@ -2318,23 +2349,18 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) break; } - if (!validate_jvs_io(bytes, "GeneralDriverOutput")) + if (!validate_jvs_io(std::max(bytes, 3u), "GeneralDriverOutput")) break; - Common::UniqueBuffer buf(bytes); - for (u32 i = 0; i < bytes; ++i) - { - buf[i] = *jvs_io++; - } - - INFO_LOG_FMT( - SERIALINTERFACE_JVSIO, - "JVS-IO: Command 0x32, GPO: {:02x} {:02x} {} {:02x}{:02x}{:02x} ({:02x})", - delay, m_rx_reply, bytes, buf[0], buf[1], buf[2], - Common::swap16(buf.data() + 1) >> 2); + const u16 seat_state = Common::swap16(jvs_io + 1) >> 2; + INFO_LOG_FMT(SERIALINTERFACE_JVSIO, + "JVS-IO: Command 0x32, GPO: delay=0x{:02x}, rx_reply=0x{:02x}," + " seat_state=0x{:04x}, bytes={}, buffer:\n{}", + delay, m_rx_reply, seat_state, bytes, HexDump(jvs_io, bytes)); + jvs_io += bytes; // Handling of the motion seat used in F-Zero AXs DX version - switch (Common::swap16(buf.data() + 1) >> 2) + switch (seat_state) { case 0x70: delay++; diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h index ab5d6bab4c..95b19a86c6 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h @@ -258,8 +258,8 @@ private: u8 m_last[2][0x80] = {}; u32 m_lastptr[2] = {}; - u16 m_coin[2] = {}; - u32 m_coin_pressed[2] = {}; + std::array m_coin{}; + std::array m_coin_pressed{}; u8 m_ic_card_data[2048] = {}; diff --git a/Source/Core/DiscIO/VolumeDisc.cpp b/Source/Core/DiscIO/VolumeDisc.cpp index 5913f63c11..ecbb4a9dc9 100644 --- a/Source/Core/DiscIO/VolumeDisc.cpp +++ b/Source/Core/DiscIO/VolumeDisc.cpp @@ -18,7 +18,7 @@ namespace DiscIO { std::string VolumeDisc::GetGameID(const Partition& partition) const { - char id[6]; + char id[6]{}; if (GetVolumeType() == Platform::Triforce) { @@ -47,7 +47,8 @@ std::string VolumeDisc::GetGameID(const Partition& partition) const break; } - memcpy(id + 4, GetMakerID().c_str(), 2); + const std::string maker_id{GetMakerID()}; + memcpy(id + 4, maker_id.c_str(), std::min(maker_id.size(), 2)); return DecodeString(id); } From 2ffa7cec0781afde855a071ae9e4bdc24ce0a825 Mon Sep 17 00:00:00 2001 From: Sepalani Date: Wed, 4 Feb 2026 19:49:09 +0400 Subject: [PATCH 19/71] SI_DeviceAMBaseboard: Fix log issues related to GeneralDriverOutput --- Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp index 326362bacf..060ff44773 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp @@ -2349,17 +2349,24 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) break; } - if (!validate_jvs_io(std::max(bytes, 3u), "GeneralDriverOutput")) + if (!validate_jvs_io(bytes, "GeneralDriverOutput")) break; - const u16 seat_state = Common::swap16(jvs_io + 1) >> 2; INFO_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x32, GPO: delay=0x{:02x}, rx_reply=0x{:02x}," - " seat_state=0x{:04x}, bytes={}, buffer:\n{}", - delay, m_rx_reply, seat_state, bytes, HexDump(jvs_io, bytes)); - jvs_io += bytes; + " bytes={}, buffer:\n{}", + delay, m_rx_reply, bytes, HexDump(jvs_io, bytes)); + + if (bytes < 3) + { + jvs_io += bytes; + break; + } // Handling of the motion seat used in F-Zero AXs DX version + const u16 seat_state = Common::swap16(jvs_io + 1) >> 2; + jvs_io += bytes; + switch (seat_state) { case 0x70: From 5d2e93fa3e30dc903e10d9e52e4fc1fb5a649d1c Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Wed, 4 Feb 2026 19:35:30 -0600 Subject: [PATCH 20/71] Common: Add SetPlatformSocketOptions function and SEND_FLAGS constant to Network.h to deal with OS-specific SIGPIPE behavior. --- Source/Core/Common/Network.cpp | 18 +++++++++++++++++- Source/Core/Common/Network.h | 15 +++++++++++++++ .../Core/HW/EXI/BBA/TAPServerConnection.cpp | 12 ++---------- Source/Core/Core/IOS/Network/Socket.cpp | 6 +----- 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/Source/Core/Common/Network.cpp b/Source/Core/Common/Network.cpp index 0ed2360267..f79b4e6d84 100644 --- a/Source/Core/Common/Network.cpp +++ b/Source/Core/Common/Network.cpp @@ -9,8 +9,9 @@ #include #ifndef _WIN32 +#include #include -#include +#include #else #include #endif @@ -19,6 +20,7 @@ #include "Common/BitUtils.h" #include "Common/CommonFuncs.h" +#include "Common/Logging/Log.h" #include "Common/Random.h" #include "Common/StringUtil.h" @@ -586,4 +588,18 @@ const char* StrNetworkError() #endif return DecodeNetworkError(error_code); } + +bool SetPlatformSocketOptions(int fd [[maybe_unused]]) +{ +#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) + int opt_no_sigpipe = 1; + if (setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &opt_no_sigpipe, sizeof(opt_no_sigpipe)) < 0) + { + ERROR_LOG_FMT(COMMON, "Failed to set SO_NOSIGPIPE on socket: {}", StrNetworkError()); + return false; + } +#endif + return true; +} + } // namespace Common diff --git a/Source/Core/Common/Network.h b/Source/Core/Common/Network.h index 8f446d7a38..4ad18d3368 100644 --- a/Source/Core/Common/Network.h +++ b/Source/Core/Common/Network.h @@ -10,6 +10,10 @@ #include #include +#if defined(__linux__) || defined(__HAIKU__) +#include +#endif + #include "Common/CommonTypes.h" struct sockaddr_in; @@ -279,4 +283,15 @@ NetworkErrorState SaveNetworkErrorState(); void RestoreNetworkErrorState(const NetworkErrorState& state); const char* DecodeNetworkError(s32 error_code); const char* StrNetworkError(); + +// Sets SO_NOSIGPIPE when available. +bool SetPlatformSocketOptions(int fd); + +// Pass this to all `send` calls to avoid SIGPIPE. +#if defined(__linux__) || defined(__HAIKU__) +static constexpr int SEND_FLAGS = MSG_NOSIGNAL; +#else +static constexpr int SEND_FLAGS = 0; +#endif + } // namespace Common diff --git a/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp index f2bd89e6b2..570221c72b 100644 --- a/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp +++ b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp @@ -31,11 +31,7 @@ using ws_ssize_t = int; using ws_ssize_t = ssize_t; #endif -#ifdef __linux__ -#define SEND_FLAGS MSG_NOSIGNAL -#else -#define SEND_FLAGS 0 -#endif +using Common::SEND_FLAGS; TAPServerConnection::TAPServerConnection(const std::string& destination, std::function recv_cb, @@ -114,11 +110,7 @@ static int ConnectToDestination(const std::string& destination) return -1; } -#ifdef __APPLE__ - int opt_no_sigpipe = 1; - if (setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &opt_no_sigpipe, sizeof(opt_no_sigpipe)) < 0) - INFO_LOG_FMT(SP1, "Failed to set SO_NOSIGPIPE on socket\n"); -#endif + Common::SetPlatformSocketOptions(fd); if (connect(fd, reinterpret_cast(&ss), ss_size) == -1) { diff --git a/Source/Core/Core/IOS/Network/Socket.cpp b/Source/Core/Core/IOS/Network/Socket.cpp index b2f72945e3..6baf371204 100644 --- a/Source/Core/Core/IOS/Network/Socket.cpp +++ b/Source/Core/Core/IOS/Network/Socket.cpp @@ -893,11 +893,7 @@ s32 WiiSockMan::AddSocket(s32 fd, bool is_rw) sock.SetWiiFd(wii_fd); m_ios.GetSystem().GetPowerPC().GetDebugInterface().NetworkLogger()->OnNewSocket(fd); -#ifdef __APPLE__ - int opt_no_sigpipe = 1; - if (setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &opt_no_sigpipe, sizeof(opt_no_sigpipe)) < 0) - ERROR_LOG_FMT(IOS_NET, "Failed to set SO_NOSIGPIPE on socket"); -#endif + Common::SetPlatformSocketOptions(fd); // Wii UDP sockets can use broadcast address by default if (!is_rw) From f4b3f3cf0f61d70f9439811c9b22dce709dfe4ce Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Wed, 4 Feb 2026 19:35:38 -0600 Subject: [PATCH 21/71] AMMediaboard: Use SetPlatformSocketOptions and SEND_FLAGS. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index c8374787b1..ae7c9ed2b4 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -70,6 +70,8 @@ static int WSAGetLastError() namespace AMMediaboard { +using Common::SEND_FLAGS; + static bool s_firmware_map = false; static bool s_test_menu = false; static SOCKET s_fd_namco_cam = 0; @@ -253,6 +255,9 @@ static SOCKET socket_(int af, int type, int protocol) Common::StrNetworkError()); return SOCKET_ERROR; } + + Common::SetPlatformSocketOptions(host_fd); + s_sockets[i] = host_fd; return i; } @@ -988,7 +993,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u len = 0; } - const int ret = send(fd, reinterpret_cast(s_network_buffer + off), len, 0); + const int ret = send(fd, reinterpret_cast(s_network_buffer + off), len, SEND_FLAGS); const int err = WSAGetLastError(); NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: send( {}({}), 0x{:08x}, {} ): {} {}\n", fd, @@ -1592,7 +1597,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u len = 0; } - const int ret = send(fd, reinterpret_cast(s_network_buffer + off), len, 0); + const int ret = send(fd, reinterpret_cast(s_network_buffer + off), len, SEND_FLAGS); const int err = WSAGetLastError(); NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: send( {}({}), 0x{:08x}, {} ): {} {}\n", fd, From 12df15eded548b1c55a5e097ae3efd55833bc67f Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Thu, 5 Feb 2026 01:12:57 -0600 Subject: [PATCH 22/71] AMMediaboard: Fix value in s_mediaboard_ranges array. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index ae7c9ed2b4..f3c39448a0 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -113,8 +113,8 @@ static const MediaBoardRanges s_mediaboard_ranges[] = { DIMMCommandVersion2}, {DIMMCommandVersion2_2, 0x89000220, s_media_buffer, sizeof(s_media_buffer_32), DIMMCommandVersion2_2}, - {NetworkCommandAddress1, NetworkBufferAddress2, s_network_command_buffer, - sizeof(s_network_command_buffer), NetworkCommandAddress1}, + {NetworkCommandAddress1, 0x1F801240, s_network_command_buffer, sizeof(s_network_command_buffer), + NetworkCommandAddress1}, {NetworkCommandAddress2, 0x89060200, s_network_command_buffer, sizeof(s_network_command_buffer), NetworkCommandAddress2}, {NetworkBufferAddress1, 0x1FA10000, s_network_buffer, sizeof(s_network_buffer), From cda344329afa7ef5bfdd814378d8457c8d15e289 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Thu, 5 Feb 2026 20:36:19 -0600 Subject: [PATCH 23/71] Common/BitUtils: Make SetBit constexpr. --- Source/Core/Common/BitUtils.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Core/Common/BitUtils.h b/Source/Core/Common/BitUtils.h index 19eea49486..458fff63a3 100644 --- a/Source/Core/Common/BitUtils.h +++ b/Source/Core/Common/BitUtils.h @@ -189,7 +189,7 @@ template } template -void SetBit(T& value, size_t bit_number, bool bit_value) +constexpr void SetBit(T& value, size_t bit_number, bool bit_value) { static_assert(std::is_unsigned(), "SetBit is only sane on unsigned types."); @@ -200,7 +200,7 @@ void SetBit(T& value, size_t bit_number, bool bit_value) } template -void SetBit(T& value, bool bit_value) +constexpr void SetBit(T& value, bool bit_value) { SetBit(value, bit_number, bit_value); } From 6940309c2dee5c5e8d2bd1aa69e6dafcfc3de240 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Thu, 5 Feb 2026 00:13:39 -0600 Subject: [PATCH 24/71] AMMediaboard: Translate between guest and host FDs to fix AMMBCommand::Select behavior. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 669 ++++++++++++----------- 1 file changed, 347 insertions(+), 322 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index f3c39448a0..be5f01ecc2 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -72,9 +72,42 @@ namespace AMMediaboard using Common::SEND_FLAGS; +enum class GuestSocket : s32 +{ +}; +static constexpr auto INVALID_GUEST_SOCKET = GuestSocket(-1); + +struct TimeVal +{ + // TODO: Verify this. + u64 seconds; + u32 microseconds; +}; + +struct GuestFdSet +{ + static constexpr std::size_t BIT_COUNT = 256; + + std::array bits{}; + + constexpr bool IsFdSet(GuestSocket s) const + { + const auto index = std::size_t(s); + assert(index < BIT_COUNT); + return Common::ExtractBit(bits[index / CHAR_BIT], index % CHAR_BIT) != 0; + } + + constexpr void SetFd(GuestSocket s, bool value = true) + { + const auto index = std::size_t(s); + assert(index < BIT_COUNT); + Common::SetBit(bits[index / CHAR_BIT], index % CHAR_BIT, value); + } +}; +static_assert(sizeof(GuestFdSet) == 32); + static bool s_firmware_map = false; static bool s_test_menu = false; -static SOCKET s_fd_namco_cam = 0; static std::array s_timeouts = {20000, 20000, 20000}; static u32 s_last_error = SSC_SUCCESS; @@ -156,18 +189,48 @@ static const std::unordered_map s_game_map = { {0x454C, VirtuaStriker3}, {0x3030, FirmwareUpdate}, }; + // Sockets FDs are required to go from 0 to 63. // Games use the FD as indexes so we have to workaround it. - static SOCKET s_sockets[64]; -static u32 SocketCheck(u32 x) -{ - if (x < std::size(s_sockets)) - return x; +// TODO: Verify this. +static constexpr u32 FIRST_VALID_FD = 1; - WARN_LOG_FMT(AMMEDIABOARD, "GC-AM: Bad SOCKET value: {}", x); - return 0; +static GuestSocket GetAvailableGuestSocket() +{ + for (u32 i = FIRST_VALID_FD; i < std::size(s_sockets); ++i) + { + if (s_sockets[i] == SOCKET_ERROR) + return GuestSocket(i); + } + + // Out of sockets. + return INVALID_GUEST_SOCKET; +} + +static SOCKET GetHostSocket(GuestSocket x) +{ + const auto index = u32(x); + + if (index < std::size(s_sockets)) + return s_sockets[index]; + + WARN_LOG_FMT(AMMEDIABOARD, "GC-AM: Bad GuestSocket value: {}", index); + return INVALID_SOCKET; +} + +static GuestSocket GetGuestSocket(SOCKET x) +{ + const auto it = std::find(std::begin(s_sockets) + FIRST_VALID_FD, std::end(s_sockets), x); + + if (it == std::end(s_sockets)) + { + ERROR_LOG_FMT(AMMEDIABOARD, "GuestSocket not found. This should not happen."); + return INVALID_GUEST_SOCKET; + } + + return GuestSocket(it - std::begin(s_sockets)); } static bool NetworkCMDBufferCheck(u32 offset, u32 length) @@ -183,6 +246,25 @@ static bool NetworkCMDBufferCheck(u32 offset, u32 length) return false; } +static constexpr u8* GetSafePtr(std::span buffer, u32 buffer_base, u32 offset, u32 length) +{ + if (offset >= buffer_base) + { + const u64 off = offset - buffer_base; + if ((off + length) <= buffer.size()) + return std::data(buffer) + off; + } + + if (offset != 0) + { + ERROR_LOG_FMT(AMMEDIABOARD, + "GetSafePtr: buffer[0x{:08x}] base=0x{:08x} offset=0x{:08x} length=0x{:08x}", + buffer.size(), buffer_base, offset, length); + } + + return nullptr; +} + static bool NetworkBufferCheck(u32 offset, u32 length) { if (offset <= std::size(s_network_buffer) && length <= std::size(s_network_buffer) - offset) @@ -242,46 +324,39 @@ static bool SafeCopyFromEmu(Memory::MemoryManager& memory, u8* destination, u32 return true; } -static SOCKET socket_(int af, int type, int protocol) +static GuestSocket socket_(int af, int type, int protocol) { - for (u32 i = 1; i < std::size(s_sockets); ++i) + const auto guest_socket = GetAvailableGuestSocket(); + if (guest_socket == INVALID_GUEST_SOCKET) + return INVALID_GUEST_SOCKET; + + const s32 host_fd = socket(af, type, protocol); + if (host_fd < 0) { - if (s_sockets[i] == SOCKET_ERROR) - { - const s32 host_fd = socket(af, type, protocol); - if (host_fd < 0) - { - ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: failed to create socket ({})", - Common::StrNetworkError()); - return SOCKET_ERROR; - } - - Common::SetPlatformSocketOptions(host_fd); - - s_sockets[i] = host_fd; - return i; - } + ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: failed to create socket ({})", Common::StrNetworkError()); + return INVALID_GUEST_SOCKET; } - // Out of sockets - return SOCKET_ERROR; + Common::SetPlatformSocketOptions(host_fd); + + s_sockets[u32(guest_socket)] = host_fd; + return guest_socket; } -static SOCKET accept_(int fd, sockaddr* addr, socklen_t* len) +static GuestSocket accept_(int fd, sockaddr* addr, socklen_t* len) { - for (u32 i = 1; i < std::size(s_sockets); ++i) - { - if (s_sockets[i] == SOCKET_ERROR) - { - s_sockets[i] = accept(fd, addr, len); - if (s_sockets[i] == SOCKET_ERROR) - return SOCKET_ERROR; - return i; - } - } + const auto guest_socket = GetAvailableGuestSocket(); + if (guest_socket == INVALID_GUEST_SOCKET) + return INVALID_GUEST_SOCKET; - // Out of sockets - return SOCKET_ERROR; + const s32 host_fd = accept(fd, addr, len); + if (host_fd < 0) + return INVALID_GUEST_SOCKET; + + Common::SetPlatformSocketOptions(host_fd); + + s_sockets[u32(guest_socket)] = host_fd; + return guest_socket; } static inline void PrintMBBuffer(u32 address, u32 length) @@ -396,33 +471,107 @@ u8* InitDIMM(u32 size) return s_dimm_disc.data(); } -static s32 NetDIMMAccept(int fd, sockaddr* addr, socklen_t* len) +// Also updates the nfds value. +static fd_set GetHostFdSetFromGuestFdSet(SOCKET* nfds_ptr, int guest_nfds, + const void* guest_fds_ptr) { - SOCKET client_sock = INVALID_SOCKET; - fd_set readfds{}; + fd_set result_fds; + FD_ZERO(&result_fds); - FD_ZERO(&readfds); - FD_SET(fd, &readfds); + if (guest_fds_ptr == nullptr) + return result_fds; + + GuestFdSet guest_fds; + std::memcpy(&guest_fds, guest_fds_ptr, sizeof(guest_fds)); + + auto nfds = *nfds_ptr; + + for (int guest_fd = FIRST_VALID_FD; guest_fd < guest_nfds; ++guest_fd) + { + if (!guest_fds.IsFdSet(GuestSocket(guest_fd))) + continue; + + const auto host_socket = GetHostSocket(GuestSocket(guest_fd)); + if (host_socket == INVALID_SOCKET) + { + ERROR_LOG_FMT(AMMEDIABOARD, "GetHostFdSetFromGuestFdSet: INVALID_SOCKET"); + continue; + } + + if (host_socket >= FD_SETSIZE) + { + ERROR_LOG_FMT(AMMEDIABOARD, "GetHostFdSetFromGuestFdSet: host_socket >= FD_SETSIZE"); + continue; + } + + FD_SET(host_socket, &result_fds); + nfds = std::max(nfds, host_socket + 1); + } + + *nfds_ptr = nfds; + return result_fds; +} + +static void WriteGuestFdSetFromHostFdSet(void* guest_fds_ptr, int nfds, const fd_set* fds) +{ + if (guest_fds_ptr == nullptr) + return; + + GuestFdSet guest_fds; + + for (int fd = 0; fd < nfds; ++fd) + { + if (!FD_ISSET(fd, fds)) + continue; + + guest_fds.SetFd(GetGuestSocket(fd)); + } + + std::memcpy(guest_fds_ptr, &guest_fds, sizeof(guest_fds)); +} + +static auto EmulateSelect(int guest_nfds, void* guest_readfds_ptr, void* guest_writefds_ptr, + void* guest_exceptfds_ptr, timeval* timeout) +{ + // TODO: Emulate using poll or similar on Linux. + + SOCKET nfds = 0; + fd_set readfds = GetHostFdSetFromGuestFdSet(&nfds, guest_nfds, guest_readfds_ptr); + fd_set writefds = GetHostFdSetFromGuestFdSet(&nfds, guest_nfds, guest_writefds_ptr); + fd_set exceptfds = GetHostFdSetFromGuestFdSet(&nfds, guest_nfds, guest_exceptfds_ptr); + + const auto result = select(nfds, &readfds, &writefds, &exceptfds, timeout); + + WriteGuestFdSetFromHostFdSet(guest_readfds_ptr, nfds, &readfds); + WriteGuestFdSetFromHostFdSet(guest_writefds_ptr, nfds, &writefds); + WriteGuestFdSetFromHostFdSet(guest_exceptfds_ptr, nfds, &exceptfds); + + return result; +} + +static GuestSocket NetDIMMAccept(GuestSocket guest_socket, sockaddr* addr, socklen_t* len) +{ + GuestFdSet readfds; + readfds.SetFd(guest_socket); timeval timeout{ - .tv_sec = 0, .tv_usec = 10000, // 10 milliseconds }; - const int result = select(fd + 1, &readfds, nullptr, nullptr, &timeout); - if (result > 0 && FD_ISSET(fd, &readfds)) - { - client_sock = accept_(fd, addr, len); - if (client_sock != INVALID_SOCKET) - { - s_last_error = SSC_SUCCESS; - return client_sock; - } + const int result = EmulateSelect(SOCKET(guest_socket) + 1, &readfds, nullptr, nullptr, &timeout); - ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: accept() failed in NetDIMMAccept ({})", - Common::StrNetworkError()); - s_last_error = SOCKET_ERROR; - return SOCKET_ERROR; + if (result > 0 && readfds.IsFdSet(guest_socket)) + { + const auto client_sock = accept_(GetHostSocket(guest_socket), addr, len); + if (client_sock == INVALID_GUEST_SOCKET) + { + ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: accept() failed in NetDIMMAccept ({})", + Common::StrNetworkError()); + s_last_error = SOCKET_ERROR; + return INVALID_GUEST_SOCKET; + } + s_last_error = SSC_SUCCESS; + return client_sock; } if (result == 0) @@ -436,11 +585,10 @@ static s32 NetDIMMAccept(int fd, sockaddr* addr, socklen_t* len) Common::StrNetworkError()); s_last_error = SOCKET_ERROR; } - - return SOCKET_ERROR; + return INVALID_GUEST_SOCKET; } -static s32 NetDIMMConnect(int fd, sockaddr_in* addr, int len) +static s32 NetDIMMConnect(GuestSocket guest_socket, sockaddr_in* addr, int len) { // All.Net Connect IP if (addr->sin_addr.s_addr == inet_addr("192.168.150.16")) @@ -461,7 +609,6 @@ static s32 NetDIMMConnect(int fd, sockaddr_in* addr, int len) // BUG: An invalid family value is being used addr->sin_family = htons(AF_INET); - s_fd_namco_cam = fd; } // Key of Avalon Client @@ -477,6 +624,8 @@ static s32 NetDIMMConnect(int fd, sockaddr_in* addr, int len) addr->sin_family = Common::swap16(addr->sin_family); + const auto fd = GetHostSocket(guest_socket); + u_long val = 1; // Set socket to non-blocking ioctlsocket(fd, FIONBIO, &val); @@ -494,6 +643,12 @@ static s32 NetDIMMConnect(int fd, sockaddr_in* addr, int len) timeout.tv_sec = 0; timeout.tv_usec = s_timeouts[0]; + // TODO: Don't use select on Linux. + if (fd >= FD_SETSIZE) + { + ERROR_LOG_FMT(AMMEDIABOARD, "NetDIMMConnect: fd >= FD_SETSIZE"); + } + ret = select(fd + 1, nullptr, &writefds, nullptr, &timeout); if (ret > 0 && FD_ISSET(fd, &writefds)) { @@ -546,6 +701,100 @@ static s32 NetDIMMConnect(int fd, sockaddr_in* addr, int len) return ret; } +static void AMMBCommandClosesocket(u32 parameter_offset) +{ + const auto guest_socket = GuestSocket(s_media_buffer_32[parameter_offset]); + const auto fd = GetHostSocket(guest_socket); + + const int ret = closesocket(fd); + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: closesocket( {}({}) ):{}", fd, + s_media_buffer_32[parameter_offset], ret); + + if (u32(guest_socket) < std::size(s_sockets)) + s_sockets[u32(guest_socket)] = SOCKET_ERROR; + + s_media_buffer_32[1] = ret; + s_last_error = SSC_SUCCESS; +} + +static void AMMBCommandConnect(u32 parameter_offset, u32 network_buffer_base) +{ + const auto guest_socket = GuestSocket(s_media_buffer_32[parameter_offset + 0]); + const u32 addr_offset = s_media_buffer_32[parameter_offset + 1]; + const u32 len = s_media_buffer_32[parameter_offset + 2]; + + if (len != sizeof(sockaddr_in)) + { + ERROR_LOG_FMT(AMMEDIABOARD_NET, "AMMBCommandConnect: Unexpected length: {}", len); + return; + } + + sockaddr_in addr; + const auto* addr_ptr = + GetSafePtr(s_network_command_buffer, network_buffer_base, addr_offset, sizeof(addr)); + + if (addr_ptr == nullptr) + { + ERROR_LOG_FMT(AMMEDIABOARD_NET, "AMMBCommandConnect: Bad address offset: {}", addr_offset); + return; + } + + memcpy(&addr, addr_ptr, sizeof(addr)); + + const int ret = NetDIMMConnect(guest_socket, &addr, len); + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: connect( {}, ({},{}:{}), {} ):{}", u32(guest_socket), + addr.sin_family, inet_ntoa(addr.sin_addr), Common::swap16(addr.sin_port), len, + ret); + + s_media_buffer[1] = s_media_buffer[8]; + s_media_buffer_32[1] = ret; +} + +static void AMMBCommandSelect(u32 parameter_offset, u32 network_buffer_base) +{ + const auto guest_nfds = int(s_media_buffer_32[parameter_offset]); + + const u32 readfds_offset = s_media_buffer_32[parameter_offset + 1]; + auto* const guest_readfds_ptr = + GetSafePtr(s_network_command_buffer, network_buffer_base, readfds_offset, sizeof(GuestFdSet)); + + const u32 writefds_offset = s_media_buffer_32[parameter_offset + 2]; + auto* const guest_writefds_ptr = GetSafePtr(s_network_command_buffer, network_buffer_base, + writefds_offset, sizeof(GuestFdSet)); + + const u32 exceptfds_offset = s_media_buffer_32[parameter_offset + 3]; + auto* const guest_exceptfds_ptr = GetSafePtr(s_network_command_buffer, network_buffer_base, + exceptfds_offset, sizeof(GuestFdSet)); + + const u32 timeout_offset = s_media_buffer_32[parameter_offset + 4]; + auto* const guest_timeout_ptr = + GetSafePtr(s_network_command_buffer, network_buffer_base, timeout_offset, sizeof(TimeVal)); + + timeval timeout{}; + if (guest_timeout_ptr != nullptr) + { + TimeVal guest_timeout; + std::memcpy(&guest_timeout, guest_timeout_ptr, sizeof(guest_timeout)); + + timeout.tv_sec = guest_timeout.seconds; + timeout.tv_usec = guest_timeout.microseconds; + } + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: select( {}, 0x{:08x} 0x{:08x} 0x{:08x} 0x{:08x} ) {}:{}", + guest_nfds, readfds_offset, writefds_offset, exceptfds_offset, timeout_offset, + guest_timeout_ptr ? timeout.tv_sec : 0, guest_timeout_ptr ? timeout.tv_usec : 0); + + const int ret = EmulateSelect(guest_nfds, guest_readfds_ptr, guest_writefds_ptr, + guest_exceptfds_ptr, (guest_timeout_ptr ? &timeout : nullptr)); + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: select result: {}", ret); + + s_media_buffer[1] = 0; + s_media_buffer_32[1] = ret; +} + static void FileWriteData(Memory::MemoryManager& memory, File::IOFile* file, u32 seek_pos, u32 address, std::size_t length) { @@ -786,15 +1035,15 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; case AMMBCommand::Accept: { - const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; - int ret = -1; + const auto guest_socket = GuestSocket(s_media_buffer_32[2]); + GuestSocket ret = INVALID_GUEST_SOCKET; sockaddr addr; socklen_t len = 0; // Handle optional parameters if (s_media_buffer_32[3] == 0 || s_media_buffer_32[4] == 0) { - ret = NetDIMMAccept(fd, nullptr, nullptr); + ret = NetDIMMAccept(guest_socket, nullptr, nullptr); } else { @@ -814,7 +1063,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u // // socklen_t might be larger than u32 // const u32 addr_len = Common::BitCastPtr(s_network_command_buffer + len_off); // len = addr_len; - ret = NetDIMMAccept(fd, &addr, &len); + ret = NetDIMMAccept(guest_socket, &addr, &len); if (len) { memcpy(s_network_command_buffer + addr_off, &addr, sizeof(sockaddr)); @@ -822,14 +1071,14 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u } } - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: accept( {}({}) ):{}\n", fd, s_media_buffer_32[2], - ret); - s_media_buffer_32[1] = ret; + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: accept( {}({}) ):{}", u32(guest_socket), + u32(guest_socket), u32(ret)); + s_media_buffer_32[1] = u32(ret); break; } case AMMBCommand::Bind: { - const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[2])); const u32 off = s_media_buffer_32[3] - NetworkCommandAddress2; const u32 len = s_media_buffer_32[4]; @@ -870,44 +1119,11 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; } case AMMBCommand::Closesocket: - { - const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; - - const int ret = closesocket(fd); - - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: closesocket( {}({}) ):{}\n", fd, - s_media_buffer_32[2], ret); - - s_sockets[SocketCheck(s_media_buffer_32[2])] = SOCKET_ERROR; - - s_media_buffer_32[1] = ret; - s_last_error = SSC_SUCCESS; + AMMBCommandClosesocket(2); break; - } case AMMBCommand::Connect: - { - const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; - const u32 off = s_media_buffer_32[3] - NetworkCommandAddress2; - const u32 len = s_media_buffer_32[4]; - - if (!NetworkCMDBufferCheck(off, std::max(sizeof(sockaddr_in), len))) - { - break; - } - - sockaddr_in addr; - memcpy(&addr, s_network_command_buffer + off, sizeof(sockaddr_in)); - - const int ret = NetDIMMConnect(fd, &addr, len); - - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: connect( {}({}), ({},{}:{}), {} ):{}\n", fd, - s_media_buffer_32[2], addr.sin_family, inet_ntoa(addr.sin_addr), - Common::swap16(addr.sin_port), len, ret); - - s_media_buffer[1] = s_media_buffer[8]; - s_media_buffer_32[1] = ret; + AMMBCommandConnect(2, NetworkCommandAddress2); break; - } case AMMBCommand::InetAddr: { const char* ip_address = reinterpret_cast(s_network_command_buffer); @@ -930,7 +1146,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u } case AMMBCommand::Listen: { - const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[2])); const u32 backlog = s_media_buffer_32[3]; const int ret = listen(fd, backlog); @@ -943,7 +1159,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u } case AMMBCommand::Recv: { - const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[2])); u32 off = s_media_buffer_32[3]; auto len = std::min(s_media_buffer_32[4], sizeof(s_network_buffer)); const u64 off_len = u64(off) + len; @@ -974,7 +1190,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u } case AMMBCommand::Send: { - const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[2])); u32 off = s_media_buffer_32[3]; auto len = std::min(s_media_buffer_32[4], sizeof(s_network_buffer)); const u64 off_len = u64(off) + len; @@ -1009,107 +1225,21 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u const u32 af = s_media_buffer_32[2]; const u32 type = s_media_buffer_32[3]; - const SOCKET fd = socket_(af, type, IPPROTO_TCP); + const GuestSocket guest_socket = socket_(af, type, IPPROTO_TCP); - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: socket( {}, {}, IPPROTO_TCP ):{}\n", af, type, fd); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: socket( {}, {}, IPPROTO_TCP ):{}\n", af, type, + u32(guest_socket)); s_media_buffer[1] = 0; - s_media_buffer_32[1] = fd; + s_media_buffer_32[1] = u32(guest_socket); break; } case AMMBCommand::Select: - { - SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2] - 1)]; - - // BUG: NAMCAM is hardcoded to call this with socket ID 0x100 which might be some magic - // thing? A valid value is needed so we use the socket from the connect. - - if (AMMediaboard::GetGameType() == MarioKartGP || - AMMediaboard::GetGameType() == MarioKartGP2) - { - if (s_media_buffer_32[2] == 256) - { - fd = s_fd_namco_cam; - } - } - - fd_set* readfds = nullptr; - fd_set* writefds = nullptr; - fd_set* exceptfds = nullptr; - - timeval timeout = {}; - std::optional timeout_offset; - - fd_set fds{}; - FD_ZERO(&fds); - FD_SET(fd, &fds); - - // Only one of 3, 4, 5 is ever set alongside 6 - if (s_media_buffer_32[6] != 0) - { - const u32 fd_set_offset = s_media_buffer_32[6] - NetworkCommandAddress2; - if (!NetworkCMDBufferCheck(fd_set_offset, sizeof(fd_set))) - { - ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: Select(error) unhandled destination:{:08x}\n", - s_media_buffer_32[6]); - break; - } - - // TODO: Ensure host's fd_set is compatible with Triforce's - Common::BitCastPtr(s_network_command_buffer + fd_set_offset) = fds; - - if (s_media_buffer_32[3] != 0) - { - readfds = &fds; - timeout_offset = s_media_buffer_32[3] - NetworkCommandAddress2; - } - else if (s_media_buffer_32[4] != 0) - { - writefds = &fds; - timeout_offset = s_media_buffer_32[4] - NetworkCommandAddress2; - } - else if (s_media_buffer_32[5] != 0) - { - exceptfds = &fds; - timeout_offset = s_media_buffer_32[5] - NetworkCommandAddress2; - } - } - - // Copy timeout if set - if (timeout_offset.has_value()) - { - if (!NetworkCMDBufferCheck(*timeout_offset, sizeof(timeval))) - break; - - std::memcpy(&timeout, s_network_command_buffer + *timeout_offset, sizeof(timeval)); - } - - // BUG: The game sets timeout to two seconds - if (AMMediaboard::GetGameType() == KeyOfAvalon) - { - timeout.tv_sec = 0; - timeout.tv_usec = 1800; - } - - const int ret = - select(fd + 1, readfds, writefds, exceptfds, timeout_offset ? &timeout : nullptr); - - const int err = WSAGetLastError(); - - NOTICE_LOG_FMT( - AMMEDIABOARD_NET, - "GC-AM: select( {}({}), 0x{:08x} 0x{:08x} 0x{:08x} 0x{:08x} ):{} {} {}:{} \n", fd, - s_media_buffer_32[2], s_media_buffer_32[3], s_media_buffer_32[4], s_media_buffer_32[5], - s_media_buffer_32[6], ret, err, timeout.tv_sec, timeout.tv_usec); - // hexdump( s_network_command_buffer, 0x40 ); - - s_media_buffer[1] = 0; - s_media_buffer_32[1] = ret; + AMMBCommandSelect(2, NetworkCommandAddress2); break; - } case AMMBCommand::SetSockOpt: { - const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[2])); const int level = static_cast(s_media_buffer_32[3]); const int optname = static_cast(s_media_buffer_32[4]); const int optlen = static_cast(s_media_buffer_32[6]); @@ -1136,7 +1266,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u } case AMMBCommand::SetTimeOuts: { - const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[2])); const u32 timeout_a = s_media_buffer_32[3]; const u32 timeout_b = s_media_buffer_32[4]; const u32 timeout_c = s_media_buffer_32[5]; @@ -1204,7 +1334,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u } case AMMBCommand::GetLastError: { - const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[2])]; + const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[2])); NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: GetLastError( {}({}) ):{}\n", fd, s_media_buffer_32[2], s_last_error); @@ -1507,47 +1637,14 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u s_media_buffer_32[1] = s_media_buffer_32[9]; break; case AMMBCommand::Closesocket: - { - const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; - - const int ret = closesocket(fd); - - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: closesocket( {}({}) ):{}\n", fd, - s_media_buffer_32[10], ret); - - s_sockets[SocketCheck(s_media_buffer_32[10])] = SOCKET_ERROR; - - s_media_buffer_32[1] = ret; - s_last_error = SSC_SUCCESS; - } - break; + AMMBCommandClosesocket(10); + break; case AMMBCommand::Connect: - { - const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; - const u32 off = s_media_buffer_32[11] - NetworkCommandAddress1; - const u32 len = s_media_buffer_32[12]; - - if (NetworkCMDBufferCheck(off, sizeof(sockaddr_in))) - { - break; - } - - sockaddr_in addr; - memcpy(&addr, s_network_command_buffer + off, sizeof(sockaddr_in)); - - const int ret = NetDIMMConnect(fd, &addr, len); - - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: connect( {}({}), ({},{}:{}), {} ):{}\n", fd, - s_media_buffer_32[10], addr.sin_family, inet_ntoa(addr.sin_addr), - Common::swap16(addr.sin_port), len, ret); - - s_media_buffer[1] = s_media_buffer[8]; - s_media_buffer_32[1] = ret; - } - break; + AMMBCommandConnect(10, NetworkCommandAddress1); + break; case AMMBCommand::Recv: { - const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; + const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[10])); u32 off = s_media_buffer_32[11]; auto len = std::min(s_media_buffer_32[12], sizeof(s_network_buffer)); const u64 off_len = u64(off) + len; @@ -1578,7 +1675,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; case AMMBCommand::Send: { - const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; + const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[10])); u32 off = s_media_buffer_32[11]; auto len = std::min(s_media_buffer_32[12], sizeof(s_network_buffer)); const u64 off_len = u64(off) + len; @@ -1613,93 +1710,21 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u const u32 af = s_media_buffer_32[10]; const u32 type = s_media_buffer_32[11]; - const SOCKET fd = socket_(af, type, IPPROTO_TCP); + const GuestSocket guest_socket = socket_(af, type, IPPROTO_TCP); - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: socket( {}, {}, IPPROTO_TCP ):{}\n", af, type, fd); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: socket( {}, {}, IPPROTO_TCP ):{}\n", af, type, + u32(guest_socket)); s_media_buffer[1] = 0; - s_media_buffer_32[1] = fd; + s_media_buffer_32[1] = u32(guest_socket); } break; case AMMBCommand::Select: - { - const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[10] - 1)]; - - fd_set* readfds = nullptr; - fd_set* writefds = nullptr; - fd_set* exceptfds = nullptr; - - timeval timeout = {}; - std::optional timeout_offset; - - fd_set fds{}; - FD_ZERO(&fds); - FD_SET(fd, &fds); - - // Only one of 11, 12, 13 is ever set alongside 14 - if (s_media_buffer_32[14] != 0) - { - const u32 fd_set_offset = s_media_buffer_32[14] - NetworkCommandAddress1; - if (!NetworkCMDBufferCheck(fd_set_offset, sizeof(fd_set))) - { - ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: Select(error) unhandled destination:{:08x}\n", - s_media_buffer_32[14]); - break; - } - - // TODO: Ensure host's fd_set is compatible with Triforce's - Common::BitCastPtr(s_network_command_buffer + fd_set_offset) = fds; - - if (s_media_buffer_32[11] != 0) - { - readfds = &fds; - timeout_offset = s_media_buffer_32[11] - NetworkCommandAddress1; - } - else if (s_media_buffer_32[12] != 0) - { - writefds = &fds; - timeout_offset = s_media_buffer_32[12] - NetworkCommandAddress1; - } - else if (s_media_buffer_32[13] != 0) - { - exceptfds = &fds; - timeout_offset = s_media_buffer_32[13] - NetworkCommandAddress1; - } - } - - // Copy timeout if set - if (timeout_offset.has_value()) - { - if (!NetworkCMDBufferCheck(*timeout_offset, sizeof(timeval))) - break; - - std::memcpy(&timeout, s_network_command_buffer + *timeout_offset, sizeof(timeval)); - } - - // BUG?: F-Zero AX Monster calls select with a two second timeout - // for unknown reasons, which slows down the game a lot - if (AMMediaboard::GetGameType() == FZeroAXMonster) - { - timeout.tv_sec = 0; - timeout.tv_usec = 1800; - } - - const int ret = - select(fd + 1, readfds, writefds, exceptfds, timeout_offset ? &timeout : nullptr); - const int err = WSAGetLastError(); - - NOTICE_LOG_FMT(AMMEDIABOARD_NET, - "GC-AM: select( {}({}), 0x{:08x} 0x{:08x} 0x{:08x} 0x{:08x} ):{} {} \n", fd, - s_media_buffer_32[10], s_media_buffer_32[11], s_media_buffer_32[14], - s_media_buffer_32[15], s_media_buffer_32[16], ret, err); - - s_media_buffer[1] = 0; - s_media_buffer_32[1] = ret; - } - break; + AMMBCommandSelect(10, NetworkCommandAddress1); + break; case AMMBCommand::SetSockOpt: { - const SOCKET fd = s_sockets[SocketCheck(s_media_buffer_32[10])]; + const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[10])); const int level = static_cast(s_media_buffer_32[11]); const int optname = static_cast(s_media_buffer_32[12]); const int optlen = static_cast(s_media_buffer_32[14]); @@ -1897,7 +1922,7 @@ void Shutdown() s_dimm_disc.clear(); // Close all sockets - for (u32 i = 1; i < std::size(s_sockets); ++i) + for (u32 i = FIRST_VALID_FD; i < std::size(s_sockets); ++i) { if (s_sockets[i] != SOCKET_ERROR) { From 65def4e235beed654a1d592b9f506cc6956b546b Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Thu, 5 Feb 2026 13:42:26 -0600 Subject: [PATCH 25/71] AMMediaboard: Deduplicate code into AMMBCommandSetSockOpt. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 80 +++++++++--------------- 1 file changed, 29 insertions(+), 51 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index be5f01ecc2..444b132336 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -795,6 +795,32 @@ static void AMMBCommandSelect(u32 parameter_offset, u32 network_buffer_base) s_media_buffer_32[1] = ret; } +static void AMMBCommandSetSockOpt(u32 parameter_offset, u32 network_buffer_base) +{ + const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[parameter_offset])); + const int level = static_cast(s_media_buffer_32[parameter_offset + 1]); + const int optname = static_cast(s_media_buffer_32[parameter_offset + 2]); + const u32 optval_offset = s_media_buffer_32[parameter_offset + 3] - network_buffer_base; + const int optlen = static_cast(s_media_buffer_32[parameter_offset + 4]); + + if (!NetworkCMDBufferCheck(optval_offset, optlen)) + { + return; + } + + const char* optval = reinterpret_cast(s_network_command_buffer + optval_offset); + + // TODO: Ensure parameters are compatible with host's setsockopt + const int ret = setsockopt(fd, level, optname, optval, optlen); + const int err = WSAGetLastError(); + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: setsockopt( {:d}, {:04x}, {}, {:p}, {} ):{:d} ({})", fd, + level, optname, optval, optlen, ret, err); + + s_media_buffer[1] = s_media_buffer[8]; + s_media_buffer_32[1] = ret; +} + static void FileWriteData(Memory::MemoryManager& memory, File::IOFile* file, u32 seek_pos, u32 address, std::size_t length) { @@ -1238,32 +1264,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u AMMBCommandSelect(2, NetworkCommandAddress2); break; case AMMBCommand::SetSockOpt: - { - const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[2])); - const int level = static_cast(s_media_buffer_32[3]); - const int optname = static_cast(s_media_buffer_32[4]); - const int optlen = static_cast(s_media_buffer_32[6]); - const u32 optval_offset = s_media_buffer_32[5] - NetworkCommandAddress2; - - if (!NetworkCMDBufferCheck(optval_offset, optlen)) - { - break; - } - - const char* optval = reinterpret_cast(s_network_command_buffer + optval_offset); - - // TODO: Ensure parameters are compatible with host's setsockopt - const int ret = setsockopt(fd, level, optname, optval, optlen); - const int err = WSAGetLastError(); - - NOTICE_LOG_FMT(AMMEDIABOARD_NET, - "GC-AM: setsockopt( {:d}, {:04x}, {}, {:p}, {} ):{:d} ({})\n", fd, level, - optname, optval, optlen, ret, err); - - s_media_buffer[1] = s_media_buffer[8]; - s_media_buffer_32[1] = ret; + AMMBCommandSetSockOpt(2, NetworkCommandAddress2); break; - } case AMMBCommand::SetTimeOuts: { const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[2])); @@ -1723,32 +1725,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u AMMBCommandSelect(10, NetworkCommandAddress1); break; case AMMBCommand::SetSockOpt: - { - const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[10])); - const int level = static_cast(s_media_buffer_32[11]); - const int optname = static_cast(s_media_buffer_32[12]); - const int optlen = static_cast(s_media_buffer_32[14]); - const u32 optval_offset = s_media_buffer_32[13] - NetworkCommandAddress1; - - if (!NetworkCMDBufferCheck(optval_offset, optlen)) - { - break; - } - - const char* optval = reinterpret_cast(s_network_command_buffer + optval_offset); - - // TODO: Ensure parameters are compatible with host's setsockopt - const int ret = setsockopt(fd, level, optname, optval, optlen); - const int err = WSAGetLastError(); - - NOTICE_LOG_FMT(AMMEDIABOARD_NET, - "GC-AM: setsockopt( {:d}, {:04x}, {}, {:p}, {} ):{:d} ({})\n", fd, level, - optname, optval, optlen, ret, err); - - s_media_buffer[1] = s_media_buffer[8]; - s_media_buffer_32[1] = ret; - } - break; + AMMBCommandSetSockOpt(10, NetworkCommandAddress1); + break; case AMMBCommand::ModifyMyIPaddr: { const u32 net_buffer_offset = s_media_buffer_32[10] - NetworkCommandAddress1; From 69c143d8f0df48c2d3513c7cf4621a8cfe5d5a9d Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Thu, 5 Feb 2026 13:50:13 -0600 Subject: [PATCH 26/71] AMMediaboard: Deduplicate code into AMMBCommandSocket. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 45 ++++++++++-------------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 444b132336..22ecf7a2b9 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -701,6 +701,21 @@ static s32 NetDIMMConnect(GuestSocket guest_socket, sockaddr_in* addr, int len) return ret; } +static void AMMBCommandSocket(u32 parameter_offset) +{ + // Protocol is not sent + const u32 af = s_media_buffer_32[parameter_offset]; + const u32 type = s_media_buffer_32[parameter_offset + 1]; + + const GuestSocket guest_socket = socket_(af, type, IPPROTO_TCP); + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: socket( {}, {}, IPPROTO_TCP ):{}", af, type, + u32(guest_socket)); + + s_media_buffer[1] = 0; + s_media_buffer_32[1] = u32(guest_socket); +} + static void AMMBCommandClosesocket(u32 parameter_offset) { const auto guest_socket = GuestSocket(s_media_buffer_32[parameter_offset]); @@ -1246,20 +1261,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; } case AMMBCommand::Socket: - { - // Protocol is not sent - const u32 af = s_media_buffer_32[2]; - const u32 type = s_media_buffer_32[3]; - - const GuestSocket guest_socket = socket_(af, type, IPPROTO_TCP); - - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: socket( {}, {}, IPPROTO_TCP ):{}\n", af, type, - u32(guest_socket)); - - s_media_buffer[1] = 0; - s_media_buffer_32[1] = u32(guest_socket); + AMMBCommandSocket(2); break; - } case AMMBCommand::Select: AMMBCommandSelect(2, NetworkCommandAddress2); break; @@ -1707,20 +1710,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u } break; case AMMBCommand::Socket: - { - // Protocol is not sent - const u32 af = s_media_buffer_32[10]; - const u32 type = s_media_buffer_32[11]; - - const GuestSocket guest_socket = socket_(af, type, IPPROTO_TCP); - - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: socket( {}, {}, IPPROTO_TCP ):{}\n", af, type, - u32(guest_socket)); - - s_media_buffer[1] = 0; - s_media_buffer_32[1] = u32(guest_socket); - } - break; + AMMBCommandSocket(10); + break; case AMMBCommand::Select: AMMBCommandSelect(10, NetworkCommandAddress1); break; From f7ff93bea698e5d0bcd79ded0b416f2b9b91e73c Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Thu, 5 Feb 2026 13:52:39 -0600 Subject: [PATCH 27/71] AMMediaboard: Deduplicate code into AMMBCommandSend. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 92 +++++++++--------------- 1 file changed, 33 insertions(+), 59 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 22ecf7a2b9..f5457c21c7 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -701,6 +701,36 @@ static s32 NetDIMMConnect(GuestSocket guest_socket, sockaddr_in* addr, int len) return ret; } +static void AMMBCommandSend(u32 parameter_offset, u32 network_buffer_base) +{ + const auto guest_socket = GuestSocket(s_media_buffer_32[parameter_offset]); + const auto fd = GetHostSocket(guest_socket); + u32 off = s_media_buffer_32[parameter_offset + 1]; + auto len = std::min(s_media_buffer_32[parameter_offset + 2], sizeof(s_network_buffer)); + const u64 off_len = u64(off) + len; + + if (off >= network_buffer_base && off_len <= network_buffer_base + sizeof(s_network_buffer)) + { + off -= network_buffer_base; + } + else if (off_len > sizeof(s_network_buffer)) + { + ERROR_LOG_FMT(AMMEDIABOARD_NET, + "GC-AM: send(error) unhandled destination or length: {:08x}, len={}", off, len); + off = 0; + len = 0; + } + + const int ret = send(fd, reinterpret_cast(s_network_buffer + off), len, SEND_FLAGS); + const int err = WSAGetLastError(); + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: send( {}({}), 0x{:08x}, {} ): {} {}", fd, + u32(guest_socket), off, len, ret, err); + + s_media_buffer[1] = s_media_buffer[8]; + s_media_buffer_32[1] = ret; +} + static void AMMBCommandSocket(u32 parameter_offset) { // Protocol is not sent @@ -1230,36 +1260,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; } case AMMBCommand::Send: - { - const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[2])); - u32 off = s_media_buffer_32[3]; - auto len = std::min(s_media_buffer_32[4], sizeof(s_network_buffer)); - const u64 off_len = u64(off) + len; - - if (off >= NetworkBufferAddress3 && - off_len <= NetworkBufferAddress3 + sizeof(s_network_buffer)) - { - off -= NetworkBufferAddress3; - } - else if (off_len > sizeof(s_network_buffer)) - { - ERROR_LOG_FMT(AMMEDIABOARD_NET, - "GC-AM: send(error) unhandled destination or length: {:08x}, len={}", off, - len); - off = 0; - len = 0; - } - - const int ret = send(fd, reinterpret_cast(s_network_buffer + off), len, SEND_FLAGS); - const int err = WSAGetLastError(); - - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: send( {}({}), 0x{:08x}, {} ): {} {}\n", fd, - s_media_buffer_32[2], off, len, ret, err); - - s_media_buffer[1] = s_media_buffer[8]; - s_media_buffer_32[1] = ret; + AMMBCommandSend(2, NetworkBufferAddress3); break; - } case AMMBCommand::Socket: AMMBCommandSocket(2); break; @@ -1679,36 +1681,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u } break; case AMMBCommand::Send: - { - const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[10])); - u32 off = s_media_buffer_32[11]; - auto len = std::min(s_media_buffer_32[12], sizeof(s_network_buffer)); - const u64 off_len = u64(off) + len; - - if (off >= NetworkBufferAddress1 && - off_len <= NetworkBufferAddress1 + sizeof(s_network_buffer)) - { - off -= NetworkBufferAddress1; - } - else if (off_len > sizeof(s_network_buffer)) - { - ERROR_LOG_FMT(AMMEDIABOARD_NET, - "GC-AM: send(error) unhandled destination or length: {:08x}, len={}", off, - len); - off = 0; - len = 0; - } - - const int ret = send(fd, reinterpret_cast(s_network_buffer + off), len, SEND_FLAGS); - const int err = WSAGetLastError(); - - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: send( {}({}), 0x{:08x}, {} ): {} {}\n", fd, - s_media_buffer_32[10], off, len, ret, err); - - s_media_buffer[1] = s_media_buffer[8]; - s_media_buffer_32[1] = ret; - } - break; + AMMBCommandSend(10, NetworkBufferAddress1); + break; case AMMBCommand::Socket: AMMBCommandSocket(10); break; From ce85c4d81d8aa02b89e99f5d2aac98001894c9b5 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Thu, 5 Feb 2026 14:06:54 -0600 Subject: [PATCH 28/71] AMMediaboard: Deduplicate code into AMMBCommandRecv. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 90 ++++++++---------------- 1 file changed, 31 insertions(+), 59 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index f5457c21c7..af5b5e3a23 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -701,6 +701,34 @@ static s32 NetDIMMConnect(GuestSocket guest_socket, sockaddr_in* addr, int len) return ret; } +static void AMMBCommandRecv(u32 parameter_offset, u32 network_buffer_base) +{ + const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[parameter_offset])); + u32 off = s_media_buffer_32[parameter_offset + 1]; + auto len = std::min(s_media_buffer_32[parameter_offset + 2], sizeof(s_network_buffer)); + const u64 off_len = u64(off) + len; + + if (off >= network_buffer_base && off_len <= network_buffer_base + sizeof(s_network_buffer)) + { + off -= network_buffer_base; + } + else if (off_len > sizeof(s_network_buffer)) + { + ERROR_LOG_FMT(AMMEDIABOARD_NET, + "GC-AM: recv(error) invalid destination or length: off={:08x}, len={}", off, len); + off = 0; + len = 0; + } + + int ret = recv(fd, reinterpret_cast(s_network_buffer + off), len, 0); + const int err = WSAGetLastError(); + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: recv( {}, 0x{:08x}, {} ):{} {}", fd, off, len, ret, err); + + s_media_buffer[1] = s_media_buffer[8]; + s_media_buffer_32[1] = ret; +} + static void AMMBCommandSend(u32 parameter_offset, u32 network_buffer_base) { const auto guest_socket = GuestSocket(s_media_buffer_32[parameter_offset]); @@ -1229,36 +1257,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; } case AMMBCommand::Recv: - { - const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[2])); - u32 off = s_media_buffer_32[3]; - auto len = std::min(s_media_buffer_32[4], sizeof(s_network_buffer)); - const u64 off_len = u64(off) + len; - - if (off >= NetworkBufferAddress4 && - off_len <= NetworkBufferAddress4 + sizeof(s_network_buffer)) - { - off -= NetworkBufferAddress4; - } - else if (off_len > sizeof(s_network_buffer)) - { - ERROR_LOG_FMT(AMMEDIABOARD_NET, - "GC-AM: recv(error) invalid destination or length: off={:08x}, len={}\n", - off, len); - off = 0; - len = 0; - } - - int ret = recv(fd, reinterpret_cast(s_network_buffer + off), len, 0); - const int err = WSAGetLastError(); - - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: recv( {}, 0x{:08x}, {} ):{} {}\n", fd, off, len, - ret, err); - - s_media_buffer[1] = s_media_buffer[8]; - s_media_buffer_32[1] = ret; + AMMBCommandRecv(2, NetworkBufferAddress4); break; - } case AMMBCommand::Send: AMMBCommandSend(2, NetworkBufferAddress3); break; @@ -1650,36 +1650,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u AMMBCommandConnect(10, NetworkCommandAddress1); break; case AMMBCommand::Recv: - { - const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[10])); - u32 off = s_media_buffer_32[11]; - auto len = std::min(s_media_buffer_32[12], sizeof(s_network_buffer)); - const u64 off_len = u64(off) + len; - - if (off >= NetworkBufferAddress5 && - off_len <= NetworkBufferAddress5 + sizeof(s_network_buffer)) - { - off -= NetworkBufferAddress5; - } - else if (off_len > sizeof(s_network_buffer)) - { - ERROR_LOG_FMT(AMMEDIABOARD_NET, - "GC-AM: recv(error) invalid destination or length: off={:08x}, len={}\n", - off, len); - off = 0; - len = 0; - } - - int ret = recv(fd, reinterpret_cast(s_network_buffer + off), len, 0); - const int err = WSAGetLastError(); - - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: recv( {}, 0x{:08x}, {} ):{} {}\n", fd, off, len, - ret, err); - - s_media_buffer[1] = s_media_buffer[8]; - s_media_buffer_32[1] = ret; - } - break; + AMMBCommandRecv(10, NetworkBufferAddress5); + break; case AMMBCommand::Send: AMMBCommandSend(10, NetworkBufferAddress1); break; From 0713045d4f678fa2cbedbf33c3edc4fdbc2919f1 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Thu, 5 Feb 2026 14:07:03 -0600 Subject: [PATCH 29/71] AMMediaboard: Deduplicate code into AMMBCommandModifyMyIPaddr. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 47 +++++++++--------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index af5b5e3a23..04f21211c5 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -894,6 +894,21 @@ static void AMMBCommandSetSockOpt(u32 parameter_offset, u32 network_buffer_base) s_media_buffer_32[1] = ret; } +static void AMMBCommandModifyMyIPaddr(u32 parameter_offset, u32 network_buffer_base) +{ + const u32 net_buffer_offset = s_media_buffer_32[parameter_offset] - network_buffer_base; + + if (!NetworkCMDBufferCheck(net_buffer_offset, MAX_IPV4_STRING_LENGTH)) + { + return; + } + + const char* ip_address = reinterpret_cast(s_network_command_buffer + net_buffer_offset); + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: modifyMyIPaddr({})", + fmt::string_view(ip_address, MAX_IPV4_STRING_LENGTH)); +} + static void FileWriteData(Memory::MemoryManager& memory, File::IOFile* file, u32 seek_pos, u32 address, std::size_t length) { @@ -1324,21 +1339,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; } case AMMBCommand::ModifyMyIPaddr: - { - const u32 net_buffer_offset = s_media_buffer_32[2] - NetworkCommandAddress2; - - if (!NetworkCMDBufferCheck(net_buffer_offset, MAX_IPV4_STRING_LENGTH)) - { - break; - } - - const char* ip_address = - reinterpret_cast(s_network_command_buffer + net_buffer_offset); - - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: modifyMyIPaddr({})\n", - fmt::string_view(ip_address, MAX_IPV4_STRING_LENGTH)); + AMMBCommandModifyMyIPaddr(2, NetworkCommandAddress2); break; - } case AMMBCommand::GetLastError: { const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[2])); @@ -1665,21 +1667,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u AMMBCommandSetSockOpt(10, NetworkCommandAddress1); break; case AMMBCommand::ModifyMyIPaddr: - { - const u32 net_buffer_offset = s_media_buffer_32[10] - NetworkCommandAddress1; - - if (!NetworkCMDBufferCheck(net_buffer_offset, MAX_IPV4_STRING_LENGTH)) - { - break; - } - - const char* ip_address = - reinterpret_cast(s_network_command_buffer + net_buffer_offset); - - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: modifyMyIPaddr({})\n", - fmt::string_view(ip_address, MAX_IPV4_STRING_LENGTH)); - } - break; + AMMBCommandModifyMyIPaddr(10, NetworkCommandAddress1); + break; // Empty reply case AMMBCommand::InitLink: NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: InitLink"); From f25c4671547ad42771ac29999516b9a7897a8de7 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Thu, 5 Feb 2026 20:41:06 -0600 Subject: [PATCH 30/71] Common/UnixUtil: Only define CreateEventFD on Linux. --- Source/Core/Common/UnixUtil.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/Core/Common/UnixUtil.h b/Source/Core/Common/UnixUtil.h index f1b5fc136f..374126e403 100644 --- a/Source/Core/Common/UnixUtil.h +++ b/Source/Core/Common/UnixUtil.h @@ -4,13 +4,18 @@ #pragma once #include + +#if defined(__linux__) #include +#endif #include "Common/CommonFuncs.h" #include "Common/Logging/Log.h" namespace UnixUtil { + +#if defined(__linux__) inline int CreateEventFD(unsigned int count, int flags) { const int result = eventfd(count, flags); @@ -21,6 +26,7 @@ inline int CreateEventFD(unsigned int count, int flags) } return result; } +#endif // Repeatedly call a function that can erroneously produce EINTR. auto RetryOnEINTR(auto func, auto... args) From 5f6cd84ad73afd7f54523b633af6d27cc86a9a38 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Thu, 5 Feb 2026 16:40:38 -0600 Subject: [PATCH 31/71] AMMediaboard: Emulate AMMBCommandSelect using poll/WSAPoll. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 196 ++++++++++++----------- 1 file changed, 104 insertions(+), 92 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 04f21211c5..0d44d13db5 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -38,6 +38,8 @@ #include +#include "Common/UnixUtil.h" + static constexpr auto* closesocket = close; static auto ioctlsocket(auto... args) { @@ -48,6 +50,7 @@ static constexpr int WSAEWOULDBLOCK = 10035; static constexpr int SOCKET_ERROR = -1; using SOCKET = int; +using WSAPOLLFD = pollfd; static constexpr SOCKET INVALID_SOCKET = SOCKET(~0); @@ -471,96 +474,24 @@ u8* InitDIMM(u32 size) return s_dimm_disc.data(); } -// Also updates the nfds value. -static fd_set GetHostFdSetFromGuestFdSet(SOCKET* nfds_ptr, int guest_nfds, - const void* guest_fds_ptr) +static int PlatformPoll(std::span pfds, std::chrono::milliseconds timeout) { - fd_set result_fds; - FD_ZERO(&result_fds); - - if (guest_fds_ptr == nullptr) - return result_fds; - - GuestFdSet guest_fds; - std::memcpy(&guest_fds, guest_fds_ptr, sizeof(guest_fds)); - - auto nfds = *nfds_ptr; - - for (int guest_fd = FIRST_VALID_FD; guest_fd < guest_nfds; ++guest_fd) - { - if (!guest_fds.IsFdSet(GuestSocket(guest_fd))) - continue; - - const auto host_socket = GetHostSocket(GuestSocket(guest_fd)); - if (host_socket == INVALID_SOCKET) - { - ERROR_LOG_FMT(AMMEDIABOARD, "GetHostFdSetFromGuestFdSet: INVALID_SOCKET"); - continue; - } - - if (host_socket >= FD_SETSIZE) - { - ERROR_LOG_FMT(AMMEDIABOARD, "GetHostFdSetFromGuestFdSet: host_socket >= FD_SETSIZE"); - continue; - } - - FD_SET(host_socket, &result_fds); - nfds = std::max(nfds, host_socket + 1); - } - - *nfds_ptr = nfds; - return result_fds; -} - -static void WriteGuestFdSetFromHostFdSet(void* guest_fds_ptr, int nfds, const fd_set* fds) -{ - if (guest_fds_ptr == nullptr) - return; - - GuestFdSet guest_fds; - - for (int fd = 0; fd < nfds; ++fd) - { - if (!FD_ISSET(fd, fds)) - continue; - - guest_fds.SetFd(GetGuestSocket(fd)); - } - - std::memcpy(guest_fds_ptr, &guest_fds, sizeof(guest_fds)); -} - -static auto EmulateSelect(int guest_nfds, void* guest_readfds_ptr, void* guest_writefds_ptr, - void* guest_exceptfds_ptr, timeval* timeout) -{ - // TODO: Emulate using poll or similar on Linux. - - SOCKET nfds = 0; - fd_set readfds = GetHostFdSetFromGuestFdSet(&nfds, guest_nfds, guest_readfds_ptr); - fd_set writefds = GetHostFdSetFromGuestFdSet(&nfds, guest_nfds, guest_writefds_ptr); - fd_set exceptfds = GetHostFdSetFromGuestFdSet(&nfds, guest_nfds, guest_exceptfds_ptr); - - const auto result = select(nfds, &readfds, &writefds, &exceptfds, timeout); - - WriteGuestFdSetFromHostFdSet(guest_readfds_ptr, nfds, &readfds); - WriteGuestFdSetFromHostFdSet(guest_writefds_ptr, nfds, &writefds); - WriteGuestFdSetFromHostFdSet(guest_exceptfds_ptr, nfds, &exceptfds); - - return result; +#if defined(_WIN32) + return WSAPoll(pfds.data(), ULONG(pfds.size()), INT(timeout.count())); +#else + return UnixUtil::RetryOnEINTR(poll, pfds.data(), pfds.size(), timeout.count()); +#endif } static GuestSocket NetDIMMAccept(GuestSocket guest_socket, sockaddr* addr, socklen_t* len) { - GuestFdSet readfds; - readfds.SetFd(guest_socket); + WSAPOLLFD pfds[1]{{.fd = GetHostSocket(guest_socket), .events = POLLIN}}; - timeval timeout{ - .tv_usec = 10000, // 10 milliseconds - }; + constexpr auto timeout = std::chrono::milliseconds{10}; - const int result = EmulateSelect(SOCKET(guest_socket) + 1, &readfds, nullptr, nullptr, &timeout); + const int result = PlatformPoll(pfds, timeout); - if (result > 0 && readfds.IsFdSet(guest_socket)) + if (result > 0 && (pfds[0].revents & POLLIN) != 0) { const auto client_sock = accept_(GetHostSocket(guest_socket), addr, len); if (client_sock == INVALID_GUEST_SOCKET) @@ -581,7 +512,7 @@ static GuestSocket NetDIMMAccept(GuestSocket guest_socket, sockaddr* addr, sockl } else { - ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: select() failed in NetDIMMAccept ({})", + ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: poll() failed in NetDIMMAccept ({})", Common::StrNetworkError()); s_last_error = SOCKET_ERROR; } @@ -825,9 +756,57 @@ static void AMMBCommandConnect(u32 parameter_offset, u32 network_buffer_base) s_media_buffer_32[1] = ret; } +// Expects a pointer to a GuestFdSet or nullptr. +static void FillPollFdsFromGuestFdSet(std::span pfds, const void* guest_fds_ptr, + short requested_events) +{ + if (guest_fds_ptr == nullptr) + return; + + GuestFdSet guest_fds; + std::memcpy(&guest_fds, guest_fds_ptr, sizeof(guest_fds)); + + u32 index = 0; + for (auto& pfd : pfds) + { + const auto guest_socket = GuestSocket(index); + if (guest_fds.IsFdSet(guest_socket)) + { + pfd.fd = GetHostSocket(guest_socket); + pfd.events |= requested_events; + } + + ++index; + } +} + +// Expects a pointer to a GuestFdSet or nullptr. +static void WriteGuestFdSetFromPollFds(void* guest_fds_ptr, std::span fds, + short returned_events) +{ + if (guest_fds_ptr == nullptr) + return; + + GuestFdSet guest_fds; + + for (const auto& fd : fds) + { + if ((fd.revents & returned_events) == 0) + continue; + + const auto guest_socket = GetGuestSocket(fd.fd); + if (guest_socket == INVALID_GUEST_SOCKET) + continue; + + guest_fds.SetFd(guest_socket); + } + + std::memcpy(guest_fds_ptr, &guest_fds, sizeof(guest_fds)); +} + static void AMMBCommandSelect(u32 parameter_offset, u32 network_buffer_base) { - const auto guest_nfds = int(s_media_buffer_32[parameter_offset]); + u32 nfds = int(s_media_buffer_32[parameter_offset]); const u32 readfds_offset = s_media_buffer_32[parameter_offset + 1]; auto* const guest_readfds_ptr = @@ -845,22 +824,55 @@ static void AMMBCommandSelect(u32 parameter_offset, u32 network_buffer_base) auto* const guest_timeout_ptr = GetSafePtr(s_network_command_buffer, network_buffer_base, timeout_offset, sizeof(TimeVal)); - timeval timeout{}; + if (nfds > std::size(s_sockets)) + { + ERROR_LOG_FMT(AMMEDIABOARD, "AMMBCommandSelect: Unexpected nfds: {}", nfds); + nfds = std::size(s_sockets); + } + + std::chrono::milliseconds timeout{-1}; if (guest_timeout_ptr != nullptr) { TimeVal guest_timeout; std::memcpy(&guest_timeout, guest_timeout_ptr, sizeof(guest_timeout)); - timeout.tv_sec = guest_timeout.seconds; - timeout.tv_usec = guest_timeout.microseconds; + timeout = duration_cast( + std::chrono::seconds(guest_timeout.seconds) + + std::chrono::microseconds(guest_timeout.microseconds)); } - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: select( {}, 0x{:08x} 0x{:08x} 0x{:08x} 0x{:08x} ) {}:{}", - guest_nfds, readfds_offset, writefds_offset, exceptfds_offset, timeout_offset, - guest_timeout_ptr ? timeout.tv_sec : 0, guest_timeout_ptr ? timeout.tv_usec : 0); + if (timeout < std::chrono::milliseconds{}) + { + // TODO: We should have a way to break out of any timeout on shutdown. + // e.g. include a "wakeup" socket in each `poll` call. + WARN_LOG_FMT(AMMEDIABOARD, "AMMBCommandSelect: Infinite timout!"); + } - const int ret = EmulateSelect(guest_nfds, guest_readfds_ptr, guest_writefds_ptr, - guest_exceptfds_ptr, (guest_timeout_ptr ? &timeout : nullptr)); + NOTICE_LOG_FMT( + AMMEDIABOARD_NET, "GC-AM: select( {}, 0x{:08x} 0x{:08x} 0x{:08x} 0x{:08x} ) timeout={}", nfds, + readfds_offset, writefds_offset, exceptfds_offset, timeout_offset, timeout.count()); + + // Fill with the host sockets for each guest socket less-than `nfds` in each GuestFdSet. + std::vector pollfds(nfds, WSAPOLLFD{.fd = INVALID_SOCKET}); + + FillPollFdsFromGuestFdSet(pollfds, guest_readfds_ptr, POLLIN); + FillPollFdsFromGuestFdSet(pollfds, guest_writefds_ptr, POLLOUT); + FillPollFdsFromGuestFdSet(pollfds, guest_exceptfds_ptr, POLLPRI); + + // Erase "INVALID" entries and also entries that weren't in any GuestFdSet. + std::erase_if(pollfds, [](const WSAPOLLFD& fd) { return fd.fd == INVALID_SOCKET; }); + + // TODO: There may be some edge cases where + // poll's (POLLIN,POLLOUT,POLLPRI) don't map 1:1 with select's (readfds,writefds,exceptfds). + + const int ret = PlatformPoll(pollfds, timeout); + + if (ret >= 0) + { + WriteGuestFdSetFromPollFds(guest_readfds_ptr, pollfds, POLLIN); + WriteGuestFdSetFromPollFds(guest_writefds_ptr, pollfds, POLLOUT); + WriteGuestFdSetFromPollFds(guest_exceptfds_ptr, pollfds, POLLPRI); + } NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: select result: {}", ret); From 587c825a9e2a42617142d3814f6d28180d2347aa Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Thu, 5 Feb 2026 20:08:57 -0600 Subject: [PATCH 32/71] AMMediaboard: Clean up and make a function for AMMBCommandAccept. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 92 +++++++++++++----------- 1 file changed, 49 insertions(+), 43 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 0d44d13db5..1a13ddddc7 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -485,7 +485,8 @@ static int PlatformPoll(std::span pfds, std::chrono::milliseconds tim static GuestSocket NetDIMMAccept(GuestSocket guest_socket, sockaddr* addr, socklen_t* len) { - WSAPOLLFD pfds[1]{{.fd = GetHostSocket(guest_socket), .events = POLLIN}}; + const auto host_socket = GetHostSocket(guest_socket); + WSAPOLLFD pfds[1]{{.fd = host_socket, .events = POLLIN}}; constexpr auto timeout = std::chrono::milliseconds{10}; @@ -493,7 +494,7 @@ static GuestSocket NetDIMMAccept(GuestSocket guest_socket, sockaddr* addr, sockl if (result > 0 && (pfds[0].revents & POLLIN) != 0) { - const auto client_sock = accept_(GetHostSocket(guest_socket), addr, len); + const auto client_sock = accept_(host_socket, addr, len); if (client_sock == INVALID_GUEST_SOCKET) { ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: accept() failed in NetDIMMAccept ({})", @@ -756,6 +757,51 @@ static void AMMBCommandConnect(u32 parameter_offset, u32 network_buffer_base) s_media_buffer_32[1] = ret; } +static void AMMBCommandAccept(u32 parameter_offset, u32 network_buffer_base) +{ + const auto guest_socket = GuestSocket(s_media_buffer_32[parameter_offset]); + const u32 addr_off = s_media_buffer_32[parameter_offset + 1]; + const u32 addrlen_off = s_media_buffer_32[parameter_offset + 2]; + + u32 ret{}; + + // Either both parameters should be provided, or neither. + if ((addr_off != 0) != (addrlen_off != 0)) + { + WARN_LOG_FMT(AMMEDIABOARD_NET, "AMMBCommandAccept: Unexpected parameters: {}, {}, {}", + u32(guest_socket), addr_off, addrlen_off); + + // TODO: Not hardware tested. + s_last_error = SSC_EFAULT; + ret = SOCKET_ERROR; + } + else + { + sockaddr addr; + socklen_t addrlen = sizeof(addr); + ret = u32(NetDIMMAccept(guest_socket, &addr, &addrlen)); + + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: accept( {} ):{}", u32(guest_socket), u32(ret)); + + auto* const addrlen_ptr = + GetSafePtr(s_network_command_buffer, network_buffer_base, addrlen_off, sizeof(u32)); + if (addrlen_ptr != nullptr) + { + // Read the buffer size. + addrlen = std::min(addrlen, Common::BitCastPtr(addrlen_ptr)); + // Write out the proper length. + Common::BitCastPtr(addrlen_ptr) = sizeof(addr); + + auto* const addr_ptr = + GetSafePtr(s_network_command_buffer, network_buffer_base, addr_off, addrlen); + if (addr_ptr != nullptr) + memcpy(addr_ptr, &addr, addrlen); + } + } + + s_media_buffer_32[1] = ret; +} + // Expects a pointer to a GuestFdSet or nullptr. static void FillPollFdsFromGuestFdSet(std::span pfds, const void* guest_fds_ptr, short requested_events) @@ -1160,48 +1206,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u s_media_buffer[4] = 1; break; case AMMBCommand::Accept: - { - const auto guest_socket = GuestSocket(s_media_buffer_32[2]); - GuestSocket ret = INVALID_GUEST_SOCKET; - sockaddr addr; - socklen_t len = 0; - - // Handle optional parameters - if (s_media_buffer_32[3] == 0 || s_media_buffer_32[4] == 0) - { - ret = NetDIMMAccept(guest_socket, nullptr, nullptr); - } - else - { - const u32 addr_off = s_media_buffer_32[3] - NetworkCommandAddress2; - const u32 len_off = s_media_buffer_32[4] - NetworkCommandAddress2; - - if (!NetworkCMDBufferCheck(addr_off, sizeof(sockaddr)) || - !NetworkCMDBufferCheck(len_off, sizeof(u32))) - { - break; - } - - // TODO: Check that the current implementation is correct. Currently, `len=0`, - // so `accept()` might not write to `addr` properly. It might be missing the - // following (assuming the code, address and endianness are correct): - // - // // socklen_t might be larger than u32 - // const u32 addr_len = Common::BitCastPtr(s_network_command_buffer + len_off); - // len = addr_len; - ret = NetDIMMAccept(guest_socket, &addr, &len); - if (len) - { - memcpy(s_network_command_buffer + addr_off, &addr, sizeof(sockaddr)); - memcpy(s_network_command_buffer + len_off, &len, sizeof(u32)); - } - } - - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: accept( {}({}) ):{}", u32(guest_socket), - u32(guest_socket), u32(ret)); - s_media_buffer_32[1] = u32(ret); + AMMBCommandAccept(2, NetworkCommandAddress2); break; - } case AMMBCommand::Bind: { const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[2])); From 4c61b9e0090c1d962cfd2156f270617f30883b1c Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Thu, 5 Feb 2026 22:45:46 -0600 Subject: [PATCH 33/71] AMMediaboard: Replace select() with PlatformPoll in NetDIMMConnect. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 31 +++++++++--------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 1a13ddddc7..821f82b465 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -556,37 +556,30 @@ static s32 NetDIMMConnect(GuestSocket guest_socket, sockaddr_in* addr, int len) addr->sin_family = Common::swap16(addr->sin_family); - const auto fd = GetHostSocket(guest_socket); + const auto host_socket = GetHostSocket(guest_socket); u_long val = 1; // Set socket to non-blocking - ioctlsocket(fd, FIONBIO, &val); + ioctlsocket(host_socket, FIONBIO, &val); - int ret = connect(fd, reinterpret_cast(addr), len); + int ret = connect(host_socket, reinterpret_cast(addr), len); const int err = WSAGetLastError(); if (ret == SOCKET_ERROR && err == WSAEWOULDBLOCK) { - fd_set writefds{}; - FD_ZERO(&writefds); - FD_SET(fd, &writefds); + WSAPOLLFD pfds[1]{{.fd = host_socket, .events = POLLOUT}}; - timeval timeout{}; - timeout.tv_sec = 0; - timeout.tv_usec = s_timeouts[0]; + const auto timeout = + duration_cast(std::chrono::microseconds{s_timeouts[0]}); - // TODO: Don't use select on Linux. - if (fd >= FD_SETSIZE) - { - ERROR_LOG_FMT(AMMEDIABOARD, "NetDIMMConnect: fd >= FD_SETSIZE"); - } + ret = PlatformPoll(pfds, timeout); - ret = select(fd + 1, nullptr, &writefds, nullptr, &timeout); - if (ret > 0 && FD_ISSET(fd, &writefds)) + if (ret > 0 && (pfds[0].revents & POLLOUT) != 0) { int so_error = 0; socklen_t optlen = sizeof(so_error); - if (getsockopt(fd, SOL_SOCKET, SO_ERROR, reinterpret_cast(&so_error), &optlen) == 0 && + if (getsockopt(host_socket, SOL_SOCKET, SO_ERROR, reinterpret_cast(&so_error), + &optlen) == 0 && so_error == 0) { s_last_error = SSC_SUCCESS; @@ -608,7 +601,7 @@ static s32 NetDIMMConnect(GuestSocket guest_socket, sockaddr_in* addr, int len) } else { - ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: select() failed in NetDIMMConnect ({})", + ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: poll() failed in NetDIMMConnect ({})", Common::StrNetworkError()); s_last_error = SOCKET_ERROR; ret = SOCKET_ERROR; @@ -628,7 +621,7 @@ static s32 NetDIMMConnect(GuestSocket guest_socket, sockaddr_in* addr, int len) // Restore blocking mode val = 0; - ioctlsocket(fd, FIONBIO, &val); + ioctlsocket(host_socket, FIONBIO, &val); return ret; } From 6a53c8f5125a0768137b322c84c7ceb4c0684c4e Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Thu, 5 Feb 2026 23:35:56 -0600 Subject: [PATCH 34/71] AMMediaboard: Make timeout 0ms in NetDIMMAccept to improve performance. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 821f82b465..4225e3a608 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -488,7 +488,8 @@ static GuestSocket NetDIMMAccept(GuestSocket guest_socket, sockaddr* addr, sockl const auto host_socket = GetHostSocket(guest_socket); WSAPOLLFD pfds[1]{{.fd = host_socket, .events = POLLIN}}; - constexpr auto timeout = std::chrono::milliseconds{10}; + // FYI: Currently using a 0ms timeout to make accept calls always non-blocking. + constexpr auto timeout = std::chrono::milliseconds{0}; const int result = PlatformPoll(pfds, timeout); From b15c593eecadb45a8541652cce65fba406a01c65 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Fri, 6 Feb 2026 02:41:47 -0600 Subject: [PATCH 35/71] BootManager: Automatically attach Triforce Baseboard hardware. --- Source/Core/Core/BootManager.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Source/Core/Core/BootManager.cpp b/Source/Core/Core/BootManager.cpp index c5a526d262..ea26339e57 100644 --- a/Source/Core/Core/BootManager.cpp +++ b/Source/Core/Core/BootManager.cpp @@ -133,6 +133,21 @@ bool BootCore(Core::System& system, std::unique_ptr boot, if (!boot->riivolution_patches.empty()) Config::SetCurrent(Config::MAIN_FAST_DISC_SPEED, true); + if (system.IsTriforce()) + { + // Attach Triforce Baseboard hardware if not overridden in any layer. + // Users may set these in their GameConfig if they want them not automatically attached. + if (GetActiveLayerForConfig(Config::MAIN_SERIAL_PORT_1) == Config::LayerType::Base) + { + Config::SetCurrent(Config::MAIN_SERIAL_PORT_1, ExpansionInterface::EXIDeviceType::Baseboard); + } + if (GetActiveLayerForConfig(Config::GetInfoForSIDevice(0)) == Config::LayerType::Base) + { + Config::SetCurrent(Config::GetInfoForSIDevice(0), + SerialInterface::SIDevices::SIDEVICE_AM_BASEBOARD); + } + } + system.Initialize(); Core::UpdateWantDeterminism(system, /*initial*/ true); From 17027f01739e43b82e300e236b0dd20f6b7c3a77 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Fri, 6 Feb 2026 02:42:26 -0600 Subject: [PATCH 36/71] MainWindow: Remove the check preventing Triforce volumes from booting without the Baseboard hardware attached. The hardware is now automatically attached later in the boot process. --- Source/Core/DolphinQt/MainWindow.cpp | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index f0ffbec687..e29539ae2d 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -1152,29 +1152,15 @@ void MainWindow::StartGame(std::unique_ptr&& parameters) return; } - // When booting Triforce games, we need to ensure that the hardware is set up correctly. const auto volume_type = std::get(parameters->parameters).volume->GetVolumeType(); - - const bool triforce_hardware_sp1 = - Config::Get(Config::MAIN_SERIAL_PORT_1) == ExpansionInterface::EXIDeviceType::Baseboard; - const bool triforce_hardware_port_1 = Config::Get(Config::GetInfoForSIDevice(0)) == - SerialInterface::SIDevices::SIDEVICE_AM_BASEBOARD; - - if (volume_type == DiscIO::Platform::Triforce) - { - if (!triforce_hardware_sp1 || !triforce_hardware_port_1) - { - ModalMessageBox::critical( - this, tr("Error"), - tr("To boot a Triforce game, SP1 and Port 1 must be set to Triforce Baseboard."), - QMessageBox::Ok); - HideRenderWidget(); - return; - } - } - else + if (volume_type != DiscIO::Platform::Triforce) { + const bool triforce_hardware_sp1 = + Config::Get(Config::MAIN_SERIAL_PORT_1) == ExpansionInterface::EXIDeviceType::Baseboard; + const bool triforce_hardware_port_1 = Config::Get(Config::GetInfoForSIDevice(0)) == + SerialInterface::SIDevices::SIDEVICE_AM_BASEBOARD; + // Some Triforce tools don't include a boot.id file, but they can still be launched. if (triforce_hardware_sp1) { From 84b136c99473b0e1d3b9e1f200ad95e0aec2dac1 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Fri, 6 Feb 2026 03:43:35 -0600 Subject: [PATCH 37/71] BootManager: Fix "enumeration value not handled in switch" warning. --- Source/Core/Core/BootManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Core/Core/BootManager.cpp b/Source/Core/Core/BootManager.cpp index ea26339e57..affa3f3d53 100644 --- a/Source/Core/Core/BootManager.cpp +++ b/Source/Core/Core/BootManager.cpp @@ -117,7 +117,7 @@ bool BootCore(Core::System& system, std::unique_ptr boot, case DiscIO::Region::NTSC_K: Config::SetCurrent(Config::SYSCONF_COUNTRY, 0x88); // South Korea break; - case DiscIO::Region::Unknown: + default: break; } } From 97ad5ad1a1c71de3931b8b31da24fb09cfc2aee0 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Fri, 6 Feb 2026 20:14:26 -0600 Subject: [PATCH 38/71] StringUtil: Add SplitStringIntoArray function template. --- Source/Core/Common/StringUtil.h | 27 ++++++++++++++++++++++ Source/UnitTests/Common/StringUtilTest.cpp | 23 ++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/Source/Core/Common/StringUtil.h b/Source/Core/Common/StringUtil.h index ea82581355..6e2c385df9 100644 --- a/Source/Core/Common/StringUtil.h +++ b/Source/Core/Common/StringUtil.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -182,6 +183,32 @@ std::from_chars_result FromChars(std::string_view sv, std::floating_point auto& std::vector SplitString(const std::string& str, char delim); +// Returns `nullopt` if subject does not contain exactly (Count - 1) delim. +template +requires(Count > 1) +std::optional> SplitStringIntoArray(std::string_view subject, + char delim) +{ + std::optional> result; + result.emplace(); + + std::size_t index = 0; + for (auto&& item : subject | std::views::split(delim)) + { + // Too many delim. + if (index == Count) + return std::nullopt; + + (*result)[index++] = std::string_view{item}; + } + + // Too few delim. + if (index != Count) + return std::nullopt; + + return result; +} + // "C:/Windows/winhelp.exe" to "C:/Windows/", "winhelp", ".exe" // This requires forward slashes to be used for the path separators, even on Windows. bool SplitPath(std::string_view full_path, std::string* path, std::string* filename, diff --git a/Source/UnitTests/Common/StringUtilTest.cpp b/Source/UnitTests/Common/StringUtilTest.cpp index 59ba99a436..d7c783497a 100644 --- a/Source/UnitTests/Common/StringUtilTest.cpp +++ b/Source/UnitTests/Common/StringUtilTest.cpp @@ -259,6 +259,29 @@ TEST(StringUtil, CaseInsensitiveContains_OverlappingMatches) EXPECT_TRUE(Common::CaseInsensitiveContains("ababababa", "bABa")); } +TEST(StringUtil, SplitStringIntoArray) +{ + constexpr auto subject0 = ""; + EXPECT_FALSE(SplitStringIntoArray<2>(subject0, '.').has_value()); + + constexpr auto subject1 = "hello"; + EXPECT_FALSE(SplitStringIntoArray<2>(subject1, '.').has_value()); + + constexpr auto subject2 = "hello.world"; + const auto split2 = SplitStringIntoArray<2>(subject2, '.'); + EXPECT_TRUE(split2.has_value() && split2->at(1) == "world"); + EXPECT_FALSE(SplitStringIntoArray<3>(subject2, '.').has_value()); + + constexpr auto subject3 = "hello.universe."; + EXPECT_FALSE(SplitStringIntoArray<2>(subject3, '.').has_value()); + const auto split3 = SplitStringIntoArray<3>(subject3, '.'); + EXPECT_TRUE(split3.has_value() && split3->at(2) == ""); + EXPECT_FALSE(SplitStringIntoArray<4>(subject3, '.').has_value()); + + constexpr auto subject4 = "="; + EXPECT_TRUE(SplitStringIntoArray<2>(subject4, '=').has_value()); +} + TEST(StringUtil, CharacterEncodingConversion) { // wstring From 5545798e00497850531525875bcbb19b6ed8920d Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Fri, 6 Feb 2026 19:32:55 -0600 Subject: [PATCH 39/71] Common/Network: Add structs: IPv4Port, IPv4PortRange and functions: IPAddressToString, ParseIPv4PortRange. --- Source/Core/Common/Network.cpp | 174 +++++++++++++++++++++ Source/Core/Common/Network.h | 23 +++ Source/UnitTests/Common/StringUtilTest.cpp | 91 +++++++++++ 3 files changed, 288 insertions(+) diff --git a/Source/Core/Common/Network.cpp b/Source/Core/Common/Network.cpp index f79b4e6d84..9c161f1b26 100644 --- a/Source/Core/Common/Network.cpp +++ b/Source/Core/Common/Network.cpp @@ -17,15 +17,189 @@ #endif #include +#include #include "Common/BitUtils.h" #include "Common/CommonFuncs.h" #include "Common/Logging/Log.h" #include "Common/Random.h" #include "Common/StringUtil.h" +#include "Common/Swap.h" namespace Common { + +static constexpr auto GetMaskFromNetworkPrefixLength(u32 network_prefix_length) +{ + return (network_prefix_length == 0) ? + u32(0) : + u32(BigEndianValue(u32(-1) >> (32 - network_prefix_length))); +} + +u32 IPv4Port::GetIPAddressValue() const +{ + return std::bit_cast>(ip_address); +} + +u16 IPv4Port::GetPortValue() const +{ + return std::bit_cast>(port); +} + +bool IPv4PortRange::IsMatch(IPv4Port subject) const +{ + const u32 ip_u32 = subject.GetIPAddressValue(); + const u16 port_u16 = subject.GetPortValue(); + + return ip_u32 >= first.GetIPAddressValue() && ip_u32 <= last.GetIPAddressValue() && + port_u16 >= first.GetPortValue() && port_u16 <= last.GetPortValue(); +} + +std::string IPAddressToString(IPAddress ip_address) +{ + return fmt::format("{}", fmt::join(ip_address, ".")); +} + +std::optional StringToIPv4PortRange(std::string_view subject) +{ + // TODO: This could be optimized and improved a lot. + + auto read_pos = std::ranges::find_first_of(subject, std::string_view{"-/:"}); + + const auto first_ip_octets = SplitStringIntoArray<4>({subject.begin(), read_pos}, '.'); + + if (!first_ip_octets.has_value()) + return std::nullopt; // Wrong number of octets. + + std::optional result; + result.emplace(); + + for (u32 i = 0; i != result->first.ip_address.size(); ++i) + { + if (!TryParse(std::string((*first_ip_octets)[i]), &result->first.ip_address[i])) + return std::nullopt; // Bad octet. + } + + if (read_pos == subject.end()) + { + // Last IP and ports not specified. + result->last.ip_address = result->first.ip_address; + result->first.port = 0; + result->last.port = u16(-1); + return result; + } + + if (*read_pos == '-') + { + // Read last IP. + ++read_pos; + + const auto last_ip_end = std::ranges::find(read_pos, subject.end(), ':'); + const auto last_ip_octets = SplitString({read_pos, last_ip_end}, '.'); + + // Copy octets from first_ip when less than 4 are specified. + // FYI: This currently allows for syntax like: 10.0.0.1-7.255 + // I don't know if it's desirable or not to allow 2 or 3 octets on the last IP. + const std::size_t octet_share_count = 4 - last_ip_octets.size(); + + if (octet_share_count >= 4) + return std::nullopt; // Too many octets. + + std::copy_n(result->first.ip_address.begin(), octet_share_count, + result->last.ip_address.begin()); + + std::size_t i = octet_share_count; + for (auto&& octet_str : last_ip_octets) + { + if (!TryParse(octet_str, &result->last.ip_address[i])) + return std::nullopt; // Bad octet. + ++i; + } + + if (result->first.ip_address > result->last.ip_address) + return std::nullopt; // Reversed range. + + read_pos = last_ip_end; + } + else if (*read_pos == '/') + { + // Read network prefix length. + ++read_pos; + + u32 network_prefix_length{}; + const auto [parse_end, ec] = FromChars({read_pos, subject.end()}, network_prefix_length); + + if (ec != std::errc{} || network_prefix_length > 32) + return std::nullopt; // Bad network prefix. + + read_pos += parse_end - std::to_address(read_pos); // Working around MSVC iterators.. + + const auto net_mask = GetMaskFromNetworkPrefixLength(network_prefix_length); + const auto first_ip_address_u32 = std::bit_cast(result->first.ip_address) & net_mask; + result->first.ip_address = std::bit_cast(first_ip_address_u32); + result->last.ip_address = std::bit_cast(first_ip_address_u32 | ~net_mask); + } + else + { + // Last IP not specified. + result->last.ip_address = result->first.ip_address; + } + + if (read_pos == subject.end()) + { + // Ports not specified. + result->first.port = 0; + result->last.port = u16(-1); + return result; + } + + if (*read_pos != ':') + return std::nullopt; // Unexpected character. + + u16 first_port_u16{}; + { + // Read first port. + ++read_pos; + + const auto [parse_end, ec] = FromChars({read_pos, subject.end()}, first_port_u16); + if (ec != std::errc{}) + return std::nullopt; // Bad first port. + + result->first.port = std::bit_cast(Common::BigEndianValue(first_port_u16)); + + read_pos += parse_end - std::to_address(read_pos); // Working around MSVC iterators.. + } + + if (read_pos == subject.end()) + { + // Last port not specified. + result->last.port = result->first.port; + return result; + } + + if (*read_pos != '-') + return std::nullopt; // Unexpected character. + + u16 first_last_u16{}; + { + // Read last port. + ++read_pos; + + const auto [parse_end, ec] = FromChars({read_pos, subject.end()}, first_last_u16); + if (ec != std::errc{} || first_last_u16 < first_port_u16) + return std::nullopt; // Bad last port. + + result->last.port = std::bit_cast(Common::BigEndianValue(first_last_u16)); + + read_pos += parse_end - std::to_address(read_pos); // Working around MSVC iterators.. + } + + if (read_pos != subject.end()) + return std::nullopt; + + return result; +} + MACAddress GenerateMacAddress(const MACConsumer type) { constexpr std::array oui_bba{{0x00, 0x09, 0xbf}}; diff --git a/Source/Core/Common/Network.h b/Source/Core/Common/Network.h index 4ad18d3368..1939d10b2c 100644 --- a/Source/Core/Common/Network.h +++ b/Source/Core/Common/Network.h @@ -268,6 +268,29 @@ struct NetworkErrorState #endif }; +struct IPv4Port +{ + IPAddress ip_address; + u16 port; // Network byte order. + + // These convert to host byte order. + u32 GetIPAddressValue() const; + u16 GetPortValue() const; +}; + +struct IPv4PortRange +{ + IPv4Port first; + IPv4Port last; + + bool IsMatch(IPv4Port subject) const; +}; + +std::string IPAddressToString(IPAddress ip_address); + +// Syntax is: first_ip[-last_ip|/network_prefix_length][:first_port[-last_port]] +std::optional StringToIPv4PortRange(std::string_view subject); + MACAddress GenerateMacAddress(MACConsumer type); std::string MacAddressToString(const MACAddress& mac); diff --git a/Source/UnitTests/Common/StringUtilTest.cpp b/Source/UnitTests/Common/StringUtilTest.cpp index d7c783497a..c8bd5f4deb 100644 --- a/Source/UnitTests/Common/StringUtilTest.cpp +++ b/Source/UnitTests/Common/StringUtilTest.cpp @@ -6,6 +6,7 @@ #include #include +#include "Common/Network.h" #include "Common/StringUtil.h" #include "Common/Swap.h" @@ -282,6 +283,96 @@ TEST(StringUtil, SplitStringIntoArray) EXPECT_TRUE(SplitStringIntoArray<2>(subject4, '=').has_value()); } +TEST(StringUtil, StringToIPv4PortRange) +{ + constexpr auto parse = Common::StringToIPv4PortRange; + + constexpr Common::IPAddress ip_network = {192, 168, 0, 0}; + constexpr Common::IPAddress test_ip = {192, 168, 0, 13}; + constexpr Common::IPAddress last_ip = {192, 168, 0, 255}; + + EXPECT_FALSE(parse("").has_value()); + EXPECT_FALSE(parse("1.2.3").has_value()); + EXPECT_FALSE(parse("1.2.3.4-").has_value()); + EXPECT_FALSE(parse("1.2.3.4.5").has_value()); + EXPECT_FALSE(parse("1.2.3.256").has_value()); + EXPECT_FALSE(parse("1.2.3.4/33").has_value()); + EXPECT_FALSE(parse("1.2.3.4:65536").has_value()); + EXPECT_FALSE(parse("1.2.3.4-1.2.3.4.5").has_value()); + EXPECT_FALSE(parse("1.1.1.1:81-80").has_value()); + EXPECT_FALSE(parse("1.1.1.2-1.1.1.1").has_value()); + + EXPECT_TRUE(parse("1.2.3.4").has_value()); + EXPECT_TRUE(parse("1.2.3.4:80").has_value()); + EXPECT_TRUE(parse("1.2.3.4:80-81").has_value()); + EXPECT_TRUE(parse("1.2.3.4/16").has_value()); + EXPECT_TRUE(parse("1.2.3.4/16:80").has_value()); + EXPECT_TRUE(parse("1.2.3.4/16:80-81").has_value()); + EXPECT_TRUE(parse("1.2.3.4-10").has_value()); + EXPECT_TRUE(parse("1.2.3.4-10:80").has_value()); + EXPECT_TRUE(parse("1.2.3.4-10:80-81").has_value()); + + { + const auto simple_result = parse("192.168.0.13").value(); + EXPECT_EQ(simple_result.first.ip_address, test_ip); + EXPECT_EQ(simple_result.last.ip_address, test_ip); + EXPECT_EQ(simple_result.first.GetPortValue(), 0); + EXPECT_EQ(simple_result.last.GetPortValue(), u16(-1)); + } + + { + const auto port_result = parse("192.168.0.13:77").value(); + EXPECT_EQ(port_result.first.ip_address, test_ip); + EXPECT_EQ(port_result.last.ip_address, test_ip); + EXPECT_EQ(port_result.first.GetPortValue(), 77); + EXPECT_EQ(port_result.last.GetPortValue(), 77); + } + + { + const auto ip_range_result = parse("192.168.0.13-192.168.0.255:123").value(); + EXPECT_EQ(ip_range_result.first.ip_address, test_ip); + EXPECT_EQ(ip_range_result.last.ip_address, last_ip); + EXPECT_EQ(ip_range_result.first.GetPortValue(), 123); + EXPECT_EQ(ip_range_result.last.GetPortValue(), 123); + } + + { + const auto port_range_result = parse("192.168.29.151:5000-5010").value(); + EXPECT_EQ(port_range_result.first.GetPortValue(), 5000); + EXPECT_EQ(port_range_result.last.GetPortValue(), 5010); + EXPECT_TRUE(port_range_result.IsMatch(parse("192.168.29.151:5008")->first)); + EXPECT_FALSE(port_range_result.IsMatch(parse("127.0.0.1:5000")->first)); + } + + { + const auto ip_range_result = parse("192.168.0.13-255:123-999").value(); + EXPECT_EQ(ip_range_result.first.ip_address, test_ip); + EXPECT_EQ(ip_range_result.last.ip_address, last_ip); + EXPECT_EQ(ip_range_result.first.GetPortValue(), 123); + EXPECT_EQ(ip_range_result.last.GetPortValue(), 999); + } + + { + const auto ip_network_result = parse("192.168.0.13/24:123-999").value(); + EXPECT_EQ(ip_network_result.first.ip_address, ip_network); + EXPECT_EQ(ip_network_result.last.ip_address, last_ip); + EXPECT_EQ(ip_network_result.first.GetPortValue(), 123); + EXPECT_EQ(ip_network_result.last.GetPortValue(), 999); + } + + EXPECT_TRUE(parse("192.168.0.13/24")->IsMatch(parse("192.168.0.99")->first)); + EXPECT_FALSE(parse("192.168.0.13/24")->IsMatch(parse("192.168.1.99")->first)); + + EXPECT_FALSE(parse("192.168.0.13/32")->IsMatch(parse("192.168.0.99")->first)); + EXPECT_TRUE(parse("192.168.0.13/0")->IsMatch(parse("10.0.0.1")->first)); + + EXPECT_TRUE(parse("192.168.0.13")->IsMatch(parse("192.168.0.13:81")->first)); + EXPECT_FALSE(parse("192.168.0.13:80")->IsMatch(parse("192.168.0.13:81")->first)); + EXPECT_TRUE(parse("192.168.0.13:80")->IsMatch(parse("192.168.0.13:80")->first)); + EXPECT_FALSE(parse("192.168.0.13:80-81")->IsMatch(parse("192.168.0.14:81")->first)); + EXPECT_TRUE(parse("192.168.0.13-14:80-81")->IsMatch(parse("192.168.0.14:81")->first)); +} + TEST(StringUtil, CharacterEncodingConversion) { // wstring From 3e2b7fee46a8fcf623821b47b4fc4d50edeb65a8 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Fri, 6 Feb 2026 20:23:40 -0600 Subject: [PATCH 40/71] AMMediaboard: Allow IP address overrides to be configured. --- Source/Core/Core/Config/MainSettings.cpp | 27 ++++ Source/Core/Core/Config/MainSettings.h | 3 + Source/Core/Core/HW/DVD/AMMediaboard.cpp | 166 ++++++++++++++++++----- Source/Core/Core/HW/DVD/AMMediaboard.h | 5 + 4 files changed, 169 insertions(+), 32 deletions(-) diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index 3d370c8b1e..6edcccf186 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -6,6 +6,7 @@ #include #include +#include #include "AudioCommon/AudioCommon.h" #include "Common/Assert.h" @@ -27,6 +28,7 @@ #include "Core/HW/HSP/HSP_Device.h" #include "Core/HW/Memmap.h" #include "Core/HW/SI/SI_Device.h" +#include "Core/IOS/Network/Socket.h" #include "Core/PowerPC/PowerPC.h" #include "Core/System.h" #include "Core/USBUtils.h" @@ -641,6 +643,31 @@ const std::array, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_VOLUM Info{{System::Main, "EmulatedUSBDevices", "LogitechMic3VolumeModifier"}, 0}, Info{{System::Main, "EmulatedUSBDevices", "LogitechMic4VolumeModifier"}, 0}}; +static std::string GetDefaultTriforceIPOverrides() +{ + constexpr std::string_view entries[] = { + // CyCraft Connect IP + "192.168.11.111=127.0.0.1", + + // Key of Avalon + // Note: The server and client can't run on the same system, + // but it's replaced with 127.0.0.1 here just for reference. + "192.168.13.1=127.0.0.1", + + // NAMCO Camera + "192.168.29.104-108=127.0.0.1", + + // All.Net Connect IP + "192.168.150.16=127.0.0.1", + }; + + return fmt::format("{}", fmt::join(entries, ",")); +} + +const Info MAIN_TRIFORCE_BIND_IP{{System::Main, "Core", "TriforceBindIP"}, "0.0.0.0"}; +const Info MAIN_TRIFORCE_IP_OVERRIDES{{System::Main, "Core", "TriforceIPOverrides"}, + GetDefaultTriforceIPOverrides()}; + // The reason we need this function is because some memory card code // expects to get a non-NTSC-K region even if we're emulating an NTSC-K Wii. DiscIO::Region ToGameCubeRegion(DiscIO::Region region) diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index 00c11ea6dd..ad28ca9119 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -386,6 +386,9 @@ extern const std::array, EMULATED_LOGITECH_MIC_COUNT> extern const std::array, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_MUTED; extern const std::array, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_VOLUME_MODIFIER; +extern const Info MAIN_TRIFORCE_BIND_IP; +extern const Info MAIN_TRIFORCE_IP_OVERRIDES; + // GameCube path utility functions // Replaces NTSC-K with some other region, and doesn't replace non-NTSC-K regions diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 4225e3a608..0dd60755b7 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -4,6 +4,8 @@ #include "Core/HW/DVD/AMMediaboard.h" #include +#include +#include #include #include @@ -18,6 +20,7 @@ #include "Core/Boot/Boot.h" #include "Core/BootManager.h" +#include "Core/Config/MainSettings.h" #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/HLE/HLE.h" @@ -521,38 +524,118 @@ static GuestSocket NetDIMMAccept(GuestSocket guest_socket, sockaddr* addr, sockl return INVALID_GUEST_SOCKET; } +std::optional> ParseIPOverride(std::string_view str) +{ + // Ignore everything after a space. Future proofing to allow for a comment/description string. + const auto ip_pair_str = std::string_view{str.begin(), std::ranges::find(str, ' ')}; + + const auto parts = SplitStringIntoArray<2>(ip_pair_str, '='); + if (parts.has_value()) + return std::make_pair((*parts)[0], (*parts)[1]); + + return std::nullopt; +} + +struct IPAddressOverride +{ + Common::IPv4PortRange match; + Common::IPv4PortRange replacement; + + // Caller should check if it matches first! + Common::IPv4Port ApplyOverride(Common::IPv4Port subject) const + { + const auto replacement_first_ip_u32 = replacement.first.GetIPAddressValue(); + const auto ip_count = 1u + u64(replacement.last.GetIPAddressValue()) - replacement_first_ip_u32; + const auto result_ip = + u32(replacement_first_ip_u32 + + ((subject.GetIPAddressValue() - match.first.GetIPAddressValue()) % ip_count)); + + Common::IPv4Port result{ + .ip_address = std::bit_cast(Common::BigEndianValue(result_ip)), + }; + + const auto replacement_first_port_u16 = replacement.first.GetPortValue(); + const auto port_count = 1u + u32(replacement.last.GetPortValue()) - replacement_first_port_u16; + + if (port_count == 65536) + { + // If the replacement includes all ports then don't alter the port. + // This allows "1.1.1.1:80=2.2.2.2" to do the obvious thing. + // This logic could probably be better. + // Ranges of different sizes will be weird in general. + + result.port = subject.port; + } + else + { + const auto result_port = + u16(replacement_first_port_u16 + + ((subject.GetPortValue() - match.first.GetPortValue()) % port_count)); + result.port = std::bit_cast(Common::BigEndianValue(result_port)); + } + + return result; + } +}; + +using IPOverrides = std::vector; +static IPOverrides GetIPOverrides() +{ + IPOverrides result; + + const auto ip_overrides_str = Config::Get(Config::MAIN_TRIFORCE_IP_OVERRIDES); + for (auto&& ip_pair : ip_overrides_str | std::views::split(',')) + { + const auto ip_pair_str = std::string_view{ip_pair}; + const auto parts = ParseIPOverride(ip_pair_str); + if (parts.has_value()) + { + const auto match = Common::StringToIPv4PortRange(parts->first); + const auto replacement = Common::StringToIPv4PortRange(parts->second); + + if (match.has_value() && replacement.has_value()) + { + result.emplace_back(*match, *replacement); + continue; + } + + ERROR_LOG_FMT(AMMEDIABOARD, "Bad IP pair string: {}", ip_pair_str); + } + } + + return result; +} + +static std::optional GetAdjustedIPv4PortFromConfig(in_addr addr, u16 port) +{ + Common::IPv4Port subject{ + .ip_address = std::bit_cast(addr), + .port = port, + }; + + // TODO: We should parse this elsewhere to avoid repeated string manipulations. + for (auto&& override : GetIPOverrides()) + { + if (override.match.IsMatch(subject)) + return override.ApplyOverride(subject); + } + + return std::nullopt; +} + static s32 NetDIMMConnect(GuestSocket guest_socket, sockaddr_in* addr, int len) { - // All.Net Connect IP - if (addr->sin_addr.s_addr == inet_addr("192.168.150.16")) - { - addr->sin_addr.s_addr = inet_addr("127.0.0.1"); - } + INFO_LOG_FMT(AMMEDIABOARD, "NetDIMMConnect: {}:{}", inet_ntoa(addr->sin_addr), + ntohs(addr->sin_port)); - // CyCraft Connect IP - if (addr->sin_addr.s_addr == inet_addr("192.168.11.111")) + const auto adjusted_ipv4port = GetAdjustedIPv4PortFromConfig(addr->sin_addr, addr->sin_port); + if (adjusted_ipv4port.has_value()) { - addr->sin_addr.s_addr = inet_addr("127.0.0.1"); - } - - // NAMCO Camera ( IPs are: 192.168.29.104-108 ) - if ((addr->sin_addr.s_addr & 0xFFFFFF00) == 0xC0A81D00) - { - addr->sin_addr.s_addr = inet_addr("127.0.0.1"); - - // BUG: An invalid family value is being used - addr->sin_family = htons(AF_INET); - } - - // Key of Avalon Client - if (addr->sin_addr.s_addr == inet_addr("192.168.13.1")) - { - // Unlike the other addresses, this one isn't converted to loopback - // because the server and client can't run on the same system. - // - // NOTE: Due to lack of touch-screen support, it's not in a playable state. - // TODO: Make the IP configurable. - addr->sin_addr.s_addr = inet_addr("10.0.0.45"); + addr->sin_addr = std::bit_cast(adjusted_ipv4port->ip_address); + addr->sin_port = adjusted_ipv4port->port; + INFO_LOG_FMT(AMMEDIABOARD, "NetDIMMConnect: Overriding to: {}:{}", + Common::IPAddressToString(adjusted_ipv4port->ip_address), + ntohs(adjusted_ipv4port->port)); } addr->sin_family = Common::swap16(addr->sin_family); @@ -1219,11 +1302,30 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u addr.sin_family = Common::swap16(addr.sin_family); // Triforce titles typically rely on hardcoded IP addresses. - // This behavior has been modified to bind to the wildcard address instead. - // - // addr.sin_addr.s_addr = htonl(addr.sin_addr.s_addr); + // Our config allows this to be overriden. - addr.sin_addr.s_addr = INADDR_ANY; + INFO_LOG_FMT(AMMEDIABOARD, "AMMBCommand::Bind: {}:{}", inet_ntoa(addr.sin_addr), + ntohs(addr.sin_port)); + + // Apply "BindIP" if it's valid. + const auto override_bind_ip = inet_addr(Config::Get(Config::MAIN_TRIFORCE_BIND_IP).c_str()); + if (override_bind_ip != INADDR_NONE) + { + addr.sin_addr.s_addr = override_bind_ip; + INFO_LOG_FMT(AMMEDIABOARD, "AMMBCommand::Bind: Overriding IP to: {}", + Common::IPAddressToString(std::bit_cast(addr.sin_addr))); + } + + // Apply "IPOverrides" in case config wants ports adjusted. + const auto adjusted_ipv4port = GetAdjustedIPv4PortFromConfig(addr.sin_addr, addr.sin_port); + if (adjusted_ipv4port.has_value()) + { + addr.sin_addr = std::bit_cast(adjusted_ipv4port->ip_address); + addr.sin_port = adjusted_ipv4port->port; + INFO_LOG_FMT(AMMEDIABOARD, "AMMBCommand::Bind: Overriding to: {}:{}", + Common::IPAddressToString(std::bit_cast(addr.sin_addr)), + ntohs(addr.sin_port)); + } const int ret = bind(fd, reinterpret_cast(&addr), len); const int err = WSAGetLastError(); diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.h b/Source/Core/Core/HW/DVD/AMMediaboard.h index d57a800107..cc0bcf9b89 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.h +++ b/Source/Core/Core/HW/DVD/AMMediaboard.h @@ -4,6 +4,9 @@ #pragma once #include +#include +#include +#include #include "Common/CommonTypes.h" @@ -234,4 +237,6 @@ u32 GetMediaType(); bool GetTestMenu(); void Shutdown(); +std::optional> ParseIPOverride(std::string_view str); + }; // namespace AMMediaboard From 7ab267269880b2007776b6b5e0090538bd577740 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Fri, 6 Feb 2026 20:24:18 -0600 Subject: [PATCH 41/71] DolphinQt: Add user interface for Triforce IP address overrides. --- Source/Core/DolphinQt/CMakeLists.txt | 2 + Source/Core/DolphinQt/DolphinQt.vcxproj | 4 +- .../Core/DolphinQt/Settings/GameCubePane.cpp | 14 +- .../TriforceBaseboardSettingsDialog.cpp | 154 ++++++++++++++++++ .../TriforceBaseboardSettingsDialog.h | 26 +++ 5 files changed, 194 insertions(+), 6 deletions(-) create mode 100644 Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.cpp create mode 100644 Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.h diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index 457ed8a157..0899ef5307 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -376,6 +376,8 @@ add_executable(dolphin-emu Settings/OnScreenDisplayPane.h Settings/PathPane.cpp Settings/PathPane.h + Settings/TriforceBaseboardSettingsDialog.cpp + Settings/TriforceBaseboardSettingsDialog.h Settings/USBDevicePicker.cpp Settings/USBDevicePicker.h Settings/WiiPane.cpp diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index 09f9163036..33741cb09c 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -223,6 +223,7 @@ + @@ -439,6 +440,7 @@ + @@ -523,4 +525,4 @@ - \ No newline at end of file + diff --git a/Source/Core/DolphinQt/Settings/GameCubePane.cpp b/Source/Core/DolphinQt/Settings/GameCubePane.cpp index 975130531e..692258edb5 100644 --- a/Source/Core/DolphinQt/Settings/GameCubePane.cpp +++ b/Source/Core/DolphinQt/Settings/GameCubePane.cpp @@ -50,6 +50,7 @@ #include "DolphinQt/QtUtils/SignalBlocking.h" #include "DolphinQt/Settings.h" #include "DolphinQt/Settings/BroadbandAdapterSettingsDialog.h" +#include "DolphinQt/Settings/TriforceBaseboardSettingsDialog.h" constexpr std::initializer_list GUI_SLOTS = { ExpansionInterface::Slot::A, ExpansionInterface::Slot::B, ExpansionInterface::Slot::SP1}; @@ -351,11 +352,12 @@ void GameCubePane::UpdateButton(ExpansionInterface::Slot slot) break; } case ExpansionInterface::Slot::SP1: - has_config = (device == ExpansionInterface::EXIDeviceType::Ethernet || - device == ExpansionInterface::EXIDeviceType::EthernetXLink || - device == ExpansionInterface::EXIDeviceType::EthernetTapServer || - device == ExpansionInterface::EXIDeviceType::EthernetBuiltIn || - device == ExpansionInterface::EXIDeviceType::ModemTapServer); + has_config = device == ExpansionInterface::EXIDeviceType::Ethernet || + device == ExpansionInterface::EXIDeviceType::EthernetXLink || + device == ExpansionInterface::EXIDeviceType::EthernetTapServer || + device == ExpansionInterface::EXIDeviceType::EthernetBuiltIn || + device == ExpansionInterface::EXIDeviceType::ModemTapServer || + device == ExpansionInterface::EXIDeviceType::Baseboard; break; case ExpansionInterface::Slot::SP2: has_config = false; @@ -421,6 +423,8 @@ void GameCubePane::OnConfigPressed(ExpansionInterface::Slot slot) } case ExpansionInterface::EXIDeviceType::Baseboard: { + TriforceBaseboardSettingsDialog dialog(this); + dialog.exec(); return; } default: diff --git a/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.cpp b/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.cpp new file mode 100644 index 0000000000..23d300dc43 --- /dev/null +++ b/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.cpp @@ -0,0 +1,154 @@ +// Copyright 2026 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "DolphinQt/Settings/TriforceBaseboardSettingsDialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "Common/Logging/Log.h" + +#include "Core/Config/MainSettings.h" +#include "Core/HW/DVD/AMMediaboard.h" + +#include "DolphinQt/QtUtils/QtUtils.h" + +TriforceBaseboardSettingsDialog::TriforceBaseboardSettingsDialog(QWidget* parent) : QDialog{parent} +{ + setWindowTitle(tr("Triforce Baseboard")); + + connect(this, &TriforceBaseboardSettingsDialog::accepted, this, + &TriforceBaseboardSettingsDialog::SaveConfig); + + auto* const dialog_layout = new QVBoxLayout{this}; + + auto* const ip_override_group = new QGroupBox{tr("IP Address Overrides")}; + dialog_layout->addWidget(ip_override_group); + + auto* const ip_override_layout = new QFormLayout{ip_override_group}; + + m_bind_ip_edit = new QLineEdit; + ip_override_layout->addRow(tr("Bind IP: "), m_bind_ip_edit); + + m_ip_overrides_table = new QTableWidget; + ip_override_layout->addRow(tr("IP Overrides: "), m_ip_overrides_table); + + m_ip_overrides_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + + m_ip_overrides_table->setColumnCount(2); + m_ip_overrides_table->setHorizontalHeaderLabels({tr("Match"), tr("Replacement")}); + + m_ip_overrides_table->setEditTriggers(QAbstractItemView::DoubleClicked | + QAbstractItemView::EditKeyPressed); + + auto* const button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + dialog_layout->addWidget(button_box); + + auto* const load_default_button = new QPushButton(tr("Default")); + connect(load_default_button, &QPushButton::clicked, this, + &TriforceBaseboardSettingsDialog::LoadDefault); + + button_box->addButton(load_default_button, QDialogButtonBox::ActionRole); + + connect(button_box, &QDialogButtonBox::accepted, this, &TriforceBaseboardSettingsDialog::accept); + connect(button_box, &QDialogButtonBox::rejected, this, &TriforceBaseboardSettingsDialog::reject); + + connect(m_ip_overrides_table, &QTableWidget::itemChanged, this, [this](QTableWidgetItem* item) { + const int row_count = m_ip_overrides_table->rowCount(); + const bool is_last_row = item->row() == row_count - 1; + if (is_last_row) + { + if (!item->text().isEmpty()) + m_ip_overrides_table->insertRow(row_count); + } + else + { + // Just remove inner rows that have text erased. + // This UX could be less weird, but it's good enough for now. + if (item->text().isEmpty()) + m_ip_overrides_table->removeRow(item->row()); + } + }); + + LoadConfig(); + + QtUtils::AdjustSizeWithinScreen(this); +} + +void TriforceBaseboardSettingsDialog::LoadConfig() +{ + m_bind_ip_edit->setText(QString::fromUtf8(Config::Get(Config::MAIN_TRIFORCE_BIND_IP))); + + m_ip_overrides_table->setRowCount(0); + + // Add the final empty row. + m_ip_overrides_table->insertRow(0); + + int row = 0; + + const auto ip_overrides_str = Config::Get(Config::MAIN_TRIFORCE_IP_OVERRIDES); + for (auto&& ip_pair : ip_overrides_str | std::views::split(',')) + { + const auto ip_pair_str = std::string_view{ip_pair}; + const auto parts = AMMediaboard::ParseIPOverride(ip_pair_str); + if (!parts.has_value()) + { + ERROR_LOG_FMT(COMMON, "Bad IP pair string: {}", ip_pair_str); + continue; + } + + m_ip_overrides_table->insertRow(row); + + m_ip_overrides_table->setItem(row, 0, new QTableWidgetItem(QString::fromUtf8(parts->first))); + m_ip_overrides_table->setItem(row, 1, new QTableWidgetItem(QString::fromUtf8(parts->second))); + + ++row; + } +} + +void TriforceBaseboardSettingsDialog::SaveConfig() +{ + Config::SetBaseOrCurrent(Config::MAIN_TRIFORCE_BIND_IP, m_bind_ip_edit->text().toStdString()); + + // Ignore our empty row. + const int row_count = m_ip_overrides_table->rowCount() - 1; + + std::vector replacements(row_count); + for (int row = 0; row < row_count; ++row) + { + // Skip incomplete rows. + if (m_ip_overrides_table->item(row, 0) == nullptr || + m_ip_overrides_table->item(row, 1) == nullptr) + { + continue; + } + + replacements[row] = + std::string(StripWhitespace(m_ip_overrides_table->item(row, 0)->text().toStdString())) + + "=" + + std::string(StripWhitespace(m_ip_overrides_table->item(row, 1)->text().toStdString())); + } + + // TODO: Ideally we'd remove duplicates based on the first IP. + + Config::SetBaseOrCurrent(Config::MAIN_TRIFORCE_IP_OVERRIDES, + fmt::format("{}", fmt::join(replacements, ","))); +} + +void TriforceBaseboardSettingsDialog::LoadDefault() +{ + // This alters the config before "OK" is pressed. Bad UX.. + Config::SetBaseOrCurrent(Config::MAIN_TRIFORCE_BIND_IP, Config::DefaultState{}); + Config::SetBaseOrCurrent(Config::MAIN_TRIFORCE_IP_OVERRIDES, Config::DefaultState{}); + + LoadConfig(); +} diff --git a/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.h b/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.h new file mode 100644 index 0000000000..e472739f71 --- /dev/null +++ b/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.h @@ -0,0 +1,26 @@ +// Copyright 2026 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +class QLineEdit; +class QTableWidget; + +class TriforceBaseboardSettingsDialog final : public QDialog +{ + Q_OBJECT + +public: + explicit TriforceBaseboardSettingsDialog(QWidget* parent); + +private: + void LoadConfig(); + void SaveConfig(); + + void LoadDefault(); + + QLineEdit* m_bind_ip_edit; + QTableWidget* m_ip_overrides_table; +}; From a876529a0226544095b3de7bcea095e82757aa63 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Fri, 6 Feb 2026 20:45:36 -0600 Subject: [PATCH 42/71] AMMediaboard: Minor adjustments to logging. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 62 ++++++++++++------------ 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 0dd60755b7..f26e355059 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -372,9 +372,9 @@ static inline void PrintMBBuffer(u32 address, u32 length) for (u32 i = 0; i < length; i += 0x10) { - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: {:08x} {:08x} {:08x} {:08x}", memory.Read_U32(address + i), - memory.Read_U32(address + i + 4), memory.Read_U32(address + i + 8), - memory.Read_U32(address + i + 12)); + DEBUG_LOG_FMT(AMMEDIABOARD, "GC-AM: {:08x} {:08x} {:08x} {:08x}", memory.Read_U32(address + i), + memory.Read_U32(address + i + 4), memory.Read_U32(address + i + 8), + memory.Read_U32(address + i + 12)); } } @@ -790,8 +790,7 @@ static void AMMBCommandClosesocket(u32 parameter_offset) const int ret = closesocket(fd); - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: closesocket( {}({}) ):{}", fd, - s_media_buffer_32[parameter_offset], ret); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: closesocket( {}({}) ):{}", fd, u32(guest_socket), ret); if (u32(guest_socket) < std::size(s_sockets)) s_sockets[u32(guest_socket)] = SOCKET_ERROR; @@ -858,7 +857,7 @@ static void AMMBCommandAccept(u32 parameter_offset, u32 network_buffer_base) socklen_t addrlen = sizeof(addr); ret = u32(NetDIMMAccept(guest_socket, &addr, &addrlen)); - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: accept( {} ):{}", u32(guest_socket), u32(ret)); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: accept( {} ):{}", u32(guest_socket), int(ret)); auto* const addrlen_ptr = GetSafePtr(s_network_command_buffer, network_buffer_base, addrlen_off, sizeof(u32)); @@ -947,11 +946,8 @@ static void AMMBCommandSelect(u32 parameter_offset, u32 network_buffer_base) auto* const guest_timeout_ptr = GetSafePtr(s_network_command_buffer, network_buffer_base, timeout_offset, sizeof(TimeVal)); - if (nfds > std::size(s_sockets)) - { - ERROR_LOG_FMT(AMMEDIABOARD, "AMMBCommandSelect: Unexpected nfds: {}", nfds); - nfds = std::size(s_sockets); - } + // Games sometimes send 256 (the bit size of GuestFdSet). + nfds = std::min(nfds, std::size(s_sockets)); std::chrono::milliseconds timeout{-1}; if (guest_timeout_ptr != nullptr) @@ -988,6 +984,8 @@ static void AMMBCommandSelect(u32 parameter_offset, u32 network_buffer_base) // TODO: There may be some edge cases where // poll's (POLLIN,POLLOUT,POLLPRI) don't map 1:1 with select's (readfds,writefds,exceptfds). + INFO_LOG_FMT(AMMEDIABOARD, "AMMBCommandSelect: Polling with socket count: {}", pollfds.size()); + const int ret = PlatformPoll(pollfds, timeout); if (ret >= 0) @@ -1242,8 +1240,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u { if (offset >= range.start && offset < range.end) { - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Read MediaBoard ({:08x},{:08x},{:08x})", offset, - range.base_offset, length); + DEBUG_LOG_FMT(AMMEDIABOARD, "GC-AM: Read MediaBoard ({:08x},{:08x},{:08x})", offset, + range.base_offset, length); SafeCopyToEmu(memory, address, range.buffer, range.buffer_size, offset - range.base_offset, length); PrintMBBuffer(address, length); @@ -1338,7 +1336,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u err_msg); } - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: bind( {}, ({},{:08x}:{}), {} ):{} ({})\n", fd, + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: bind( {}, ({},{:08x}:{}), {} ):{} ({})", fd, addr.sin_family, addr.sin_addr.s_addr, Common::swap16(addr.sin_port), len, ret, err); @@ -1360,13 +1358,13 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u // TODO: Shouldn't this look at 16 characters for lack of null-termination? if (strnlen(ip_address, MAX_IPV4_STRING_LENGTH) > MAX_IPV4_STRING_LENGTH) { - ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: Invalid size for address: InetAddr():{}\n", + ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: Invalid size for address: InetAddr():{}", strlen(ip_address)); break; } const u32 ip = inet_addr(ip_address); - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: InetAddr( {} )\n", ip_address); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: InetAddr( {} )", ip_address); s_media_buffer[1] = s_media_buffer[8]; s_media_buffer_32[1] = Common::swap32(ip); @@ -1379,7 +1377,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u const int ret = listen(fd, backlog); - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: listen( {}, {} ):{:d}\n", fd, backlog, ret); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: listen( {}, {} ):{:d}", fd, backlog, ret); s_media_buffer[1] = s_media_buffer[8]; s_media_buffer_32[1] = ret; @@ -1402,7 +1400,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; case AMMBCommand::SetTimeOuts: { - const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[2])); + const auto guest_socket = GuestSocket(s_media_buffer_32[2]); + const auto host_socket = GetHostSocket(guest_socket); const u32 timeout_a = s_media_buffer_32[3]; const u32 timeout_b = s_media_buffer_32[4]; const u32 timeout_c = s_media_buffer_32[5]; @@ -1413,29 +1412,30 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u int ret = SOCKET_ERROR; - if (fd != INVALID_SOCKET) + if (host_socket != INVALID_SOCKET) { - ret = setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast(&timeout_b), - sizeof(int)); + ret = setsockopt(host_socket, SOL_SOCKET, SO_SNDTIMEO, + reinterpret_cast(&timeout_b), sizeof(int)); if (ret < 0) { ret = WSAGetLastError(); } else { - ret = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&timeout_c), - sizeof(int)); + ret = setsockopt(host_socket, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&timeout_c), sizeof(int)); if (ret < 0) ret = WSAGetLastError(); } - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: SetTimeOuts( {:d}, {}, {}, {} ):{}\n", fd, - timeout_a, timeout_b, timeout_c, ret); + INFO_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: SetTimeOuts( {}({}), {}, {}, {} ):{}", host_socket, + int(guest_socket), timeout_a, timeout_b, timeout_c, ret); } else { - ERROR_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: Invalid Socket: SetTimeOuts( {}, {}, {} ):{}\n", - timeout_a, timeout_b, timeout_c, ret); + ERROR_LOG_FMT(AMMEDIABOARD_NET, + "GC-AM: Invalid Socket: SetTimeOuts( {}({}), {}, {}, {} ):{}", host_socket, + int(guest_socket), timeout_a, timeout_b, timeout_c, ret); } s_media_buffer[1] = s_media_buffer[8]; @@ -1446,7 +1446,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u { const u32 value = s_media_buffer_32[2]; - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: GetParambyDHCPExec({})\n", value); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: GetParambyDHCPExec({})", value); s_media_buffer[1] = 0; s_media_buffer_32[1] = 0; @@ -1459,7 +1459,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u { const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[2])); - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: GetLastError( {}({}) ):{}\n", fd, + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: GetLastError( {}({}) ):{}", fd, s_media_buffer_32[2], s_last_error); s_media_buffer[1] = s_media_buffer[8]; @@ -1661,8 +1661,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u { if (offset >= range.start && offset < range.end) { - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Write MediaBoard ({:08x},{:08x},{:08x})", offset, - range.base_offset, length); + DEBUG_LOG_FMT(AMMEDIABOARD, "GC-AM: Write MediaBoard ({:08x},{:08x},{:08x})", offset, + range.base_offset, length); SafeCopyFromEmu(memory, range.buffer, address, range.buffer_size, offset - range.base_offset, length); PrintMBBuffer(address, length); From 0075313dc9b00915574335699b4a7978412ded05 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Fri, 6 Feb 2026 23:56:22 -0600 Subject: [PATCH 43/71] AMMediaboard: Don't swap32 the IPv4 result in AMMBCommand::InetAddr. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index f26e355059..5f141dbd81 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -1367,7 +1367,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: InetAddr( {} )", ip_address); s_media_buffer[1] = s_media_buffer[8]; - s_media_buffer_32[1] = Common::swap32(ip); + s_media_buffer_32[1] = ip; break; } case AMMBCommand::Listen: From f8f74b58fdc1bda1c932ac8f861f1c64cd7a74fd Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 7 Feb 2026 00:01:29 -0600 Subject: [PATCH 44/71] AMMediaboard: Clean up AMMBCommandModifyMyIPaddr. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 30 ++++++++++++++++-------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 5f141dbd81..23317e2f24 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -271,6 +271,22 @@ static constexpr u8* GetSafePtr(std::span buffer, u32 buffer_base, u32 offse return nullptr; } +static constexpr std::string_view GetSafeString(std::span buffer, u32 buffer_base, u32 offset, + u32 max_length) +{ + auto* const ptr = GetSafePtr(buffer, buffer_base, offset, 0); + + if (ptr == nullptr) + return {}; + + // Don't exceed max_length or end of buffer. + const auto adjusted_length = + std::min(max_length, buffer.data() + buffer.size() - ptr); + const auto length = strnlen(reinterpret_cast(ptr), adjusted_length); + + return {reinterpret_cast(ptr), length}; +} + static bool NetworkBufferCheck(u32 offset, u32 length) { if (offset <= std::size(s_network_buffer) && length <= std::size(s_network_buffer) - offset) @@ -1029,17 +1045,11 @@ static void AMMBCommandSetSockOpt(u32 parameter_offset, u32 network_buffer_base) static void AMMBCommandModifyMyIPaddr(u32 parameter_offset, u32 network_buffer_base) { - const u32 net_buffer_offset = s_media_buffer_32[parameter_offset] - network_buffer_base; + const u32 ip_address_offset = s_media_buffer_32[parameter_offset]; + const auto ip_address_str = GetSafeString(s_network_command_buffer, network_buffer_base, + ip_address_offset, MAX_IPV4_STRING_LENGTH); - if (!NetworkCMDBufferCheck(net_buffer_offset, MAX_IPV4_STRING_LENGTH)) - { - return; - } - - const char* ip_address = reinterpret_cast(s_network_command_buffer + net_buffer_offset); - - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: modifyMyIPaddr({})", - fmt::string_view(ip_address, MAX_IPV4_STRING_LENGTH)); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: modifyMyIPaddr({})", ip_address_str); } static void FileWriteData(Memory::MemoryManager& memory, File::IOFile* file, u32 seek_pos, From af5e90a1609b7fd77e929f43a82621269072b066 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 7 Feb 2026 00:37:20 -0600 Subject: [PATCH 45/71] AMMediaboard: Introduce GuestSocketAddress to fix bad sin_family values. I think the "family" field is only one byte. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 126 ++++++++++++++--------- 1 file changed, 75 insertions(+), 51 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 23317e2f24..eff81d0a0f 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -112,6 +112,20 @@ struct GuestFdSet }; static_assert(sizeof(GuestFdSet) == 32); +// This seems to be based on VxWorks sockaddr_in. +struct GuestSocketAddress +{ + // Seemingly always zero or random values ? Game bug ? + // This is the struct size in VxWorks. + u8 unknown_value; + + u8 ip_family; + u16 port; // Network byte order. + Common::IPAddress ip_address; + std::array padding; +}; +static_assert(sizeof(GuestSocketAddress) == 16); + static bool s_firmware_map = false; static bool s_test_menu = false; static std::array s_timeouts = {20000, 20000, 20000}; @@ -622,13 +636,8 @@ static IPOverrides GetIPOverrides() return result; } -static std::optional GetAdjustedIPv4PortFromConfig(in_addr addr, u16 port) +static std::optional GetAdjustedIPv4PortFromConfig(Common::IPv4Port subject) { - Common::IPv4Port subject{ - .ip_address = std::bit_cast(addr), - .port = port, - }; - // TODO: We should parse this elsewhere to avoid repeated string manipulations. for (auto&& override : GetIPOverrides()) { @@ -639,22 +648,30 @@ static std::optional GetAdjustedIPv4PortFromConfig(in_addr add return std::nullopt; } -static s32 NetDIMMConnect(GuestSocket guest_socket, sockaddr_in* addr, int len) +static s32 NetDIMMConnect(GuestSocket guest_socket, const GuestSocketAddress& guest_addr) { - INFO_LOG_FMT(AMMEDIABOARD, "NetDIMMConnect: {}:{}", inet_ntoa(addr->sin_addr), - ntohs(addr->sin_port)); + INFO_LOG_FMT(AMMEDIABOARD, "NetDIMMConnect: {}:{}", + Common::IPAddressToString(guest_addr.ip_address), ntohs(guest_addr.port)); - const auto adjusted_ipv4port = GetAdjustedIPv4PortFromConfig(addr->sin_addr, addr->sin_port); + sockaddr_in addr{ + .sin_family = guest_addr.ip_family, + }; + + const auto adjusted_ipv4port = + GetAdjustedIPv4PortFromConfig({guest_addr.ip_address, guest_addr.port}); if (adjusted_ipv4port.has_value()) { - addr->sin_addr = std::bit_cast(adjusted_ipv4port->ip_address); - addr->sin_port = adjusted_ipv4port->port; + addr.sin_addr = std::bit_cast(adjusted_ipv4port->ip_address); + addr.sin_port = adjusted_ipv4port->port; INFO_LOG_FMT(AMMEDIABOARD, "NetDIMMConnect: Overriding to: {}:{}", Common::IPAddressToString(adjusted_ipv4port->ip_address), ntohs(adjusted_ipv4port->port)); } - - addr->sin_family = Common::swap16(addr->sin_family); + else + { + addr.sin_addr = std::bit_cast(guest_addr.ip_address); + addr.sin_port = guest_addr.port; + } const auto host_socket = GetHostSocket(guest_socket); @@ -662,7 +679,7 @@ static s32 NetDIMMConnect(GuestSocket guest_socket, sockaddr_in* addr, int len) // Set socket to non-blocking ioctlsocket(host_socket, FIONBIO, &val); - int ret = connect(host_socket, reinterpret_cast(addr), len); + int ret = connect(host_socket, reinterpret_cast(&addr), sizeof(addr)); const int err = WSAGetLastError(); if (ret == SOCKET_ERROR && err == WSAEWOULDBLOCK) @@ -821,29 +838,26 @@ static void AMMBCommandConnect(u32 parameter_offset, u32 network_buffer_base) const u32 addr_offset = s_media_buffer_32[parameter_offset + 1]; const u32 len = s_media_buffer_32[parameter_offset + 2]; - if (len != sizeof(sockaddr_in)) + GuestSocketAddress addr; + + if (len != sizeof(addr)) { ERROR_LOG_FMT(AMMEDIABOARD_NET, "AMMBCommandConnect: Unexpected length: {}", len); return; } - sockaddr_in addr; const auto* addr_ptr = GetSafePtr(s_network_command_buffer, network_buffer_base, addr_offset, sizeof(addr)); - if (addr_ptr == nullptr) - { - ERROR_LOG_FMT(AMMEDIABOARD_NET, "AMMBCommandConnect: Bad address offset: {}", addr_offset); return; - } memcpy(&addr, addr_ptr, sizeof(addr)); - const int ret = NetDIMMConnect(guest_socket, &addr, len); + const int ret = NetDIMMConnect(guest_socket, addr); - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: connect( {}, ({},{}:{}), {} ):{}", u32(guest_socket), - addr.sin_family, inet_ntoa(addr.sin_addr), Common::swap16(addr.sin_port), len, - ret); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: connect( {}, ({},{},{}:{}), {} ):{}", u32(guest_socket), + addr.struct_size, addr.ip_family, Common::IPAddressToString(addr.ip_address), + ntohs(addr.port), len, ret); s_media_buffer[1] = s_media_buffer[8]; s_media_buffer_32[1] = ret; @@ -1295,47 +1309,59 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; case AMMBCommand::Bind: { - const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[2])); - const u32 off = s_media_buffer_32[3] - NetworkCommandAddress2; + const auto host_socket = GetHostSocket(GuestSocket(s_media_buffer_32[2])); + const u32 addr_offset = s_media_buffer_32[3]; const u32 len = s_media_buffer_32[4]; - if (!NetworkCMDBufferCheck(off, std::max(sizeof(sockaddr_in), len))) + GuestSocketAddress guest_addr; + + if (len != sizeof(guest_addr)) { + ERROR_LOG_FMT(AMMEDIABOARD_NET, "AMMBCommand::Bind: Unexpected length: {}", len); break; } - sockaddr_in addr; - memcpy(&addr, s_network_command_buffer + off, sizeof(sockaddr_in)); + const auto* addr_ptr = GetSafePtr(s_network_command_buffer, NetworkCommandAddress2, + addr_offset, sizeof(guest_addr)); + if (addr_ptr == nullptr) + break; - addr.sin_family = Common::swap16(addr.sin_family); + memcpy(&guest_addr, addr_ptr, sizeof(guest_addr)); // Triforce titles typically rely on hardcoded IP addresses. // Our config allows this to be overriden. - INFO_LOG_FMT(AMMEDIABOARD, "AMMBCommand::Bind: {}:{}", inet_ntoa(addr.sin_addr), - ntohs(addr.sin_port)); + INFO_LOG_FMT(AMMEDIABOARD, "AMMBCommand::Bind: {}, {}, {}:{}", guest_addr.struct_size, + guest_addr.ip_family, Common::IPAddressToString(guest_addr.ip_address), + ntohs(guest_addr.port)); // Apply "BindIP" if it's valid. const auto override_bind_ip = inet_addr(Config::Get(Config::MAIN_TRIFORCE_BIND_IP).c_str()); if (override_bind_ip != INADDR_NONE) { - addr.sin_addr.s_addr = override_bind_ip; + guest_addr.ip_address = std::bit_cast(override_bind_ip); INFO_LOG_FMT(AMMEDIABOARD, "AMMBCommand::Bind: Overriding IP to: {}", - Common::IPAddressToString(std::bit_cast(addr.sin_addr))); + Common::IPAddressToString(guest_addr.ip_address)); } // Apply "IPOverrides" in case config wants ports adjusted. - const auto adjusted_ipv4port = GetAdjustedIPv4PortFromConfig(addr.sin_addr, addr.sin_port); + const auto adjusted_ipv4port = + GetAdjustedIPv4PortFromConfig({guest_addr.ip_address, guest_addr.port}); if (adjusted_ipv4port.has_value()) { - addr.sin_addr = std::bit_cast(adjusted_ipv4port->ip_address); - addr.sin_port = adjusted_ipv4port->port; + guest_addr.ip_address = adjusted_ipv4port->ip_address; + guest_addr.port = adjusted_ipv4port->port; INFO_LOG_FMT(AMMEDIABOARD, "AMMBCommand::Bind: Overriding to: {}:{}", - Common::IPAddressToString(std::bit_cast(addr.sin_addr)), - ntohs(addr.sin_port)); + Common::IPAddressToString(guest_addr.ip_address), ntohs(guest_addr.port)); } - const int ret = bind(fd, reinterpret_cast(&addr), len); + sockaddr_in addr{ + .sin_family = guest_addr.ip_family, + .sin_port = guest_addr.port, + .sin_addr = std::bit_cast(guest_addr.ip_address), + }; + + const int ret = bind(host_socket, reinterpret_cast(&addr), sizeof(addr)); const int err = WSAGetLastError(); if (ret < 0) @@ -1346,9 +1372,9 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u err_msg); } - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: bind( {}, ({},{:08x}:{}), {} ):{} ({})", fd, - addr.sin_family, addr.sin_addr.s_addr, Common::swap16(addr.sin_port), len, - ret, err); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: bind( {}, ({},{:08x}:{}), {} ):{} ({})", + host_socket, addr.sin_family, addr.sin_addr.s_addr, + Common::swap16(addr.sin_port), len, ret, err); s_media_buffer_32[1] = ret; s_last_error = SSC_SUCCESS; @@ -1802,11 +1828,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; case AMMBCommand::SetupLink: { - sockaddr_in addra{}; - sockaddr_in addrb{}; - - addra.sin_addr.s_addr = s_media_buffer_32[12]; - addrb.sin_addr.s_addr = s_media_buffer_32[13]; + const auto addra = std::bit_cast(s_media_buffer_32[12]); + const auto addrb = std::bit_cast(s_media_buffer_32[13]); const u16 size = s_media_buffer[0x24] | s_media_buffer[0x25] << 8; const u16 port = Common::swap16(s_media_buffer[0x27] | s_media_buffer[0x26] << 8); @@ -1818,9 +1841,10 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM:LinkNum:({:02x})", s_media_buffer[0x28]); NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: ({:02x})", s_media_buffer[0x2A]); NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: ({:04x})", unknown); - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: IP: ({})", inet_ntoa(addra.sin_addr)); // IP ? NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: IP: ({})", - inet_ntoa(addrb.sin_addr)); // Target IP + Common::IPAddressToString(addra)); // IP ? + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: IP: ({})", + Common::IPAddressToString(addrb)); // Target IP NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: ({:08x})", Common::swap32(s_media_buffer_32[14])); // some RAM address NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: ({:08x})", From 0d7aef298ebd499f95c4b3ee107ffb99d4dec670 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 7 Feb 2026 19:43:49 -0600 Subject: [PATCH 46/71] AMMediaboard: Increase size of NetworkBufferAddress3 area to fix Mario Kart Arcade GP multicab. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index eff81d0a0f..748089b796 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -174,7 +174,7 @@ static const MediaBoardRanges s_mediaboard_ranges[] = { NetworkBufferAddress1}, {NetworkBufferAddress2, 0x1FD10000, s_network_buffer, sizeof(s_network_buffer), NetworkBufferAddress2}, - {NetworkBufferAddress3, 0x89110000, s_network_buffer, sizeof(s_network_buffer), + {NetworkBufferAddress3, 0x89150000, s_network_buffer, sizeof(s_network_buffer), NetworkBufferAddress3}, {NetworkBufferAddress4, 0x89240000, s_network_buffer, sizeof(s_network_buffer), NetworkBufferAddress4}, From bd3739ce089ffb77261a973f6180fda9aa4a41ef Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 7 Feb 2026 20:26:13 -0600 Subject: [PATCH 47/71] AMMediaboard: Flatten the NetDIMMConnect function. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 116 ++++++++++++----------- 1 file changed, 63 insertions(+), 53 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 748089b796..7ff732e2fa 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -17,6 +17,7 @@ #include "Common/IOFile.h" #include "Common/Logging/Log.h" #include "Common/Network.h" +#include "Common/ScopeGuard.h" #include "Core/Boot/Boot.h" #include "Core/BootManager.h" @@ -675,72 +676,79 @@ static s32 NetDIMMConnect(GuestSocket guest_socket, const GuestSocketAddress& gu const auto host_socket = GetHostSocket(guest_socket); - u_long val = 1; // Set socket to non-blocking - ioctlsocket(host_socket, FIONBIO, &val); + { + u_long val = 1; + ioctlsocket(host_socket, FIONBIO, &val); + } + // Restore blocking mode + Common::ScopeGuard guard{[&] { + u_long val = 0; + ioctlsocket(host_socket, FIONBIO, &val); + }}; - int ret = connect(host_socket, reinterpret_cast(&addr), sizeof(addr)); + const int connect_result = + connect(host_socket, reinterpret_cast(&addr), sizeof(addr)); const int err = WSAGetLastError(); - if (ret == SOCKET_ERROR && err == WSAEWOULDBLOCK) + if (connect_result == 0) [[unlikely]] { - WSAPOLLFD pfds[1]{{.fd = host_socket, .events = POLLOUT}}; - - const auto timeout = - duration_cast(std::chrono::microseconds{s_timeouts[0]}); - - ret = PlatformPoll(pfds, timeout); - - if (ret > 0 && (pfds[0].revents & POLLOUT) != 0) - { - int so_error = 0; - socklen_t optlen = sizeof(so_error); - if (getsockopt(host_socket, SOL_SOCKET, SO_ERROR, reinterpret_cast(&so_error), - &optlen) == 0 && - so_error == 0) - { - s_last_error = SSC_SUCCESS; - ret = 0; - } - else - { - ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: getsockopt() failed in NetDIMMConnect ({})", - Common::StrNetworkError()); - s_last_error = SOCKET_ERROR; - ret = SOCKET_ERROR; - } - } - else if (ret == 0) - { - // Timeout - s_last_error = SSC_EWOULDBLOCK; - ret = SOCKET_ERROR; - } - else - { - ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: poll() failed in NetDIMMConnect ({})", - Common::StrNetworkError()); - s_last_error = SOCKET_ERROR; - ret = SOCKET_ERROR; - } + // Immediate success. + s_last_error = SSC_SUCCESS; + return 0; } - else if (ret == SOCKET_ERROR) + + if (err != WSAEWOULDBLOCK) { // Immediate failure (e.g. WSAECONNREFUSED) - ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: NetDIMMConnect failed, connect() = {} ({})", err, - Common::DecodeNetworkError(err)); - s_last_error = ret; + WARN_LOG_FMT(AMMEDIABOARD, "NetDIMMConnect: connect: {} ({})", err, + Common::DecodeNetworkError(err)); + + s_last_error = SOCKET_ERROR; + return SOCKET_ERROR; } - else + + WSAPOLLFD pfds[1]{{.fd = host_socket, .events = POLLOUT}}; + + const auto timeout = + duration_cast(std::chrono::microseconds{s_timeouts[0]}); + + const int poll_result = PlatformPoll(pfds, timeout); + + if (poll_result < 0) [[unlikely]] + { + // Poll failure. + ERROR_LOG_FMT(AMMEDIABOARD, "NetDIMMConnect: PlatformPoll: {}", Common::StrNetworkError()); + + s_last_error = SOCKET_ERROR; + return SOCKET_ERROR; + } + + if ((pfds[0].revents & POLLOUT) == 0) + { + // Timeout. + s_last_error = SSC_EWOULDBLOCK; + return SOCKET_ERROR; + } + + int so_error = 0; + socklen_t optlen = sizeof(so_error); + const int getsockopt_result = + getsockopt(host_socket, SOL_SOCKET, SO_ERROR, reinterpret_cast(&so_error), &optlen); + + if (getsockopt_result != 0) [[unlikely]] + { + // getsockopt failure. + ERROR_LOG_FMT(AMMEDIABOARD, "NetDIMMConnect: getsockopt: {}", Common::StrNetworkError()); + } + else if (so_error == 0) { s_last_error = SSC_SUCCESS; + return 0; } - // Restore blocking mode - val = 0; - ioctlsocket(host_socket, FIONBIO, &val); - - return ret; + s_last_error = SOCKET_ERROR; + return SOCKET_ERROR; } static void AMMBCommandRecv(u32 parameter_offset, u32 network_buffer_base) @@ -1498,6 +1506,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: GetLastError( {}({}) ):{}", fd, s_media_buffer_32[2], s_last_error); + // Good enough, assuming it's called for the same socket right after an error. + // TODO: Implement something similar per socket. s_media_buffer[1] = s_media_buffer[8]; s_media_buffer_32[1] = s_last_error; } From 63dc3b19722215e89af8d6e25ae96c005b63a78e Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 7 Feb 2026 21:09:21 -0600 Subject: [PATCH 48/71] AMMediaboard: Adjust the verbosity of many LOG entries. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 54 ++++++++++++------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 7ff732e2fa..8533def250 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -773,7 +773,7 @@ static void AMMBCommandRecv(u32 parameter_offset, u32 network_buffer_base) int ret = recv(fd, reinterpret_cast(s_network_buffer + off), len, 0); const int err = WSAGetLastError(); - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: recv( {}, 0x{:08x}, {} ):{} {}", fd, off, len, ret, err); + DEBUG_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: recv( {}, 0x{:08x}, {} ):{} {}", fd, off, len, ret, err); s_media_buffer[1] = s_media_buffer[8]; s_media_buffer_32[1] = ret; @@ -802,8 +802,8 @@ static void AMMBCommandSend(u32 parameter_offset, u32 network_buffer_base) const int ret = send(fd, reinterpret_cast(s_network_buffer + off), len, SEND_FLAGS); const int err = WSAGetLastError(); - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: send( {}({}), 0x{:08x}, {} ): {} {}", fd, - u32(guest_socket), off, len, ret, err); + DEBUG_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: send( {}({}), 0x{:08x}, {} ): {} {}", fd, + u32(guest_socket), off, len, ret, err); s_media_buffer[1] = s_media_buffer[8]; s_media_buffer_32[1] = ret; @@ -817,8 +817,7 @@ static void AMMBCommandSocket(u32 parameter_offset) const GuestSocket guest_socket = socket_(af, type, IPPROTO_TCP); - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: socket( {}, {}, IPPROTO_TCP ):{}", af, type, - u32(guest_socket)); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: socket( {}, {} ):{}", af, type, u32(guest_socket)); s_media_buffer[1] = 0; s_media_buffer_32[1] = u32(guest_socket); @@ -863,9 +862,9 @@ static void AMMBCommandConnect(u32 parameter_offset, u32 network_buffer_base) const int ret = NetDIMMConnect(guest_socket, addr); - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: connect( {}, ({},{},{}:{}), {} ):{}", u32(guest_socket), - addr.struct_size, addr.ip_family, Common::IPAddressToString(addr.ip_address), - ntohs(addr.port), len, ret); + INFO_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: connect( {}, ({},{},{}:{}), {} ):{}", u32(guest_socket), + addr.struct_size, addr.ip_family, Common::IPAddressToString(addr.ip_address), + ntohs(addr.port), len, ret); s_media_buffer[1] = s_media_buffer[8]; s_media_buffer_32[1] = ret; @@ -895,7 +894,7 @@ static void AMMBCommandAccept(u32 parameter_offset, u32 network_buffer_base) socklen_t addrlen = sizeof(addr); ret = u32(NetDIMMAccept(guest_socket, &addr, &addrlen)); - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: accept( {} ):{}", u32(guest_socket), int(ret)); + INFO_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: accept( {} ):{}", u32(guest_socket), int(ret)); auto* const addrlen_ptr = GetSafePtr(s_network_command_buffer, network_buffer_base, addrlen_off, sizeof(u32)); @@ -1005,9 +1004,9 @@ static void AMMBCommandSelect(u32 parameter_offset, u32 network_buffer_base) WARN_LOG_FMT(AMMEDIABOARD, "AMMBCommandSelect: Infinite timout!"); } - NOTICE_LOG_FMT( - AMMEDIABOARD_NET, "GC-AM: select( {}, 0x{:08x} 0x{:08x} 0x{:08x} 0x{:08x} ) timeout={}", nfds, - readfds_offset, writefds_offset, exceptfds_offset, timeout_offset, timeout.count()); + DEBUG_LOG_FMT(AMMEDIABOARD_NET, + "GC-AM: select( {}, 0x{:08x} 0x{:08x} 0x{:08x} 0x{:08x} ) timeout={}", nfds, + readfds_offset, writefds_offset, exceptfds_offset, timeout_offset, timeout.count()); // Fill with the host sockets for each guest socket less-than `nfds` in each GuestFdSet. std::vector pollfds(nfds, WSAPOLLFD{.fd = INVALID_SOCKET}); @@ -1022,7 +1021,7 @@ static void AMMBCommandSelect(u32 parameter_offset, u32 network_buffer_base) // TODO: There may be some edge cases where // poll's (POLLIN,POLLOUT,POLLPRI) don't map 1:1 with select's (readfds,writefds,exceptfds). - INFO_LOG_FMT(AMMEDIABOARD, "AMMBCommandSelect: Polling with socket count: {}", pollfds.size()); + DEBUG_LOG_FMT(AMMEDIABOARD, "AMMBCommandSelect: Polling with socket count: {}", pollfds.size()); const int ret = PlatformPoll(pollfds, timeout); @@ -1033,7 +1032,7 @@ static void AMMBCommandSelect(u32 parameter_offset, u32 network_buffer_base) WriteGuestFdSetFromPollFds(guest_exceptfds_ptr, pollfds, POLLPRI); } - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: select result: {}", ret); + DEBUG_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: select result: {}", ret); s_media_buffer[1] = 0; s_media_buffer_32[1] = ret; @@ -1285,8 +1284,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u { const AMMBCommand ammb_command = Common::BitCastPtr(s_media_buffer + 0x202); - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Execute command: (2){0:04X}", - static_cast(ammb_command)); + DEBUG_LOG_FMT(AMMEDIABOARD, "GC-AM: Execute command: (2){0:04X}", + static_cast(ammb_command)); memcpy(s_media_buffer, s_media_buffer + 0x200, 0x20); memset(s_media_buffer + 0x200, 0, 0x20); @@ -1339,9 +1338,9 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u // Triforce titles typically rely on hardcoded IP addresses. // Our config allows this to be overriden. - INFO_LOG_FMT(AMMEDIABOARD, "AMMBCommand::Bind: {}, {}, {}:{}", guest_addr.struct_size, - guest_addr.ip_family, Common::IPAddressToString(guest_addr.ip_address), - ntohs(guest_addr.port)); + NOTICE_LOG_FMT(AMMEDIABOARD, "AMMBCommand::Bind: {}, {}, {}:{}", guest_addr.struct_size, + guest_addr.ip_family, Common::IPAddressToString(guest_addr.ip_address), + ntohs(guest_addr.port)); // Apply "BindIP" if it's valid. const auto override_bind_ip = inet_addr(Config::Get(Config::MAIN_TRIFORCE_BIND_IP).c_str()); @@ -1380,9 +1379,9 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u err_msg); } - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: bind( {}, ({},{:08x}:{}), {} ):{} ({})", - host_socket, addr.sin_family, addr.sin_addr.s_addr, - Common::swap16(addr.sin_port), len, ret, err); + INFO_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: bind( {}, ({},{:08x}:{}), {} ):{} ({})", host_socket, + addr.sin_family, addr.sin_addr.s_addr, Common::swap16(addr.sin_port), len, ret, + err); s_media_buffer_32[1] = ret; s_last_error = SSC_SUCCESS; @@ -1501,10 +1500,11 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; case AMMBCommand::GetLastError: { - const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[2])); + const auto guest_socket = GuestSocket(s_media_buffer_32[2]); + const auto host_socket = GetHostSocket(guest_socket); - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: GetLastError( {}({}) ):{}", fd, - s_media_buffer_32[2], s_last_error); + DEBUG_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: GetLastError( {}({}) ):{}", host_socket, + int(guest_socket), int(s_last_error)); // Good enough, assuming it's called for the same socket right after an error. // TODO: Implement something similar per socket. @@ -1646,8 +1646,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u { const AMMBCommand ammb_command = Common::BitCastPtr(s_media_buffer + 0x22); - INFO_LOG_FMT(AMMEDIABOARD, "GC-AM: Execute command: (1):{0:04X}", - static_cast(ammb_command)); + DEBUG_LOG_FMT(AMMEDIABOARD, "GC-AM: Execute command: (1):{0:04X}", + static_cast(ammb_command)); memset(s_media_buffer, 0, 0x20); From aaa709444245ada15b5679e60a02fdfde2e4510c Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 8 Feb 2026 03:25:04 -0600 Subject: [PATCH 49/71] Triforce: Implement save states. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 92 ++++++++++++++--- Source/Core/Core/HW/DVD/AMMediaboard.h | 2 + Source/Core/Core/HW/DVD/DVDInterface.cpp | 6 ++ .../Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp | 98 +++++++++++++++---- Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h | 7 ++ 5 files changed, 175 insertions(+), 30 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 8533def250..0c2260628c 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -152,6 +152,10 @@ static u8 s_network_buffer[512 * 1024]; static u8 s_allnet_buffer[4096]; static u8 s_allnet_settings[0x8500]; +// Fake loading the game to have a chance to enter test mode +static u32 s_board_status = LoadingGameProgram; +static u32 s_load_progress = 80; + static constexpr std::size_t MAX_IPV4_STRING_LENGTH = 15; constexpr char s_allnet_reply[] = { @@ -441,6 +445,9 @@ void Init() std::ranges::fill(s_allnet_buffer, 0); std::ranges::fill(s_allnet_settings, 0); + s_board_status = LoadingGameProgram; + s_load_progress = 80; + s_firmware_map = false; s_test_menu = false; @@ -1746,19 +1753,15 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; case AMMBCommand::GetMediaBoardStatus: { - // Fake loading the game to have a chance to enter test mode - static u32 status = LoadingGameProgram; - static u32 progress = 80; - - s_media_buffer_32[1] = status; - s_media_buffer_32[2] = progress; - if (progress < 100) + s_media_buffer_32[1] = s_board_status; + s_media_buffer_32[2] = s_load_progress; + if (s_load_progress < 100) { - progress++; + s_load_progress++; } else { - status = LoadedGameProgram; + s_board_status = LoadedGameProgram; } } break; @@ -1974,6 +1977,18 @@ bool GetTestMenu() return s_test_menu; } +static void CloseAllSockets() +{ + for (u32 i = FIRST_VALID_FD; i < std::size(s_sockets); ++i) + { + if (s_sockets[i] != SOCKET_ERROR) + { + closesocket(s_sockets[i]); + s_sockets[i] = SOCKET_ERROR; + } + } +} + void Shutdown() { s_netcfg.Close(); @@ -1983,12 +1998,63 @@ void Shutdown() s_dimm.Close(); s_dimm_disc.clear(); - // Close all sockets - for (u32 i = FIRST_VALID_FD; i < std::size(s_sockets); ++i) + CloseAllSockets(); +} + +void DoState(PointerWrap& p) +{ + p.Do(s_firmware_map); + p.Do(s_test_menu); + p.Do(s_timeouts); + p.Do(s_last_error); + p.Do(s_gcam_key_a); + p.Do(s_gcam_key_b); + p.Do(s_gcam_key_c); + p.Do(s_firmware); + p.Do(s_media_buffer_32); + p.Do(s_network_command_buffer); + p.Do(s_network_buffer); + p.Do(s_allnet_buffer); + p.Do(s_allnet_settings); + + p.Do(s_board_status); + p.Do(s_load_progress); + + // TODO: Handle the files better. + // Data corruption is probably currently possible. + + // s_netcfg + // s_netctrl + // s_extra + // s_backup + // s_dimm + + // TODO: Handle sockets better. + // For now, we just recreate a TCP socket for any socket that existed. + // We should probably re-bind sockets and handle UDP sockets. + + GuestFdSet created_sockets{}; + if (p.IsWriteMode() || p.IsVerifyMode()) { - if (s_sockets[i] != SOCKET_ERROR) + for (u32 i = FIRST_VALID_FD; i < std::size(s_sockets); ++i) { - closesocket(s_sockets[i]); + if (s_sockets[i] != SOCKET_ERROR) + created_sockets.SetFd(GuestSocket(i)); + } + } + + p.Do(created_sockets); + + if (p.IsReadMode()) + { + CloseAllSockets(); + + for (u32 i = FIRST_VALID_FD; i < std::size(s_sockets); ++i) + { + if (!created_sockets.IsFdSet(GuestSocket(i))) + continue; + + s_sockets[i] = socket(AF_INET, SOCK_STREAM, 0); } } } diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.h b/Source/Core/Core/HW/DVD/AMMediaboard.h index cc0bcf9b89..f5d19662ea 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.h +++ b/Source/Core/Core/HW/DVD/AMMediaboard.h @@ -8,6 +8,7 @@ #include #include +#include "Common/ChunkFile.h" #include "Common/CommonTypes.h" enum GameType @@ -236,6 +237,7 @@ u32 GetGameType(); u32 GetMediaType(); bool GetTestMenu(); void Shutdown(); +void DoState(PointerWrap& p); std::optional> ParseIPOverride(std::string_view str); diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index 0801b8d26a..33b0efc548 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -124,6 +124,12 @@ void DVDInterface::DoState(PointerWrap& p) m_system.GetDVDThread().DoState(p); m_adpcm_decoder.DoState(p); + + if (m_system.IsTriforce()) + { + AMMediaboard::DoState(p); + p.DoMarker("AMMediaboard"); + } } size_t DVDInterface::ProcessDTKSamples(s16* target_samples, size_t target_block_count, diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp index 060ff44773..6841d81702 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp @@ -228,9 +228,6 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) std::array data_out{}; u32 data_offset = 0; - static u32 dip_switch_1 = 0xFE; - static u32 dip_switch_0 = 0xFF; - data_out[data_offset++] = 1; data_out[data_offset++] = 1; @@ -304,18 +301,18 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) if (AMMediaboard::GetGameType() == FZeroAX || AMMediaboard::GetGameType() == FZeroAXMonster) { - dip_switch_0 &= ~0x20; + m_dip_switch_0 &= ~0x20; } // Disable camera in MKGP1/2 if (AMMediaboard::GetGameType() == MarioKartGP || AMMediaboard::GetGameType() == MarioKartGP2) { - dip_switch_0 &= ~0x10; + m_dip_switch_0 &= ~0x10; } - data_out[data_offset++] = dip_switch_0; - data_out[data_offset++] = dip_switch_1; + data_out[data_offset++] = m_dip_switch_0; + data_out[data_offset++] = m_dip_switch_1; break; } case GCAMCommand::SerialNumber: @@ -1656,8 +1653,6 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) JVSIOMessage message; - static int delay = 0; - const u8* const frame = &data_in[0]; const u8 nr_bytes = frame[3]; // Byte after E0 xx u32 frame_len = nr_bytes + 3; // Header(2) + length byte + payload + checksum @@ -1694,8 +1689,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) " - jvs_io(begin={}, current={}, end={}, n={})\n" " - delay={}, node={}\n" " - frame(begin={}, len={})", - fmt::ptr(jvs_begin), fmt::ptr(jvs_io), fmt::ptr(jvs_end), n, delay, node, - fmt::ptr(frame), frame_len); + fmt::ptr(jvs_begin), fmt::ptr(jvs_io), fmt::ptr(jvs_end), n, m_delay, + node, fmt::ptr(frame), frame_len); jvs_io = jvs_end; return false; }; @@ -2355,7 +2350,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) INFO_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x32, GPO: delay=0x{:02x}, rx_reply=0x{:02x}," " bytes={}, buffer:\n{}", - delay, m_rx_reply, bytes, HexDump(jvs_io, bytes)); + m_delay, m_rx_reply, bytes, HexDump(jvs_io, bytes)); if (bytes < 3) { @@ -2370,8 +2365,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) switch (seat_state) { case 0x70: - delay++; - if ((delay % 10) == 0) + m_delay++; + if ((m_delay % 10) == 0) { m_rx_reply = 0xFB; } @@ -2429,13 +2424,13 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) if (*jvs_io++ == 0xD9) { NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0xF0, Reset"); - delay = 0; + m_delay = 0; m_wheel_init = 0; m_ic_card_state = 0x20; } message.AddData(StatusOkay); - dip_switch_1 |= 1; + m_dip_switch_1 |= 1; break; case JVSIOCommand::SetAddress: if (!validate_jvs_io(1, "SetAddress")) @@ -2444,7 +2439,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0xF1, SetAddress: node={}", node); message.AddData(node == 1); - dip_switch_1 &= ~1u; + m_dip_switch_1 &= ~1u; break; default: ERROR_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Unhandled: node={}, command={:02x}", @@ -2739,4 +2734,73 @@ GCPadStatus CSIDevice_AMBaseboard::GetPadStatus() return pad_status; } +void CSIDevice_AMBaseboard::DoState(PointerWrap& p) +{ + p.Do(m_origin); + p.Do(m_mode); + p.Do(m_timer_button_combo_start); + p.Do(m_last_button_combo); + + p.Do(m_last); + p.Do(m_lastptr); + + p.Do(m_coin); + p.Do(m_coin_pressed); + + p.Do(m_ic_card_data); + + // Setup IC-card + p.Do(m_ic_card_state); + p.Do(m_ic_card_status); + p.Do(m_ic_card_session); + + p.Do(m_ic_write_buffer); + p.Do(m_ic_write_offset); + p.Do(m_ic_write_size); + + p.Do(m_card_memory); + p.Do(m_card_read_packet); + p.Do(m_card_buffer); + + // Setup CARD + p.Do(m_card_memory_size); + p.Do(m_card_is_inserted); + + p.Do(m_card_command); + p.Do(m_card_clean); + p.Do(m_card_write_length); + p.Do(m_card_wrote); + p.Do(m_card_read_length); + p.Do(m_card_read); + p.Do(m_card_bit); + p.Do(m_card_shutter); + p.Do(m_card_state_call_count); + p.Do(m_card_offset); + + // Serial + p.Do(m_wheel_init); + + p.Do(m_motor_init); + p.Do(m_motor_reply); + p.Do(m_motor_force_y); + + // F-Zero AX (DX) + p.Do(m_fzdx_seatbelt); + p.Do(m_fzdx_motion_stop); + p.Do(m_fzdx_sensor_right); + p.Do(m_fzdx_sensor_left); + p.Do(m_rx_reply); + + // F-Zero AX (CyCraft) + p.Do(m_fzcc_seatbelt); + p.Do(m_fzcc_sensor); + p.Do(m_fzcc_emergency); + p.Do(m_fzcc_service); + + p.Do(m_dip_switch_1); + p.Do(m_dip_switch_0); + + p.Do(m_delay); +} + } // namespace SerialInterface diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h index 95b19a86c6..974325c7c2 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h @@ -71,6 +71,8 @@ public: static bool NetPlay_GetInput(int pad_num, GCPadStatus* status); static int NetPlay_InGamePadToLocalPad(int pad_num); + void DoState(PointerWrap&) override; + protected: struct SOrigin { @@ -311,6 +313,11 @@ private: bool m_fzcc_emergency = false; bool m_fzcc_service = false; + u32 m_dip_switch_1 = 0xFE; + u32 m_dip_switch_0 = 0xFF; + + int m_delay = 0; + void ICCardSendReply(ICCommand* iccommand, u8* buffer, u32* length); }; From 3681cade416a025ca24c35b4d176537c53b48a4f Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 7 Feb 2026 23:01:05 -0600 Subject: [PATCH 50/71] DiscIO: Add IsCached virtual function to BlobReader. --- Source/Core/DiscIO/Blob.h | 3 +++ Source/Core/DiscIO/CachedBlob.cpp | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/Source/Core/DiscIO/Blob.h b/Source/Core/DiscIO/Blob.h index 841a827b97..3551c31d44 100644 --- a/Source/Core/DiscIO/Blob.h +++ b/Source/Core/DiscIO/Blob.h @@ -97,6 +97,9 @@ public: return false; } + // Returns true only for CachedBlobReader. + virtual bool IsCached() const { return false; } + protected: BlobReader() {} }; diff --git a/Source/Core/DiscIO/CachedBlob.cpp b/Source/Core/DiscIO/CachedBlob.cpp index 98ed8ba31f..f1bbf055ee 100644 --- a/Source/Core/DiscIO/CachedBlob.cpp +++ b/Source/Core/DiscIO/CachedBlob.cpp @@ -219,6 +219,8 @@ public: return m_cache_filler->Read(offset, size, out_ptr) || m_reader->Read(offset, size, out_ptr); } + bool IsCached() const override { return true; } + private: // A shared object does the cache filling for sensible CopyReader behavior. const std::shared_ptr m_cache_filler; @@ -228,11 +230,17 @@ private: std::unique_ptr CreateCachedBlobReader(std::unique_ptr reader) { + if (reader->IsCached()) // This is already CachedBlobReader. + return reader; + return std::make_unique(std::move(reader), false); } std::unique_ptr CreateScrubbingCachedBlobReader(std::unique_ptr reader) { + if (reader->IsCached()) // This reader is already a CachedBlobReader. + return reader; + return std::make_unique(std::move(reader), true); } From a4fd5f8343a1007f46aff877a71cb853a2c6ecee Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 7 Feb 2026 23:08:37 -0600 Subject: [PATCH 51/71] Triforce: Lazily load the game into memory using CachedBlobReader. --- Source/Core/Core/Boot/Boot_BS2Emu.cpp | 11 +------- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 36 +++++++++--------------- Source/Core/Core/HW/DVD/AMMediaboard.h | 4 ++- 3 files changed, 18 insertions(+), 33 deletions(-) diff --git a/Source/Core/Core/Boot/Boot_BS2Emu.cpp b/Source/Core/Core/Boot/Boot_BS2Emu.cpp index b462e89edc..a44796dd4f 100644 --- a/Source/Core/Core/Boot/Boot_BS2Emu.cpp +++ b/Source/Core/Core/Boot/Boot_BS2Emu.cpp @@ -233,17 +233,8 @@ bool CBoot::RunApploader(Core::System& system, const Core::CPUThreadGuard& guard if (system.IsTriforce()) { auto& memory = system.GetMemory(); - const u32 disc_size = volume.GetDataSize(); - // Load game into RAM, like on the actual Triforce - u8* dimm = AMMediaboard::InitDIMM(disc_size); - if (!dimm) - { - ERROR_LOG_FMT(BOOT, "AMMediaboard::InitDIMM failed"); - return false; - } - - volume.Read(0, disc_size, dimm, DiscIO::PARTITION_NONE); + AMMediaboard::InitDIMM(volume); // Triforce disc register obfuscation AMMediaboard::InitKeys(memory.Read_U32(0), memory.Read_U32(4), memory.Read_U32(8)); diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 0c2260628c..4ceca81925 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -11,7 +11,6 @@ #include -#include "Common/Buffer.h" #include "Common/CommonTypes.h" #include "Common/FileUtil.h" #include "Common/IOFile.h" @@ -37,6 +36,8 @@ #include "Core/Movie.h" #include "Core/System.h" +#include "DiscIO/CachedBlob.h" + #if defined(__linux__) or defined(__APPLE__) or defined(__FreeBSD__) or defined(__NetBSD__) or \ defined(__HAIKU__) @@ -142,7 +143,7 @@ static File::IOFile s_extra; static File::IOFile s_backup; static File::IOFile s_dimm; -static Common::UniqueBuffer s_dimm_disc; +static std::unique_ptr s_dimm_disc; static u8 s_firmware[2 * 1024 * 1024]; static u32 s_media_buffer_32[192]; @@ -499,20 +500,10 @@ void Init() s_test_menu = true; } -u8* InitDIMM(u32 size) +void InitDIMM(const DiscIO::Volume& volume) { - if (size == 0) - return nullptr; - - s_dimm_disc.reset(size); - if (s_dimm_disc.empty()) - { - PanicAlertFmt("Failed to allocate DIMM memory."); - return nullptr; - } - - s_firmware_map = false; - return s_dimm_disc.data(); + // Load game into RAM, like on the actual Triforce. + s_dimm_disc = DiscIO::CreateCachedBlobReader(volume.GetBlobReader().CopyReader()); } static int PlatformPoll(std::span pfds, std::chrono::milliseconds timeout) @@ -1556,13 +1547,13 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u return 0; } - if (s_dimm_disc.size()) + if (const auto span = memory.GetSpanForAddress(address); span.size() < length) + { + ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: Invalid DIMM Disc read from: offset={}, length={}", + offset, length); + } + else if (s_dimm_disc->Read(offset, length, span.data())) { - if (!SafeCopyToEmu(memory, address, s_dimm_disc.data(), s_dimm_disc.size(), offset, length)) - { - ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: Invalid DIMM Disc read from: offset={}, length={}", - offset, length); - } return 0; } @@ -1996,7 +1987,8 @@ void Shutdown() s_extra.Close(); s_backup.Close(); s_dimm.Close(); - s_dimm_disc.clear(); + + s_dimm_disc.reset(); CloseAllSockets(); } diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.h b/Source/Core/Core/HW/DVD/AMMediaboard.h index f5d19662ea..8ca7c7d9fd 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.h +++ b/Source/Core/Core/HW/DVD/AMMediaboard.h @@ -11,6 +11,8 @@ #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" +#include "DiscIO/Volume.h" + enum GameType { FZeroAX = 1, @@ -230,7 +232,7 @@ enum SocketStatusCodes void Init(); void FirmwareMap(bool on); -u8* InitDIMM(u32 size); +void InitDIMM(const DiscIO::Volume& volume); void InitKeys(u32 key_a, u32 key_b, u32 key_c); u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u32 length); u32 GetGameType(); From 32e4d88aa9ea0afb74373ad41f9fa303019cf5db Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 7 Feb 2026 23:51:13 -0600 Subject: [PATCH 52/71] AMMediaboard: Improve connect() and bind() logging. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 26 +++++++++++++----------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 4ceca81925..2092d7cf65 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -689,6 +689,10 @@ static s32 NetDIMMConnect(GuestSocket guest_socket, const GuestSocketAddress& gu connect(host_socket, reinterpret_cast(&addr), sizeof(addr)); const int err = WSAGetLastError(); + INFO_LOG_FMT(AMMEDIABOARD_NET, "NetDIMMConnect: connect( {}({}), ({},{}:{}) ):{} ({})", + host_socket, u32(guest_socket), addr.sin_family, inet_ntoa(addr.sin_addr), + Common::swap16(addr.sin_port), connect_result, err); + if (connect_result == 0) [[unlikely]] { // Immediate success. @@ -860,10 +864,6 @@ static void AMMBCommandConnect(u32 parameter_offset, u32 network_buffer_base) const int ret = NetDIMMConnect(guest_socket, addr); - INFO_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: connect( {}, ({},{},{}:{}), {} ):{}", u32(guest_socket), - addr.struct_size, addr.ip_family, Common::IPAddressToString(addr.ip_address), - ntohs(addr.port), len, ret); - s_media_buffer[1] = s_media_buffer[8]; s_media_buffer_32[1] = ret; } @@ -1314,7 +1314,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; case AMMBCommand::Bind: { - const auto host_socket = GetHostSocket(GuestSocket(s_media_buffer_32[2])); + const auto guest_socket = GuestSocket(s_media_buffer_32[2]); + const auto host_socket = GetHostSocket(guest_socket); const u32 addr_offset = s_media_buffer_32[3]; const u32 len = s_media_buffer_32[4]; @@ -1366,10 +1367,15 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u .sin_addr = std::bit_cast(guest_addr.ip_address), }; - const int ret = bind(host_socket, reinterpret_cast(&addr), sizeof(addr)); + const int bind_result = + bind(host_socket, reinterpret_cast(&addr), sizeof(addr)); const int err = WSAGetLastError(); - if (ret < 0) + INFO_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: bind( {}({}), ({},{}:{}) ):{} ({})", host_socket, + u32(guest_socket), addr.sin_family, inet_ntoa(addr.sin_addr), + Common::swap16(addr.sin_port), bind_result, err); + + if (bind_result < 0) { const auto err_msg = Common::DecodeNetworkError(err); PanicAlertFmt("Failed to bind socket (error {}: {})", err, err_msg); @@ -1377,11 +1383,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u err_msg); } - INFO_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: bind( {}, ({},{:08x}:{}), {} ):{} ({})", host_socket, - addr.sin_family, addr.sin_addr.s_addr, Common::swap16(addr.sin_port), len, ret, - err); - - s_media_buffer_32[1] = ret; + s_media_buffer_32[1] = bind_result; s_last_error = SSC_SUCCESS; break; } From e14a58c1d8f6d873b359e71158ad20c5525f1969 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 8 Feb 2026 05:02:31 -0600 Subject: [PATCH 53/71] AMMediaboard: Make AMMBCommandSocket determine socket protocol from domain and type rather than forcing TCP. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 2092d7cf65..1aa450a39f 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -813,13 +813,13 @@ static void AMMBCommandSend(u32 parameter_offset, u32 network_buffer_base) static void AMMBCommandSocket(u32 parameter_offset) { - // Protocol is not sent - const u32 af = s_media_buffer_32[parameter_offset]; + // Protocol is not sent (determined automatically). + const u32 domain = s_media_buffer_32[parameter_offset]; const u32 type = s_media_buffer_32[parameter_offset + 1]; - const GuestSocket guest_socket = socket_(af, type, IPPROTO_TCP); + const GuestSocket guest_socket = socket_(int(domain), int(type), 0); - NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: socket( {}, {} ):{}", af, type, u32(guest_socket)); + NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: socket( {}, {} ):{}", domain, type, u32(guest_socket)); s_media_buffer[1] = 0; s_media_buffer_32[1] = u32(guest_socket); From 535c81c8dfd58759d3da9bb271d191e318d8edd1 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 8 Feb 2026 06:31:40 -0600 Subject: [PATCH 54/71] Triforce: Automatically set SuggestedAspectRatio as ForceStandard to work around widescreen heuristic issues. --- Source/Core/Core/BootManager.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Source/Core/Core/BootManager.cpp b/Source/Core/Core/BootManager.cpp index affa3f3d53..85444ef0b6 100644 --- a/Source/Core/Core/BootManager.cpp +++ b/Source/Core/Core/BootManager.cpp @@ -25,6 +25,7 @@ #include "Core/AchievementManager.h" #include "Core/Boot/Boot.h" +#include "Core/Config/GraphicsSettings.h" #include "Core/Config/MainSettings.h" #include "Core/ConfigLoaders/BaseConfigLoader.h" #include "Core/ConfigLoaders/NetPlayConfigLoader.h" @@ -146,6 +147,13 @@ bool BootCore(Core::System& system, std::unique_ptr boot, Config::SetCurrent(Config::GetInfoForSIDevice(0), SerialInterface::SIDevices::SIDEVICE_AM_BASEBOARD); } + + // Mario Kart Arcade GP has widescreen heuristic issues. + // All Triforce games are 4:3 so we'll just disable the heuristic for now. + if (GetActiveLayerForConfig(Config::GFX_SUGGESTED_ASPECT_RATIO) == Config::LayerType::Base) + { + Config::SetCurrent(Config::GFX_SUGGESTED_ASPECT_RATIO, AspectMode::ForceStandard); + } } system.Initialize(); From 2c2bfc1fe174ec392bf4c7d905efa675837ee6e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joshua=20Vanda=C3=ABle?= Date: Sun, 8 Feb 2026 15:05:26 +0100 Subject: [PATCH 55/71] Enums: Unhandled switch statement --- Source/Core/DiscIO/Enums.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Core/DiscIO/Enums.cpp b/Source/Core/DiscIO/Enums.cpp index e995479cff..919b10b940 100644 --- a/Source/Core/DiscIO/Enums.cpp +++ b/Source/Core/DiscIO/Enums.cpp @@ -407,7 +407,7 @@ std::string GetSysMenuVersionString(u16 title_version, bool is_vwii) case Region::NTSC_K: region_letter = 'K'; break; - case Region::Unknown: + default: WARN_LOG_FMT(DISCIO, "Unknown region for Wii Menu version {}", title_version); break; } From 7b0ee7784061a2dd3ffd546698fcf58dd9e8cf51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joshua=20Vanda=C3=ABle?= Date: Sun, 8 Feb 2026 15:06:13 +0100 Subject: [PATCH 56/71] SI_DeviceAMBaseboard: Comparing an unsigned int and an int --- Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp index 6841d81702..52d1dd883f 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp @@ -177,7 +177,7 @@ void CSIDevice_AMBaseboard::ICCardSendReply(ICCommand* iccommand, u8* buffer, u3 const auto iccommand_data = reinterpret_cast(iccommand); const u8 crc = CheckSumXOR(iccommand_data + 2, iccommand->pktlen - 1); - for (u32 i = 0; i < iccommand->pktlen + 1; ++i) + for (u32 i = 0; i <= iccommand->pktlen; ++i) { buffer[(*length)++] = iccommand_data[i]; } From b2bbbb64bd3433c33c8b1e7b00c5713f0eb19c02 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 8 Feb 2026 21:54:46 -0600 Subject: [PATCH 57/71] SI_DeviceAMBaseboard: Fix signed/unsigned comparison. --- .../Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp index 52d1dd883f..b2a65c10a3 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp @@ -608,21 +608,23 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) const u16 page = m_ic_write_buffer[5]; const u16 count = m_ic_write_buffer[7]; + const u32 write_size = u32(count) * 8; + const u32 write_offset = u32(page) * 8; - if ((page * 8 + count * 8) > sizeof(m_ic_card_data) || - (10 + count * 8) > sizeof(m_ic_write_buffer)) + if ((write_size + write_offset) > sizeof(m_ic_card_data) || + (10 + write_size) > sizeof(m_ic_write_buffer)) { ERROR_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 25 (IC-CARD) Write Pages overflow:\n" " - m_ic_card_data(offset={}, size={})\n" " - m_ic_write_buffer(offset={}, size={})\n" " - size={}, page={}, count={}\n", - page * 8, sizeof(m_ic_card_data), 10, sizeof(m_ic_write_buffer), - count * 8, page, count); + write_offset, sizeof(m_ic_card_data), 10, + sizeof(m_ic_write_buffer), write_size, page, count); data_in = data_in_end; break; } - memcpy(m_ic_card_data + page * 8, m_ic_write_buffer + 10, count * 8); + memcpy(m_ic_card_data + write_offset, m_ic_write_buffer + 10, write_size); INFO_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command 25 (IC-CARD) Write Pages:{} Count:{}({:x})", page, @@ -808,6 +810,8 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) const u16 size = Common::swap16(data_in + 2); const u16 page = Common::swap16(data_in + 6) & 0xFF; // 255 is max page const u16 count = Common::swap16(data_in + 8); + const u32 write_size = u32(count) * 8; + const u32 write_offset = u32(page) * 8; // We got a complete packet if (pksize - 5 == size) @@ -818,7 +822,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } else { - if (page * 8 + count * 8 > sizeof(m_ic_card_data)) + if (write_size + write_offset > sizeof(m_ic_card_data)) { ERROR_LOG_FMT( SERIALINTERFACE_CARD, @@ -827,9 +831,9 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } else { - if (!validate_data_in_out(13 + count * 8, 0, "SerialA (IC-CARD)")) + if (!validate_data_in_out(13 + write_size, 0, "SerialA (IC-CARD)")) break; - memcpy(m_ic_card_data + page * 8, data_in + 13, count * 8); + memcpy(m_ic_card_data + write_offset, data_in + 13, write_size); } } From dc4645d2556f6edc76021277fed7edcb5b6adfdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joshua=20Vanda=C3=ABle?= Date: Sun, 8 Feb 2026 15:25:16 +0100 Subject: [PATCH 58/71] SI_DeviceAMBaseboard: Shadowed variable "command" --- Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp index b2a65c10a3..7cc5d93c01 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp @@ -207,10 +207,10 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) u32 buffer_position = 0; while (buffer_position < buffer_length) { - BaseBoardCommand command = static_cast(buffer[buffer_position]); + const auto bb_command = static_cast(buffer[buffer_position]); buffer_position++; - switch (command) + switch (bb_command) { case BaseBoardCommand::GCAM_Reset: // Returns ID and dip switches { @@ -2523,7 +2523,7 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) } default: { - ERROR_LOG_FMT(SERIALINTERFACE, "Unknown SI command (0x{:08x})", (u32)command); + ERROR_LOG_FMT(SERIALINTERFACE, "Unknown SI command (0x{:08x})", (u32)bb_command); PanicAlertFmt("SI: Unknown command"); buffer_position = buffer_length; } From 93565eb8faf1d6b99e74785dfb3b0a0298809a1f Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 8 Feb 2026 11:29:40 -0600 Subject: [PATCH 59/71] AMMediaboard: Save the IP from AMMBCommandModifyMyIPaddr for later use. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 1aa450a39f..1add79d05c 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -153,6 +153,8 @@ static u8 s_network_buffer[512 * 1024]; static u8 s_allnet_buffer[4096]; static u8 s_allnet_settings[0x8500]; +static Common::IPAddress s_game_modified_ip_address; + // Fake loading the game to have a chance to enter test mode static u32 s_board_status = LoadingGameProgram; static u32 s_load_progress = 80; @@ -446,6 +448,8 @@ void Init() std::ranges::fill(s_allnet_buffer, 0); std::ranges::fill(s_allnet_settings, 0); + s_game_modified_ip_address = {}; + s_board_status = LoadingGameProgram; s_load_progress = 80; @@ -1069,6 +1073,9 @@ static void AMMBCommandModifyMyIPaddr(u32 parameter_offset, u32 network_buffer_b ip_address_offset, MAX_IPV4_STRING_LENGTH); NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: modifyMyIPaddr({})", ip_address_str); + + if (const auto parse_result = Common::StringToIPv4PortRange(ip_address_str)) + s_game_modified_ip_address = parse_result->first.ip_address; } static void FileWriteData(Memory::MemoryManager& memory, File::IOFile* file, u32 seek_pos, @@ -2011,6 +2018,8 @@ void DoState(PointerWrap& p) p.Do(s_allnet_buffer); p.Do(s_allnet_settings); + p.Do(s_game_modified_ip_address); + p.Do(s_board_status); p.Do(s_load_progress); From cf8573421cf67359c870915fbbbe967239e36aa0 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 8 Feb 2026 23:06:47 -0600 Subject: [PATCH 60/71] AMMediaboard: Clean up the defined memory ranges. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 88 +++++++++++------------- Source/Core/Core/HW/DVD/AMMediaboard.h | 8 ++- 2 files changed, 47 insertions(+), 49 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 1add79d05c..2fd1897783 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -75,6 +75,11 @@ static int WSAGetLastError() #endif +MediaBoardRange::MediaBoardRange(u32 start_, u32 size_, std::span buffer_) + : start{start_}, end{start_ + size_}, buffer{buffer_.data()}, buffer_size{buffer_.size()} +{ +} + namespace AMMediaboard { @@ -145,13 +150,13 @@ static File::IOFile s_dimm; static std::unique_ptr s_dimm_disc; -static u8 s_firmware[2 * 1024 * 1024]; -static u32 s_media_buffer_32[192]; -static u8* const s_media_buffer = reinterpret_cast(s_media_buffer_32); -static u8 s_network_command_buffer[0x4FFE00]; -static u8 s_network_buffer[512 * 1024]; -static u8 s_allnet_buffer[4096]; -static u8 s_allnet_settings[0x8500]; +static std::array s_firmware; +static std::array s_media_buffer_32; +static u8* const s_media_buffer = reinterpret_cast(s_media_buffer_32.data()); +static std::array s_network_command_buffer; +static std::array s_network_buffer; +static std::array s_allnet_buffer; +static std::array s_allnet_settings; static Common::IPAddress s_game_modified_ip_address; @@ -167,29 +172,19 @@ constexpr char s_allnet_reply[] = { "second=12&place_id=1234&setting=0x123®ion0=jap®ion_name0=japan®ion_name1=usa®ion_" "name2=asia®ion_name3=export&end"}; -static const MediaBoardRanges s_mediaboard_ranges[] = { - {DIMMCommandVersion1, 0x1F900040, s_media_buffer, sizeof(s_media_buffer_32), - DIMMCommandVersion1}, - {DIMMCommandVersion2, 0x84000060, s_media_buffer, sizeof(s_media_buffer_32), - DIMMCommandVersion2}, - {DIMMCommandVersion2_2, 0x89000220, s_media_buffer, sizeof(s_media_buffer_32), - DIMMCommandVersion2_2}, - {NetworkCommandAddress1, 0x1F801240, s_network_command_buffer, sizeof(s_network_command_buffer), - NetworkCommandAddress1}, - {NetworkCommandAddress2, 0x89060200, s_network_command_buffer, sizeof(s_network_command_buffer), - NetworkCommandAddress2}, - {NetworkBufferAddress1, 0x1FA10000, s_network_buffer, sizeof(s_network_buffer), - NetworkBufferAddress1}, - {NetworkBufferAddress2, 0x1FD10000, s_network_buffer, sizeof(s_network_buffer), - NetworkBufferAddress2}, - {NetworkBufferAddress3, 0x89150000, s_network_buffer, sizeof(s_network_buffer), - NetworkBufferAddress3}, - {NetworkBufferAddress4, 0x89240000, s_network_buffer, sizeof(s_network_buffer), - NetworkBufferAddress4}, - {NetworkBufferAddress5, 0x1FB10000, s_network_buffer, sizeof(s_network_buffer), - NetworkBufferAddress5}, - {AllNetSettings, 0x1F000000, s_allnet_settings, sizeof(s_allnet_settings), AllNetSettings}, - {AllNetBuffer, 0x89011000, s_allnet_buffer, sizeof(s_allnet_buffer), AllNetBuffer}, +static const MediaBoardRange s_mediaboard_ranges[] = { + {DIMMCommandVersion1, 0x40, Common::AsWritableU8Span(s_media_buffer_32)}, + {DIMMCommandVersion2, 0x60, Common::AsWritableU8Span(s_media_buffer_32)}, + {DIMMCommandVersion2_2, 0x220, Common::AsWritableU8Span(s_media_buffer_32)}, + {NetworkCommandAddress1, 0x1040, s_network_command_buffer}, + {NetworkCommandAddress2, 0x20000, s_network_command_buffer}, + {NetworkBufferAddress1, 0x10000, s_network_buffer}, + {NetworkBufferAddress2, 0x10000, s_network_buffer}, + {NetworkBufferAddress3, 0x50000, s_network_buffer}, + {NetworkBufferAddress4, 0xc0000, s_network_buffer}, + {NetworkBufferAddress5, 0x10000, s_network_buffer}, + {AllNetSettings, 0x8000, s_allnet_settings}, + {AllNetBuffer, 0x1000, s_allnet_buffer}, }; static const std::unordered_map s_game_map = { @@ -499,7 +494,7 @@ void Init() } const u64 length = std::min(sega_boot.GetSize(), sizeof(s_firmware)); - sega_boot.ReadBytes(s_firmware, length); + sega_boot.ReadBytes(s_firmware.data(), length); s_test_menu = true; } @@ -776,7 +771,7 @@ static void AMMBCommandRecv(u32 parameter_offset, u32 network_buffer_base) len = 0; } - int ret = recv(fd, reinterpret_cast(s_network_buffer + off), len, 0); + int ret = recv(fd, reinterpret_cast(s_network_buffer.data() + off), len, 0); const int err = WSAGetLastError(); DEBUG_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: recv( {}, 0x{:08x}, {} ):{} {}", fd, off, len, ret, err); @@ -805,7 +800,7 @@ static void AMMBCommandSend(u32 parameter_offset, u32 network_buffer_base) len = 0; } - const int ret = send(fd, reinterpret_cast(s_network_buffer + off), len, SEND_FLAGS); + const int ret = send(fd, reinterpret_cast(s_network_buffer.data() + off), len, SEND_FLAGS); const int err = WSAGetLastError(); DEBUG_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: send( {}({}), 0x{:08x}, {} ): {} {}", fd, @@ -1053,7 +1048,7 @@ static void AMMBCommandSetSockOpt(u32 parameter_offset, u32 network_buffer_base) return; } - const char* optval = reinterpret_cast(s_network_command_buffer + optval_offset); + const char* optval = reinterpret_cast(s_network_command_buffer.data() + optval_offset); // TODO: Ensure parameters are compatible with host's setsockopt const int ret = setsockopt(fd, level, optname, optval, optlen); @@ -1223,7 +1218,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; default: PrintMBBuffer(address, length); - PanicAlertFmtT("Unhandled Media Board Read:{0:08x}", offset); + PanicAlertFmtT("Unhandled Media Board Read: offset={0:08x} length={0:08x}", offset, length); break; } return 0; @@ -1277,8 +1272,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u if (offset >= range.start && offset < range.end) { DEBUG_LOG_FMT(AMMEDIABOARD, "GC-AM: Read MediaBoard ({:08x},{:08x},{:08x})", offset, - range.base_offset, length); - SafeCopyToEmu(memory, address, range.buffer, range.buffer_size, offset - range.base_offset, + range.start, length); + SafeCopyToEmu(memory, address, range.buffer, range.buffer_size, offset - range.start, length); PrintMBBuffer(address, length); return 0; @@ -1402,7 +1397,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; case AMMBCommand::InetAddr: { - const char* ip_address = reinterpret_cast(s_network_command_buffer); + const char* ip_address = reinterpret_cast(s_network_command_buffer.data()); // IP address shouldn't be longer than 15 // TODO: Shouldn't this look at 16 characters for lack of null-termination? @@ -1542,13 +1537,13 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u // Max GC disc offset if (offset >= 0x57058000) { - PanicAlertFmtT("Unhandled Media Board Read:{0:08x}", offset); + PanicAlertFmtT("Unhandled Media Board Read: offset={0:08x} length={0:08x}", offset, length); return 0; } if (s_firmware_map) { - if (!SafeCopyToEmu(memory, address, s_firmware, sizeof(s_firmware), offset, length)) + if (!SafeCopyToEmu(memory, address, s_firmware.data(), s_firmware.size(), offset, length)) { ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: Invalid firmware buffer range: offset={}, length={}", offset, length); @@ -1589,7 +1584,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u if (offset >= 0x00400000 && offset <= 0x600000) { const u32 fw_offset = offset - 0x00400000; - if (!SafeCopyFromEmu(memory, s_firmware, address, sizeof(s_firmware), fw_offset, length)) + if (!SafeCopyFromEmu(memory, s_firmware.data(), address, s_firmware.size(), fw_offset, + length)) { ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: Invalid firmware write: offset={}, length={}", fw_offset, length); @@ -1715,9 +1711,9 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u if (offset >= range.start && offset < range.end) { DEBUG_LOG_FMT(AMMEDIABOARD, "GC-AM: Write MediaBoard ({:08x},{:08x},{:08x})", offset, - range.base_offset, length); - SafeCopyFromEmu(memory, range.buffer, address, range.buffer_size, - offset - range.base_offset, length); + range.start, length); + SafeCopyFromEmu(memory, range.buffer, address, range.buffer_size, offset - range.start, + length); PrintMBBuffer(address, length); return 0; } @@ -1727,7 +1723,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u if (offset >= 0x57058000) { PrintMBBuffer(address, length); - PanicAlertFmtT("Unhandled Media Board Write:{0:08x}", offset); + PanicAlertFmtT("Unhandled Media Board Write: offset={0:08x} length={0:08x}", offset, length); } break; case AMMBDICommand::Execute: @@ -1882,7 +1878,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; } - const u8* data = s_network_buffer + (off + addr - NetworkBufferAddress2); + const u8* data = s_network_buffer.data() + (off + addr - NetworkBufferAddress2); for (u32 i = 0; i < 0x20; i += 0x10) { diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.h b/Source/Core/Core/HW/DVD/AMMediaboard.h index 8ca7c7d9fd..5079a7fdd3 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.h +++ b/Source/Core/Core/HW/DVD/AMMediaboard.h @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -64,13 +65,14 @@ class CPUThreadGuard; class System; } // namespace Core -struct MediaBoardRanges +struct MediaBoardRange { u32 start; u32 end; u8* buffer; - size_t buffer_size; - u32 base_offset; + std::size_t buffer_size; + + MediaBoardRange(u32 start_, u32 size_, std::span buffer_); }; namespace AMMediaboard From d893565b781a0a033ae2ff12962df436de695343 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 8 Feb 2026 11:37:38 -0600 Subject: [PATCH 61/71] AMMediaboard: IP override improvements and other cleanups. Overrides now also apply in reverse for accepted connections. IP Override UI now includes a description. Mario Kart Arcade GP 4x Multicabinet now works on one PC without any tricky IP configurations. Added AMMBCommandBind, NetDIMMBind, and some helper functions. Removed redundant settings. --- Source/Core/Core/Config/MainSettings.cpp | 36 +- Source/Core/Core/Config/MainSettings.h | 1 - Source/Core/Core/HW/DVD/AMMediaboard.cpp | 474 +++++++++++------- Source/Core/Core/HW/DVD/AMMediaboard.h | 9 +- .../TriforceBaseboardSettingsDialog.cpp | 82 +-- .../TriforceBaseboardSettingsDialog.h | 2 +- 6 files changed, 379 insertions(+), 225 deletions(-) diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index 6edcccf186..bc749b4b37 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -645,26 +645,40 @@ const std::array, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_VOLUM static std::string GetDefaultTriforceIPOverrides() { +#if defined(ANDROID) + return "0.0.0.0/0=127.0.0.1"; +#else constexpr std::string_view entries[] = { + + // Mario Kart Arcade GP 1 + 2 + // This config allows for 4x Multicabinet on the same PC. + "192.168.29.150:5000-5008=127.0.0.1:5000-5008 MarioKart Seat #1 Static", + "192.168.29.151:5000-5008=127.0.0.1:5010-5018 MarioKart Seat #2 Static", + "192.168.29.152:5000-5008=127.0.0.1:5020-5028 MarioKart Seat #3 Static", + "192.168.29.153:5000-5008=127.0.0.1:5030-5038 MarioKart Seat #4 Static", + // Ephemeral ports are constrained to differentiate incoming connections. + "192.168.29.150=127.0.0.1:50000-50999 MarioKart Seat #1 Ephemeral", + "192.168.29.151=127.0.0.1:51000-51999 MarioKart Seat #2 Ephemeral", + "192.168.29.152=127.0.0.1:52000-52999 MarioKart Seat #3 Ephemeral", + "192.168.29.153=127.0.0.1:53000-53999 MarioKart Seat #4 Ephemeral", + // The cameras. + "192.168.29.104-107=127.0.0.1 MarioKart namcam2", + // CyCraft Connect IP - "192.168.11.111=127.0.0.1", + "192.168.11.0/24=127.0.0.1 CyCraft", - // Key of Avalon - // Note: The server and client can't run on the same system, - // but it's replaced with 127.0.0.1 here just for reference. - "192.168.13.1=127.0.0.1", + // The Key of Avalon + // This config isn't usable as-is. It's just here for reference. + "192.168.13.0/24=127.0.0.1 The Key of Avalon", - // NAMCO Camera - "192.168.29.104-108=127.0.0.1", - - // All.Net Connect IP - "192.168.150.16=127.0.0.1", + // Sega ALL.Net + "192.168.150.0/24=127.0.0.1 ALL.Net", }; return fmt::format("{}", fmt::join(entries, ",")); +#endif } -const Info MAIN_TRIFORCE_BIND_IP{{System::Main, "Core", "TriforceBindIP"}, "0.0.0.0"}; const Info MAIN_TRIFORCE_IP_OVERRIDES{{System::Main, "Core", "TriforceIPOverrides"}, GetDefaultTriforceIPOverrides()}; diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index ad28ca9119..dfbf166758 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -386,7 +386,6 @@ extern const std::array, EMULATED_LOGITECH_MIC_COUNT> extern const std::array, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_MUTED; extern const std::array, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_VOLUME_MODIFIER; -extern const Info MAIN_TRIFORCE_BIND_IP; extern const Info MAIN_TRIFORCE_IP_OVERRIDES; // GameCube path utility functions diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 2fd1897783..4a79a01252 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -514,95 +515,64 @@ static int PlatformPoll(std::span pfds, std::chrono::milliseconds tim #endif } -static GuestSocket NetDIMMAccept(GuestSocket guest_socket, sockaddr* addr, socklen_t* len) +std::optional ParseIPOverride(std::string_view str) { - const auto host_socket = GetHostSocket(guest_socket); - WSAPOLLFD pfds[1]{{.fd = host_socket, .events = POLLIN}}; - - // FYI: Currently using a 0ms timeout to make accept calls always non-blocking. - constexpr auto timeout = std::chrono::milliseconds{0}; - - const int result = PlatformPoll(pfds, timeout); - - if (result > 0 && (pfds[0].revents & POLLIN) != 0) - { - const auto client_sock = accept_(host_socket, addr, len); - if (client_sock == INVALID_GUEST_SOCKET) - { - ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: accept() failed in NetDIMMAccept ({})", - Common::StrNetworkError()); - s_last_error = SOCKET_ERROR; - return INVALID_GUEST_SOCKET; - } - s_last_error = SSC_SUCCESS; - return client_sock; - } - - if (result == 0) - { - // Timeout - s_last_error = SSC_EWOULDBLOCK; - } - else - { - ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: poll() failed in NetDIMMAccept ({})", - Common::StrNetworkError()); - s_last_error = SOCKET_ERROR; - } - return INVALID_GUEST_SOCKET; -} - -std::optional> ParseIPOverride(std::string_view str) -{ - // Ignore everything after a space. Future proofing to allow for a comment/description string. + // Everything after a space is the description. const auto ip_pair_str = std::string_view{str.begin(), std::ranges::find(str, ' ')}; const auto parts = SplitStringIntoArray<2>(ip_pair_str, '='); - if (parts.has_value()) - return std::make_pair((*parts)[0], (*parts)[1]); + if (!parts.has_value()) + return std::nullopt; - return std::nullopt; + const bool have_description = ip_pair_str.size() != str.size(); + + return ParsedIPOverride{ + .original = (*parts)[0], + .replacement = (*parts)[1], + .description = have_description ? str.substr(ip_pair_str.size() + 1) : std::string_view{}, + }; } struct IPAddressOverride { - Common::IPv4PortRange match; + Common::IPv4PortRange original; Common::IPv4PortRange replacement; // Caller should check if it matches first! Common::IPv4Port ApplyOverride(Common::IPv4Port subject) const { + // This logic could probably be better. + // Ranges of different sizes will be weird in general. + const auto replacement_first_ip_u32 = replacement.first.GetIPAddressValue(); const auto ip_count = 1u + u64(replacement.last.GetIPAddressValue()) - replacement_first_ip_u32; const auto result_ip = u32(replacement_first_ip_u32 + - ((subject.GetIPAddressValue() - match.first.GetIPAddressValue()) % ip_count)); + ((subject.GetIPAddressValue() - original.first.GetIPAddressValue()) % ip_count)); - Common::IPv4Port result{ - .ip_address = std::bit_cast(Common::BigEndianValue(result_ip)), - }; + subject.ip_address = std::bit_cast(Common::BigEndianValue{result_ip}); const auto replacement_first_port_u16 = replacement.first.GetPortValue(); const auto port_count = 1u + u32(replacement.last.GetPortValue()) - replacement_first_port_u16; - if (port_count == 65536) + // If the replacement includes all ports then we don't alter the port. + // This allows "10.0.0.1:80-88=10.0.0.2" to have the expected behavior. + if (port_count != 65536u) { - // If the replacement includes all ports then don't alter the port. - // This allows "1.1.1.1:80=2.2.2.2" to do the obvious thing. - // This logic could probably be better. - // Ranges of different sizes will be weird in general. - - result.port = subject.port; - } - else - { - const auto result_port = + const auto result_port_u16 = u16(replacement_first_port_u16 + - ((subject.GetPortValue() - match.first.GetPortValue()) % port_count)); - result.port = std::bit_cast(Common::BigEndianValue(result_port)); + ((subject.GetPortValue() - original.first.GetPortValue()) % port_count)); + subject.port = std::bit_cast(Common::BigEndianValue{result_port_u16}); } - return result; + return subject; + } + + Common::IPv4Port ReverseOverride(Common::IPv4Port subject) const + { + // Low effort implementation.. + return IPAddressOverride{.original = replacement, .replacement = original}.ApplyOverride( + subject); } }; @@ -618,12 +588,12 @@ static IPOverrides GetIPOverrides() const auto parts = ParseIPOverride(ip_pair_str); if (parts.has_value()) { - const auto match = Common::StringToIPv4PortRange(parts->first); - const auto replacement = Common::StringToIPv4PortRange(parts->second); + const auto original = Common::StringToIPv4PortRange(parts->original); + const auto replacement = Common::StringToIPv4PortRange(parts->replacement); - if (match.has_value() && replacement.has_value()) + if (original.has_value() && replacement.has_value()) { - result.emplace_back(*match, *replacement); + result.emplace_back(*original, *replacement); continue; } @@ -634,18 +604,62 @@ static IPOverrides GetIPOverrides() return result; } -static std::optional GetAdjustedIPv4PortFromConfig(Common::IPv4Port subject) +static std::optional AdjustIPv4PortFromConfig(Common::IPv4Port subject) { // TODO: We should parse this elsewhere to avoid repeated string manipulations. for (auto&& override : GetIPOverrides()) { - if (override.match.IsMatch(subject)) + if (override.original.IsMatch(subject)) return override.ApplyOverride(subject); } return std::nullopt; } +static std::optional ReverseAdjustIPv4PortFromConfig(Common::IPv4Port subject) +{ + // TODO: We should parse this elsewhere to avoid repeated string manipulations. + for (auto&& override : GetIPOverrides()) + { + if (override.replacement.IsMatch(subject)) + return override.ReverseOverride(subject); + } + + return std::nullopt; +} + +// Ports are in host byte order. +static bool BindEphemeralPort(SOCKET host_socket, Common::IPAddress ip_address, + u16 first_port_value, u16 last_port_value, u32 attempt_count) +{ + std::mt19937 rng(u32(Clock::now().time_since_epoch().count())); + std::uniform_int_distribution port_distribution{first_port_value, last_port_value}; + + sockaddr_in addr = { + .sin_family = AF_INET, + .sin_addr = std::bit_cast(ip_address), + }; + + while (attempt_count-- != 0) + { + const u16 port_value = port_distribution(rng); + + addr.sin_port = htons(port_value); + + const auto bind_result = bind(host_socket, reinterpret_cast(&addr), sizeof(addr)); + const int bind_err = WSAGetLastError(); + + INFO_LOG_FMT(AMMEDIABOARD, "BindEphemeralPort: bind ({}:{}) = {} ({})", + Common::IPAddressToString(ip_address), port_value, bind_result, + Common::DecodeNetworkError(bind_err)); + + if (bind_result == 0) + return true; + } + + return false; +} + static s32 NetDIMMConnect(GuestSocket guest_socket, const GuestSocketAddress& guest_addr) { INFO_LOG_FMT(AMMEDIABOARD, "NetDIMMConnect: {}:{}", @@ -655,12 +669,13 @@ static s32 NetDIMMConnect(GuestSocket guest_socket, const GuestSocketAddress& gu .sin_family = guest_addr.ip_family, }; - const auto adjusted_ipv4port = - GetAdjustedIPv4PortFromConfig({guest_addr.ip_address, guest_addr.port}); + // Adjust destination IP and port. + const auto adjusted_ipv4port = AdjustIPv4PortFromConfig({guest_addr.ip_address, guest_addr.port}); if (adjusted_ipv4port.has_value()) { addr.sin_addr = std::bit_cast(adjusted_ipv4port->ip_address); addr.sin_port = adjusted_ipv4port->port; + INFO_LOG_FMT(AMMEDIABOARD, "NetDIMMConnect: Overriding to: {}:{}", Common::IPAddressToString(adjusted_ipv4port->ip_address), ntohs(adjusted_ipv4port->port)); @@ -673,6 +688,34 @@ static s32 NetDIMMConnect(GuestSocket guest_socket, const GuestSocketAddress& gu const auto host_socket = GetHostSocket(guest_socket); + // See if we have an override for the game modified IP. + // If so, adjust the source IP by binding the socket. + const auto adjusted_source_ipv4port = AdjustIPv4PortFromConfig({s_game_modified_ip_address, 0}); + if (adjusted_source_ipv4port.has_value()) + { + // FYI: We don't handle the situation if games bind outgoing TCP themselves. + // But I think that's unlikely. + + const u16 first_port_value = adjusted_source_ipv4port->GetPortValue(); + + // If port zero is included then we don't care about the port number. + const bool use_any_port = first_port_value == 0; + const u32 attempt_count = use_any_port ? 1 : 10; + + // TODO: Handle the range properly. AdjustIPv4PortFromConfig should return a port range. + // This magic 999 is here just to match our default config.. + const u16 last_port_value = use_any_port ? 0 : first_port_value + 999; + + const auto bind_result = BindEphemeralPort(host_socket, adjusted_source_ipv4port->ip_address, + first_port_value, last_port_value, attempt_count); + + if (!bind_result) + { + s_last_error = SOCKET_ERROR; + return SOCKET_ERROR; + } + } + // Set socket to non-blocking { u_long val = 1; @@ -752,6 +795,156 @@ static s32 NetDIMMConnect(GuestSocket guest_socket, const GuestSocketAddress& gu return SOCKET_ERROR; } +static GuestSocket NetDIMMAccept(GuestSocket guest_socket, u8* guest_addr_ptr, + u8* guest_addrlen_ptr) +{ + // Either both parameters should be provided, or neither. + if ((guest_addr_ptr != nullptr) != (guest_addrlen_ptr != nullptr)) + { + ERROR_LOG_FMT(AMMEDIABOARD_NET, "NetDIMMAccept: bad parmeters"); + + // TODO: Not hardware tested. + s_last_error = SSC_EFAULT; + return INVALID_GUEST_SOCKET; + } + + const auto host_socket = GetHostSocket(guest_socket); + WSAPOLLFD pfds[1]{{.fd = host_socket, .events = POLLIN}}; + + // FYI: Currently using a 0ms timeout to make accept calls always non-blocking. + constexpr auto timeout = std::chrono::milliseconds{0}; + + DEBUG_LOG_FMT(AMMEDIABOARD, "NetDIMMAccept: {}({})", host_socket, int(guest_socket)); + + const int poll_result = PlatformPoll(pfds, timeout); + + if (poll_result < 0) [[unlikely]] + { + // Poll failure. + ERROR_LOG_FMT(AMMEDIABOARD, "NetDIMMAccept: PlatformPoll: {}", Common::StrNetworkError()); + + s_last_error = SOCKET_ERROR; + return INVALID_GUEST_SOCKET; + } + + if ((pfds[0].revents & POLLIN) == 0) + { + // Timeout. + DEBUG_LOG_FMT(AMMEDIABOARD, "NetDIMMAccept: Timeout."); + + s_last_error = SSC_EWOULDBLOCK; + return INVALID_GUEST_SOCKET; + } + + sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + const auto client_sock = accept_(host_socket, reinterpret_cast(&addr), &addrlen); + + if (client_sock == INVALID_GUEST_SOCKET) + { + ERROR_LOG_FMT(AMMEDIABOARD, "AMMBCommandAccept: accept: ({})", Common::StrNetworkError()); + s_last_error = SOCKET_ERROR; + return INVALID_GUEST_SOCKET; + } + + s_last_error = SSC_SUCCESS; + + NOTICE_LOG_FMT(AMMEDIABOARD, "AMMBCommandAccept: {}:{}", + Common::IPAddressToString(std::bit_cast(addr.sin_addr)), + ntohs(addr.sin_port)); + + if (guest_addr_ptr == nullptr) + return client_sock; + + GuestSocketAddress guest_addr{ + .ip_family = u8(addr.sin_family), + .port = addr.sin_port, + .ip_address = std::bit_cast(addr.sin_addr), + }; + + if (const auto adjusted_ipv4port = + ReverseAdjustIPv4PortFromConfig({guest_addr.ip_address, guest_addr.port})) + { + guest_addr.ip_address = adjusted_ipv4port->ip_address; + guest_addr.port = adjusted_ipv4port->port; + + NOTICE_LOG_FMT(AMMEDIABOARD, "AMMBCommandAccept: Translating result to: {}:{}", + Common::IPAddressToString(guest_addr.ip_address), ntohs(guest_addr.port)); + } + + const auto write_size = + std::min(Common::BitCastPtr(guest_addrlen_ptr), sizeof(guest_addr)); + + // Write out the addr. + std::memcpy(guest_addr_ptr, &guest_addr, write_size); + + // Write out the addrlen. + *guest_addrlen_ptr = sizeof(guest_addr); + + return client_sock; +} + +static Common::IPv4Port GetAdjustedBindIPv4Port(Common::IPv4Port socket_addr) +{ + auto considered_ipv4 = socket_addr; + + if (std::bit_cast(considered_ipv4.ip_address) == INADDR_ANY) + { + // Because the game is binding to "0.0.0.0", + // use the "game modified" IP for override purposes. + // If no override applies, then we still bind "0.0.0.0". + considered_ipv4.ip_address = s_game_modified_ip_address; + INFO_LOG_FMT(AMMEDIABOARD, "GetAdjustedBindIPv4Port: Considering game modified IP: {}", + Common::IPAddressToString(s_game_modified_ip_address)); + } + + if (const auto adjusted_ipv4 = AdjustIPv4PortFromConfig(considered_ipv4)) + { + socket_addr = *adjusted_ipv4; + INFO_LOG_FMT(AMMEDIABOARD, "GetAdjustedBindIPv4Port: Overriding to: {}:{}", + Common::IPAddressToString(socket_addr.ip_address), ntohs(socket_addr.port)); + } + + return socket_addr; +} + +static u32 NetDIMMBind(GuestSocket guest_socket, const GuestSocketAddress& guest_addr) +{ + const auto host_socket = GetHostSocket(guest_socket); + + NOTICE_LOG_FMT(AMMEDIABOARD, "NetDIMMBind: {}({}) {}, {}, {}:{}", host_socket, int(guest_socket), + guest_addr.unknown_value, guest_addr.ip_family, + Common::IPAddressToString(guest_addr.ip_address), ntohs(guest_addr.port)); + + const auto adjusted_ipv4port = GetAdjustedBindIPv4Port({guest_addr.ip_address, guest_addr.port}); + + sockaddr_in addr{ + .sin_family = guest_addr.ip_family, + .sin_port = adjusted_ipv4port.port, + .sin_addr = std::bit_cast(adjusted_ipv4port.ip_address), + }; + + const int bind_result = bind(host_socket, reinterpret_cast(&addr), sizeof(addr)); + const int err = WSAGetLastError(); + + INFO_LOG_FMT(AMMEDIABOARD_NET, "NetDIMMBind: bind( {}({}), ({},{}:{}) ):{}", host_socket, + u32(guest_socket), addr.sin_family, + Common::IPAddressToString(adjusted_ipv4port.ip_address), + Common::swap16(adjusted_ipv4port.port), bind_result); + + if (bind_result < 0) + { + const auto* const err_msg = Common::DecodeNetworkError(err); + ERROR_LOG_FMT(AMMEDIABOARD, "NetDIMMBind bind() = {} ({})", err, err_msg); + + PanicAlertFmt("Failed to bind socket {}:{}\nError: {} ({})", + Common::IPAddressToString(adjusted_ipv4port.ip_address), + ntohs(adjusted_ipv4port.port), err, err_msg); + } + + return bind_result; +} + static void AMMBCommandRecv(u32 parameter_offset, u32 network_buffer_base) { const auto fd = GetHostSocket(GuestSocket(s_media_buffer_32[parameter_offset])); @@ -873,43 +1066,44 @@ static void AMMBCommandAccept(u32 parameter_offset, u32 network_buffer_base) const u32 addr_off = s_media_buffer_32[parameter_offset + 1]; const u32 addrlen_off = s_media_buffer_32[parameter_offset + 2]; - u32 ret{}; + auto* const addrlen_ptr = + GetSafePtr(s_network_command_buffer, network_buffer_base, addrlen_off, sizeof(u32)); - // Either both parameters should be provided, or neither. - if ((addr_off != 0) != (addrlen_off != 0)) + auto* const addr_ptr = (addrlen_ptr == nullptr) ? + nullptr : + GetSafePtr(s_network_command_buffer, network_buffer_base, addr_off, + Common::BitCastPtr(addrlen_ptr)); + + const auto accept_result = NetDIMMAccept(guest_socket, addr_ptr, addrlen_ptr); + + s_media_buffer_32[1] = u32(accept_result); +} + +static void AMMBCommandBind() +{ + const auto guest_socket = GuestSocket(s_media_buffer_32[2]); + const u32 addr_offset = s_media_buffer_32[3]; + const u32 len = s_media_buffer_32[4]; + + GuestSocketAddress guest_addr; + + if (len != sizeof(guest_addr)) { - WARN_LOG_FMT(AMMEDIABOARD_NET, "AMMBCommandAccept: Unexpected parameters: {}, {}, {}", - u32(guest_socket), addr_off, addrlen_off); - - // TODO: Not hardware tested. - s_last_error = SSC_EFAULT; - ret = SOCKET_ERROR; - } - else - { - sockaddr addr; - socklen_t addrlen = sizeof(addr); - ret = u32(NetDIMMAccept(guest_socket, &addr, &addrlen)); - - INFO_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: accept( {} ):{}", u32(guest_socket), int(ret)); - - auto* const addrlen_ptr = - GetSafePtr(s_network_command_buffer, network_buffer_base, addrlen_off, sizeof(u32)); - if (addrlen_ptr != nullptr) - { - // Read the buffer size. - addrlen = std::min(addrlen, Common::BitCastPtr(addrlen_ptr)); - // Write out the proper length. - Common::BitCastPtr(addrlen_ptr) = sizeof(addr); - - auto* const addr_ptr = - GetSafePtr(s_network_command_buffer, network_buffer_base, addr_off, addrlen); - if (addr_ptr != nullptr) - memcpy(addr_ptr, &addr, addrlen); - } + ERROR_LOG_FMT(AMMEDIABOARD_NET, "AMMBCommandBind: Unexpected length: {}", len); + return; } - s_media_buffer_32[1] = ret; + const auto* addr_ptr = + GetSafePtr(s_network_command_buffer, NetworkCommandAddress2, addr_offset, sizeof(guest_addr)); + if (addr_ptr == nullptr) + return; + + memcpy(&guest_addr, addr_ptr, sizeof(guest_addr)); + + const auto bind_result = NetDIMMBind(guest_socket, guest_addr); + + s_media_buffer_32[1] = bind_result; + s_last_error = SSC_SUCCESS; } // Expects a pointer to a GuestFdSet or nullptr. @@ -1315,80 +1509,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u AMMBCommandAccept(2, NetworkCommandAddress2); break; case AMMBCommand::Bind: - { - const auto guest_socket = GuestSocket(s_media_buffer_32[2]); - const auto host_socket = GetHostSocket(guest_socket); - const u32 addr_offset = s_media_buffer_32[3]; - const u32 len = s_media_buffer_32[4]; - - GuestSocketAddress guest_addr; - - if (len != sizeof(guest_addr)) - { - ERROR_LOG_FMT(AMMEDIABOARD_NET, "AMMBCommand::Bind: Unexpected length: {}", len); - break; - } - - const auto* addr_ptr = GetSafePtr(s_network_command_buffer, NetworkCommandAddress2, - addr_offset, sizeof(guest_addr)); - if (addr_ptr == nullptr) - break; - - memcpy(&guest_addr, addr_ptr, sizeof(guest_addr)); - - // Triforce titles typically rely on hardcoded IP addresses. - // Our config allows this to be overriden. - - NOTICE_LOG_FMT(AMMEDIABOARD, "AMMBCommand::Bind: {}, {}, {}:{}", guest_addr.struct_size, - guest_addr.ip_family, Common::IPAddressToString(guest_addr.ip_address), - ntohs(guest_addr.port)); - - // Apply "BindIP" if it's valid. - const auto override_bind_ip = inet_addr(Config::Get(Config::MAIN_TRIFORCE_BIND_IP).c_str()); - if (override_bind_ip != INADDR_NONE) - { - guest_addr.ip_address = std::bit_cast(override_bind_ip); - INFO_LOG_FMT(AMMEDIABOARD, "AMMBCommand::Bind: Overriding IP to: {}", - Common::IPAddressToString(guest_addr.ip_address)); - } - - // Apply "IPOverrides" in case config wants ports adjusted. - const auto adjusted_ipv4port = - GetAdjustedIPv4PortFromConfig({guest_addr.ip_address, guest_addr.port}); - if (adjusted_ipv4port.has_value()) - { - guest_addr.ip_address = adjusted_ipv4port->ip_address; - guest_addr.port = adjusted_ipv4port->port; - INFO_LOG_FMT(AMMEDIABOARD, "AMMBCommand::Bind: Overriding to: {}:{}", - Common::IPAddressToString(guest_addr.ip_address), ntohs(guest_addr.port)); - } - - sockaddr_in addr{ - .sin_family = guest_addr.ip_family, - .sin_port = guest_addr.port, - .sin_addr = std::bit_cast(guest_addr.ip_address), - }; - - const int bind_result = - bind(host_socket, reinterpret_cast(&addr), sizeof(addr)); - const int err = WSAGetLastError(); - - INFO_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: bind( {}({}), ({},{}:{}) ):{} ({})", host_socket, - u32(guest_socket), addr.sin_family, inet_ntoa(addr.sin_addr), - Common::swap16(addr.sin_port), bind_result, err); - - if (bind_result < 0) - { - const auto err_msg = Common::DecodeNetworkError(err); - PanicAlertFmt("Failed to bind socket (error {}: {})", err, err_msg); - ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: AMMBCommand::Bind failed, bind() = {} ({})", err, - err_msg); - } - - s_media_buffer_32[1] = bind_result; - s_last_error = SSC_SUCCESS; + AMMBCommandBind(); break; - } case AMMBCommand::Closesocket: AMMBCommandClosesocket(2); break; diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.h b/Source/Core/Core/HW/DVD/AMMediaboard.h index 5079a7fdd3..937d2b9228 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.h +++ b/Source/Core/Core/HW/DVD/AMMediaboard.h @@ -7,7 +7,6 @@ #include #include #include -#include #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" @@ -243,6 +242,12 @@ bool GetTestMenu(); void Shutdown(); void DoState(PointerWrap& p); -std::optional> ParseIPOverride(std::string_view str); +struct ParsedIPOverride +{ + std::string_view original; + std::string_view replacement; + std::string_view description; +}; +std::optional ParseIPOverride(std::string_view str); }; // namespace AMMediaboard diff --git a/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.cpp b/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.cpp index 23d300dc43..5ae697c148 100644 --- a/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.cpp +++ b/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.cpp @@ -4,7 +4,6 @@ #include "DolphinQt/Settings/TriforceBaseboardSettingsDialog.h" #include -#include #include #include #include @@ -22,30 +21,28 @@ #include "DolphinQt/QtUtils/QtUtils.h" +static constexpr int COLUMN_EMULATED = 0; +static constexpr int COLUMN_REAL = 1; +static constexpr int COLUMN_DESCRIPTION = 2; + TriforceBaseboardSettingsDialog::TriforceBaseboardSettingsDialog(QWidget* parent) : QDialog{parent} { setWindowTitle(tr("Triforce Baseboard")); - connect(this, &TriforceBaseboardSettingsDialog::accepted, this, - &TriforceBaseboardSettingsDialog::SaveConfig); - auto* const dialog_layout = new QVBoxLayout{this}; auto* const ip_override_group = new QGroupBox{tr("IP Address Overrides")}; dialog_layout->addWidget(ip_override_group); - auto* const ip_override_layout = new QFormLayout{ip_override_group}; - - m_bind_ip_edit = new QLineEdit; - ip_override_layout->addRow(tr("Bind IP: "), m_bind_ip_edit); + auto* const ip_override_layout = new QVBoxLayout{ip_override_group}; m_ip_overrides_table = new QTableWidget; - ip_override_layout->addRow(tr("IP Overrides: "), m_ip_overrides_table); + ip_override_layout->addWidget(m_ip_overrides_table); + m_ip_overrides_table->setColumnCount(3); + m_ip_overrides_table->setHorizontalHeaderLabels({tr("Emulated"), tr("Real"), tr("Description")}); m_ip_overrides_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); - - m_ip_overrides_table->setColumnCount(2); - m_ip_overrides_table->setHorizontalHeaderLabels({tr("Match"), tr("Replacement")}); + m_ip_overrides_table->setSizeAdjustPolicy(QTableWidget::AdjustToContents); m_ip_overrides_table->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed); @@ -57,11 +54,18 @@ TriforceBaseboardSettingsDialog::TriforceBaseboardSettingsDialog(QWidget* parent connect(load_default_button, &QPushButton::clicked, this, &TriforceBaseboardSettingsDialog::LoadDefault); + auto* const clear_button = new QPushButton(tr("Clear")); + connect(clear_button, &QPushButton::clicked, this, &TriforceBaseboardSettingsDialog::OnClear); + button_box->addButton(load_default_button, QDialogButtonBox::ActionRole); + button_box->addButton(clear_button, QDialogButtonBox::ActionRole); connect(button_box, &QDialogButtonBox::accepted, this, &TriforceBaseboardSettingsDialog::accept); connect(button_box, &QDialogButtonBox::rejected, this, &TriforceBaseboardSettingsDialog::reject); + connect(this, &TriforceBaseboardSettingsDialog::accepted, this, + &TriforceBaseboardSettingsDialog::SaveConfig); + connect(m_ip_overrides_table, &QTableWidget::itemChanged, this, [this](QTableWidgetItem* item) { const int row_count = m_ip_overrides_table->rowCount(); const bool is_last_row = item->row() == row_count - 1; @@ -70,12 +74,11 @@ TriforceBaseboardSettingsDialog::TriforceBaseboardSettingsDialog(QWidget* parent if (!item->text().isEmpty()) m_ip_overrides_table->insertRow(row_count); } - else + else if (item->column() != COLUMN_DESCRIPTION && item->text().isEmpty()) { // Just remove inner rows that have text erased. // This UX could be less weird, but it's good enough for now. - if (item->text().isEmpty()) - m_ip_overrides_table->removeRow(item->row()); + m_ip_overrides_table->removeRow(item->row()); } }); @@ -86,8 +89,6 @@ TriforceBaseboardSettingsDialog::TriforceBaseboardSettingsDialog(QWidget* parent void TriforceBaseboardSettingsDialog::LoadConfig() { - m_bind_ip_edit->setText(QString::fromUtf8(Config::Get(Config::MAIN_TRIFORCE_BIND_IP))); - m_ip_overrides_table->setRowCount(0); // Add the final empty row. @@ -99,8 +100,8 @@ void TriforceBaseboardSettingsDialog::LoadConfig() for (auto&& ip_pair : ip_overrides_str | std::views::split(',')) { const auto ip_pair_str = std::string_view{ip_pair}; - const auto parts = AMMediaboard::ParseIPOverride(ip_pair_str); - if (!parts.has_value()) + const auto parsed = AMMediaboard::ParseIPOverride(ip_pair_str); + if (!parsed.has_value()) { ERROR_LOG_FMT(COMMON, "Bad IP pair string: {}", ip_pair_str); continue; @@ -108,8 +109,12 @@ void TriforceBaseboardSettingsDialog::LoadConfig() m_ip_overrides_table->insertRow(row); - m_ip_overrides_table->setItem(row, 0, new QTableWidgetItem(QString::fromUtf8(parts->first))); - m_ip_overrides_table->setItem(row, 1, new QTableWidgetItem(QString::fromUtf8(parts->second))); + m_ip_overrides_table->setItem(row, COLUMN_EMULATED, + new QTableWidgetItem(QString::fromUtf8(parsed->original))); + m_ip_overrides_table->setItem(row, COLUMN_REAL, + new QTableWidgetItem(QString::fromUtf8(parsed->replacement))); + m_ip_overrides_table->setItem(row, COLUMN_DESCRIPTION, + new QTableWidgetItem(QString::fromUtf8(parsed->description))); ++row; } @@ -117,29 +122,32 @@ void TriforceBaseboardSettingsDialog::LoadConfig() void TriforceBaseboardSettingsDialog::SaveConfig() { - Config::SetBaseOrCurrent(Config::MAIN_TRIFORCE_BIND_IP, m_bind_ip_edit->text().toStdString()); - // Ignore our empty row. const int row_count = m_ip_overrides_table->rowCount() - 1; std::vector replacements(row_count); for (int row = 0; row < row_count; ++row) { + auto* const original_item = m_ip_overrides_table->item(row, COLUMN_EMULATED); + auto* const replacement_item = m_ip_overrides_table->item(row, COLUMN_REAL); + // Skip incomplete rows. - if (m_ip_overrides_table->item(row, 0) == nullptr || - m_ip_overrides_table->item(row, 1) == nullptr) - { + if (original_item == nullptr || replacement_item == nullptr) continue; - } - replacements[row] = - std::string(StripWhitespace(m_ip_overrides_table->item(row, 0)->text().toStdString())) + - "=" + - std::string(StripWhitespace(m_ip_overrides_table->item(row, 1)->text().toStdString())); + replacements[row] = fmt::format("{}={}", StripWhitespace(original_item->text().toStdString()), + replacement_item->text().toStdString()); + + auto* const description_item = m_ip_overrides_table->item(row, COLUMN_DESCRIPTION); + if (description_item == nullptr) + continue; + + const auto description = description_item->text().toStdString(); + + if (!description.empty()) + (replacements[row] += ' ') += description; } - // TODO: Ideally we'd remove duplicates based on the first IP. - Config::SetBaseOrCurrent(Config::MAIN_TRIFORCE_IP_OVERRIDES, fmt::format("{}", fmt::join(replacements, ","))); } @@ -147,8 +155,14 @@ void TriforceBaseboardSettingsDialog::SaveConfig() void TriforceBaseboardSettingsDialog::LoadDefault() { // This alters the config before "OK" is pressed. Bad UX.. - Config::SetBaseOrCurrent(Config::MAIN_TRIFORCE_BIND_IP, Config::DefaultState{}); + Config::SetBaseOrCurrent(Config::MAIN_TRIFORCE_IP_OVERRIDES, Config::DefaultState{}); LoadConfig(); } + +void TriforceBaseboardSettingsDialog::OnClear() +{ + Config::SetBaseOrCurrent(Config::MAIN_TRIFORCE_IP_OVERRIDES, ""); + LoadConfig(); +} diff --git a/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.h b/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.h index e472739f71..f57d562da1 100644 --- a/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.h +++ b/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.h @@ -20,7 +20,7 @@ private: void SaveConfig(); void LoadDefault(); + void OnClear(); - QLineEdit* m_bind_ip_edit; QTableWidget* m_ip_overrides_table; }; From f657b3252149c6e1854aa82a5378a9d3a97f3978 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Tue, 10 Feb 2026 01:56:00 -0600 Subject: [PATCH 62/71] HW: Rewrite MagCard features of SI_DeviceAMBaseboard in new MagneticCardReader class. A huge thank you goes to GXTX and https://github.com/GXTX/YACardEmu which this code is based on. --- Externals/licenses.md | 2 + Source/Core/Core/CMakeLists.txt | 6 + Source/Core/Core/HW/MagCard/C1231BR.cpp | 82 ++ Source/Core/Core/HW/MagCard/C1231BR.h | 37 + Source/Core/Core/HW/MagCard/C1231LR.cpp | 27 + Source/Core/Core/HW/MagCard/C1231LR.h | 26 + .../Core/HW/MagCard/MagneticCardReader.cpp | 1002 +++++++++++++++++ .../Core/Core/HW/MagCard/MagneticCardReader.h | 207 ++++ .../Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp | 573 +--------- Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h | 23 +- Source/Core/DolphinLib.props | 6 + 11 files changed, 1462 insertions(+), 529 deletions(-) create mode 100644 Source/Core/Core/HW/MagCard/C1231BR.cpp create mode 100644 Source/Core/Core/HW/MagCard/C1231BR.h create mode 100644 Source/Core/Core/HW/MagCard/C1231LR.cpp create mode 100644 Source/Core/Core/HW/MagCard/C1231LR.h create mode 100644 Source/Core/Core/HW/MagCard/MagneticCardReader.cpp create mode 100644 Source/Core/Core/HW/MagCard/MagneticCardReader.h diff --git a/Externals/licenses.md b/Externals/licenses.md index 727a41e2a1..7aed5313ac 100644 --- a/Externals/licenses.md +++ b/Externals/licenses.md @@ -76,6 +76,8 @@ Dolphin includes or links code of the following third-party software projects: [MIT](https://github.com/microsoft/wil/blob/master/LICENSE) - [xxHash](https://github.com/Cyan4973/xxHash): [BSD 2-Clause](https://github.com/Cyan4973/xxHash/blob/master/LICENSE) +- [YACardEmu](https://github.com/GXTX/YACardEmu) + [GPLv2+](https://github.com/GXTX/YACardEmu/blob/master/license.txt) - [zlib-ng](https://github.com/zlib-ng/zlib-ng): [zlib license](https://github.com/zlib-ng/zlib-ng/blob/develop/LICENSE.md) - [Zstandard](https://facebook.github.io/zstd/): diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index e0cad85863..e2ae3e9919 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -254,6 +254,12 @@ add_library(core HW/HSP/HSP_DeviceNull.h HW/HW.cpp HW/HW.h + HW/MagCard/C1231BR.cpp + HW/MagCard/C1231BR.h + HW/MagCard/C1231LR.cpp + HW/MagCard/C1231LR.h + HW/MagCard/MagneticCardReader.cpp + HW/MagCard/MagneticCardReader.h HW/Memmap.cpp HW/Memmap.h HW/MemoryInterface.cpp diff --git a/Source/Core/Core/HW/MagCard/C1231BR.cpp b/Source/Core/Core/HW/MagCard/C1231BR.cpp new file mode 100644 index 0000000000..de76df1839 --- /dev/null +++ b/Source/Core/Core/HW/MagCard/C1231BR.cpp @@ -0,0 +1,82 @@ +// Copyright 2026 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on: https://github.com/GXTX/YACardEmu +// Copyright (C) 2020-2023 wutno (https://github.com/GXTX) +// Copyright (C) 2022-2023 tugpoat (https://github.com/tugpoat) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/HW/MagCard/C1231BR.h" + +namespace MagCard +{ + +void C1231BR::Command_10_Initialize() +{ + // TODO: Not hardware tested. + if (!m_current_packet.empty()) + { + if (InitMode(m_current_packet[0]) == InitMode::EjectAfter) + { + m_is_shutter_open = true; + MoveCard(R::ENTRY_SLOT); + } + } + + // m_status.SoftReset(); // TODO: Needed ? + FinishCommand(); +} + +void C1231BR::Command_D0_ControlShutter() +{ + enum class Action : u8 + { + Close = 0x30, + Open = 0x31, + }; + + if (m_current_packet.empty()) + { + SetPError(P::SYSTEM_ERR); + FinishCommand(); + return; + } + + m_is_shutter_open = Action(m_current_packet[0]) == Action::Open; + + INFO_LOG_FMT(SERIALINTERFACE_CARD, "Shutter: {}", m_is_shutter_open ? "Open" : "Close"); + + FinishCommand(); +} + +u8 C1231BR::GetPositionValue() +{ + // True "R" (Card Position) values. + // FYI: I see a 0xd0 command transition from: 11000 -> 01110 -> 00111 + // Are these 5 bits individual sensors placed within the machine ? + static constexpr std::array POSITION_VALUES{ + 0b00000, // NO_CARD + 0b11000, // READ_WRITE_HEAD + 0b00111, // THERMAL_HEAD + 0b11100, // DISPENSER_THERMAL + 0b00001, // ENTRY_SLOT + }; + + return (m_is_shutter_open ? 0x80u : 0x40u) | + (m_card_settings->report_dispenser_empty ? 0x00u : 0x20u) | + POSITION_VALUES[u8(m_status.r)]; +} + +void C1231BR::DoState(PointerWrap& p) +{ + MagneticCardReader::DoState(p); + + p.Do(m_is_shutter_open); +} + +bool C1231BR::IsReadyForCard() +{ + return !IsCardPresent() && m_is_shutter_open; +} + +} // namespace MagCard diff --git a/Source/Core/Core/HW/MagCard/C1231BR.h b/Source/Core/Core/HW/MagCard/C1231BR.h new file mode 100644 index 0000000000..d339b2d5bb --- /dev/null +++ b/Source/Core/Core/HW/MagCard/C1231BR.h @@ -0,0 +1,37 @@ +// Copyright 2026 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on: https://github.com/GXTX/YACardEmu +// Copyright (C) 2020-2023 wutno (https://github.com/GXTX) +// Copyright (C) 2022-2023 tugpoat (https://github.com/tugpoat) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "Core/HW/MagCard/MagneticCardReader.h" + +namespace MagCard +{ + +// CRP-1231BR-10 +// Used by F-Zero AX +class C1231BR final : public MagneticCardReader +{ +public: + using MagneticCardReader::MagneticCardReader; + +protected: + // Specific to this model. + bool m_is_shutter_open = true; + + bool IsReadyForCard() override; + + u8 GetPositionValue() override; + + void Command_10_Initialize() override; + void Command_D0_ControlShutter() override; + + void DoState(PointerWrap& p) override; +}; + +} // namespace MagCard diff --git a/Source/Core/Core/HW/MagCard/C1231LR.cpp b/Source/Core/Core/HW/MagCard/C1231LR.cpp new file mode 100644 index 0000000000..0e9c32fa15 --- /dev/null +++ b/Source/Core/Core/HW/MagCard/C1231LR.cpp @@ -0,0 +1,27 @@ +// Copyright 2026 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on: https://github.com/GXTX/YACardEmu +// Copyright (C) 2020-2023 wutno (https://github.com/GXTX) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/HW/MagCard/C1231LR.h" + +namespace MagCard +{ + +u8 C1231LR::GetPositionValue() +{ + // True "R" (Card Position) values. + static constexpr std::array POSITION_VALUES{ + 0x30, // NO_CARD + 0x31, // READ_WRITE_HEAD + 0x32, // THERMAL_HEAD + 0x33, // DISPENSER_THERMAL + 0x34, // ENTRY_SLOT + }; + + return POSITION_VALUES[u8(m_status.r)]; +} + +} // namespace MagCard diff --git a/Source/Core/Core/HW/MagCard/C1231LR.h b/Source/Core/Core/HW/MagCard/C1231LR.h new file mode 100644 index 0000000000..e7ee627e1b --- /dev/null +++ b/Source/Core/Core/HW/MagCard/C1231LR.h @@ -0,0 +1,26 @@ +// Copyright 2026 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on: https://github.com/GXTX/YACardEmu +// Copyright (C) 2020-2023 wutno (https://github.com/GXTX) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "Core/HW/MagCard/MagneticCardReader.h" + +namespace MagCard +{ + +// CRP-1231LR-10NAB +// Used by Mario Kart Arcade GP + GP2 +class C1231LR final : public MagneticCardReader +{ +public: + using MagneticCardReader::MagneticCardReader; + +protected: + u8 GetPositionValue() override; +}; + +} // namespace MagCard diff --git a/Source/Core/Core/HW/MagCard/MagneticCardReader.cpp b/Source/Core/Core/HW/MagCard/MagneticCardReader.cpp new file mode 100644 index 0000000000..e84ef48a98 --- /dev/null +++ b/Source/Core/Core/HW/MagCard/MagneticCardReader.cpp @@ -0,0 +1,1002 @@ +// Copyright 2026 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on: https://github.com/GXTX/YACardEmu +// Copyright (C) 2020-2023 wutno (https://github.com/GXTX) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/HW/MagCard/MagneticCardReader.h" + +#include +#include + +#include "Common/BitUtils.h" +#include "Common/DirectIOFile.h" +#include "Common/FileUtil.h" +#include "Common/Lazy.h" +#include "Common/Logging/Log.h" +#include "Common/SmallVector.h" +#include "Common/StringUtil.h" +#include "Core/Core.h" + +namespace +{ + +constexpr std::string GetHexDump(const auto& data) +{ + const auto u8_span = Common::AsU8Span(data); + return HexDump(u8_span.data(), u8_span.size()); +} + +void AppendRange(auto* container, const auto& range) +{ + const auto prev_size = container->size(); + container->resize(prev_size + range.size()); + std::ranges::copy(range, container->data() + prev_size); +} + +} // namespace + +namespace MagCard +{ + +constexpr std::string_view VERSION_STRING = "AP:S1234-5678,OS:S9012-3456,0000"; + +constexpr u8 START_OF_TEXT = 0x02; +constexpr u8 END_OF_TEXT = 0x03; +constexpr u8 ENQUIRY = 0x05; +constexpr u8 ACK = 0x06; +constexpr u8 NACK = 0x15; + +enum class BitMode : u8 +{ + SevenBitParity = 0x30, + EightBitNoParity = 0x31, +}; + +enum class Track : u8 +{ + Track_1 = 0x30, + Track_2 = 0x31, + Track_3 = 0x32, + Track_1_And_2 = 0x33, + Track_1_And_3 = 0x34, + Track_2_And_3 = 0x35, + Track_1_2_And_3 = 0x36, +}; + +static auto GetTrackIndicesForTrackType(Track track) +{ + Common::SmallVector result; + + switch (track) + { + case Track::Track_1: + case Track::Track_2: + case Track::Track_3: + result.emplace_back(std::size_t(track) - 0x30); + break; + + case Track::Track_1_And_2: + case Track::Track_1_And_3: + case Track::Track_2_And_3: + result.emplace_back((track == Track::Track_2_And_3) ? 1 : 0); + result.emplace_back((track == Track::Track_1_And_2) ? 1 : 2); + break; + + case Track::Track_1_2_And_3: + result.emplace_back(0); + result.emplace_back(1); + result.emplace_back(2); + break; + + default: + break; + } + + return result; +} + +MagneticCardReader::MagneticCardReader(MagneticCardReader::Settings* settings) + : m_card_settings{settings} +{ +} + +MagneticCardReader::~MagneticCardReader() +{ + if (IsCardLoaded()) + { + // Games do seem to write the entire save data in a single command. + // But the printed messages are another story. + // I suppose there's potential for data loss here. + WARN_LOG_FMT(SERIALINTERFACE_CARD, "Shutdown before ejecting card!"); + } +} + +void MagneticCardReader::Command_10_Initialize() +{ + EjectCard(); + m_status.SoftReset(); + FinishCommand(); +} + +void MagneticCardReader::Command_20_ReadStatus() +{ + m_status.SoftReset(); + FinishCommand(); +} + +void MagneticCardReader::Command_33_ReadData() +{ + enum class Mode : u8 + { + Standard = 0x30, // read 69-bytes + ReadVariable = 0x31, // variable length read + CardCapture = 0x32, // pull in card + }; + + if (m_current_packet.size() < 3) + { + SetPError(P::SYSTEM_ERR); + return; + } + + const auto mode = Mode(m_current_packet[0]); + // const auto bit_mode = BitMode(m_current_packet[1]); + const auto track = Track(m_current_packet[2]); + + switch (m_current_step) + { + case 1: + if (mode == Mode::CardCapture) + INFO_LOG_FMT(SERIALINTERFACE_CARD, "Command_33_ReadData: CardCapture"); + break; + case 2: + { + // Doesn't return card data. + if (mode == Mode::CardCapture) + { + if (!IsCardPresent()) + { + m_status.s = S::WAITING_FOR_CARD; + --m_current_step; + break; + } + + // How does hardware behave when the card is already elsewhere in the machine ? + if (m_status.r != R::READ_WRITE_HEAD) + { + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "Capturing Card."); + + MoveCard(R::READ_WRITE_HEAD); + ReadCardFile(); + + Core::DisplayMessage("Inserted Magnetic Card", 4000); + } + break; + } + + DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "Command_33_ReadData: mode {:02x} track {:02x}", u8(mode), + u8(track)); + + const auto track_indices = GetTrackIndicesForTrackType(track); + + if (!IsCardLoaded() || track_indices.size() == 0) + { + SetPError(P::ILLEGAL_ERR); + return; + } + + for (auto track_index : track_indices) + { + auto& track_data = m_card_data[track_index]; + if (!track_data.has_value()) + { + SetPError(P::READ_ERR); + WARN_LOG_FMT(SERIALINTERFACE_CARD, "Command_33_ReadData: Empty track {:02x}", track_index); + // TODO: Are we supposed to return partial data ? + return; + } + + AppendRange(&m_command_payload, *track_data); + } + } + default: + break; + } + + if (m_current_step >= 2) + FinishCommand(); +} + +void MagneticCardReader::Command_35_GetData() +{ + switch (m_current_step) + { + case 2: + if (!IsCardLoaded()) + { + // TODO: Is this correct ? + SetPError(P::ILLEGAL_ERR); + return; + } + + for (auto& track_data : m_card_data) + { + if (!track_data.has_value()) + { + SetPError(P::READ_ERR); + // TODO: Are we supposed to return partial data ? + return; + } + + AppendRange(&m_command_payload, *track_data); + } + break; + default: + break; + } + + if (m_current_step >= 2) + FinishCommand(); +} + +void MagneticCardReader::Command_40_Cancel() +{ + m_status.SoftReset(); + FinishCommand(); +} + +void MagneticCardReader::Command_53_WriteData() +{ + enum class Mode : u8 + { + Standard = 0x30, // 69-bytes + }; + + if (m_current_packet.size() < 3) + { + SetPError(P::SYSTEM_ERR); + return; + } + + if (m_current_step < 2) + return; + + const auto mode = Mode(m_current_packet[0]); + // const auto bit_mode = BitMode(m_current_packet[1]); + auto track = Track(m_current_packet[2]); + + const auto track_indices = GetTrackIndicesForTrackType(track); + const auto proper_payload_size = track_indices.size() * TRACK_SIZE; + const auto payload = std::span{m_current_packet}.subspan(3); + + if (mode != Mode::Standard || !IsCardLoaded() || track_indices.size() == 0 || + payload.size() != proper_payload_size) + { + WARN_LOG_FMT(SERIALINTERFACE_CARD, "Command_53_WriteData: Failed."); + SetPError(P::ILLEGAL_ERR); + return; + } + + INFO_LOG_FMT(SERIALINTERFACE_CARD, "Command_53_WriteData: Writing {} track(s) to memory.", + track_indices.size()); + + for (auto track_index : track_indices) + { + auto& track_data = m_card_data[track_index]; + track_data.emplace(); + std::ranges::copy(payload.subspan(track_index * TRACK_SIZE, TRACK_SIZE), track_data->data()); + } + + Core::DisplayMessage("Writing Magnetic Card", 4000); + + WriteCardFile(); + + FinishCommand(); +} + +void MagneticCardReader::Command_78_PrintSettings() +{ + m_status.SoftReset(); + FinishCommand(); +} + +void MagneticCardReader::Command_7A_RegisterFont() +{ + m_status.SoftReset(); + FinishCommand(); +} + +void MagneticCardReader::Command_7B_PrintImage() +{ + // TODO: Are these print functions supposed to succeed before the card is actually grabbed ? + if (!IsCardPresent()) + { + SetPError(P::PRINT_ERR); + return; + } + m_status.SoftReset(); + FinishCommand(); +} + +void MagneticCardReader::Command_7C_PrintLine() +{ + if (m_current_packet.size() < 3) + { + SetPError(P::SYSTEM_ERR); + return; + } + + switch (m_current_step) + { + case 1: + { + if (!IsCardPresent()) + { + SetPError(P::PRINT_ERR); + return; + } + + // FIXME: Should we only move the head when we're actually about to print? + MoveCard(R::THERMAL_HEAD); + break; + } + case 2: + MoveCard(R::READ_WRITE_HEAD); + break; + default: + break; + } + + if (m_current_step >= 2) + FinishCommand(); +} + +void MagneticCardReader::Command_7D_Erase() +{ + switch (m_current_step) + { + case 1: + if (!IsCardPresent()) + { + SetPError(P::PRINT_ERR); + return; + } + MoveCard(R::THERMAL_HEAD); + break; + case 2: + MoveCard(R::READ_WRITE_HEAD); + break; + default: + break; + } + + if (m_current_step >= 2) + FinishCommand(); +} + +void MagneticCardReader::Command_7E_PrintBarcode() +{ + switch (m_current_step) + { + case 2: + if (!IsCardPresent()) + { + SetPError(P::ILLEGAL_ERR); + return; + } + break; + default: + break; + } + + if (m_current_step >= 2) + FinishCommand(); +} + +void MagneticCardReader::Command_80_EjectCard() +{ + if (m_current_step >= 3) + { + EjectCard(); + FinishCommand(); + } +} + +void MagneticCardReader::Command_A0_Clean() +{ + switch (m_current_step) + { + case 2: + if (!IsCardPresent()) + { + m_status.s = S::WAITING_FOR_CARD; + --m_current_step; + break; + } + break; + case 3: + MoveCard(R::THERMAL_HEAD); + break; + case 4: + EjectCard(); + break; + default: + break; + } + + if (m_current_step >= 4) + FinishCommand(); +} + +void MagneticCardReader::Command_B0_DispenseCard() +{ + enum class Mode : u8 + { + Dispense = 0x31, + CheckOnly = 0x32, + }; + + // MarioKart GP1 issues this command without options. + auto mode = Mode::Dispense; + if (!m_current_packet.empty()) + { + mode = Mode(m_current_packet[0]); + } + + switch (m_current_step) + { + case 2: + if (mode == Mode::CheckOnly) + { + if (m_card_settings->report_dispenser_empty) + { + m_status.s = S::DISPENSER_EMPTY; + } + else + { + m_status.s = S::CARD_FULL; + } + } + else + { + DispenseCard(); + } + break; + case 3: + if (m_status.r == R::DISPENSER_THERMAL) + { + MoveCard(R::READ_WRITE_HEAD); + } + break; + default: + break; + } + + if (m_current_step >= 3) + FinishCommand(); +} + +void MagneticCardReader::Command_C0_ControlLED() +{ + // We don't need to handle this properly but let's leave some notes. + enum class Mode : u8 + { + Off = 0x30, + On = 0x31, + SlowBlink = 0x32, + FastBlink = 0x33, + }; + + m_status.SoftReset(); + FinishCommand(); +} + +void MagneticCardReader::Command_C1_SetPrintRetry() +{ + // We don't need to handle this properly but let's leave some notes + // m_current_packet[0] == 0x31 NONE ~ 0x39 MAX8 + m_status.SoftReset(); + FinishCommand(); +} + +void MagneticCardReader::Command_D0_ControlShutter() +{ + // Only BR model supports this command. + SetSError(S::ILLEGAL_COMMAND); +} + +void MagneticCardReader::Command_F0_GetVersion() +{ + AppendRange(&m_command_payload, VERSION_STRING); + + m_status.SoftReset(); + FinishCommand(); +} + +void MagneticCardReader::ClearCardInMemory() +{ + m_card_data.fill(std::nullopt); +} + +void MagneticCardReader::ReadCardFile() +{ + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "Reading card data from: {}", m_card_settings->card_name); + + ClearCardInMemory(); + + const std::string full_path = m_card_settings->card_path + m_card_settings->card_name; + + File::DirectIOFile file{full_path, File::AccessMode::Read}; + + const auto file_size = file.GetSize(); + const auto complete_track_count = file_size / TRACK_SIZE; + const auto excess_byte_count = file_size % TRACK_SIZE; + + if (file_size == 0 || excess_byte_count != 0 || complete_track_count > NUM_TRACKS) + { + ERROR_LOG_FMT(SERIALINTERFACE_CARD, "ReadCardFile: Incorrect card file size: {}", file_size); + } + + const auto tracks_to_read = std::min(complete_track_count, NUM_TRACKS); + for (u32 track_num = 0; track_num != tracks_to_read; ++track_num) + { + auto& track_data = m_card_data[track_num]; + track_data.emplace(); + + if (!file.Read(*track_data)) + { + ERROR_LOG_FMT(SERIALINTERFACE_CARD, "File read failed."); + track_data.reset(); + break; + } + } + + DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "ReadCard: 0:{} 1:{} 2:{}", GetHexDump(m_card_data[0]), + GetHexDump(m_card_data[1]), GetHexDump(m_card_data[2])); +} + +void MagneticCardReader::WriteCardFile() +{ + // Note: Creating empty upsets games when reinserting them later. + // Games expect to dispense their own blank cards, not have such cards inserted. + // We only write files when the game itself writes, so this won't happen. + + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "Writing card data to: {}", m_card_settings->card_name); + DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "WriteCard: 0:{} 1:{} 2:{}", GetHexDump(m_card_data[0]), + GetHexDump(m_card_data[1]), GetHexDump(m_card_data[2])); + + auto full_path = m_card_settings->card_path + m_card_settings->card_name; + + File::DirectIOFile file{full_path, File::AccessMode::Write}; + + for (auto& track_data : m_card_data) + { + if (track_data.has_value() && !file.Write(*track_data)) + { + ERROR_LOG_FMT(SERIALINTERFACE_CARD, "File write failed."); + break; + } + } +} + +void MagneticCardReader::SetPError(P error_code) +{ + WARN_LOG_FMT(SERIALINTERFACE_CARD, "SetPError: {:02x}", u8(error_code)); + m_status.p = error_code; + FinishCommand(); +} + +void MagneticCardReader::SetSError(S error_code) +{ + WARN_LOG_FMT(SERIALINTERFACE_CARD, "SetSError: {:02x}", u8(error_code)); + m_status.s = error_code; + FinishCommand(); +} + +void MagneticCardReader::Process(std::vector* read, std::vector* write) +{ + while (!read->empty()) + { + const u8 first_byte = read->front(); + if (first_byte == ENQUIRY) + { + // ENQUIRY + // The host sends this regularly. + // This triggers sending of the latest command buffer. + // We also step the state machine here. + + DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "Process: ENQ"); + + // TODO: Get time from Core rather than assuming. + const auto elapsed_time = std::chrono::milliseconds{50}; + + StepStateMachine(elapsed_time); + StepStatePerson(elapsed_time); + + BuildPacket(*write); + + read->erase(read->begin()); // TODO: SLOW ! + continue; + } + + if (first_byte != START_OF_TEXT) + { + ERROR_LOG_FMT(SERIALINTERFACE_CARD, "Process: Unexpected {:02x}", first_byte); + read->erase(read->begin()); // TODO: SLOW ! + continue; + } + + // START_OF_TEXT + // This is a command packet. The next byte provides the size. + // Upon read they ACK or NACK and start processing of a command. + + if (read->size() < 2) + break; // Wait for more data. + + const std::size_t packet_size = (*read)[1]; + if (packet_size > read->size() - 2) + break; // Wait for more data. + + if (ReceivePacket(std::span{*read}.subspan(2, packet_size))) + { + DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "Writing ACK"); + write->emplace_back(ACK); + } + else + { + WARN_LOG_FMT(SERIALINTERFACE_CARD, "Writing NACK"); + write->emplace_back(NACK); + } + + read->erase(read->begin(), read->begin() + packet_size + 2); // TODO: SLOW ! + } +} + +bool MagneticCardReader::ReceivePacket(std::span packet) +{ + DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "ReceivePacket: {}", GetHexDump(packet)); + + if (packet.size() < 6) + { + WARN_LOG_FMT(SERIALINTERFACE_CARD, "ReceivePacket: Bad packet size!"); + return false; + } + + if (packet[packet.size() - 2] != END_OF_TEXT) + { + WARN_LOG_FMT(SERIALINTERFACE_CARD, "ReceivePacket: Missing END_OF_TEXT!"); + return false; + } + + // Checksum is XOR of the previous bytes. + // This includes the count byte that we already chopped off. + const u8 read_checksum = packet.back(); + + // TODO C++23: + // std::ranges::fold_left(packet.first(packet.size() - 1), u8(packet.size()), std::bit_xor{}); + const auto range_to_checksum = packet.first(packet.size() - 1); + const auto proper_checksum = std::accumulate(range_to_checksum.begin(), range_to_checksum.end(), + u8(packet.size()), std::bit_xor{}); + + // Verify checksum. Skip packet if invalid. + if (read_checksum != proper_checksum) + { + WARN_LOG_FMT(SERIALINTERFACE_CARD, "ReceivePacket: Bad checksum!"); + return false; + } + + const u8 new_command = packet[0]; + + if (IsRunningCommand()) + { + if (new_command == 0x40) // Cancel. + { + INFO_LOG_FMT(SERIALINTERFACE_CARD, "ReceivePacket: Cancelling command: {:02x}", + m_current_command); + } + else + { + // What does the real hardware do here ? + WARN_LOG_FMT(SERIALINTERFACE_CARD, + "ReceivePacket: Running command: {:02x} interrupted by command: {:02x}", + m_current_command, new_command); + } + } + + m_current_command = new_command; + + // Copy the payload. + // We're skipping the command number and the host's 3 status bytes, + // and dropping the END_OF_TEXT and checksum. + m_current_packet.resize(packet.size() - 6); + std::ranges::copy(packet.subspan(4, m_current_packet.size()), m_current_packet.data()); + + // TODO: Is this sensible for every command ? + m_status.SoftReset(); + + // Start the new command. + m_status.s = S::RUNNING_COMMAND; + m_current_step = 1; + m_command_payload.clear(); + + return true; +} + +void MagneticCardReader::StepStatePerson(DT elapsed_time) +{ + m_time_since_person_moved_card += elapsed_time; + + // F-Zero AX: Polls for a card with command 0x20 ReadStatus(). + // It demands a card is removed from "ENTRY_SLOT" during boot. + // It pauses and spams ReadStatus() until that happens. + // When wanted, it loads a card detected at the "ENTRY_SLOT" position. + // There's not a perfectly reliably way to detect when it wants a card. + // + // MarioKart GP: Polls for wanted cards with command 0x33 ReadData(Mode:CardCapture). + // It does this regardless of the card being detected in the "ENTRY_SLOT" position. + // This makes it easy to detect when cards are wanted. + // It pauses until cards are pulled from "ENTRY_SLOT" on game over. + + // This logic tries to emulate a person following those rules. + + // People can't interact with cards inside the machine. + if (IsCardLoaded()) + return; + + Common::Lazy card_exists{[&] { + const auto full_path = m_card_settings->card_path + m_card_settings->card_name; + return File::Exists(full_path); + }}; + + if (!IsCardPresent()) + { + if (m_card_settings->insert_card_when_possible && IsReadyForCard() && + m_time_since_person_moved_card > std::chrono::milliseconds{500} && *card_exists) + { + // Place the card in the entry slot after a delay. + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "Inserting card."); + m_status.r = R::ENTRY_SLOT; + m_time_since_person_moved_card = {}; + } + + return; + } + + const bool machine_moved_card_last = + m_time_since_machine_moved_card < m_time_since_person_moved_card; + if ((machine_moved_card_last && + m_time_since_machine_moved_card > std::chrono::milliseconds{100}) || + !*card_exists) + { + // Take the card from the machine when ejected or if the file is deleted. + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "Removing card."); + m_status.r = R::NO_CARD; + m_time_since_person_moved_card = {}; + } +} + +void MagneticCardReader::StepStateMachine(DT elapsed_time) +{ + m_time_since_machine_moved_card += elapsed_time; + + if (!IsRunningCommand()) + return; + + DEBUG_LOG_FMT(SERIALINTERFACE_CARD, "StepStateMachine command: {:02x}", GetCurrentCommand()); + + switch (GetCurrentCommand()) + { + case 0x10: + Command_10_Initialize(); + break; + case 0x20: + Command_20_ReadStatus(); + break; + case 0x33: + Command_33_ReadData(); + break; + case 0x35: + Command_35_GetData(); + break; + case 0x40: + Command_40_Cancel(); + break; + case 0x53: + Command_53_WriteData(); + break; + case 0x78: + Command_78_PrintSettings(); + break; + case 0x7A: + Command_7A_RegisterFont(); + break; + case 0x7B: + Command_7B_PrintImage(); + break; + case 0x7C: + Command_7C_PrintLine(); + break; + case 0x7D: + Command_7D_Erase(); + break; + case 0x7E: + Command_7E_PrintBarcode(); + break; + case 0x80: + Command_80_EjectCard(); + break; + case 0xA0: + Command_A0_Clean(); + break; + case 0xB0: + Command_B0_DispenseCard(); + break; + case 0xC0: + Command_C0_ControlLED(); + break; + case 0xC1: + Command_C1_SetPrintRetry(); + break; + case 0xD0: + Command_D0_ControlShutter(); + break; + case 0xF0: + Command_F0_GetVersion(); + break; + default: + WARN_LOG_FMT(SERIALINTERFACE_CARD, "HandlePacket: Unhandled command {:02x}", + GetCurrentCommand()); + SetSError(S::ILLEGAL_COMMAND); + break; + } + + if (IsRunningCommand()) + ++m_current_step; +} + +void MagneticCardReader::BuildPacket(std::vector& write_buffer) +{ + // Header and footer add 6 bytes. + const u8 payload_size = u8(m_command_payload.size() + 6); + // + START_OF_TEXT + the count byte + const auto total_write_size = payload_size + 2; + + const auto prev_buffer_size = write_buffer.size(); + write_buffer.resize(prev_buffer_size + total_write_size); + + auto* out_ptr = write_buffer.data() + prev_buffer_size; + u8 packet_checksum = 0; + + const auto write_and_checksum = [&](u8 value) { + *(out_ptr++) = value; + packet_checksum ^= value; + }; + + // Write the header. + *(out_ptr++) = START_OF_TEXT; + write_and_checksum(payload_size); + write_and_checksum(GetCurrentCommand()); + write_and_checksum(GetPositionValue()); + write_and_checksum(u8(m_status.p)); + write_and_checksum(u8(m_status.s)); + + // Write the payload. + std::ranges::for_each(m_command_payload, write_and_checksum); + + // Write the footer. + write_and_checksum(END_OF_TEXT); + *(out_ptr++) = packet_checksum; + + DEBUG_LOG_FMT( + SERIALINTERFACE_CARD, "BuildPacket: {}", + GetHexDump(std::span{write_buffer}.subspan(write_buffer.size() - payload_size - 2))); +} + +bool MagneticCardReader::IsRunningCommand() const +{ + return m_current_step > 0; +} + +void MagneticCardReader::FinishCommand() +{ + m_current_step = 0; + + if (m_status.s != S::ILLEGAL_COMMAND) + m_status.s = S::NO_JOB; +} + +u8 MagneticCardReader::GetCurrentCommand() const +{ + return m_current_command; +} + +bool MagneticCardReader::IsCardPresent() const +{ + return m_status.r != R::NO_CARD; +} + +bool MagneticCardReader::IsCardLoaded() const +{ + return m_status.r != R::NO_CARD && m_status.r != R::ENTRY_SLOT; +} + +void MagneticCardReader::MoveCard(R new_position) +{ + if (m_status.r == new_position) + return; + + static constexpr const char* position_descriptions[] = { + "NO_CARD", "READ_WRITE_HEAD", "THERMAL_HEAD", "DISPENSER_THERMAL", "ENTRY_SLOT", + }; + + INFO_LOG_FMT(SERIALINTERFACE_CARD, "MoveCard: {}", + position_descriptions[std::size_t(new_position)]); + + m_status.r = new_position; + m_time_since_machine_moved_card = {}; +} + +void MagneticCardReader::EjectCard() +{ + if (IsCardLoaded()) + { + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "Ejecting card."); + + ClearCardInMemory(); + MoveCard(R::ENTRY_SLOT); + + Core::DisplayMessage("Ejected Magnetic Card", 4000); + } +} + +void MagneticCardReader::DispenseCard() +{ + // TODO: Think about how multiple card management UX should work. + // Since this is a new card, we maybe shouldn't be overwriting existing files. + + if (IsCardLoaded()) + { + SetSError(S::ILLEGAL_COMMAND); + return; + } + + if (m_card_settings->report_dispenser_empty) + { + m_status.s = S::DISPENSER_EMPTY; + return; + } + + NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "Dispensing a new card."); + Core::DisplayMessage("Created Magnetic Card", 4000); + + // TODO: This behavior doesn't seem to match protocol dumps. + MoveCard(R::DISPENSER_THERMAL); +} + +bool MagneticCardReader::IsReadyForCard() +{ + return !IsCardPresent(); +} + +void MagneticCardReader::DoState(PointerWrap& p) +{ + // Outgoing packet. + p.Do(m_status); + p.Do(m_current_command); + p.Do(m_command_payload); + + // Command state. + p.Do(m_current_packet); + p.Do(m_current_step); + + // The card data in memory. + for (auto& track : m_card_data) + p.Do(track); + + p.Do(m_time_since_machine_moved_card); + p.Do(m_time_since_person_moved_card); +} + +} // namespace MagCard diff --git a/Source/Core/Core/HW/MagCard/MagneticCardReader.h b/Source/Core/Core/HW/MagCard/MagneticCardReader.h new file mode 100644 index 0000000000..b60514a6ab --- /dev/null +++ b/Source/Core/Core/HW/MagCard/MagneticCardReader.h @@ -0,0 +1,207 @@ +// Copyright 2026 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Based on: https://github.com/GXTX/YACardEmu +// Copyright (C) 2020-2023 wutno (https://github.com/GXTX) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include "Common/ChunkFile.h" +#include "Common/CommonTypes.h" + +namespace MagCard +{ + +class MagneticCardReader +{ +public: + static constexpr std::size_t TRACK_SIZE = 69; // A nice amount of data. + static constexpr std::size_t NUM_TRACKS = 3; + + // This data is not part of the card reader state. + struct Settings + { + std::string card_name; + std::string card_path; + + bool insert_card_when_possible = true; + bool report_dispenser_empty = false; + }; + + explicit MagneticCardReader(Settings* settings); + virtual ~MagneticCardReader(); + + MagneticCardReader(const MagneticCardReader&) = delete; + MagneticCardReader& operator=(const MagneticCardReader&) = delete; + MagneticCardReader(MagneticCardReader&&) = delete; + MagneticCardReader& operator=(MagneticCardReader&&) = delete; + + // TODO: This std:vector buffer interface is a bit funky.. + + // read = MagCard <-- Baseboard. + // write = Magcard --> Baseboard. + void Process(std::vector* read, std::vector* write); + + virtual void DoState(PointerWrap& p); + +protected: + // Status bytes: + enum class R : u8 + { + // Note: Derived classes define the real values in GetPositionValue(). + NO_CARD, + READ_WRITE_HEAD, + THERMAL_HEAD, + DISPENSER_THERMAL, + ENTRY_SLOT, + + MAX_POSITIONS, + }; + enum class P : u8 + { + NO_ERR = 0x30, + READ_ERR = 0x31, + WRITE_ERR = 0x32, + CARD_JAM = 0x33, + MOTOR_ERR = 0x34, // transport system motor error + PRINT_ERR = 0x35, + ILLEGAL_ERR = 0x38, // generic error + BATTERY_ERR = 0x40, // low battery voltage + SYSTEM_ERR = 0x41, // reader/writer system err + TRACK_1_READ_ERR = 0x51, + TRACK_2_READ_ERR = 0x52, + TRACK_3_READ_ERR = 0x53, + TRACK_1_AND_2_READ_ERR = 0x54, + TRACK_1_AND_3_READ_ERR = 0x55, + TRACK_2_AND_3_READ_ERR = 0x56, + }; + enum class S : u8 + { + NO_JOB = 0x30, + ILLEGAL_COMMAND = 0x32, + RUNNING_COMMAND = 0x33, + WAITING_FOR_CARD = 0x34, + DISPENSER_EMPTY = 0x35, + NO_DISPENSER = 0x36, + CARD_FULL = 0x37, + }; + + struct Status + { + R r = R::NO_CARD; + P p = P::NO_ERR; + S s = S::NO_JOB; + + void SoftReset() + { + p = P::NO_ERR; + s = S::NO_JOB; + } + }; + + enum class InitMode : u8 + { + Standard = 0x30, + EjectAfter = 0x31, + ResetSpecifications = 0x32, + }; + + bool ReceivePacket(std::span packet); + void BuildPacket(std::vector& write_buffer); + + void StepStateMachine(DT elapsed_time); + void StepStatePerson(DT elapsed_time); + + // This is what actually maps the "R" position value to a number. + virtual u8 GetPositionValue() = 0; + + void SetPError(P error_code); + void SetSError(S error_code); + + void ClearCardInMemory(); + + // Read/Write with host filesystem. + void ReadCardFile(); + void WriteCardFile(); + + bool IsRunningCommand() const; + void FinishCommand(); + + u8 GetCurrentCommand() const; + + // Is a card anywhere the machine can sense it ? + bool IsCardPresent() const; + + // Is a card beyond the "ENTRY_SLOT" position ? + bool IsCardLoaded() const; + + virtual bool IsReadyForCard(); + + // Move the card through the machine. + void MoveCard(R new_position); + + // Unloads the card and moves it to the "ENTRY_SLOT" position. + void EjectCard(); + + // Feeds a new card into the machine from an internal hopper. + void DispenseCard(); + + // Commands + virtual void Command_10_Initialize(); + void Command_20_ReadStatus(); + void Command_33_ReadData(); // Multi-track read. + void Command_35_GetData(); // Read the entire card. + void Command_40_Cancel(); + void Command_53_WriteData(); // Multi-track write. + void Command_78_PrintSettings(); + void Command_7A_RegisterFont(); + void Command_7B_PrintImage(); + void Command_7C_PrintLine(); + void Command_7D_Erase(); // Erase the printed image. + void Command_7E_PrintBarcode(); + void Command_80_EjectCard(); + void Command_A0_Clean(); + void Command_B0_DispenseCard(); + void Command_C0_ControlLED(); + void Command_C1_SetPrintRetry(); + virtual void Command_D0_ControlShutter(); + void Command_F0_GetVersion(); + + Settings* const m_card_settings; + + // R P S values that are sent in every ENQUIRY response. + Status m_status{}; + + // A byte sent in every ENQUIRY response. + // TODO: What's the default value on just powered up hardware ? + u8 m_current_command = 0; + + // The data included in every ENQUIRY response. + std::vector m_command_payload; + + // The card track data in memory. + using TrackData = std::optional>; + std::array m_card_data; + + // The parameter data of the command being processed. + std::vector m_current_packet; + + // The progress state of the current command. + // 0 == not running a command. + // TODO: Change this to emulated milliseconds or something. + s32 m_current_step = 0; + + // Used to delay re-insertion of cards after ejection. + // This avoids having to expose card "Take"/"Insert" actions. + DT m_time_since_machine_moved_card{}; + DT m_time_since_person_moved_card{}; +}; + +} // namespace MagCard diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp index 7cc5d93c01..0d23288539 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp @@ -9,10 +9,8 @@ #include -#include "Common/Buffer.h" #include "Common/CommonTypes.h" #include "Common/FileUtil.h" -#include "Common/IOFile.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" #include "Common/Swap.h" @@ -28,6 +26,8 @@ #include "Core/HW/EXI/EXI.h" #include "Core/HW/GCPad.h" #include "Core/HW/MMIO.h" +#include "Core/HW/MagCard/C1231BR.h" +#include "Core/HW/MagCard/C1231LR.h" #include "Core/HW/Memmap.h" #include "Core/HW/ProcessorInterface.h" #include "Core/HW/SI/SI.h" @@ -160,6 +160,26 @@ CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices dev // Use count m_ic_card_data[0x28] = 0xFF; m_ic_card_data[0x29] = 0xFF; + + // Magnetic Card Reader + m_mag_card_settings.card_path = File::GetUserPath(D_TRIUSER_IDX); + m_mag_card_settings.card_name = fmt::format("tricard_{}.bin", SConfig::GetInstance().GetGameID()); + + // TODO: Do any other games use the Magnetic Card Reader ? + switch (AMMediaboard::GetGameType()) + { + case FZeroAX: + m_mag_card_reader = std::make_unique(&m_mag_card_settings); + break; + + case MarioKartGP: + case MarioKartGP2: + m_mag_card_reader = std::make_unique(&m_mag_card_settings); + break; + + default: + break; + } } constexpr u32 SI_XFER_LENGTH_MASK = 0x7f; @@ -1147,506 +1167,43 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) case GCAMCommand::SerialB: { DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 32 (CARD-Interface)"); + if (!validate_data_in_out(1, 0, "SerialB")) break; - const u32 length = *data_in++; - if (!validate_data_in_out(length, 0, "SerialB")) + const u32 in_length = *data_in++; + + static constexpr u32 max_packet_size = 0x2f; + + // Also accounting for the 2-byte header. + if (!validate_data_in_out(in_length, max_packet_size + 2, "SerialB")) break; - if (length) + + if (m_mag_card_reader) { - // Send Card Reply - if (length == 1 && data_in[0] == 0x05) - { - if (m_card_read_length) - { - if (!validate_data_in_out(0, 1, "SerialB")) - break; - data_out[data_offset++] = gcam_command; - u32 read_length = m_card_read_length - m_card_read; + // Append the data to our buffer. + const auto prev_size = m_mag_card_in_buffer.size(); + m_mag_card_in_buffer.resize(prev_size + in_length); + std::ranges::copy(std::span{data_in, in_length}, + m_mag_card_in_buffer.data() + prev_size); - if (AMMediaboard::GetGameType() == FZeroAX) - { - read_length = std::min(read_length, 0x2F); - } - - if (!validate_data_in_out(0, 1, "SerialB")) - break; - data_out[data_offset++] = read_length; // 0x2F (max size per packet) - - if (!validate_data_in_out(0, read_length, "SerialB")) - break; - if (u64{m_card_read} + read_length > sizeof(m_card_read_packet)) - { - ERROR_LOG_FMT(SERIALINTERFACE_AMBB, - "GC-AM: Command SerialB, m_card_read_packet overflow:\n" - " - m_card_read_packet = {}\n" - " - m_card_read = {}\n" - " - read_length = {}", - fmt::ptr(m_card_read_packet), m_card_read, read_length); - data_in = data_in_end; - break; - } - memcpy(data_out.data() + data_offset, m_card_read_packet + m_card_read, - read_length); - - data_offset += read_length; - m_card_read += read_length; - - if (m_card_read >= m_card_read_length) - m_card_read_length = 0; - - data_in += length; - break; - } - - if (!validate_data_in_out(0, 5, "SerialB")) - break; - - data_out[data_offset++] = gcam_command; - const u32 command_length_offset = data_offset; - data_out[data_offset++] = 0x00; // len - - data_out[data_offset++] = 0x02; - const u32 checksum_start = data_offset; - - data_out[data_offset++] = 0x00; // 0x00 len - - data_out[data_offset++] = m_card_command; // 0x01 command - - switch (CARDCommand(m_card_command)) - { - case CARDCommand::Init: - if (!validate_data_in_out(0, 2, "SerialB")) - break; - data_out[data_offset++] = 0x00; // 0x02 - data_out[data_offset++] = 0x30; // 0x03 - break; - case CARDCommand::GetState: - if (!validate_data_in_out(0, 2, "SerialB")) - break; - data_out[data_offset++] = 0x20 | m_card_bit; // 0x02 - - // bit 0: Please take your card - // bit 1: Endless waiting causes UNK_E to be called - data_out[data_offset++] = 0x00; // 0x03 - break; - case CARDCommand::Read: - if (!validate_data_in_out(0, 2, "SerialB")) - break; - data_out[data_offset++] = 0x02; // 0x02 - data_out[data_offset++] = 0x53; // 0x03 - break; - case CARDCommand::IsPresent: - if (!validate_data_in_out(0, 2, "SerialB")) - break; - data_out[data_offset++] = 0x22; // 0x02 - data_out[data_offset++] = 0x30; // 0x03 - break; - case CARDCommand::Write: - if (!validate_data_in_out(0, 2, "SerialB")) - break; - data_out[data_offset++] = 0x02; // 0x02 - data_out[data_offset++] = 0x00; // 0x03 - break; - case CARDCommand::SetPrintParam: - case CARDCommand::RegisterFont: - if (!validate_data_in_out(0, 2, "SerialB")) - break; - data_out[data_offset++] = 0x00; // 0x02 - data_out[data_offset++] = 0x00; // 0x03 - break; - case CARDCommand::WriteInfo: - if (!validate_data_in_out(0, 2, "SerialB")) - break; - data_out[data_offset++] = 0x02; // 0x02 - data_out[data_offset++] = 0x00; // 0x03 - break; - case CARDCommand::Erase: - // TODO: CARDCommand::Erase is not handled. - ERROR_LOG_FMT(SERIALINTERFACE_AMBB, "CARDCommand::Erase is not handled."); - break; - case CARDCommand::Eject: - if (!validate_data_in_out(0, 2, "SerialB")) - break; - if (AMMediaboard::GetGameType() == FZeroAX) - { - data_out[data_offset++] = 0x01; // 0x02 - } - else - { - data_out[data_offset++] = 0x31; // 0x02 - } - data_out[data_offset++] = 0x30; // 0x03 - break; - case CARDCommand::Clean: - if (!validate_data_in_out(0, 2, "SerialB")) - break; - data_out[data_offset++] = 0x02; // 0x02 - data_out[data_offset++] = 0x00; // 0x03 - break; - case CARDCommand::Load: - if (!validate_data_in_out(0, 2, "SerialB")) - break; - data_out[data_offset++] = 0x02; // 0x02 - data_out[data_offset++] = 0x30; // 0x03 - break; - case CARDCommand::SetShutter: - if (!validate_data_in_out(0, 2, "SerialB")) - break; - data_out[data_offset++] = 0x00; // 0x02 - data_out[data_offset++] = 0x00; // 0x03 - break; - } - - if (!validate_data_in_out(0, 3, "SerialB")) - break; - data_out[data_offset++] = 0x30; // 0x04 - data_out[data_offset++] = 0x00; // 0x05 - - data_out[data_offset++] = 0x03; // 0x06 - - data_out[checksum_start] = data_offset - checksum_start; // 0x00 len - - if (!validate_data_in_out(0, 1, "SerialB")) - break; - data_out[data_offset] = 0; // 0x07 - for (u32 i = 0; i < data_out[checksum_start]; ++i) - data_out[data_offset] ^= data_out[checksum_start + i]; - - if (!validate_data_in_out(0, 1, "SerialB")) - break; - data_offset++; - - data_out[command_length_offset] = data_out[checksum_start] + 2; - } - else - { - if (!validate_data_in_out(length, 0, "SerialB")) - break; - if (u64{m_card_offset} + length > std::size(m_card_buffer)) - { - ERROR_LOG_FMT(SERIALINTERFACE_AMBB, - "GC-AM: Command SerialB, m_card_buffer overflow:\n" - " - m_card_buffer = {}\n" - " - m_card_offset = {}\n" - " - length = {}", - fmt::ptr(m_card_buffer), m_card_offset, length); - data_in = data_in_end; - break; - } - for (u32 i = 0; i < length; ++i) - m_card_buffer[m_card_offset + i] = data_in[i]; - - m_card_offset += length; - - // Check if we got a complete command - if (m_card_buffer[0] == 0x02) - { - if (m_card_offset < 2) - { - ERROR_LOG_FMT( - SERIALINTERFACE_AMBB, - "GC-AM: Command SerialB, m_card_buffer overflow (m_card_offset < 2):\n" - " - m_card_buffer = {}\n" - " - m_card_offset = {}\n" - " - length = {}", - fmt::ptr(m_card_buffer), m_card_offset, length); - data_in = data_in_end; - break; - } - if (const u32 offset = m_card_offset - 2; - m_card_buffer[1] == offset && m_card_buffer[offset] == 0x03) - { - m_card_command = m_card_buffer[2]; - - switch (CARDCommand(m_card_command)) - { - case CARDCommand::Init: - NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Init"); - - m_card_write_length = 0; - m_card_bit = 0; - m_card_memory_size = 0; - m_card_state_call_count = 0; - break; - case CARDCommand::GetState: - { - NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD GetState({:02X})", - m_card_bit); - - if (m_card_memory_size == 0) - { - const std::string card_filename( - fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX), - SConfig::GetInstance().GetGameID())); - - if (File::Exists(card_filename)) - { - File::IOFile card(card_filename, "rb+"); - m_card_memory_size = static_cast(card.GetSize()); - if (m_card_memory_size > sizeof(m_card_memory)) - { - ERROR_LOG_FMT(SERIALINTERFACE_CARD, - "GC-AM: Command CARD GetState overflow:\n" - " - file name = {}\n" - " - file size = {}\n" - " - card size = {}", - card_filename, m_card_memory_size, sizeof(m_card_memory)); - data_in = data_in_end; - break; - } - card.ReadBytes(m_card_memory, m_card_memory_size); - card.Close(); - - m_card_is_inserted = true; - } - } - - if (AMMediaboard::GetGameType() == FZeroAX && m_card_memory_size) - { - m_card_state_call_count++; - if (m_card_state_call_count > 10) - { - if (m_card_bit & 2) - m_card_bit &= ~2u; - else - m_card_bit |= 2; - - m_card_state_call_count = 0; - } - } - - if (m_card_clean == 1) - { - m_card_clean = 2; - } - else if (m_card_clean == 2) - { - const std::string card_filename( - fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX), - SConfig::GetInstance().GetGameID())); - - if (File::Exists(card_filename)) - { - m_card_memory_size = static_cast(File::GetSize(card_filename)); - if (m_card_memory_size) - { - if (AMMediaboard::GetGameType() == FZeroAX) - { - m_card_bit = 2; - } - else - { - m_card_bit = 1; - } - } - } - m_card_clean = 0; - } - break; - } - case CARDCommand::IsPresent: - NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD IsPresent"); - break; - case CARDCommand::RegisterFont: - NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD RegisterFont"); - break; - case CARDCommand::Load: - { - const u8 mode = m_card_buffer[6]; - NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Load({:02X})", mode); - break; - } - case CARDCommand::Clean: - NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Clean"); - m_card_clean = 1; - break; - case CARDCommand::Read: - { - const u8 mode = m_card_buffer[6]; - const u8 bitmode = m_card_buffer[7]; - const u8 track = m_card_buffer[8]; - - NOTICE_LOG_FMT(SERIALINTERFACE_CARD, - "GC-AM: Command CARD Read({:02X},{:02X},{:02X})", mode, bitmode, - track); - - // Prepare read packet - memset(m_card_read_packet, 0, 0xDB); - - const std::string card_filename( - fmt::format("{}tricard_{}.bin", File::GetUserPath(D_TRIUSER_IDX), - SConfig::GetInstance().GetGameID())); - - if (File::Exists(card_filename)) - { - File::IOFile card(card_filename, "rb+"); - if (m_card_memory_size == 0) - { - m_card_memory_size = static_cast(card.GetSize()); - } - - if (m_card_memory_size > sizeof(m_card_memory)) - { - ERROR_LOG_FMT(SERIALINTERFACE_CARD, - "GC-AM: Command CARD Read overflow:\n" - " - file name = {}\n" - " - file size = {}\n" - " - card size = {}", - card_filename, m_card_memory_size, sizeof(m_card_memory)); - data_in = data_in_end; - break; - } - card.ReadBytes(m_card_memory, m_card_memory_size); - card.Close(); - - m_card_is_inserted = true; - } - else if (m_card_memory_size > sizeof(m_card_memory)) - { - ERROR_LOG_FMT(SERIALINTERFACE_CARD, - "GC-AM: Command CARD Read overflow:\n" - " - requested size = {}\n" - " - card size = {}", - m_card_memory_size, sizeof(m_card_memory)); - data_in = data_in_end; - break; - } - - m_card_read_packet[0] = 0x02; // SUB CMD - m_card_read_packet[1] = 0x00; // SUB CMDLen - - m_card_read_packet[2] = 0x33; // CARD CMD - - if (m_card_is_inserted) // CARD Status - { - m_card_read_packet[3] = 0x31; - } - else - { - m_card_read_packet[3] = 0x30; - } - - m_card_read_packet[4] = 0x30; - m_card_read_packet[5] = 0x30; - - u32 packet_offset = 6; - // Data reply - static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 6); - memcpy(m_card_read_packet + packet_offset, m_card_memory, m_card_memory_size); - packet_offset += m_card_memory_size; - - static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 7); - m_card_read_packet[packet_offset++] = 0x03; - - m_card_read_packet[1] = packet_offset - 1; // SUB CMDLen - - static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 8); - for (u32 i = 0; i < packet_offset - 1; ++i) - m_card_read_packet[packet_offset] ^= m_card_read_packet[1 + i]; - - static_assert(sizeof(m_card_read_packet) >= sizeof(m_card_memory) + 9); - packet_offset++; - - m_card_read_length = packet_offset; - m_card_read = 0; - break; - } - case CARDCommand::Write: - { - const u8 mode = m_card_buffer[6]; - const u8 bitmode = m_card_buffer[7]; - const u8 track = m_card_buffer[8]; - - m_card_memory_size = m_card_buffer[1] - 9; - if (m_card_memory_size > sizeof(m_card_memory)) - { - ERROR_LOG_FMT(SERIALINTERFACE_CARD, - "GC-AM: Command CARD Write overflow:\n" - " - write size = {}\n" - " - card size = {}", - m_card_memory_size, sizeof(m_card_memory)); - data_in = data_in_end; - break; - } - - static_assert(sizeof(m_card_buffer) >= sizeof(m_card_memory) + 9); - memcpy(m_card_memory, m_card_buffer + 9, m_card_memory_size); - - NOTICE_LOG_FMT(SERIALINTERFACE_CARD, - "GC-AM: Command CARD Write: {:02X} {:02X} {:02X} {}", mode, - bitmode, track, m_card_memory_size); - - const std::string card_filename(File::GetUserPath(D_TRIUSER_IDX) + "tricard_" + - SConfig::GetInstance().GetGameID() + ".bin"); - - File::IOFile card(card_filename, "wb+"); - card.WriteBytes(m_card_memory, m_card_memory_size); - card.Close(); - - m_card_bit = 2; - - m_card_state_call_count = 0; - break; - } - case CARDCommand::SetPrintParam: - NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD SetPrintParam"); - break; - case CARDCommand::WriteInfo: - NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD WriteInfo"); - break; - case CARDCommand::Erase: - NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Erase"); - break; - case CARDCommand::Eject: - NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD Eject"); - if (AMMediaboard::GetGameType() != FZeroAX) - { - m_card_bit = 0; - } - break; - case CARDCommand::SetShutter: - NOTICE_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: Command CARD SetShutter"); - if (AMMediaboard::GetGameType() != FZeroAX) - { - m_card_bit = 0; - } - // Close - if (m_card_buffer[6] == 0x30) - { - m_card_shutter = false; - } - // Open - else if (m_card_buffer[6] == 0x31) - { - m_card_shutter = true; - } - break; - default: - ERROR_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: CARD:Unhandled command!"); - ERROR_LOG_FMT(SERIALINTERFACE_CARD, "GC-AM: CARD:[{:08X}]", m_card_command); - // hexdump( m_card_buffer, m_card_offset ); - break; - } - m_card_offset = 0; - } - } - - if (!validate_data_in_out(0, 3, "SerialB")) - break; - data_out[data_offset++] = 0x32; - data_out[data_offset++] = 0x01; // len - data_out[data_offset++] = 0x06; // OK - } + // Send and receive data with the magnetic card reader. + m_mag_card_reader->Process(&m_mag_card_in_buffer, &m_mag_card_out_buffer); } - else - { - if (!validate_data_in_out(0, 2, "SerialB")) - break; - data_out[data_offset++] = gcam_command; - data_out[data_offset++] = 0x00; // len - } - data_in += length; + + data_in += in_length; + const auto out_length = std::min(u32(m_mag_card_out_buffer.size()), max_packet_size); + + // Write the 2-byte header. + data_out[data_offset++] = gcam_command; + data_out[data_offset++] = u8(out_length); + + // Write the data. + std::copy_n(m_mag_card_out_buffer.data(), out_length, data_out.data() + data_offset); + data_offset += out_length; + + // Remove the data from our buffer. + m_mag_card_out_buffer.erase(m_mag_card_out_buffer.begin(), + m_mag_card_out_buffer.begin() + s32(out_length)); break; } case GCAMCommand::JVSIOA: @@ -2762,24 +2319,14 @@ void CSIDevice_AMBaseboard::DoState(PointerWrap& p) p.Do(m_ic_write_offset); p.Do(m_ic_write_size); - p.Do(m_card_memory); - p.Do(m_card_read_packet); - p.Do(m_card_buffer); + // Magnetic Card Reader + if (m_mag_card_reader) + { + m_mag_card_reader->DoState(p); - // Setup CARD - p.Do(m_card_memory_size); - p.Do(m_card_is_inserted); - - p.Do(m_card_command); - p.Do(m_card_clean); - p.Do(m_card_write_length); - p.Do(m_card_wrote); - p.Do(m_card_read_length); - p.Do(m_card_read); - p.Do(m_card_bit); - p.Do(m_card_shutter); - p.Do(m_card_state_call_count); - p.Do(m_card_offset); + p.Do(m_mag_card_in_buffer); + p.Do(m_mag_card_out_buffer); + } // Serial p.Do(m_wheel_init); diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h index 974325c7c2..31ed04053c 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h @@ -4,7 +4,9 @@ #pragma once #include "Core/HW/GCPad.h" +#include "Core/HW/MagCard/MagneticCardReader.h" #include "Core/HW/SI/SI_Device.h" + #include "InputCommon/GCPadStatus.h" namespace Movie @@ -274,24 +276,13 @@ private: u32 m_ic_write_offset = 0; u32 m_ic_write_size = 0; - u8 m_card_memory[0xD0] = {}; - u8 m_card_read_packet[0xDB] = {}; - u8 m_card_buffer[0x100] = {}; + // Magnetic Card Reader + MagCard::MagneticCardReader::Settings m_mag_card_settings; - // Setup CARD - u32 m_card_memory_size = 0; - bool m_card_is_inserted = false; + std::vector m_mag_card_in_buffer; + std::vector m_mag_card_out_buffer; - u32 m_card_command = 0; - u32 m_card_clean = 0; - u32 m_card_write_length = 0; - u32 m_card_wrote = 0; - u32 m_card_read_length = 0; - u32 m_card_read = 0; - u32 m_card_bit = 0; - bool m_card_shutter = true; // Open - u32 m_card_state_call_count = 0; - u8 m_card_offset = 0; + std::unique_ptr m_mag_card_reader; // Serial u32 m_wheel_init = 0; diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 7ed0b580e3..34e4b83f13 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -317,6 +317,9 @@ + + + @@ -1015,6 +1018,9 @@ + + + From 2d9b3c4118d772e57c99a55153843740f13fce17 Mon Sep 17 00:00:00 2001 From: Sepalani Date: Tue, 10 Feb 2026 00:56:38 +0400 Subject: [PATCH 63/71] Debugger/NetworkWidget: Add Triforce support --- Source/Core/Common/Network.cpp | 15 +++ Source/Core/Common/Network.h | 1 + Source/Core/Core/HW/DVD/AMMediaboard.cpp | 87 ++++++------ Source/Core/Core/HW/DVD/AMMediaboard.h | 21 +++ .../Core/DolphinQt/Debugger/NetworkWidget.cpp | 127 +++++++++++++++--- .../Core/DolphinQt/Debugger/NetworkWidget.h | 7 + 6 files changed, 196 insertions(+), 62 deletions(-) diff --git a/Source/Core/Common/Network.cpp b/Source/Core/Common/Network.cpp index 9c161f1b26..24289cc04d 100644 --- a/Source/Core/Common/Network.cpp +++ b/Source/Core/Common/Network.cpp @@ -55,6 +55,21 @@ bool IPv4PortRange::IsMatch(IPv4Port subject) const port_u16 >= first.GetPortValue() && port_u16 <= last.GetPortValue(); } +std::string IPv4PortRange::ToString() const +{ + const std::string ip_range = first.ip_address == last.ip_address ? + Common::IPAddressToString(first.ip_address) : + fmt::format("{}-{}", Common::IPAddressToString(first.ip_address), + Common::IPAddressToString(last.ip_address)); + + if (first.port == 0) + return ip_range; + else if (first.port == last.port) + return fmt::format("{}:{}", ip_range, first.GetPortValue()); + else + return fmt::format("{}:{}-{}", ip_range, first.GetPortValue(), last.GetPortValue()); +} + std::string IPAddressToString(IPAddress ip_address) { return fmt::format("{}", fmt::join(ip_address, ".")); diff --git a/Source/Core/Common/Network.h b/Source/Core/Common/Network.h index 1939d10b2c..5e0da6559e 100644 --- a/Source/Core/Common/Network.h +++ b/Source/Core/Common/Network.h @@ -284,6 +284,7 @@ struct IPv4PortRange IPv4Port last; bool IsMatch(IPv4Port subject) const; + std::string ToString() const; }; std::string IPAddressToString(IPAddress ip_address); diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 4a79a01252..5768203e28 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -16,7 +16,6 @@ #include "Common/FileUtil.h" #include "Common/IOFile.h" #include "Common/Logging/Log.h" -#include "Common/Network.h" #include "Common/ScopeGuard.h" #include "Core/Boot/Boot.h" @@ -216,7 +215,8 @@ static const std::unordered_map s_game_map = { // Sockets FDs are required to go from 0 to 63. // Games use the FD as indexes so we have to workaround it. -static SOCKET s_sockets[64]; + +static SOCKET s_sockets[SOCKET_FD_MAX]; // TODO: Verify this. static constexpr u32 FIRST_VALID_FD = 1; @@ -533,51 +533,48 @@ std::optional ParseIPOverride(std::string_view str) }; } -struct IPAddressOverride +// Caller should check if it matches first! +Common::IPv4Port IPAddressOverride::ApplyOverride(Common::IPv4Port subject) const { - Common::IPv4PortRange original; - Common::IPv4PortRange replacement; + // This logic could probably be better. + // Ranges of different sizes will be weird in general. - // Caller should check if it matches first! - Common::IPv4Port ApplyOverride(Common::IPv4Port subject) const + const auto replacement_first_ip_u32 = replacement.first.GetIPAddressValue(); + const auto ip_count = 1u + u64(replacement.last.GetIPAddressValue()) - replacement_first_ip_u32; + const auto result_ip = + u32(replacement_first_ip_u32 + + ((subject.GetIPAddressValue() - original.first.GetIPAddressValue()) % ip_count)); + + subject.ip_address = std::bit_cast(Common::BigEndianValue{result_ip}); + + const auto replacement_first_port_u16 = replacement.first.GetPortValue(); + const auto port_count = 1u + u32(replacement.last.GetPortValue()) - replacement_first_port_u16; + + // If the replacement includes all ports then we don't alter the port. + // This allows "10.0.0.1:80-88=10.0.0.2" to have the expected behavior. + if (port_count != 65536u) { - // This logic could probably be better. - // Ranges of different sizes will be weird in general. - - const auto replacement_first_ip_u32 = replacement.first.GetIPAddressValue(); - const auto ip_count = 1u + u64(replacement.last.GetIPAddressValue()) - replacement_first_ip_u32; - const auto result_ip = - u32(replacement_first_ip_u32 + - ((subject.GetIPAddressValue() - original.first.GetIPAddressValue()) % ip_count)); - - subject.ip_address = std::bit_cast(Common::BigEndianValue{result_ip}); - - const auto replacement_first_port_u16 = replacement.first.GetPortValue(); - const auto port_count = 1u + u32(replacement.last.GetPortValue()) - replacement_first_port_u16; - - // If the replacement includes all ports then we don't alter the port. - // This allows "10.0.0.1:80-88=10.0.0.2" to have the expected behavior. - if (port_count != 65536u) - { - const auto result_port_u16 = - u16(replacement_first_port_u16 + - ((subject.GetPortValue() - original.first.GetPortValue()) % port_count)); - subject.port = std::bit_cast(Common::BigEndianValue{result_port_u16}); - } - - return subject; + const auto result_port_u16 = + u16(replacement_first_port_u16 + + ((subject.GetPortValue() - original.first.GetPortValue()) % port_count)); + subject.port = std::bit_cast(Common::BigEndianValue{result_port_u16}); } - Common::IPv4Port ReverseOverride(Common::IPv4Port subject) const - { - // Low effort implementation.. - return IPAddressOverride{.original = replacement, .replacement = original}.ApplyOverride( - subject); - } -}; + return subject; +} -using IPOverrides = std::vector; -static IPOverrides GetIPOverrides() +Common::IPv4Port IPAddressOverride::ReverseOverride(Common::IPv4Port subject) const +{ + // Low effort implementation.. + return IPAddressOverride{.original = replacement, .replacement = original}.ApplyOverride(subject); +} + +std::string IPAddressOverride::ToString() const +{ + return fmt::format("{}={}", original.ToString(), replacement.ToString()); +} + +IPOverrides GetIPOverrides() { IPOverrides result; @@ -2180,4 +2177,12 @@ void DoState(PointerWrap& p) } } +s32 DebuggerGetSocket(u32 triforce_fd) +{ + if (triforce_fd < std::size(s_sockets)) + return s32(s_sockets[triforce_fd]); + + WARN_LOG_FMT(AMMEDIABOARD, "GC-AM: Bad socket fd used by the debugger: {}", triforce_fd); + return -1; +} } // namespace AMMediaboard diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.h b/Source/Core/Core/HW/DVD/AMMediaboard.h index 937d2b9228..0e0a573a4d 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.h +++ b/Source/Core/Core/HW/DVD/AMMediaboard.h @@ -10,6 +10,7 @@ #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" +#include "Common/Network.h" #include "DiscIO/Volume.h" @@ -231,6 +232,8 @@ enum SocketStatusCodes SSC_SUCCESS = 70, }; +static constexpr std::size_t SOCKET_FD_MAX = 64; + void Init(); void FirmwareMap(bool on); void InitDIMM(const DiscIO::Volume& volume); @@ -248,6 +251,24 @@ struct ParsedIPOverride std::string_view replacement; std::string_view description; }; + std::optional ParseIPOverride(std::string_view str); +struct IPAddressOverride +{ + Common::IPv4PortRange original; + Common::IPv4PortRange replacement; + + // Caller should check if it matches first! + Common::IPv4Port ApplyOverride(Common::IPv4Port subject) const; + Common::IPv4Port ReverseOverride(Common::IPv4Port subject) const; + std::string ToString() const; +}; + +using IPOverrides = std::vector; + +IPOverrides GetIPOverrides(); + +s32 DebuggerGetSocket(u32 triforce_fd); + }; // namespace AMMediaboard diff --git a/Source/Core/DolphinQt/Debugger/NetworkWidget.cpp b/Source/Core/DolphinQt/Debugger/NetworkWidget.cpp index d7ca4c8dc2..48b027b7b1 100644 --- a/Source/Core/DolphinQt/Debugger/NetworkWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/NetworkWidget.cpp @@ -21,9 +21,13 @@ #include #endif +#include + #include "Common/FileUtil.h" +#include "Common/Network.h" #include "Core/Config/MainSettings.h" #include "Core/Core.h" +#include "Core/HW/DVD/AMMediaboard.h" #include "Core/IOS/Network/SSL.h" #include "Core/IOS/Network/Socket.h" #include "Core/System.h" @@ -139,6 +143,49 @@ QTableWidgetItem* GetSocketName(s32 host_fd) return new QTableWidgetItem(QStringLiteral("%1->%2").arg(sock_name).arg(peer_name)); } + +QTableWidgetItem* GetSocketRedirections(s32 host_fd, const AMMediaboard::IPOverrides& ip_overrides) +{ + if (host_fd < 0 || ip_overrides.empty()) + return new QTableWidgetItem(); + + sockaddr_in sock_addr; + socklen_t sock_addr_len = sizeof(sockaddr_in); + if (getsockname(host_fd, reinterpret_cast(&sock_addr), &sock_addr_len) != 0) + return new QTableWidgetItem(); + const Common::IPv4Port sock_ip_port{std::bit_cast(sock_addr.sin_addr), + sock_addr.sin_port}; + + sockaddr_in peer_addr; + socklen_t peer_addr_len = sizeof(sockaddr_in); + bool has_peer = + getpeername(host_fd, reinterpret_cast(&peer_addr), &peer_addr_len) == 0; + const Common::IPv4Port peer_ip_port = + has_peer ? Common::IPv4Port{std::bit_cast(peer_addr.sin_addr), + peer_addr.sin_port} : + Common::IPv4Port{}; + + QStringList sock_rules; + QStringList peer_rules; + for (const auto& rule : ip_overrides) + { + if (rule.replacement.IsMatch(sock_ip_port)) + sock_rules << QString::fromStdString(rule.ToString()); + if (!has_peer) + continue; + if (rule.replacement.IsMatch(peer_ip_port)) + peer_rules << QString::fromStdString(rule.ToString()); + } + + if (sock_rules.isEmpty() && peer_rules.isEmpty()) + return new QTableWidgetItem(); + if (peer_rules.isEmpty()) + return new QTableWidgetItem(sock_rules.join(QStringLiteral("\n"))); + return new QTableWidgetItem(QStringLiteral("Sock rules:\n%1\n" + "\nPeer rules:%2") + .arg(sock_rules.join(QStringLiteral("\n"))) + .arg(peer_rules.join(QStringLiteral("\n")))); +} } // namespace NetworkWidget::NetworkWidget(QWidget* parent) : QDockWidget(parent) @@ -254,24 +301,12 @@ void NetworkWidget::ConnectWidgets() }); } -void NetworkWidget::Update() +void NetworkWidget::UpdateWiiSocketTable(Core::System& system) { - if (!isVisible()) - return; - - auto& system = Core::System::GetInstance(); - if (Core::GetState(system) != Core::State::Paused) - { - m_socket_table->setDisabled(true); - m_ssl_table->setDisabled(true); - return; - } - - m_socket_table->setDisabled(false); - m_ssl_table->setDisabled(false); - - // needed because there's a race condition on the IOS instance otherwise - const Core::CPUThreadGuard guard(system); + // Show Wii socket blocking state + m_socket_table->showColumn(4); + // Hide Triforce IP overrides + m_socket_table->hideColumn(6); auto* ios = system.GetIOS(); if (!ios) @@ -281,7 +316,6 @@ void NetworkWidget::Update() if (!socket_manager) return; - m_socket_table->setRowCount(0); for (s32 wii_fd = 0; wii_fd < IOS::HLE::WII_SOCKET_FD_MAX; wii_fd++) { m_socket_table->insertRow(wii_fd); @@ -293,9 +327,7 @@ void NetworkWidget::Update() m_socket_table->setItem(wii_fd, 4, GetSocketBlocking(*socket_manager, wii_fd)); m_socket_table->setItem(wii_fd, 5, GetSocketName(host_fd)); } - m_socket_table->resizeColumnsToContents(); - m_ssl_table->setRowCount(0); for (s32 ssl_id = 0; ssl_id < IOS::HLE::NET_SSL_MAXINSTANCES; ssl_id++) { m_ssl_table->insertRow(ssl_id); @@ -312,6 +344,58 @@ void NetworkWidget::Update() m_ssl_table->setItem(ssl_id, 3, GetSocketState(host_fd)); m_ssl_table->setItem(ssl_id, 4, GetSocketName(host_fd)); } +} + +void NetworkWidget::UpdateTriforceSocketTable() +{ + // No easy way to get socket blocking state on Windows + m_socket_table->hideColumn(4); + // Show active IP overrides + m_socket_table->showColumn(6); + const auto ip_overrides = AMMediaboard::GetIPOverrides(); + for (s32 triforce_fd = 0; triforce_fd < AMMediaboard::SOCKET_FD_MAX; triforce_fd++) + { + m_socket_table->insertRow(triforce_fd); + const s32 host_fd = AMMediaboard::DebuggerGetSocket(triforce_fd); + m_socket_table->setItem(triforce_fd, 0, new QTableWidgetItem(QString::number(triforce_fd))); + m_socket_table->setItem(triforce_fd, 1, GetSocketDomain(host_fd)); + m_socket_table->setItem(triforce_fd, 2, GetSocketType(host_fd)); + m_socket_table->setItem(triforce_fd, 3, GetSocketState(host_fd)); + m_socket_table->setItem(triforce_fd, 4, new QTableWidgetItem(QTableWidget::tr("Unknown"))); + m_socket_table->setItem(triforce_fd, 5, GetSocketName(host_fd)); + m_socket_table->setItem(triforce_fd, 6, GetSocketRedirections(host_fd, ip_overrides)); + } +} + +void NetworkWidget::Update() +{ + if (!isVisible()) + return; + + auto& system = Core::System::GetInstance(); + if (Core::GetState(system) != Core::State::Paused) + { + m_socket_table->setDisabled(true); + m_ssl_table->setDisabled(true); + return; + } + + // needed because there's a race condition on the IOS instance otherwise + const Core::CPUThreadGuard guard(system); + m_socket_table->setDisabled(false); + m_socket_table->setRowCount(0); + m_ssl_table->setRowCount(0); + if (system.IsTriforce()) + { + UpdateTriforceSocketTable(); + } + else if (system.IsWii()) + { + m_ssl_table->setDisabled(false); + UpdateWiiSocketTable(system); + } + m_socket_table->resizeColumnsToContents(); + m_socket_table->resizeRowsToContents(); m_ssl_table->resizeColumnsToContents(); const bool is_pcap = Config::Get(Config::MAIN_NETWORK_DUMP_AS_PCAP); @@ -348,7 +432,8 @@ QGroupBox* NetworkWidget::CreateSocketTableGroup() m_socket_table = new QTableWidget(); // i18n: FD stands for file descriptor (and in this case refers to sockets, not regular files) - QStringList header{tr("FD"), tr("Domain"), tr("Type"), tr("State"), tr("Blocking"), tr("Name")}; + QStringList header{tr("FD"), tr("Domain"), tr("Type"), tr("State"), + tr("Blocking"), tr("Name"), tr("Redirections")}; m_socket_table->setColumnCount(static_cast(header.size())); m_socket_table->setHorizontalHeaderLabels(header); diff --git a/Source/Core/DolphinQt/Debugger/NetworkWidget.h b/Source/Core/DolphinQt/Debugger/NetworkWidget.h index cfcf50a5c7..c2bb13e1d6 100644 --- a/Source/Core/DolphinQt/Debugger/NetworkWidget.h +++ b/Source/Core/DolphinQt/Debugger/NetworkWidget.h @@ -14,6 +14,11 @@ class QShowEvent; class QTableWidget; class QTableWidgetItem; +namespace Core +{ +class System; +} + class NetworkWidget : public QDockWidget { Q_OBJECT @@ -29,6 +34,8 @@ private: void CreateWidgets(); void ConnectWidgets(); + void UpdateWiiSocketTable(Core::System& system); + void UpdateTriforceSocketTable(); void Update(); QGroupBox* CreateSocketTableGroup(); From 0fc8f463f2dacb66afc54ebac9ebc05de1a39191 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Fri, 13 Feb 2026 19:36:06 -0600 Subject: [PATCH 64/71] DolphinQt/Debugger/NetworkWidget: Minor cleanups. --- .../Core/DolphinQt/Debugger/NetworkWidget.cpp | 80 +++++++++---------- 1 file changed, 36 insertions(+), 44 deletions(-) diff --git a/Source/Core/DolphinQt/Debugger/NetworkWidget.cpp b/Source/Core/DolphinQt/Debugger/NetworkWidget.cpp index 48b027b7b1..77733bf252 100644 --- a/Source/Core/DolphinQt/Debugger/NetworkWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/NetworkWidget.cpp @@ -100,7 +100,7 @@ QTableWidgetItem* GetSocketState(s32 host_fd) return new QTableWidgetItem(QTableWidget::tr("Unbound")); } -static QTableWidgetItem* GetSocketBlocking(const IOS::HLE::WiiSockMan& socket_manager, s32 wii_fd) +QTableWidgetItem* GetSocketBlocking(const IOS::HLE::WiiSockMan& socket_manager, s32 wii_fd) { if (socket_manager.GetHostSocket(wii_fd) < 0) return new QTableWidgetItem(); @@ -108,7 +108,7 @@ static QTableWidgetItem* GetSocketBlocking(const IOS::HLE::WiiSockMan& socket_ma return new QTableWidgetItem(is_blocking ? QTableWidget::tr("Yes") : QTableWidget::tr("No")); } -static QString GetAddressAndPort(const sockaddr_in& addr) +QString GetAddressAndPort(const sockaddr_in& addr) { char buffer[16]; const char* addr_str = inet_ntop(AF_INET, &addr.sin_addr, buffer, sizeof(buffer)); @@ -231,7 +231,7 @@ void NetworkWidget::closeEvent(QCloseEvent*) Settings::Instance().SetNetworkVisible(false); } -void NetworkWidget::showEvent(QShowEvent* event) +void NetworkWidget::showEvent(QShowEvent*) { Update(); } @@ -255,45 +255,34 @@ void NetworkWidget::ConnectWidgets() { connect(m_dump_format_combo, &QComboBox::currentIndexChanged, this, &NetworkWidget::OnDumpFormatComboChanged); + #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) - connect(m_dump_ssl_read_checkbox, &QCheckBox::checkStateChanged, [](Qt::CheckState state) { - Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_READ, state == Qt::Checked); - }); - connect(m_dump_ssl_write_checkbox, &QCheckBox::checkStateChanged, [](Qt::CheckState state) { - Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_WRITE, state == Qt::Checked); - }); - connect(m_dump_root_ca_checkbox, &QCheckBox::checkStateChanged, [](Qt::CheckState state) { - Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_ROOT_CA, state == Qt::Checked); - }); - connect(m_dump_peer_cert_checkbox, &QCheckBox::checkStateChanged, [](Qt::CheckState state) { - Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_PEER_CERT, state == Qt::Checked); - }); - connect(m_verify_certificates_checkbox, &QCheckBox::checkStateChanged, [](Qt::CheckState state) { - Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_VERIFY_CERTIFICATES, state == Qt::Checked); - }); - connect(m_dump_bba_checkbox, &QCheckBox::checkStateChanged, [](Qt::CheckState state) { - Config::SetBaseOrCurrent(Config::MAIN_NETWORK_DUMP_BBA, state == Qt::Checked); - }); + using CheckState = Qt::CheckState; + static constexpr auto checkStateChanged = &QCheckBox::checkStateChanged; #else - connect(m_dump_ssl_read_checkbox, &QCheckBox::stateChanged, [](int state) { + using CheckState = int; + static constexpr auto checkStateChanged = &QCheckBox::stateChanged; +#endif + + connect(m_dump_ssl_read_checkbox, checkStateChanged, [](CheckState state) { Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_READ, state == Qt::Checked); }); - connect(m_dump_ssl_write_checkbox, &QCheckBox::stateChanged, [](int state) { + connect(m_dump_ssl_write_checkbox, checkStateChanged, [](CheckState state) { Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_WRITE, state == Qt::Checked); }); - connect(m_dump_root_ca_checkbox, &QCheckBox::stateChanged, [](int state) { + connect(m_dump_root_ca_checkbox, checkStateChanged, [](CheckState state) { Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_ROOT_CA, state == Qt::Checked); }); - connect(m_dump_peer_cert_checkbox, &QCheckBox::stateChanged, [](int state) { + connect(m_dump_peer_cert_checkbox, checkStateChanged, [](CheckState state) { Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_DUMP_PEER_CERT, state == Qt::Checked); }); - connect(m_verify_certificates_checkbox, &QCheckBox::stateChanged, [](int state) { + connect(m_verify_certificates_checkbox, checkStateChanged, [](CheckState state) { Config::SetBaseOrCurrent(Config::MAIN_NETWORK_SSL_VERIFY_CERTIFICATES, state == Qt::Checked); }); - connect(m_dump_bba_checkbox, &QCheckBox::stateChanged, [](int state) { + connect(m_dump_bba_checkbox, checkStateChanged, [](CheckState state) { Config::SetBaseOrCurrent(Config::MAIN_NETWORK_DUMP_BBA, state == Qt::Checked); }); -#endif + connect(m_open_dump_folder, &QPushButton::clicked, [] { const std::string location = File::GetUserPath(D_DUMPSSL_IDX); const QUrl url = QUrl::fromLocalFile(QString::fromStdString(location)); @@ -353,7 +342,7 @@ void NetworkWidget::UpdateTriforceSocketTable() // Show active IP overrides m_socket_table->showColumn(6); const auto ip_overrides = AMMediaboard::GetIPOverrides(); - for (s32 triforce_fd = 0; triforce_fd < AMMediaboard::SOCKET_FD_MAX; triforce_fd++) + for (s32 triforce_fd = 0; triforce_fd != AMMediaboard::SOCKET_FD_MAX; ++triforce_fd) { m_socket_table->insertRow(triforce_fd); const s32 host_fd = AMMediaboard::DebuggerGetSocket(triforce_fd); @@ -412,22 +401,25 @@ void NetworkWidget::Update() const int combo_index = int([is_pcap, is_ssl_read, is_ssl_write]() -> FormatComboId { if (is_pcap) return FormatComboId::PCAP; - else if (is_ssl_read && is_ssl_write) + + if (is_ssl_read && is_ssl_write) return FormatComboId::BinarySSL; - else if (is_ssl_read) + + if (is_ssl_read) return FormatComboId::BinarySSLRead; - else if (is_ssl_write) + + if (is_ssl_write) return FormatComboId::BinarySSLWrite; - else - return FormatComboId::None; + + return FormatComboId::None; }()); m_dump_format_combo->setCurrentIndex(combo_index); } QGroupBox* NetworkWidget::CreateSocketTableGroup() { - QGroupBox* socket_table_group = new QGroupBox(tr("Socket table")); - QGridLayout* socket_table_layout = new QGridLayout; + auto* const socket_table_group = new QGroupBox(tr("Socket table")); + auto* const socket_table_layout = new QGridLayout; socket_table_group->setLayout(socket_table_layout); m_socket_table = new QTableWidget(); @@ -450,8 +442,8 @@ QGroupBox* NetworkWidget::CreateSocketTableGroup() QGroupBox* NetworkWidget::CreateSSLContextGroup() { - QGroupBox* ssl_context_group = new QGroupBox(tr("SSL context")); - QGridLayout* ssl_context_layout = new QGridLayout; + auto* const ssl_context_group = new QGroupBox(tr("SSL context")); + auto* const ssl_context_layout = new QGridLayout; ssl_context_group->setLayout(ssl_context_layout); m_ssl_table = new QTableWidget(); @@ -472,8 +464,8 @@ QGroupBox* NetworkWidget::CreateSSLContextGroup() QGroupBox* NetworkWidget::CreateDumpOptionsGroup() { - auto* dump_options_group = new QGroupBox(tr("Dump options")); - auto* dump_options_layout = new QVBoxLayout; + auto* const dump_options_group = new QGroupBox(tr("Dump options")); + auto* const dump_options_layout = new QVBoxLayout; dump_options_group->setLayout(dump_options_layout); m_dump_format_combo = CreateDumpFormatCombo(); @@ -487,9 +479,9 @@ QGroupBox* NetworkWidget::CreateDumpOptionsGroup() m_open_dump_folder = new QPushButton(tr("Open dump folder")); m_open_dump_folder->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - auto* combo_label = new QLabel(tr("Network dump format:")); + auto* const combo_label = new QLabel(tr("Network dump format:")); combo_label->setBuddy(m_dump_format_combo); - auto* combo_layout = new QHBoxLayout; + auto* const combo_layout = new QHBoxLayout; combo_layout->addWidget(combo_label); const int combo_label_space = combo_label->fontMetrics().boundingRect(QStringLiteral("__")).width(); @@ -511,8 +503,8 @@ QGroupBox* NetworkWidget::CreateDumpOptionsGroup() QGroupBox* NetworkWidget::CreateSecurityOptionsGroup() { - auto* security_options_group = new QGroupBox(tr("Security options")); - auto* security_options_layout = new QVBoxLayout; + auto* const security_options_group = new QGroupBox(tr("Security options")); + auto* const security_options_layout = new QVBoxLayout; security_options_group->setLayout(security_options_layout); m_verify_certificates_checkbox = new QCheckBox(tr("Verify certificates")); From f00b1c590e5513e6248b32102a20a22a5d7d0849 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Fri, 13 Feb 2026 02:36:31 -0600 Subject: [PATCH 65/71] DolphinQt: Add Triforce tab to main settings window. --- Source/Core/DolphinQt/CMakeLists.txt | 4 +- .../Config/GamecubeControllersWidget.cpp | 4 +- .../DolphinQt/Config/Mapping/GCPadEmu.cpp | 13 +-- .../Core/DolphinQt/Config/Mapping/GCPadEmu.h | 20 +++- .../Config/Mapping/MappingWindow.cpp | 20 ++-- .../DolphinQt/Config/Mapping/MappingWindow.h | 2 + .../Core/DolphinQt/Config/SettingsWindow.cpp | 4 +- Source/Core/DolphinQt/Config/SettingsWindow.h | 1 + Source/Core/DolphinQt/DolphinQt.vcxproj | 4 +- Source/Core/DolphinQt/MainWindow.cpp | 6 + Source/Core/DolphinQt/MainWindow.h | 2 + .../Core/DolphinQt/Settings/GameCubePane.cpp | 10 +- Source/Core/DolphinQt/Settings/GameCubePane.h | 6 +- .../TriforceBaseboardSettingsDialog.h | 26 ----- ...ardSettingsDialog.cpp => TriforcePane.cpp} | 109 +++++++++++++----- Source/Core/DolphinQt/Settings/TriforcePane.h | 14 +++ 16 files changed, 156 insertions(+), 89 deletions(-) delete mode 100644 Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.h rename Source/Core/DolphinQt/Settings/{TriforceBaseboardSettingsDialog.cpp => TriforcePane.cpp} (61%) create mode 100644 Source/Core/DolphinQt/Settings/TriforcePane.h diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index 0899ef5307..c61edc244b 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -376,8 +376,8 @@ add_executable(dolphin-emu Settings/OnScreenDisplayPane.h Settings/PathPane.cpp Settings/PathPane.h - Settings/TriforceBaseboardSettingsDialog.cpp - Settings/TriforceBaseboardSettingsDialog.h + Settings/TriforcePane.cpp + Settings/TriforcePane.h Settings/USBDevicePicker.cpp Settings/USBDevicePicker.h Settings/WiiPane.cpp diff --git a/Source/Core/DolphinQt/Config/GamecubeControllersWidget.cpp b/Source/Core/DolphinQt/Config/GamecubeControllersWidget.cpp index f995a67a1a..33c54edbd6 100644 --- a/Source/Core/DolphinQt/Config/GamecubeControllersWidget.cpp +++ b/Source/Core/DolphinQt/Config/GamecubeControllersWidget.cpp @@ -133,7 +133,6 @@ void GamecubeControllersWidget::OnGCPadConfigure(size_t index) case SerialInterface::SIDEVICE_NONE: case SerialInterface::SIDEVICE_GC_GBA: return; - case SerialInterface::SIDEVICE_AM_BASEBOARD: case SerialInterface::SIDEVICE_GC_CONTROLLER: type = MappingWindow::Type::MAPPING_GCPAD; break; @@ -158,6 +157,9 @@ void GamecubeControllersWidget::OnGCPadConfigure(size_t index) case SerialInterface::SIDEVICE_GC_KEYBOARD: type = MappingWindow::Type::MAPPING_GC_KEYBOARD; break; + case SerialInterface::SIDEVICE_AM_BASEBOARD: + type = MappingWindow::Type::MAPPING_AM_BASEBOARD; + break; default: return; } diff --git a/Source/Core/DolphinQt/Config/Mapping/GCPadEmu.cpp b/Source/Core/DolphinQt/Config/Mapping/GCPadEmu.cpp index 7ae4a68083..a8af073792 100644 --- a/Source/Core/DolphinQt/Config/Mapping/GCPadEmu.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/GCPadEmu.cpp @@ -8,19 +8,15 @@ #include "Core/HW/GCPad.h" #include "Core/HW/GCPadEmu.h" -#include "Core/HW/SI/SI.h" -#include "Core/HW/SI/SI_DeviceAMBaseboard.h" - -#include "Core/Config/MainSettings.h" #include "InputCommon/InputConfig.h" -GCPadEmu::GCPadEmu(MappingWindow* window) : MappingWidget(window) +GCPadEmu::GCPadEmu(MappingWindow* window, SubType sub_type) : MappingWidget(window) { - CreateMainLayout(); + CreateMainLayout(sub_type); } -void GCPadEmu::CreateMainLayout() +void GCPadEmu::CreateMainLayout(SubType sub_type) { auto* layout = new QGridLayout; @@ -28,8 +24,7 @@ void GCPadEmu::CreateMainLayout() 0); layout->addWidget(CreateGroupBox(tr("D-Pad"), Pad::GetGroup(GetPort(), PadGroup::DPad)), 1, 0); - if (Config::Get(Config::GetInfoForSIDevice(0)) == - SerialInterface::SIDevices::SIDEVICE_AM_BASEBOARD) + if (sub_type == SubType::AMBaseboard) { layout->addWidget(CreateGroupBox(tr("Triforce"), Pad::GetGroup(GetPort(), PadGroup::Triforce)), 2, 0); diff --git a/Source/Core/DolphinQt/Config/Mapping/GCPadEmu.h b/Source/Core/DolphinQt/Config/Mapping/GCPadEmu.h index dad55b4c76..6b315b2a2c 100644 --- a/Source/Core/DolphinQt/Config/Mapping/GCPadEmu.h +++ b/Source/Core/DolphinQt/Config/Mapping/GCPadEmu.h @@ -9,12 +9,28 @@ class GCPadEmu final : public MappingWidget { Q_OBJECT public: - explicit GCPadEmu(MappingWindow* window); + enum class SubType + { + StandardController, + AMBaseboard, + }; + + GCPadEmu(MappingWindow* window, SubType sub_type); InputConfig* GetConfig() override; private: void LoadSettings() override; void SaveSettings() override; - void CreateMainLayout(); + void CreateMainLayout(SubType sub_type); }; + +[[nodiscard]] inline auto CreateStandardControllerMappingWidget(MappingWindow* parent) +{ + return new GCPadEmu{parent, GCPadEmu::SubType::StandardController}; +} + +[[nodiscard]] inline auto CreateAMBaseboardMappingWidget(MappingWindow* parent) +{ + return new GCPadEmu{parent, GCPadEmu::SubType::AMBaseboard}; +} diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp index d2151a0f06..d52e950a27 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp @@ -460,18 +460,9 @@ void MappingWindow::SetMappingType(MappingWindow::Type type) case Type::MAPPING_GC_STEERINGWHEEL: case Type::MAPPING_GC_DANCEMAT: case Type::MAPPING_GCPAD: - widget = new GCPadEmu(this); - if (Config::Get(Config::GetInfoForSIDevice(GetPort())) == - SerialInterface::SIDevices::SIDEVICE_AM_BASEBOARD) - { - setWindowTitle(tr("Triforce Baseboard at Port %1").arg(GetPort() + 1)); - AddWidget(tr("Triforce Baseboard"), widget); - } - else - { - setWindowTitle(tr("GameCube Controller at Port %1").arg(GetPort() + 1)); - AddWidget(tr("GameCube Controller"), widget); - } + widget = CreateStandardControllerMappingWidget(this); + setWindowTitle(tr("GameCube Controller at Port %1").arg(GetPort() + 1)); + AddWidget(tr("GameCube Controller"), widget); break; case Type::MAPPING_GC_MICROPHONE: widget = new GCMicrophone(this); @@ -528,6 +519,11 @@ void MappingWindow::SetMappingType(MappingWindow::Type type) setWindowTitle(tr("Free Look Controller %1").arg(GetPort() + 1)); } break; + case Type::MAPPING_AM_BASEBOARD: + widget = CreateAMBaseboardMappingWidget(this); + setWindowTitle(tr("Triforce Baseboard at Port %1").arg(GetPort() + 1)); + AddWidget(tr("Triforce Baseboard"), widget); + break; default: return; } diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h index 222d3e3a58..40789b8469 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h @@ -45,6 +45,8 @@ public: MAPPING_HOTKEYS, // Freelook MAPPING_FREELOOK, + // Triforce + MAPPING_AM_BASEBOARD, }; explicit MappingWindow(QWidget* parent, Type type, int port_num); diff --git a/Source/Core/DolphinQt/Config/SettingsWindow.cpp b/Source/Core/DolphinQt/Config/SettingsWindow.cpp index 2898b4cd8a..e80b1fa7ff 100644 --- a/Source/Core/DolphinQt/Config/SettingsWindow.cpp +++ b/Source/Core/DolphinQt/Config/SettingsWindow.cpp @@ -29,6 +29,7 @@ #include "DolphinQt/Settings/InterfacePane.h" #include "DolphinQt/Settings/OnScreenDisplayPane.h" #include "DolphinQt/Settings/PathPane.h" +#include "DolphinQt/Settings/TriforcePane.h" #include "DolphinQt/Settings/WiiPane.h" StackedSettingsWindow::StackedSettingsWindow(QWidget* parent) : QDialog{parent} @@ -197,8 +198,9 @@ SettingsWindow::SettingsWindow(MainWindow* parent) : StackedSettingsWindow{paren AddWrappedPane(new OnScreenDisplayPane, tr("On-Screen Display")); AddWrappedPane(new AudioPane, tr("Audio")); AddWrappedPane(new PathPane, tr("Paths")); - AddWrappedPane(new GameCubePane, tr("GameCube")); + AddWrappedPane(new GameCubePane{parent}, tr("GameCube")); AddWrappedPane(new WiiPane, tr("Wii")); + AddWrappedPane(new TriforcePane, tr("Triforce")); AddWrappedPane(new AdvancedPane, tr("Advanced")); OnDoneCreatingPanes(); diff --git a/Source/Core/DolphinQt/Config/SettingsWindow.h b/Source/Core/DolphinQt/Config/SettingsWindow.h index 9048e83385..3874c75ae0 100644 --- a/Source/Core/DolphinQt/Config/SettingsWindow.h +++ b/Source/Core/DolphinQt/Config/SettingsWindow.h @@ -49,6 +49,7 @@ enum class SettingsWindowPaneIndex : int Paths, GameCube, Wii, + Triforce, Advanced, }; diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index 33741cb09c..9df5caf413 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -223,7 +223,7 @@ - + @@ -440,7 +440,7 @@ - + diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index e29539ae2d..962f75e96c 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -1315,6 +1315,12 @@ void MainWindow::ShowControllersWindow() m_settings_window->SelectPane(SettingsWindowPaneIndex::Controllers); } +void MainWindow::ShowTriforceWindow() +{ + ShowSettingsWindow(); + m_settings_window->SelectPane(SettingsWindowPaneIndex::Triforce); +} + void MainWindow::ShowFreeLookWindow() { if (!m_freelook_window) diff --git a/Source/Core/DolphinQt/MainWindow.h b/Source/Core/DolphinQt/MainWindow.h index bb7307c98c..c0d5bd110e 100644 --- a/Source/Core/DolphinQt/MainWindow.h +++ b/Source/Core/DolphinQt/MainWindow.h @@ -92,6 +92,8 @@ public: bool eventFilter(QObject* object, QEvent* event) override; QMenu* createPopupMenu() override; + void ShowTriforceWindow(); + signals: void ReadOnlyModeChanged(bool read_only); void RecordingStatusChanged(bool recording); diff --git a/Source/Core/DolphinQt/Settings/GameCubePane.cpp b/Source/Core/DolphinQt/Settings/GameCubePane.cpp index 692258edb5..b4ee1f25ad 100644 --- a/Source/Core/DolphinQt/Settings/GameCubePane.cpp +++ b/Source/Core/DolphinQt/Settings/GameCubePane.cpp @@ -31,6 +31,7 @@ #include "Core/Core.h" #include "Core/HW/EXI/EXI.h" #include "Core/HW/GCMemcard/GCMemcard.h" +#include "DolphinQt/Config/SettingsWindow.h" #ifdef HAS_LIBMGBA #include "Core/NetPlayServer.h" #endif @@ -50,16 +51,18 @@ #include "DolphinQt/QtUtils/SignalBlocking.h" #include "DolphinQt/Settings.h" #include "DolphinQt/Settings/BroadbandAdapterSettingsDialog.h" -#include "DolphinQt/Settings/TriforceBaseboardSettingsDialog.h" +#include "DolphinQt/Settings/TriforcePane.h" constexpr std::initializer_list GUI_SLOTS = { ExpansionInterface::Slot::A, ExpansionInterface::Slot::B, ExpansionInterface::Slot::SP1}; -GameCubePane::GameCubePane() +GameCubePane::GameCubePane(MainWindow* main_window) { CreateWidgets(); LoadSettings(); ConnectWidgets(); + + connect(this, &GameCubePane::ShowTriforceWindow, main_window, &MainWindow::ShowTriforceWindow); } void GameCubePane::CreateWidgets() @@ -423,8 +426,7 @@ void GameCubePane::OnConfigPressed(ExpansionInterface::Slot slot) } case ExpansionInterface::EXIDeviceType::Baseboard: { - TriforceBaseboardSettingsDialog dialog(this); - dialog.exec(); + ShowTriforceWindow(); return; } default: diff --git a/Source/Core/DolphinQt/Settings/GameCubePane.h b/Source/Core/DolphinQt/Settings/GameCubePane.h index bf7c7c2e6d..a97e82af3f 100644 --- a/Source/Core/DolphinQt/Settings/GameCubePane.h +++ b/Source/Core/DolphinQt/Settings/GameCubePane.h @@ -11,6 +11,7 @@ #include "Common/EnumMap.h" #include "Core/HW/EXI/EXI.h" +#include "DolphinQt/MainWindow.h" class ConfigBool; class ConfigChoice; @@ -28,10 +29,13 @@ class GameCubePane : public QWidget { Q_OBJECT public: - explicit GameCubePane(); + explicit GameCubePane(MainWindow* main_window); static std::string GetOpenGBARom(std::string_view title); +signals: + void ShowTriforceWindow(); + private: void CreateWidgets(); void ConnectWidgets(); diff --git a/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.h b/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.h deleted file mode 100644 index f57d562da1..0000000000 --- a/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.h +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2026 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include - -class QLineEdit; -class QTableWidget; - -class TriforceBaseboardSettingsDialog final : public QDialog -{ - Q_OBJECT - -public: - explicit TriforceBaseboardSettingsDialog(QWidget* parent); - -private: - void LoadConfig(); - void SaveConfig(); - - void LoadDefault(); - void OnClear(); - - QTableWidget* m_ip_overrides_table; -}; diff --git a/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.cpp b/Source/Core/DolphinQt/Settings/TriforcePane.cpp similarity index 61% rename from Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.cpp rename to Source/Core/DolphinQt/Settings/TriforcePane.cpp index 5ae697c148..6efb9b94b2 100644 --- a/Source/Core/DolphinQt/Settings/TriforceBaseboardSettingsDialog.cpp +++ b/Source/Core/DolphinQt/Settings/TriforcePane.cpp @@ -1,12 +1,11 @@ // Copyright 2026 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "DolphinQt/Settings/TriforceBaseboardSettingsDialog.h" +#include "DolphinQt/Settings/TriforcePane.h" #include #include #include -#include #include #include #include @@ -19,25 +18,84 @@ #include "Core/Config/MainSettings.h" #include "Core/HW/DVD/AMMediaboard.h" +#include "DolphinQt/Config/Mapping/MappingWindow.h" +#include "DolphinQt/QtUtils/NonDefaultQPushButton.h" #include "DolphinQt/QtUtils/QtUtils.h" -static constexpr int COLUMN_EMULATED = 0; -static constexpr int COLUMN_REAL = 1; -static constexpr int COLUMN_DESCRIPTION = 2; - -TriforceBaseboardSettingsDialog::TriforceBaseboardSettingsDialog(QWidget* parent) : QDialog{parent} +namespace { - setWindowTitle(tr("Triforce Baseboard")); - auto* const dialog_layout = new QVBoxLayout{this}; +constexpr int COLUMN_EMULATED = 0; +constexpr int COLUMN_REAL = 1; +constexpr int COLUMN_DESCRIPTION = 2; + +class IPAddressOverridesDialog final : public QDialog +{ +public: + explicit IPAddressOverridesDialog(QWidget* parent); + +private: + void LoadConfig(); + void SaveConfig() const; + + void LoadDefault(); + void OnClear(); + + QTableWidget* m_ip_overrides_table; +}; + +} // namespace + +TriforcePane::TriforcePane() +{ + auto* const main_layout = new QVBoxLayout{this}; + + auto* const controllers_group = new QGroupBox{tr("Controllers")}; + main_layout->addWidget(controllers_group); + + auto* const controllers_layout = new QVBoxLayout{controllers_group}; + + auto* const configure_controllers_button = new NonDefaultQPushButton{tr("Configure")}; + controllers_layout->addWidget(configure_controllers_button); + + connect(configure_controllers_button, &QPushButton::clicked, this, [this] { + auto* const window = new MappingWindow(this, MappingWindow::Type::MAPPING_AM_BASEBOARD, 0); + window->setAttribute(Qt::WA_DeleteOnClose, true); + window->setWindowModality(Qt::WindowModality::WindowModal); + window->show(); + }); auto* const ip_override_group = new QGroupBox{tr("IP Address Overrides")}; - dialog_layout->addWidget(ip_override_group); + main_layout->addWidget(ip_override_group); auto* const ip_override_layout = new QVBoxLayout{ip_override_group}; + auto* const configure_ip_overrides_button = new NonDefaultQPushButton{tr("Configure")}; + ip_override_layout->addWidget(configure_ip_overrides_button); + + connect(configure_ip_overrides_button, &QPushButton::clicked, this, [this] { + auto* const ip_overrides = new IPAddressOverridesDialog{this}; + ip_overrides->setAttribute(Qt::WA_DeleteOnClose); + ip_overrides->open(); + }); + + main_layout->addStretch(1); +} + +IPAddressOverridesDialog::IPAddressOverridesDialog(QWidget* parent) : QDialog{parent} +{ + setWindowTitle(tr("IP Address Overrides")); + + auto* const main_layout = new QVBoxLayout{this}; + + auto* const load_default_button = new QPushButton(tr("Default")); + connect(load_default_button, &QPushButton::clicked, this, &IPAddressOverridesDialog::LoadDefault); + + auto* const clear_button = new QPushButton(tr("Clear")); + connect(clear_button, &QPushButton::clicked, this, &IPAddressOverridesDialog::OnClear); + m_ip_overrides_table = new QTableWidget; - ip_override_layout->addWidget(m_ip_overrides_table); + main_layout->addWidget(m_ip_overrides_table); m_ip_overrides_table->setColumnCount(3); m_ip_overrides_table->setHorizontalHeaderLabels({tr("Emulated"), tr("Real"), tr("Description")}); @@ -48,23 +106,15 @@ TriforceBaseboardSettingsDialog::TriforceBaseboardSettingsDialog(QWidget* parent QAbstractItemView::EditKeyPressed); auto* const button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - dialog_layout->addWidget(button_box); - - auto* const load_default_button = new QPushButton(tr("Default")); - connect(load_default_button, &QPushButton::clicked, this, - &TriforceBaseboardSettingsDialog::LoadDefault); - - auto* const clear_button = new QPushButton(tr("Clear")); - connect(clear_button, &QPushButton::clicked, this, &TriforceBaseboardSettingsDialog::OnClear); + main_layout->addWidget(button_box); button_box->addButton(load_default_button, QDialogButtonBox::ActionRole); button_box->addButton(clear_button, QDialogButtonBox::ActionRole); - connect(button_box, &QDialogButtonBox::accepted, this, &TriforceBaseboardSettingsDialog::accept); - connect(button_box, &QDialogButtonBox::rejected, this, &TriforceBaseboardSettingsDialog::reject); + connect(button_box, &QDialogButtonBox::accepted, this, &IPAddressOverridesDialog::accept); + connect(button_box, &QDialogButtonBox::rejected, this, &IPAddressOverridesDialog::reject); - connect(this, &TriforceBaseboardSettingsDialog::accepted, this, - &TriforceBaseboardSettingsDialog::SaveConfig); + connect(this, &IPAddressOverridesDialog::accepted, this, &IPAddressOverridesDialog::SaveConfig); connect(m_ip_overrides_table, &QTableWidget::itemChanged, this, [this](QTableWidgetItem* item) { const int row_count = m_ip_overrides_table->rowCount(); @@ -87,7 +137,7 @@ TriforceBaseboardSettingsDialog::TriforceBaseboardSettingsDialog(QWidget* parent QtUtils::AdjustSizeWithinScreen(this); } -void TriforceBaseboardSettingsDialog::LoadConfig() +void IPAddressOverridesDialog::LoadConfig() { m_ip_overrides_table->setRowCount(0); @@ -120,7 +170,7 @@ void TriforceBaseboardSettingsDialog::LoadConfig() } } -void TriforceBaseboardSettingsDialog::SaveConfig() +void IPAddressOverridesDialog::SaveConfig() const { // Ignore our empty row. const int row_count = m_ip_overrides_table->rowCount() - 1; @@ -152,7 +202,7 @@ void TriforceBaseboardSettingsDialog::SaveConfig() fmt::format("{}", fmt::join(replacements, ","))); } -void TriforceBaseboardSettingsDialog::LoadDefault() +void IPAddressOverridesDialog::LoadDefault() { // This alters the config before "OK" is pressed. Bad UX.. @@ -161,8 +211,9 @@ void TriforceBaseboardSettingsDialog::LoadDefault() LoadConfig(); } -void TriforceBaseboardSettingsDialog::OnClear() +void IPAddressOverridesDialog::OnClear() { - Config::SetBaseOrCurrent(Config::MAIN_TRIFORCE_IP_OVERRIDES, ""); - LoadConfig(); + m_ip_overrides_table->setRowCount(0); + // Add the final empty row. + m_ip_overrides_table->insertRow(0); } diff --git a/Source/Core/DolphinQt/Settings/TriforcePane.h b/Source/Core/DolphinQt/Settings/TriforcePane.h new file mode 100644 index 0000000000..49038b3c5c --- /dev/null +++ b/Source/Core/DolphinQt/Settings/TriforcePane.h @@ -0,0 +1,14 @@ +// Copyright 2026 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +class TriforcePane final : public QWidget +{ + Q_OBJECT + +public: + TriforcePane(); +}; From 282e14cce6c6472c668c8efb05cb2f2b05a2af1f Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Fri, 13 Feb 2026 19:39:10 -0600 Subject: [PATCH 66/71] AMMediaboard: Move things from global namespace to AMMediaboard namespace. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 6 +++--- Source/Core/Core/HW/DVD/AMMediaboard.h | 18 +++++++++--------- .../Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp | 4 ++-- .../Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp | 2 ++ 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 5768203e28..1d395efc26 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -75,14 +75,14 @@ static int WSAGetLastError() #endif +namespace AMMediaboard +{ + MediaBoardRange::MediaBoardRange(u32 start_, u32 size_, std::span buffer_) : start{start_}, end{start_ + size_}, buffer{buffer_.data()}, buffer_size{buffer_.size()} { } -namespace AMMediaboard -{ - using Common::SEND_FLAGS; enum class GuestSocket : s32 diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.h b/Source/Core/Core/HW/DVD/AMMediaboard.h index 0e0a573a4d..f33dc177ec 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.h +++ b/Source/Core/Core/HW/DVD/AMMediaboard.h @@ -14,6 +14,15 @@ #include "DiscIO/Volume.h" +namespace Core +{ +class CPUThreadGuard; +class System; +} // namespace Core + +namespace AMMediaboard +{ + enum GameType { FZeroAX = 1, @@ -59,12 +68,6 @@ enum InquiryType Version2 = 0x29484100, }; -namespace Core -{ -class CPUThreadGuard; -class System; -} // namespace Core - struct MediaBoardRange { u32 start; @@ -75,9 +78,6 @@ struct MediaBoardRange MediaBoardRange(u32 start_, u32 size_, std::span buffer_); }; -namespace AMMediaboard -{ - enum class AMMBDICommand : u16 { Inquiry = 0x12, diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp index fb449860ad..3381c8fbbc 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp @@ -77,8 +77,8 @@ CEXIBaseboard::CEXIBaseboard(Core::System& system) : IEXIDevice(system) // Virtua Striker 4 and Gekitou Pro Yakyuu need a higher FIRM version // Which is read from the backup data?! - if (AMMediaboard::GetGameType() == VirtuaStriker4 || - AMMediaboard::GetGameType() == GekitouProYakyuu) + if (AMMediaboard::GetGameType() == AMMediaboard::VirtuaStriker4 || + AMMediaboard::GetGameType() == AMMediaboard::GekitouProYakyuu) { const u64 backup_size = m_backup.GetSize(); if (backup_size >= 0x20C + 0x1F4) diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp index 0d23288539..58f182fc9a 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp @@ -42,6 +42,8 @@ namespace SerialInterface { +using namespace AMMediaboard; + void JVSIOMessage::Start(int node) { m_last_start = m_pointer; From 0ff8fb71a32d8791b6ec4398762091d7f04ab7ce Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Fri, 13 Feb 2026 19:42:45 -0600 Subject: [PATCH 67/71] Triforce: Minor cleanups. --- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 40 ++++++++----------- .../Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp | 8 +--- .../Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp | 6 --- 3 files changed, 17 insertions(+), 37 deletions(-) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 1d395efc26..38f98ef53d 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -6,32 +6,24 @@ #include #include #include -#include #include #include #include +#include "Common/BitUtils.h" #include "Common/CommonTypes.h" #include "Common/FileUtil.h" #include "Common/IOFile.h" #include "Common/Logging/Log.h" #include "Common/ScopeGuard.h" -#include "Core/Boot/Boot.h" -#include "Core/BootManager.h" #include "Core/Config/MainSettings.h" #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/HLE/HLE.h" -#include "Core/HW/DVD/DVDInterface.h" -#include "Core/HW/DVD/DVDThread.h" -#include "Core/HW/EXI/EXI.h" #include "Core/HW/EXI/EXI_DeviceBaseboard.h" -#include "Core/HW/MMIO.h" #include "Core/HW/Memmap.h" -#include "Core/HW/SI/SI.h" -#include "Core/HW/SI/SI_Device.h" #include "Core/IOS/Network/Socket.h" #include "Core/Movie.h" #include "Core/System.h" @@ -216,7 +208,7 @@ static const std::unordered_map s_game_map = { // Sockets FDs are required to go from 0 to 63. // Games use the FD as indexes so we have to workaround it. -static SOCKET s_sockets[SOCKET_FD_MAX]; +static std::array s_sockets; // TODO: Verify this. static constexpr u32 FIRST_VALID_FD = 1; @@ -436,13 +428,13 @@ static File::IOFile OpenOrCreateFile(const std::string& filename) void Init() { - std::ranges::fill(s_media_buffer_32, 0); - std::ranges::fill(s_network_buffer, 0); - std::ranges::fill(s_network_command_buffer, 0); - std::ranges::fill(s_firmware, -1); - std::ranges::fill(s_sockets, SOCKET_ERROR); - std::ranges::fill(s_allnet_buffer, 0); - std::ranges::fill(s_allnet_settings, 0); + s_media_buffer_32.fill(0); + s_network_buffer.fill(0); + s_network_command_buffer.fill(0); + s_firmware.fill(-1); + s_sockets.fill(SOCKET_ERROR); + s_allnet_buffer.fill(0); + s_allnet_settings.fill(0); s_game_modified_ip_address = {}; @@ -961,7 +953,7 @@ static void AMMBCommandRecv(u32 parameter_offset, u32 network_buffer_base) len = 0; } - int ret = recv(fd, reinterpret_cast(s_network_buffer.data() + off), len, 0); + const int ret = recv(fd, reinterpret_cast(s_network_buffer.data() + off), len, 0); const int err = WSAGetLastError(); DEBUG_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: recv( {}, 0x{:08x}, {} ):{} {}", fd, off, len, ret, err); @@ -1984,9 +1976,9 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u // This sends a UDP packet to previously defined Target IP/Port case AMMBCommand::SearchDevices: { - u16 unknown = s_media_buffer[0x25] | s_media_buffer[0x24] << 8; - u16 off = s_media_buffer[0x26] | s_media_buffer[0x27] << 8; - u32 addr = s_media_buffer_32[10]; + const u16 unknown = s_media_buffer[0x25] | s_media_buffer[0x24] << 8; + const u16 off = s_media_buffer[0x26] | s_media_buffer[0x27] << 8; + const u32 addr = s_media_buffer_32[10]; NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: SearchDevices: ({})", unknown); NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: Offset: ({:04x})", off); @@ -1997,7 +1989,7 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u break; } - const u8* data = s_network_buffer.data() + (off + addr - NetworkBufferAddress2); + const u8* const data = s_network_buffer.data() + (off + addr - NetworkBufferAddress2); for (u32 i = 0; i < 0x20; i += 0x10) { @@ -2012,8 +2004,8 @@ u32 ExecuteCommand(std::array& dicmd_buf, u32* diimm_buf, u32 address, u case AMMBCommand::Unknown_608: { const u32 ip = s_media_buffer_32[10]; - u16 port = Common::swap16(s_media_buffer[6] | s_media_buffer[7] << 8); - u16 flag = s_media_buffer[10] | s_media_buffer[11] << 8; + const u16 port = Common::swap16(s_media_buffer[6] | s_media_buffer[7] << 8); + const u16 flag = s_media_buffer[10] | s_media_buffer[11] << 8; NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: 0x608( {} {} {} )", ip, port, flag); } diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp index 3381c8fbbc..c285d69189 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp @@ -8,24 +8,18 @@ #include +#include "Common/BitUtils.h" #include "Common/Buffer.h" #include "Common/CommonTypes.h" #include "Common/FileUtil.h" #include "Common/IOFile.h" #include "Common/Logging/Log.h" -#include "Core/Boot/Boot.h" -#include "Core/BootManager.h" #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/HW/DVD/AMMediaboard.h" -#include "Core/HW/DVD/DVDInterface.h" #include "Core/HW/EXI/EXI.h" -#include "Core/HW/EXI/EXI_Device.h" -#include "Core/HW/MMIO.h" #include "Core/HW/Memmap.h" -#include "Core/Movie.h" -#include "Core/PowerPC/PowerPC.h" #include "Core/System.h" static bool s_interrupt_set = false; diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp index 58f182fc9a..5532c06b87 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp @@ -15,17 +15,11 @@ #include "Common/MsgHandler.h" #include "Common/Swap.h" -#include "Core/Boot/Boot.h" -#include "Core/BootManager.h" -#include "Core/Config/MainSettings.h" #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/CoreTiming.h" #include "Core/HW/DVD/AMMediaboard.h" -#include "Core/HW/DVD/DVDInterface.h" -#include "Core/HW/EXI/EXI.h" #include "Core/HW/GCPad.h" -#include "Core/HW/MMIO.h" #include "Core/HW/MagCard/C1231BR.h" #include "Core/HW/MagCard/C1231LR.h" #include "Core/HW/Memmap.h" From cbd43914a51f8b9c20133cf0b5f9204b7d6c8b7f Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Fri, 13 Feb 2026 20:44:40 -0600 Subject: [PATCH 68/71] Network: Improve IPv4PortRange::ToString to support CIDR notation and only last octet difference. --- Source/Core/Common/Network.cpp | 36 +++++++++++++++++----- Source/UnitTests/Common/StringUtilTest.cpp | 36 ++++++++++++++++++++++ 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/Source/Core/Common/Network.cpp b/Source/Core/Common/Network.cpp index 24289cc04d..d44d106da5 100644 --- a/Source/Core/Common/Network.cpp +++ b/Source/Core/Common/Network.cpp @@ -57,17 +57,39 @@ bool IPv4PortRange::IsMatch(IPv4Port subject) const std::string IPv4PortRange::ToString() const { - const std::string ip_range = first.ip_address == last.ip_address ? - Common::IPAddressToString(first.ip_address) : - fmt::format("{}-{}", Common::IPAddressToString(first.ip_address), - Common::IPAddressToString(last.ip_address)); + const u32 first_ip_value = first.GetIPAddressValue(); + const u32 last_ip_value = last.GetIPAddressValue(); + const u32 different_bits = first_ip_value ^ last_ip_value; + const u32 common_high_bits = std::countl_zero(different_bits); + + std::string ip_range = Common::IPAddressToString(first.ip_address); + if (common_high_bits == 32) + { + // Identical first and last IP. + } + else if ((last_ip_value - first_ip_value + 1) << common_high_bits == 0) + { + // An exact network range can use CIDR notation. + ip_range = fmt::format("{}/{}", ip_range, common_high_bits); + } + else if (common_high_bits >= 24) + { + // Only the last octet is different. + ip_range = fmt::format("{}-{}", ip_range, last.ip_address.back()); + } + else + { + // Plainly specified range. + ip_range = fmt::format("{}-{}", ip_range, Common::IPAddressToString(last.ip_address)); + } if (first.port == 0) return ip_range; - else if (first.port == last.port) + + if (first.port == last.port) return fmt::format("{}:{}", ip_range, first.GetPortValue()); - else - return fmt::format("{}:{}-{}", ip_range, first.GetPortValue(), last.GetPortValue()); + + return fmt::format("{}:{}-{}", ip_range, first.GetPortValue(), last.GetPortValue()); } std::string IPAddressToString(IPAddress ip_address) diff --git a/Source/UnitTests/Common/StringUtilTest.cpp b/Source/UnitTests/Common/StringUtilTest.cpp index c8bd5f4deb..f7c3b9169b 100644 --- a/Source/UnitTests/Common/StringUtilTest.cpp +++ b/Source/UnitTests/Common/StringUtilTest.cpp @@ -373,6 +373,42 @@ TEST(StringUtil, StringToIPv4PortRange) EXPECT_TRUE(parse("192.168.0.13-14:80-81")->IsMatch(parse("192.168.0.14:81")->first)); } +TEST(StringUtil, IPv4PortRangeToString) +{ + Common::IPv4PortRange subject{ + .first = {{10, 3, 0, 127}, 0}, + .last = {{10, 3, 0, 127}, 0}, + }; + + EXPECT_EQ(subject.ToString(), "10.3.0.127"); + + subject.last.port = Common::swap16(80); // First port is still zero. + EXPECT_EQ(subject.ToString(), "10.3.0.127"); + + subject.first.port = Common::swap16(80); + EXPECT_EQ(subject.ToString(), "10.3.0.127:80"); + + subject.last.port = Common::swap16(88); + EXPECT_EQ(subject.ToString(), "10.3.0.127:80-88"); + + subject.last.ip_address = {10, 3, 0, 128}; + EXPECT_EQ(subject.ToString(), "10.3.0.127-128:80-88"); + + subject.last.ip_address = {10, 3, 7, 35}; + EXPECT_EQ(subject.ToString(), "10.3.0.127-10.3.7.35:80-88"); + + subject.first.ip_address = {192, 168, 0, 0}; + subject.last.ip_address = {192, 168, 0, 255}; + EXPECT_EQ(subject.ToString(), "192.168.0.0/24:80-88"); + + subject.last.ip_address = {192, 168, 3, 255}; + EXPECT_EQ(subject.ToString(), "192.168.0.0/22:80-88"); + + subject.first.ip_address = {}; + subject.last.ip_address = {255, 255, 255, 255}; + EXPECT_EQ(subject.ToString(), "0.0.0.0/0:80-88"); +} + TEST(StringUtil, CharacterEncodingConversion) { // wstring From c28ec7a6d33fdbfe1c40646339eb975be6292647 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 14 Feb 2026 02:58:52 -0600 Subject: [PATCH 69/71] Triforce: Rename IPOverrides to IPRedirections. --- Source/Core/Core/Config/MainSettings.cpp | 6 +- Source/Core/Core/Config/MainSettings.h | 2 +- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 46 +++--- Source/Core/Core/HW/DVD/AMMediaboard.h | 14 +- .../Core/DolphinQt/Debugger/NetworkWidget.cpp | 15 +- .../Core/DolphinQt/Settings/TriforcePane.cpp | 136 +++++++++--------- 6 files changed, 111 insertions(+), 108 deletions(-) diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index bc749b4b37..6d5f260f07 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -643,7 +643,7 @@ const std::array, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_VOLUM Info{{System::Main, "EmulatedUSBDevices", "LogitechMic3VolumeModifier"}, 0}, Info{{System::Main, "EmulatedUSBDevices", "LogitechMic4VolumeModifier"}, 0}}; -static std::string GetDefaultTriforceIPOverrides() +static std::string GetDefaultTriforceIPRedirections() { #if defined(ANDROID) return "0.0.0.0/0=127.0.0.1"; @@ -679,8 +679,8 @@ static std::string GetDefaultTriforceIPOverrides() #endif } -const Info MAIN_TRIFORCE_IP_OVERRIDES{{System::Main, "Core", "TriforceIPOverrides"}, - GetDefaultTriforceIPOverrides()}; +const Info MAIN_TRIFORCE_IP_REDIRECTIONS{ + {System::Main, "Core", "TriforceIPRedirections"}, GetDefaultTriforceIPRedirections()}; // The reason we need this function is because some memory card code // expects to get a non-NTSC-K region even if we're emulating an NTSC-K Wii. diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index dfbf166758..38c8fdc365 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -386,7 +386,7 @@ extern const std::array, EMULATED_LOGITECH_MIC_COUNT> extern const std::array, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_MUTED; extern const std::array, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_VOLUME_MODIFIER; -extern const Info MAIN_TRIFORCE_IP_OVERRIDES; +extern const Info MAIN_TRIFORCE_IP_REDIRECTIONS; // GameCube path utility functions diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index 38f98ef53d..5cd69f8a62 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -507,7 +507,7 @@ static int PlatformPoll(std::span pfds, std::chrono::milliseconds tim #endif } -std::optional ParseIPOverride(std::string_view str) +std::optional ParseIPRedirection(std::string_view str) { // Everything after a space is the description. const auto ip_pair_str = std::string_view{str.begin(), std::ranges::find(str, ' ')}; @@ -518,7 +518,7 @@ std::optional ParseIPOverride(std::string_view str) const bool have_description = ip_pair_str.size() != str.size(); - return ParsedIPOverride{ + return ParsedIPRedirection{ .original = (*parts)[0], .replacement = (*parts)[1], .description = have_description ? str.substr(ip_pair_str.size() + 1) : std::string_view{}, @@ -526,7 +526,7 @@ std::optional ParseIPOverride(std::string_view str) } // Caller should check if it matches first! -Common::IPv4Port IPAddressOverride::ApplyOverride(Common::IPv4Port subject) const +Common::IPv4Port IPRedirection::Apply(Common::IPv4Port subject) const { // This logic could probably be better. // Ranges of different sizes will be weird in general. @@ -555,26 +555,26 @@ Common::IPv4Port IPAddressOverride::ApplyOverride(Common::IPv4Port subject) cons return subject; } -Common::IPv4Port IPAddressOverride::ReverseOverride(Common::IPv4Port subject) const +Common::IPv4Port IPRedirection::Reverse(Common::IPv4Port subject) const { // Low effort implementation.. - return IPAddressOverride{.original = replacement, .replacement = original}.ApplyOverride(subject); + return IPRedirection{.original = replacement, .replacement = original}.Apply(subject); } -std::string IPAddressOverride::ToString() const +std::string IPRedirection::ToString() const { return fmt::format("{}={}", original.ToString(), replacement.ToString()); } -IPOverrides GetIPOverrides() +IPRedirections GetIPRedirections() { - IPOverrides result; + IPRedirections result; - const auto ip_overrides_str = Config::Get(Config::MAIN_TRIFORCE_IP_OVERRIDES); - for (auto&& ip_pair : ip_overrides_str | std::views::split(',')) + const auto ip_redirections_str = Config::Get(Config::MAIN_TRIFORCE_IP_REDIRECTIONS); + for (auto&& ip_pair : ip_redirections_str | std::views::split(',')) { const auto ip_pair_str = std::string_view{ip_pair}; - const auto parts = ParseIPOverride(ip_pair_str); + const auto parts = ParseIPRedirection(ip_pair_str); if (parts.has_value()) { const auto original = Common::StringToIPv4PortRange(parts->original); @@ -586,7 +586,7 @@ IPOverrides GetIPOverrides() continue; } - ERROR_LOG_FMT(AMMEDIABOARD, "Bad IP pair string: {}", ip_pair_str); + ERROR_LOG_FMT(AMMEDIABOARD, "Bad IP redirection string: {}", ip_pair_str); } } @@ -596,10 +596,10 @@ IPOverrides GetIPOverrides() static std::optional AdjustIPv4PortFromConfig(Common::IPv4Port subject) { // TODO: We should parse this elsewhere to avoid repeated string manipulations. - for (auto&& override : GetIPOverrides()) + for (auto&& redirection : GetIPRedirections()) { - if (override.original.IsMatch(subject)) - return override.ApplyOverride(subject); + if (redirection.original.IsMatch(subject)) + return redirection.Apply(subject); } return std::nullopt; @@ -608,10 +608,10 @@ static std::optional AdjustIPv4PortFromConfig(Common::IPv4Port static std::optional ReverseAdjustIPv4PortFromConfig(Common::IPv4Port subject) { // TODO: We should parse this elsewhere to avoid repeated string manipulations. - for (auto&& override : GetIPOverrides()) + for (auto&& redirection : GetIPRedirections()) { - if (override.replacement.IsMatch(subject)) - return override.ReverseOverride(subject); + if (redirection.replacement.IsMatch(subject)) + return redirection.Reverse(subject); } return std::nullopt; @@ -665,7 +665,7 @@ static s32 NetDIMMConnect(GuestSocket guest_socket, const GuestSocketAddress& gu addr.sin_addr = std::bit_cast(adjusted_ipv4port->ip_address); addr.sin_port = adjusted_ipv4port->port; - INFO_LOG_FMT(AMMEDIABOARD, "NetDIMMConnect: Overriding to: {}:{}", + INFO_LOG_FMT(AMMEDIABOARD, "NetDIMMConnect: Redirecting to: {}:{}", Common::IPAddressToString(adjusted_ipv4port->ip_address), ntohs(adjusted_ipv4port->port)); } @@ -677,7 +677,7 @@ static s32 NetDIMMConnect(GuestSocket guest_socket, const GuestSocketAddress& gu const auto host_socket = GetHostSocket(guest_socket); - // See if we have an override for the game modified IP. + // See if we have a redirection for the game modified IP. // If so, adjust the source IP by binding the socket. const auto adjusted_source_ipv4port = AdjustIPv4PortFromConfig({s_game_modified_ip_address, 0}); if (adjusted_source_ipv4port.has_value()) @@ -880,8 +880,8 @@ static Common::IPv4Port GetAdjustedBindIPv4Port(Common::IPv4Port socket_addr) if (std::bit_cast(considered_ipv4.ip_address) == INADDR_ANY) { // Because the game is binding to "0.0.0.0", - // use the "game modified" IP for override purposes. - // If no override applies, then we still bind "0.0.0.0". + // use the "game modified" IP for redirection purposes. + // If no redirection applies, then we still bind "0.0.0.0". considered_ipv4.ip_address = s_game_modified_ip_address; INFO_LOG_FMT(AMMEDIABOARD, "GetAdjustedBindIPv4Port: Considering game modified IP: {}", Common::IPAddressToString(s_game_modified_ip_address)); @@ -890,7 +890,7 @@ static Common::IPv4Port GetAdjustedBindIPv4Port(Common::IPv4Port socket_addr) if (const auto adjusted_ipv4 = AdjustIPv4PortFromConfig(considered_ipv4)) { socket_addr = *adjusted_ipv4; - INFO_LOG_FMT(AMMEDIABOARD, "GetAdjustedBindIPv4Port: Overriding to: {}:{}", + INFO_LOG_FMT(AMMEDIABOARD, "GetAdjustedBindIPv4Port: Redirecting to: {}:{}", Common::IPAddressToString(socket_addr.ip_address), ntohs(socket_addr.port)); } diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.h b/Source/Core/Core/HW/DVD/AMMediaboard.h index f33dc177ec..4fa649c8bb 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.h +++ b/Source/Core/Core/HW/DVD/AMMediaboard.h @@ -245,29 +245,29 @@ bool GetTestMenu(); void Shutdown(); void DoState(PointerWrap& p); -struct ParsedIPOverride +struct ParsedIPRedirection { std::string_view original; std::string_view replacement; std::string_view description; }; -std::optional ParseIPOverride(std::string_view str); +std::optional ParseIPRedirection(std::string_view str); -struct IPAddressOverride +struct IPRedirection { Common::IPv4PortRange original; Common::IPv4PortRange replacement; // Caller should check if it matches first! - Common::IPv4Port ApplyOverride(Common::IPv4Port subject) const; - Common::IPv4Port ReverseOverride(Common::IPv4Port subject) const; + Common::IPv4Port Apply(Common::IPv4Port subject) const; + Common::IPv4Port Reverse(Common::IPv4Port subject) const; std::string ToString() const; }; -using IPOverrides = std::vector; +using IPRedirections = std::vector; -IPOverrides GetIPOverrides(); +IPRedirections GetIPRedirections(); s32 DebuggerGetSocket(u32 triforce_fd); diff --git a/Source/Core/DolphinQt/Debugger/NetworkWidget.cpp b/Source/Core/DolphinQt/Debugger/NetworkWidget.cpp index 77733bf252..c32f55c1b5 100644 --- a/Source/Core/DolphinQt/Debugger/NetworkWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/NetworkWidget.cpp @@ -144,9 +144,10 @@ QTableWidgetItem* GetSocketName(s32 host_fd) return new QTableWidgetItem(QStringLiteral("%1->%2").arg(sock_name).arg(peer_name)); } -QTableWidgetItem* GetSocketRedirections(s32 host_fd, const AMMediaboard::IPOverrides& ip_overrides) +QTableWidgetItem* GetSocketRedirections(s32 host_fd, + const AMMediaboard::IPRedirections& ip_redirections) { - if (host_fd < 0 || ip_overrides.empty()) + if (host_fd < 0 || ip_redirections.empty()) return new QTableWidgetItem(); sockaddr_in sock_addr; @@ -167,7 +168,7 @@ QTableWidgetItem* GetSocketRedirections(s32 host_fd, const AMMediaboard::IPOverr QStringList sock_rules; QStringList peer_rules; - for (const auto& rule : ip_overrides) + for (const auto& rule : ip_redirections) { if (rule.replacement.IsMatch(sock_ip_port)) sock_rules << QString::fromStdString(rule.ToString()); @@ -294,7 +295,7 @@ void NetworkWidget::UpdateWiiSocketTable(Core::System& system) { // Show Wii socket blocking state m_socket_table->showColumn(4); - // Hide Triforce IP overrides + // Hide Triforce IP redirections m_socket_table->hideColumn(6); auto* ios = system.GetIOS(); @@ -339,9 +340,9 @@ void NetworkWidget::UpdateTriforceSocketTable() { // No easy way to get socket blocking state on Windows m_socket_table->hideColumn(4); - // Show active IP overrides + // Show active IP redirections m_socket_table->showColumn(6); - const auto ip_overrides = AMMediaboard::GetIPOverrides(); + const auto ip_redirections = AMMediaboard::GetIPRedirections(); for (s32 triforce_fd = 0; triforce_fd != AMMediaboard::SOCKET_FD_MAX; ++triforce_fd) { m_socket_table->insertRow(triforce_fd); @@ -352,7 +353,7 @@ void NetworkWidget::UpdateTriforceSocketTable() m_socket_table->setItem(triforce_fd, 3, GetSocketState(host_fd)); m_socket_table->setItem(triforce_fd, 4, new QTableWidgetItem(QTableWidget::tr("Unknown"))); m_socket_table->setItem(triforce_fd, 5, GetSocketName(host_fd)); - m_socket_table->setItem(triforce_fd, 6, GetSocketRedirections(host_fd, ip_overrides)); + m_socket_table->setItem(triforce_fd, 6, GetSocketRedirections(host_fd, ip_redirections)); } } diff --git a/Source/Core/DolphinQt/Settings/TriforcePane.cpp b/Source/Core/DolphinQt/Settings/TriforcePane.cpp index 6efb9b94b2..025bbe0bd1 100644 --- a/Source/Core/DolphinQt/Settings/TriforcePane.cpp +++ b/Source/Core/DolphinQt/Settings/TriforcePane.cpp @@ -29,10 +29,10 @@ constexpr int COLUMN_EMULATED = 0; constexpr int COLUMN_REAL = 1; constexpr int COLUMN_DESCRIPTION = 2; -class IPAddressOverridesDialog final : public QDialog +class IPRedirectionsDialog final : public QDialog { public: - explicit IPAddressOverridesDialog(QWidget* parent); + explicit IPRedirectionsDialog(QWidget* parent); private: void LoadConfig(); @@ -41,7 +41,7 @@ private: void LoadDefault(); void OnClear(); - QTableWidget* m_ip_overrides_table; + QTableWidget* m_ip_redirections_table; }; } // namespace @@ -65,45 +65,46 @@ TriforcePane::TriforcePane() window->show(); }); - auto* const ip_override_group = new QGroupBox{tr("IP Address Overrides")}; - main_layout->addWidget(ip_override_group); + auto* const ip_redirection_group = new QGroupBox{tr("IP Address Redirections")}; + main_layout->addWidget(ip_redirection_group); - auto* const ip_override_layout = new QVBoxLayout{ip_override_group}; + auto* const ip_redirection_layout = new QVBoxLayout{ip_redirection_group}; - auto* const configure_ip_overrides_button = new NonDefaultQPushButton{tr("Configure")}; - ip_override_layout->addWidget(configure_ip_overrides_button); + auto* const configure_ip_redirections_button = new NonDefaultQPushButton{tr("Configure")}; + ip_redirection_layout->addWidget(configure_ip_redirections_button); - connect(configure_ip_overrides_button, &QPushButton::clicked, this, [this] { - auto* const ip_overrides = new IPAddressOverridesDialog{this}; - ip_overrides->setAttribute(Qt::WA_DeleteOnClose); - ip_overrides->open(); + connect(configure_ip_redirections_button, &QPushButton::clicked, this, [this] { + auto* const ip_redirections = new IPRedirectionsDialog{this}; + ip_redirections->setAttribute(Qt::WA_DeleteOnClose); + ip_redirections->open(); }); main_layout->addStretch(1); } -IPAddressOverridesDialog::IPAddressOverridesDialog(QWidget* parent) : QDialog{parent} +IPRedirectionsDialog::IPRedirectionsDialog(QWidget* parent) : QDialog{parent} { - setWindowTitle(tr("IP Address Overrides")); + setWindowTitle(tr("IP Address Redirections")); auto* const main_layout = new QVBoxLayout{this}; auto* const load_default_button = new QPushButton(tr("Default")); - connect(load_default_button, &QPushButton::clicked, this, &IPAddressOverridesDialog::LoadDefault); + connect(load_default_button, &QPushButton::clicked, this, &IPRedirectionsDialog::LoadDefault); auto* const clear_button = new QPushButton(tr("Clear")); - connect(clear_button, &QPushButton::clicked, this, &IPAddressOverridesDialog::OnClear); + connect(clear_button, &QPushButton::clicked, this, &IPRedirectionsDialog::OnClear); - m_ip_overrides_table = new QTableWidget; - main_layout->addWidget(m_ip_overrides_table); + m_ip_redirections_table = new QTableWidget; + main_layout->addWidget(m_ip_redirections_table); - m_ip_overrides_table->setColumnCount(3); - m_ip_overrides_table->setHorizontalHeaderLabels({tr("Emulated"), tr("Real"), tr("Description")}); - m_ip_overrides_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); - m_ip_overrides_table->setSizeAdjustPolicy(QTableWidget::AdjustToContents); + m_ip_redirections_table->setColumnCount(3); + m_ip_redirections_table->setHorizontalHeaderLabels( + {tr("Emulated"), tr("Real"), tr("Description")}); + m_ip_redirections_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + m_ip_redirections_table->setSizeAdjustPolicy(QTableWidget::AdjustToContents); - m_ip_overrides_table->setEditTriggers(QAbstractItemView::DoubleClicked | - QAbstractItemView::EditKeyPressed); + m_ip_redirections_table->setEditTriggers(QAbstractItemView::DoubleClicked | + QAbstractItemView::EditKeyPressed); auto* const button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); main_layout->addWidget(button_box); @@ -111,75 +112,76 @@ IPAddressOverridesDialog::IPAddressOverridesDialog(QWidget* parent) : QDialog{pa button_box->addButton(load_default_button, QDialogButtonBox::ActionRole); button_box->addButton(clear_button, QDialogButtonBox::ActionRole); - connect(button_box, &QDialogButtonBox::accepted, this, &IPAddressOverridesDialog::accept); - connect(button_box, &QDialogButtonBox::rejected, this, &IPAddressOverridesDialog::reject); + connect(button_box, &QDialogButtonBox::accepted, this, &IPRedirectionsDialog::accept); + connect(button_box, &QDialogButtonBox::rejected, this, &IPRedirectionsDialog::reject); - connect(this, &IPAddressOverridesDialog::accepted, this, &IPAddressOverridesDialog::SaveConfig); + connect(this, &IPRedirectionsDialog::accepted, this, &IPRedirectionsDialog::SaveConfig); - connect(m_ip_overrides_table, &QTableWidget::itemChanged, this, [this](QTableWidgetItem* item) { - const int row_count = m_ip_overrides_table->rowCount(); - const bool is_last_row = item->row() == row_count - 1; - if (is_last_row) - { - if (!item->text().isEmpty()) - m_ip_overrides_table->insertRow(row_count); - } - else if (item->column() != COLUMN_DESCRIPTION && item->text().isEmpty()) - { - // Just remove inner rows that have text erased. - // This UX could be less weird, but it's good enough for now. - m_ip_overrides_table->removeRow(item->row()); - } - }); + connect(m_ip_redirections_table, &QTableWidget::itemChanged, this, + [this](QTableWidgetItem* item) { + const int row_count = m_ip_redirections_table->rowCount(); + const bool is_last_row = item->row() == row_count - 1; + if (is_last_row) + { + if (!item->text().isEmpty()) + m_ip_redirections_table->insertRow(row_count); + } + else if (item->column() != COLUMN_DESCRIPTION && item->text().isEmpty()) + { + // Just remove inner rows that have text erased. + // This UX could be less weird, but it's good enough for now. + m_ip_redirections_table->removeRow(item->row()); + } + }); LoadConfig(); QtUtils::AdjustSizeWithinScreen(this); } -void IPAddressOverridesDialog::LoadConfig() +void IPRedirectionsDialog::LoadConfig() { - m_ip_overrides_table->setRowCount(0); + m_ip_redirections_table->setRowCount(0); // Add the final empty row. - m_ip_overrides_table->insertRow(0); + m_ip_redirections_table->insertRow(0); int row = 0; - const auto ip_overrides_str = Config::Get(Config::MAIN_TRIFORCE_IP_OVERRIDES); - for (auto&& ip_pair : ip_overrides_str | std::views::split(',')) + const auto ip_redirections_str = Config::Get(Config::MAIN_TRIFORCE_IP_REDIRECTIONS); + for (auto&& ip_pair : ip_redirections_str | std::views::split(',')) { const auto ip_pair_str = std::string_view{ip_pair}; - const auto parsed = AMMediaboard::ParseIPOverride(ip_pair_str); + const auto parsed = AMMediaboard::ParseIPRedirection(ip_pair_str); if (!parsed.has_value()) { - ERROR_LOG_FMT(COMMON, "Bad IP pair string: {}", ip_pair_str); + ERROR_LOG_FMT(COMMON, "Bad IP redirection string: {}", ip_pair_str); continue; } - m_ip_overrides_table->insertRow(row); + m_ip_redirections_table->insertRow(row); - m_ip_overrides_table->setItem(row, COLUMN_EMULATED, - new QTableWidgetItem(QString::fromUtf8(parsed->original))); - m_ip_overrides_table->setItem(row, COLUMN_REAL, - new QTableWidgetItem(QString::fromUtf8(parsed->replacement))); - m_ip_overrides_table->setItem(row, COLUMN_DESCRIPTION, - new QTableWidgetItem(QString::fromUtf8(parsed->description))); + m_ip_redirections_table->setItem(row, COLUMN_EMULATED, + new QTableWidgetItem(QString::fromUtf8(parsed->original))); + m_ip_redirections_table->setItem(row, COLUMN_REAL, + new QTableWidgetItem(QString::fromUtf8(parsed->replacement))); + m_ip_redirections_table->setItem(row, COLUMN_DESCRIPTION, + new QTableWidgetItem(QString::fromUtf8(parsed->description))); ++row; } } -void IPAddressOverridesDialog::SaveConfig() const +void IPRedirectionsDialog::SaveConfig() const { // Ignore our empty row. - const int row_count = m_ip_overrides_table->rowCount() - 1; + const int row_count = m_ip_redirections_table->rowCount() - 1; std::vector replacements(row_count); for (int row = 0; row < row_count; ++row) { - auto* const original_item = m_ip_overrides_table->item(row, COLUMN_EMULATED); - auto* const replacement_item = m_ip_overrides_table->item(row, COLUMN_REAL); + auto* const original_item = m_ip_redirections_table->item(row, COLUMN_EMULATED); + auto* const replacement_item = m_ip_redirections_table->item(row, COLUMN_REAL); // Skip incomplete rows. if (original_item == nullptr || replacement_item == nullptr) @@ -188,7 +190,7 @@ void IPAddressOverridesDialog::SaveConfig() const replacements[row] = fmt::format("{}={}", StripWhitespace(original_item->text().toStdString()), replacement_item->text().toStdString()); - auto* const description_item = m_ip_overrides_table->item(row, COLUMN_DESCRIPTION); + auto* const description_item = m_ip_redirections_table->item(row, COLUMN_DESCRIPTION); if (description_item == nullptr) continue; @@ -198,22 +200,22 @@ void IPAddressOverridesDialog::SaveConfig() const (replacements[row] += ' ') += description; } - Config::SetBaseOrCurrent(Config::MAIN_TRIFORCE_IP_OVERRIDES, + Config::SetBaseOrCurrent(Config::MAIN_TRIFORCE_IP_REDIRECTIONS, fmt::format("{}", fmt::join(replacements, ","))); } -void IPAddressOverridesDialog::LoadDefault() +void IPRedirectionsDialog::LoadDefault() { // This alters the config before "OK" is pressed. Bad UX.. - Config::SetBaseOrCurrent(Config::MAIN_TRIFORCE_IP_OVERRIDES, Config::DefaultState{}); + Config::SetBaseOrCurrent(Config::MAIN_TRIFORCE_IP_REDIRECTIONS, Config::DefaultState{}); LoadConfig(); } -void IPAddressOverridesDialog::OnClear() +void IPRedirectionsDialog::OnClear() { - m_ip_overrides_table->setRowCount(0); + m_ip_redirections_table->setRowCount(0); // Add the final empty row. - m_ip_overrides_table->insertRow(0); + m_ip_redirections_table->insertRow(0); } From 051133787e77a154c83fcf54c7acc83b76fe7d81 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 7 Feb 2026 11:03:11 +0100 Subject: [PATCH 70/71] Android: Add Triforce Baseboard This lets users select Triforce Baseboard for SI and SP1. Limitations: * The test, service and coin buttons can be bound to gamepads and other physical inputs, but aren't available in the touch input overlay. * The IP redirections are exposed to the user as a raw string rather than a table like in DolphinQt. --- .../features/settings/model/StringSetting.kt | 6 ++++++ .../settings/ui/SettingsFragmentPresenter.kt | 14 ++++++++++++-- .../dolphinemu/dolphinemu/overlay/InputOverlay.kt | 8 ++++++-- Source/Android/app/src/main/res/values/arrays.xml | 4 ++++ Source/Android/app/src/main/res/values/strings.xml | 3 +++ 5 files changed, 31 insertions(+), 4 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt index 29640aa92f..c4f1107bcd 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt @@ -69,6 +69,12 @@ enum class StringSetting( "" ), MAIN_WFS_PATH(Settings.FILE_DOLPHIN, Settings.SECTION_INI_GENERAL, "WFSPath", ""), + MAIN_TRIFORCE_IP_REDIRECTIONS( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_CORE, + "TriforceIPRedirections", + "0.0.0.0/0=127.0.0.1" + ), GFX_ENHANCE_POST_SHADER( Settings.FILE_GFX, Settings.SECTION_GFX_ENHANCEMENTS, diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt index 714ec84f9f..1f68bbbd1a 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt @@ -1276,7 +1276,17 @@ class SettingsFragmentPresenter( } private fun addSerialPortSubSettings(sl: ArrayList, serialPort1Type: Int) { - if (serialPort1Type == 10) { + if (serialPort1Type == 6) { + // Triforce Baseboard + sl.add( + InputStringSetting( + context, + StringSetting.MAIN_TRIFORCE_IP_REDIRECTIONS, + R.string.triforce_ip_redirections, + 0 + ) + ) + } else if (serialPort1Type == 10) { // Broadband Adapter (XLink Kai) sl.add(HyperLinkHeaderSetting(context, R.string.xlink_kai_guide_header, 0)) sl.add( @@ -2352,7 +2362,7 @@ class SettingsFragmentPresenter( private fun addGcPadSubSettings(sl: ArrayList, gcPadNumber: Int, gcPadType: Int) { when (gcPadType) { - 6, 8, 9, 10 -> { + 6, 8, 9, 10, 11 -> { // Emulated val gcPad = EmulatedController.getGcPad(gcPadNumber) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.kt index 7964cc1ebc..3d991fed1e 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.kt @@ -2285,6 +2285,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex const val OVERLAY_NONE = 5 private const val DISABLED_GAMECUBE_CONTROLLER = 0 private const val EMULATED_GAMECUBE_CONTROLLER = 6 + private const val EMULATED_AM_BASEBOARD = 11 private const val GAMECUBE_ADAPTER = 12 // Buttons that have special positions in Wiimote only @@ -2337,8 +2338,11 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex if (controllerIndex in 0 until 4) { // GameCube controller - if (getSettingForSIDevice(controllerIndex).int == 6) - return OVERLAY_GAMECUBE + when (getSettingForSIDevice(controllerIndex).int) { + EMULATED_GAMECUBE_CONTROLLER, EMULATED_AM_BASEBOARD -> { + return OVERLAY_GAMECUBE + } + } } else if (controllerIndex in 4 until 8) { // Wii Remote val wiimoteIndex = controllerIndex - 4 diff --git a/Source/Android/app/src/main/res/values/arrays.xml b/Source/Android/app/src/main/res/values/arrays.xml index 8892f7f218..d919edb1d2 100644 --- a/Source/Android/app/src/main/res/values/arrays.xml +++ b/Source/Android/app/src/main/res/values/arrays.xml @@ -105,6 +105,7 @@ @string/broadband_adapter_hle @string/broadband_adapter_tapserver @string/modem_adapter_tapserver + @string/sp1_am_baseboard 255 @@ -113,6 +114,7 @@ 12 11 13 + 6 @@ -375,6 +377,7 @@ @string/gcpad_steering_wheel @string/gcpad_dance_mat @string/gcpad_taru_konga + @string/gcpad_am_baseboard @string/gcpad_gc_adapter @@ -384,6 +387,7 @@ 8 9 10 + 11 12 diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index b1630def81..855cb56ab8 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -142,6 +142,7 @@ Enter the socket path or netloc (address:port) of the tapserver instance DNS Server Use 8.8.8.8 for normal DNS, else enter your custom one + IP Redirections Interface @@ -707,6 +708,7 @@ It can efficiently compress both junk data and encrypted Wii data. Broadband Adapter (HLE) Broadband Adapter (tapserver) Modem Adapter (tapserver) + Triforce Baseboard Mono @@ -811,6 +813,7 @@ It can efficiently compress both junk data and encrypted Wii data. Steering Wheel Dance Mat DK Bongos + Triforce Baseboard GameCube Adapter From bc0c3d9f6a4f547ee3427f42d1704f5d1ae67b7e Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 14 Feb 2026 00:44:35 -0600 Subject: [PATCH 71/71] HW/GCPad: Make Android default to shaking the device for Triforce coin input. --- Source/Core/Core/HW/GCPadEmu.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Core/Core/HW/GCPadEmu.cpp b/Source/Core/Core/HW/GCPadEmu.cpp index 4c6bb432f5..dff79653c3 100644 --- a/Source/Core/Core/HW/GCPadEmu.cpp +++ b/Source/Core/Core/HW/GCPadEmu.cpp @@ -194,6 +194,9 @@ void GCPad::LoadDefaults(const ControllerInterface& ciface) #ifdef ANDROID // Rumble m_rumble->SetControlExpression(0, "`Android/0/Device Sensors:Motor 0`"); + + // Triforce Coin + m_triforce->SetControlExpression(2, "pulse(`Android/0/Device Sensors:Accel Down` > 15, 0.1)"); #else // Buttons: A, B, X, Y, Z m_buttons->SetControlExpression(0, "`X`");