diff --git a/Data/Sys/GameSettings/SBEY.ini b/Data/Sys/GameSettings/SBEY.ini new file mode 100644 index 0000000000..2e79beb657 --- /dev/null +++ b/Data/Sys/GameSettings/SBEY.ini @@ -0,0 +1,4 @@ +# SBEY - Virtua Striker 2002 (Export, Japan, Type 3) + +[Core] +FPRF = True diff --git a/Data/Sys/GameSettings/SBGG.ini b/Data/Sys/GameSettings/SBGG.ini new file mode 100644 index 0000000000..6296633236 --- /dev/null +++ b/Data/Sys/GameSettings/SBGG.ini @@ -0,0 +1,5 @@ +# SBGG - F-ZERO AX + +[Core] +FPRF = True +CPUThread = True diff --git a/Data/Sys/GameSettings/SBHA.ini b/Data/Sys/GameSettings/SBHA.ini new file mode 100644 index 0000000000..b62a599c92 --- /dev/null +++ b/Data/Sys/GameSettings/SBHA.ini @@ -0,0 +1,5 @@ +# SBHA - F-ZERO AX (Monster) + +[Core] +FPRF = True +CPUThread = True diff --git a/Data/Sys/GameSettings/SBHN.ini b/Data/Sys/GameSettings/SBHN.ini new file mode 100644 index 0000000000..31be499dca --- /dev/null +++ b/Data/Sys/GameSettings/SBHN.ini @@ -0,0 +1,4 @@ +# SBHN - VIRTUA STRIKER 4 VER.A + +[Core] +FPRF = True diff --git a/Data/Sys/GameSettings/SBHZ.ini b/Data/Sys/GameSettings/SBHZ.ini new file mode 100644 index 0000000000..a15b861658 --- /dev/null +++ b/Data/Sys/GameSettings/SBHZ.ini @@ -0,0 +1,4 @@ +# SBHZ - VIRTUA STRIKER 4 (Asia) + +[Core] +FPRF = True diff --git a/Data/Sys/GameSettings/SBJA.ini b/Data/Sys/GameSettings/SBJA.ini new file mode 100644 index 0000000000..028fd4b6c0 --- /dev/null +++ b/Data/Sys/GameSettings/SBJA.ini @@ -0,0 +1,4 @@ +# SBJA - VIRTUA STRIKER 4 (Export) + +[Core] +FPRF = True diff --git a/Data/Sys/GameSettings/SBLK.ini b/Data/Sys/GameSettings/SBLK.ini new file mode 100644 index 0000000000..8931c60490 --- /dev/null +++ b/Data/Sys/GameSettings/SBLK.ini @@ -0,0 +1,4 @@ +# SBLK - VIRTUA STRIKER 4 Ver.2006 (Japan) + +[Core] +FPRF = True diff --git a/Data/Sys/GameSettings/SBLL.ini b/Data/Sys/GameSettings/SBLL.ini new file mode 100644 index 0000000000..deb8b08379 --- /dev/null +++ b/Data/Sys/GameSettings/SBLL.ini @@ -0,0 +1,4 @@ +# SBLL - VIRTUA STRIKER 4 Ver.2006 (Export) + +[Core] +FPRF = True diff --git a/Source/Core/Common/CommonPaths.h b/Source/Core/Common/CommonPaths.h index 16cc22de19..e37ec2a200 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" @@ -156,6 +158,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 32f1bbc722..a20bd30c46 100644 --- a/Source/Core/Common/FileUtil.cpp +++ b/Source/Core/Common/FileUtil.cpp @@ -844,6 +844,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 134dbda9f9..8cbb72f504 100644 --- a/Source/Core/Common/FileUtil.h +++ b/Source/Core/Common/FileUtil.h @@ -28,7 +28,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 4b40297836..157d40ea43 100644 --- a/Source/Core/Common/Logging/Log.h +++ b/Source/Core/Common/Logging/Log.h @@ -28,6 +28,7 @@ enum class LogType : int DSP_MAIL, DSPINTERFACE, DVDINTERFACE, + DVDINTERFACE_AMMB, DYNA_REC, EXPANSIONINTERFACE, FILEMON, @@ -59,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 d0117ad07f..1814e7d53c 100644 --- a/Source/Core/Common/Logging/LogManager.cpp +++ b/Source/Core/Common/Logging/LogManager.cpp @@ -113,6 +113,7 @@ 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::DVDINTERFACE_AMMB] = {"DVD_AMMB", "AMMB Interface"}; m_log[LogType::DYNA_REC] = {"JIT", "JIT Dynamic Recompiler"}; m_log[LogType::EXPANSIONINTERFACE] = {"EXI", "Expansion Interface"}; m_log[LogType::FILEMON] = {"FileMon", "File Monitor"}; @@ -144,6 +145,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 e43ffc52d4..5185cf7118 100644 --- a/Source/Core/Core/Boot/Boot.cpp +++ b/Source/Core/Core/Boot/Boot.cpp @@ -37,6 +37,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" @@ -374,7 +375,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; @@ -408,6 +410,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; @@ -458,6 +461,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; PowerPC::MSRUpdated(ppc_state); @@ -491,6 +502,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 884a11bd87..01a7a97caf 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" @@ -227,12 +228,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(); + u32 dsize = volume.GetDataSize(); - memory.Memset(0, 0, 12); + // Load game into RAM, like on the actual Triforce + u8* dimm_disc = AMMediaboard::InitDIMM(dsize); + + volume.Read(0, dsize, dimm_disc, DiscIO::PARTITION_NONE); + + // Triforce disc register obfucation + 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 6e7ff1a0bc..2827d70dd0 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -183,6 +183,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 @@ -276,6 +278,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 699e0d1cd7..78f1ecb042 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" @@ -623,9 +625,17 @@ DiscIO::Region ToGameCubeRegion(DiscIO::Region region) const char* GetDirectoryForRegion(DiscIO::Region region, RegionDirectoryStyle style) { + auto& system = Core::System::GetInstance(); + if (region == DiscIO::Region::Unknown) region = ToGameCubeRegion(Config::Get(Config::MAIN_FALLBACK_REGION)); + // Triforce IPL + if (system.IsTriforce()) + { + return DEV_DIR; + } + switch (region) { case DiscIO::Region::NTSC_J: diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index 4e0b7dd3ac..4dee4c212c 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -126,12 +126,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); @@ -147,7 +141,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, @@ -156,14 +150,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()); } } @@ -181,28 +175,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; @@ -235,7 +226,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(); diff --git a/Source/Core/Core/ConfigManager.h b/Source/Core/Core/ConfigManager.h index f441cd888b..9196c17317 100644 --- a/Source/Core/Core/ConfigManager.h +++ b/Source/Core/Core/ConfigManager.h @@ -57,7 +57,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. @@ -67,7 +66,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(); @@ -122,15 +120,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 3292abbff9..e583000335 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -52,6 +52,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" @@ -299,6 +300,13 @@ void Stop(Core::System& system) // - Hammertime! system.GetFifo().ExitGpuLoop(); } + + 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..9851de82c9 --- /dev/null +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -0,0 +1,1844 @@ +// Copyright 2017 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/HW/DVD/AMMediaboard.h" + +#include +#include +#include +#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; + +static u8* s_dimm_disc = nullptr; + +static u8 s_firmware[2 * 1024 * 1024]; +static u8 s_media_buffer[0x300]; +static u8 s_network_command_buffer[0x4FFE00]; +static u8 s_network_buffer[256 * 1024]; +static u8 s_allnet_buffer[4096]; +static u8 s_allnet_settings[0x8500]; + +/* 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, struct 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) +{ + auto& system = Core::System::GetInstance(); + auto& memory = system.GetMemory(); + + for (u32 i = 0; i < length; i += 0x10) + { + INFO_LOG_FMT(DVDINTERFACE_AMMB, "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(void) +{ + std::ranges::fill(s_media_buffer, 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; + + 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 + "s_netcfg.bin"); + if (!s_netctrl.IsOpen()) + PanicAlertFmt("Failed to open/create: {}", base_path + "s_netctrl.bin"); + if (!s_extra.IsOpen()) + PanicAlertFmt("Failed to open/create: {}", base_path + "s_extra.bin"); + if (!s_dimm.IsOpen()) + PanicAlertFmt("Failed to open/create: {}", base_path + "s_dimm.bin"); + if (!s_backup.IsOpen()) + PanicAlertFmt("Failed to open/create: {}", base_path + "s_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; + } + + 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) + { + s_dimm_disc = new (std::nothrow) u8[size]; + if (!s_dimm_disc) + { + PanicAlertFmt("Failed to allocate DIMM memory."); + return nullptr; + } + } + + s_firmware_map = 0; + return s_dimm_disc; +} + +static s32 NetDIMMAccept(int fd, struct 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 + + int result = select(0, &readfds, NULL, NULL, &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, struct sockaddr_in* addr, int len) +{ + // 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); + //*(u32*)(&addr.sin_addr) = Common::swap32(*(u32*)(&addr.sin_addr)); + + int ret = 0; + int err = 0; + u_long val = 1; + + // Set socket to non-blocking + ioctlsocket(fd, FIONBIO, &val); + + ret = connect(fd, (const sockaddr*)addr, len); + 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(0, NULL, &writefds, NULL, &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, (char*)&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, 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; + + 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)); + } + } + + u32 command = DICMDBUF[0] << 24; + u32 offset = DICMDBUF[1] << 2; + + INFO_LOG_FMT(DVDINTERFACE_AMMB, + "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) + { + u32 dimmoffset = offset - DIMMMemory; + s_dimm.Seek(dimmoffset, File::SeekOrigin::Begin); + s_dimm.ReadBytes(memory.GetSpanForAddress(address).data(), length); + return 0; + } + + if (offset >= AllNetBuffer && offset < 0x89011000) + { + u32 allnet_offset = offset - AllNetBuffer; + INFO_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: Read All.Net BUFFER (1) ({:08x},{})", offset, length); + memcpy(memory.GetSpanForAddress(address).data(), s_allnet_buffer + allnet_offset, length); + return 0; + } + + if (offset >= DIMMCommandVersion1 && offset < 0x1F900040) + { + u32 dimmoffset = offset - DIMMCommandVersion1; + memcpy(memory.GetSpanForAddress(address).data(), s_media_buffer + dimmoffset, length); + + INFO_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: Read MEDIA BOARD COMM AREA (1) ({:08x},{})", offset, + length); + PrintMBBuffer(address, length); + return 0; + } + + if (offset >= NetworkBufferAddress4 && offset < 0x891C0000) + { + u32 dimmoffset = offset - NetworkBufferAddress4; + INFO_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: Read NETWORK BUFFER (4) ({:08x},{})", offset, length); + memcpy(memory.GetSpanForAddress(address).data(), s_network_buffer + dimmoffset, length); + return 0; + } + + if (offset >= NetworkBufferAddress5 && offset < 0x1FB10000) + { + u32 dimmoffset = offset - NetworkBufferAddress5; + INFO_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: Read NETWORK BUFFER (5) ({:08x},{})", offset, length); + memcpy(memory.GetSpanForAddress(address).data(), s_network_buffer + dimmoffset, length); + return 0; + } + + if (offset >= NetworkCommandAddress && offset < 0x1FD00000) + { + u32 dimmoffset = offset - NetworkCommandAddress; + INFO_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: Read NETWORK COMMAND BUFFER ({:08x},{})", offset, + length); + memcpy(memory.GetSpanForAddress(address).data(), s_network_command_buffer + dimmoffset, + length); + return 0; + } + + if (offset >= NetworkCommandAddress2 && offset < 0x89060200) + { + u32 dimmoffset = offset - NetworkCommandAddress2; + INFO_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: Read NETWORK COMMAND BUFFER (2) ({:08x},{})", offset, + length); + memcpy(memory.GetSpanForAddress(address).data(), s_network_command_buffer + dimmoffset, + length); + return 0; + } + + if (offset >= NetworkBufferAddress1 && offset < 0x1FA10000) + { + u32 dimmoffset = offset - NetworkBufferAddress1; + INFO_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: Read NETWORK BUFFER (1) ({:08x},{})", offset, length); + memcpy(memory.GetSpanForAddress(address).data(), s_network_buffer + dimmoffset, length); + return 0; + } + + if (offset >= NetworkBufferAddress2 && offset < 0x1FD10000) + { + u32 dimmoffset = offset - NetworkBufferAddress2; + INFO_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: Read NETWORK BUFFER (2) ({:08x},{})", offset, length); + memcpy(memory.GetSpanForAddress(address).data(), s_network_buffer + dimmoffset, length); + return 0; + } + + if (offset >= NetworkBufferAddress3 && offset < 0x89110000) + { + u32 dimmoffset = offset - NetworkBufferAddress3; + INFO_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: Read NETWORK BUFFER (3) ({:08x},{})", offset, length); + memcpy(memory.GetSpanForAddress(address).data(), s_network_buffer + dimmoffset, length); + return 0; + } + + if (offset >= DIMMCommandVersion2 && offset < 0x84000060) + { + u32 dimmoffset = offset - DIMMCommandVersion2; + memcpy(memory.GetSpanForAddress(address).data(), s_media_buffer + dimmoffset, length); + + INFO_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: Read MEDIA BOARD COMM AREA (2) ({:08x},{})", offset, + length); + PrintMBBuffer(address, length); + return 0; + } + + if (offset == DIMMCommandExecute2) + { + INFO_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: EXECUTE MEDIA BOARD COMMAND"); + + memcpy(s_media_buffer, s_media_buffer + 0x200, 0x20); + memset(s_media_buffer + 0x200, 0, 0x20); + s_media_buffer[0x204] = 1; + + // Recast for easier access + u32* media_buffer_32 = (u32*)(s_media_buffer); + + switch (AMMBCommand(*(u16*)(s_media_buffer + 2))) + { + case AMMBCommand::Unknown_001: + media_buffer_32[1] = 1; + break; + case AMMBCommand::GetNetworkFirmVersion: + 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: + { + u32 fd = s_sockets[SocketCheck(media_buffer_32[2])]; + int ret = -1; + + // Handle optional parameters + if (media_buffer_32[3] == 0 || media_buffer_32[4] == 0) + { + ret = NetDIMMAccept(fd, nullptr, nullptr); + } + else + { + u32 addr_off = media_buffer_32[3] - NetworkCommandAddress2; + u32 len_off = media_buffer_32[4] - NetworkCommandAddress2; + + struct sockaddr* addr = (struct sockaddr*)(s_network_command_buffer + addr_off); + int* len = (int*)(s_network_command_buffer + len_off); + + ret = NetDIMMAccept(fd, addr, len); + } + + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: accept( {}({}) ):{}\n", fd, media_buffer_32[2], + ret); + media_buffer_32[1] = ret; + break; + } + case AMMBCommand::Bind: + { + struct sockaddr_in addr; + + u32 fd = s_sockets[SocketCheck(media_buffer_32[2])]; + u32 off = media_buffer_32[3] - NetworkCommandAddress2; + u32 len = media_buffer_32[4]; + + memcpy((void*)&addr, s_network_command_buffer + off, sizeof(struct sockaddr_in)); + + addr.sin_family = Common::swap16(addr.sin_family); + *(u32*)(&addr.sin_addr) = Common::swap32(*(u32*)(&addr.sin_addr)); + + /* + 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 = INADDR_ANY; + + int ret = bind(fd, (const sockaddr*)&addr, len); + int err = WSAGetLastError(); + + if (ret < 0) + PanicAlertFmt("Socket Bind Failed with{0}", err); + + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: bind( {}, ({},{:08x}:{}), {} ):{} ({})\n", fd, + addr.sin_family, addr.sin_addr.s_addr, Common::swap16(addr.sin_port), len, + ret, err); + + media_buffer_32[1] = ret; + s_last_error = SSC_SUCCESS; + break; + } + case AMMBCommand::Closesocket: + { + u32 fd = s_sockets[SocketCheck(media_buffer_32[2])]; + + int ret = closesocket(fd); + + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: closesocket( {}({}) ):{}\n", fd, + media_buffer_32[2], ret); + + s_sockets[media_buffer_32[2]] = SOCKET_ERROR; + + media_buffer_32[1] = ret; + s_last_error = SSC_SUCCESS; + break; + } + case AMMBCommand::Connect: + { + struct sockaddr_in addr; + + u32 fd = s_sockets[SocketCheck(media_buffer_32[2])]; + u32 off = media_buffer_32[3] - NetworkCommandAddress2; + u32 len = media_buffer_32[4]; + + int ret = 0; + int err = 0; + + memcpy((void*)&addr, s_network_command_buffer + off, sizeof(struct sockaddr_in)); + + ret = NetDIMMConnect(fd, &addr, len); + + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: connect( {}({}), ({},{}:{}), {} ):{} ({})\n", fd, + media_buffer_32[2], addr.sin_family, inet_ntoa(addr.sin_addr), + Common::swap16(addr.sin_port), len, ret, err); + + s_media_buffer[1] = s_media_buffer[8]; + media_buffer_32[1] = ret; + break; + } + case AMMBCommand::InetAddr: + { + u32 ip = inet_addr((char*)s_network_command_buffer); + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: InetAddr( {} )\n", + (char*)s_network_command_buffer); + + s_media_buffer[1] = s_media_buffer[8]; + media_buffer_32[1] = Common::swap32(ip); + break; + } + case AMMBCommand::Listen: + { + u32 fd = s_sockets[SocketCheck(media_buffer_32[2])]; + u32 backlog = media_buffer_32[3]; + + int ret = listen(fd, backlog); + + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: listen( {}, {} ):{:d}\n", fd, backlog, ret); + + s_media_buffer[1] = s_media_buffer[8]; + media_buffer_32[1] = ret; + break; + } + case AMMBCommand::Recv: + { + u32 fd = s_sockets[SocketCheck(media_buffer_32[2])]; + u32 off = media_buffer_32[3]; + u32 len = media_buffer_32[4]; + + if (len >= sizeof(s_network_buffer)) + { + len = sizeof(s_network_buffer); + } + + int ret = 0; + char* buffer = (char*)(s_network_buffer + off); + + if (off >= NetworkBufferAddress4 && off < NetworkBufferAddress4 + sizeof(s_network_buffer)) + { + buffer = (char*)(s_network_buffer + off - NetworkBufferAddress4); + } + else + { + PanicAlertFmt("RECV: Buffer overrun:{0} {1} ", off, len); + } + + int err = 0; + + ret = recv(fd, buffer, len, 0); + err = WSAGetLastError(); + + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: recv( {}, 0x{:08x}, {} ):{} {}\n", fd, off, len, + ret, err); + + s_media_buffer[1] = s_media_buffer[8]; + media_buffer_32[1] = ret; + break; + } + case AMMBCommand::Send: + { + u32 fd = s_sockets[SocketCheck(media_buffer_32[2])]; + u32 off = media_buffer_32[3]; + u32 len = media_buffer_32[4]; + + int ret = 0; + + if (off >= NetworkBufferAddress3 && off < NetworkBufferAddress3 + sizeof(s_network_buffer)) + { + off -= NetworkBufferAddress3; + } + else + { + ERROR_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: send(error) unhandled destination:{:08x}\n", + off); + } + + ret = send(fd, (char*)(s_network_buffer + off), len, 0); + int err = WSAGetLastError(); + + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: send( {}({}), 0x{:08x}, {} ): {} {}\n", fd, + media_buffer_32[2], off, len, ret, err); + + s_media_buffer[1] = s_media_buffer[8]; + media_buffer_32[1] = ret; + break; + } + case AMMBCommand::Socket: + { + // Protocol is not sent + u32 af = media_buffer_32[2]; + u32 type = media_buffer_32[3]; + + SOCKET fd = socket_(af, type, IPPROTO_TCP); + + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: socket( {}, {}, IPPROTO_TCP ):{}\n", af, type, + fd); + + s_media_buffer[1] = 0; + media_buffer_32[1] = fd; + break; + } + case AMMBCommand::Select: + { + u32 nfds = s_sockets[SocketCheck(media_buffer_32[2] - 1)]; + + /* + BUG: NAMCAM is hardcoded to call this with socket ID 0x100 which might be some magic + thing? Winsocks expects a valid socket so we take the socket from the connect. + */ + if (AMMediaboard::GetGameType() == MarioKartGP2) + { + if (media_buffer_32[2] == 256) + { + nfds = s_namco_cam; + } + } + + fd_set* readfds = nullptr; + fd_set* writefds = nullptr; + fd_set* exceptfds = nullptr; + timeval* timeout = nullptr; + + // Only one of 3, 4, 5 is ever set alongside 6 + if (media_buffer_32[3] && media_buffer_32[6]) + { + u32 ROffset = media_buffer_32[6] - NetworkCommandAddress2; + readfds = (fd_set*)(s_network_command_buffer + ROffset); + FD_ZERO(readfds); + FD_SET(nfds, readfds); + + timeout = + (timeval*)(s_network_command_buffer + media_buffer_32[3] - NetworkCommandAddress2); + } + else if (media_buffer_32[4] && media_buffer_32[6]) + { + u32 WOffset = media_buffer_32[6] - NetworkCommandAddress2; + writefds = (fd_set*)(s_network_command_buffer + WOffset); + FD_ZERO(writefds); + FD_SET(nfds, writefds); + + timeout = + (timeval*)(s_network_command_buffer + media_buffer_32[4] - NetworkCommandAddress2); + } + else if (media_buffer_32[5] && media_buffer_32[6]) + { + u32 EOffset = media_buffer_32[6] - NetworkCommandAddress2; + exceptfds = (fd_set*)(s_network_command_buffer + EOffset); + FD_ZERO(exceptfds); + FD_SET(nfds, exceptfds); + + timeout = + (timeval*)(s_network_command_buffer + media_buffer_32[5] - NetworkCommandAddress2); + } + + if (AMMediaboard::GetGameType() == KeyOfAvalon) + { + timeout->tv_sec = 0; + timeout->tv_usec = 1800; + } + + int ret = select(nfds + 1, readfds, writefds, exceptfds, timeout); + + int err = WSAGetLastError(); + + NOTICE_LOG_FMT( + DVDINTERFACE_AMMB, + "GC-AM: select( {}({}), 0x{:08x} 0x{:08x} 0x{:08x} 0x{:08x} ):{} {} {}:{} \n", nfds, + media_buffer_32[2], media_buffer_32[3], media_buffer_32[4], media_buffer_32[5], + media_buffer_32[6], ret, err, timeout->tv_sec, timeout->tv_usec); + // hexdump( s_network_command_buffer, 0x40 ); + + s_media_buffer[1] = 0; + media_buffer_32[1] = ret; + break; + } + case AMMBCommand::SetSockOpt: + { + SOCKET fd = (SOCKET)(s_sockets[SocketCheck(media_buffer_32[2])]); + int level = (int)(media_buffer_32[3]); + int optname = (int)(media_buffer_32[4]); + const char* optval = + (char*)(s_network_command_buffer + media_buffer_32[5] - NetworkCommandAddress2); + int optlen = (int)(media_buffer_32[6]); + + int ret = setsockopt(fd, level, optname, optval, optlen); + + int err = WSAGetLastError(); + + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, + "GC-AM: setsockopt( {:d}, {:04x}, {}, {:p}, {} ):{:d} ({})\n", fd, level, + optname, optval, optlen, ret, err); + + s_media_buffer[1] = s_media_buffer[8]; + media_buffer_32[1] = ret; + break; + } + case AMMBCommand::SetTimeOuts: + { + u32 fd = s_sockets[SocketCheck(media_buffer_32[2])]; + u32 timeoutA = media_buffer_32[3]; + u32 timeoutB = media_buffer_32[4]; + u32 timeoutC = 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(DVDINTERFACE_AMMB, "GC-AM: SetTimeOuts( {}, {}, {}, {} ):{}\n", fd, timeoutA, + timeoutB, timeoutC, ret); + + s_media_buffer[1] = s_media_buffer[8]; + media_buffer_32[1] = ret; + break; + } + case AMMBCommand::GetParambyDHCPExec: + { + u32 value = media_buffer_32[2]; + + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: GetParambyDHCPExec({})\n", value); + + s_media_buffer[1] = 0; + media_buffer_32[1] = 0; + break; + } + case AMMBCommand::ModifyMyIPaddr: + { + u32 NetBufferOffset = *(u32*)(s_media_buffer + 8) - NetworkCommandAddress2; + + char* IP = (char*)(s_network_command_buffer + NetBufferOffset); + + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: modifyMyIPaddr({})\n", IP); + break; + } + case AMMBCommand::GetLastError: + { + u32 fd = s_sockets[SocketCheck(media_buffer_32[2])]; + + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: GetLastError( {}({}) ):{}\n", fd, + media_buffer_32[2], s_last_error); + + s_media_buffer[1] = s_media_buffer[8]; + media_buffer_32[1] = s_last_error; + } + break; + case AMMBCommand::InitLink: + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: InitLink"); + break; + case AMMBCommand::AllNetInit: + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: AllNetInit"); + break; + default: + ERROR_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: Command:{:03X}", *(u16*)(s_media_buffer + 2)); + ERROR_LOG_FMT(DVDINTERFACE_AMMB, "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) + { + u32 dimmoffset = offset - DIMMCommandVersion2_2; + memcpy(memory.GetSpanForAddress(address).data(), s_media_buffer + dimmoffset, length); + + INFO_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: Read MEDIA BOARD COMM AREA (3) ({:08x})", dimmoffset); + PrintMBBuffer(address, length); + return 0; + } + + // DIMM memory (8MB) + if (offset >= DIMMMemory2 && offset <= 0xFF800000) + { + u32 dimmoffset = offset - DIMMMemory2; + s_dimm.Seek(dimmoffset, File::SeekOrigin::Begin); + s_dimm.ReadBytes(memory.GetSpanForAddress(address).data(), length); + return 0; + } + + if (offset >= AllNetSettings && offset <= 0x1F000000) + { + 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) + { + memcpy(memory.GetSpanForAddress(address).data(), s_dimm_disc + 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)) + { + 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)) + { + u32 dimmoffset = offset - DIMMMemory; + FileWriteData(&s_dimm, dimmoffset, memory.GetSpanForAddress(address).data(), length); + return 0; + } + + if ((offset >= AllNetBuffer) && (offset <= 0x89011000)) + { + u32 allnet_offset = offset - AllNetBuffer; + memcpy(s_allnet_buffer + allnet_offset, memory.GetSpanForAddress(address).data(), length); + return 0; + } + + if ((offset >= NetworkCommandAddress) && (offset < 0x1F801240)) + { + u32 dimmoffset = offset - NetworkCommandAddress; + + memcpy(s_network_command_buffer + dimmoffset, memory.GetSpanForAddress(address).data(), + length); + + INFO_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: Write NETWORK COMMAND BUFFER ({:08x},{})", dimmoffset, + length); + PrintMBBuffer(address, length); + return 0; + } + + if ((offset >= NetworkCommandAddress2) && (offset <= 0x890601FF)) + { + u32 dimmoffset = offset - NetworkCommandAddress2; + + memcpy(s_network_command_buffer + dimmoffset, memory.GetSpanForAddress(address).data(), + length); + + INFO_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: Write NETWORK COMMAND BUFFER (2) ({:08x},{})", + dimmoffset, length); + PrintMBBuffer(address, length); + return 0; + } + + if ((offset >= NetworkBufferAddress1) && (offset <= 0x1FA1FFFF)) + { + u32 dimmoffset = offset - 0x1FA00000; + + memcpy(s_network_buffer + dimmoffset, memory.GetSpanForAddress(address).data(), length); + + INFO_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: Write NETWORK BUFFER (1) ({:08x},{})", dimmoffset, + length); + PrintMBBuffer(address, length); + return 0; + } + + if ((offset >= NetworkBufferAddress2) && (offset <= 0x1FD0FFFF)) + { + u32 dimmoffset = offset - 0x1FD00000; + + memcpy(s_network_buffer + dimmoffset, memory.GetSpanForAddress(address).data(), length); + + INFO_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: Write NETWORK BUFFER (2) ({:08x},{})", dimmoffset, + length); + PrintMBBuffer(address, length); + return 0; + } + + if ((offset >= NetworkBufferAddress3) && (offset <= 0x8910FFFF)) + { + u32 dimmoffset = offset - 0x89100000; + + memcpy(s_network_buffer + dimmoffset, memory.GetSpanForAddress(address).data(), length); + + INFO_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: Write NETWORK BUFFER (3) ({:08x},{})", dimmoffset, + length); + PrintMBBuffer(address, length); + return 0; + } + + if ((offset >= DIMMCommandVersion1) && (offset <= 0x1F90003F)) + { + u32 dimmoffset = offset - 0x1F900000; + memcpy(s_media_buffer + dimmoffset, memory.GetSpanForAddress(address).data(), length); + + INFO_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: Write MEDIA BOARD COMM AREA (1) ({:08x},{})", offset, + length); + PrintMBBuffer(address, length); + return 0; + } + + if ((offset >= DIMMCommandVersion2) && (offset <= 0x8400005F)) + { + u32 dimmoffset = offset - 0x84000000; + INFO_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: Write MEDIA BOARD COMM AREA (2) ({:08x},{})", offset, + length); + PrintMBBuffer(address, length); + + u8 cmd_flag = memory.Read_U8(address); + + if (dimmoffset == 0x40 && cmd_flag == 1) + { + // Recast for easier access + u32* media_buffer_in_32 = (u32*)(s_media_buffer + 0x20); + u16* media_buffer_in_16 = (u16*)(s_media_buffer + 0x20); + u32* media_buffer_out_32 = (u32*)(s_media_buffer); + u16* media_buffer_out_16 = (u16*)(s_media_buffer); + + INFO_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: Execute command:{:03X}", media_buffer_in_16[1]); + + memset(s_media_buffer, 0, 0x20); + + media_buffer_out_32[0] = media_buffer_in_32[0] | 0x80000000; // Set command okay flag + + memcpy(s_media_buffer + 0x40, s_media_buffer, 0x20); + + switch (static_cast(media_buffer_in_16[1])) + { + case AMMBCommand::Unknown_000: + media_buffer_out_32[1] = 1; + break; + case AMMBCommand::GetDIMMSize: + media_buffer_out_32[1] = 0x1FFF8000; + break; + case AMMBCommand::GetMediaBoardStatus: + // Status + media_buffer_out_32[1] = LoadedGameProgram; + // Progress in % + media_buffer_out_32[2] = 100; + break; + // SegaBoot version: 3.09 + case AMMBCommand::GetSegaBootVersion: + // Version + media_buffer_out_16[2] = Common::swap16(0x0309); + // Unknown + media_buffer_out_16[3] = 2; + media_buffer_out_32[2] = 0x4746; // "GF" + media_buffer_out_32[4] = 0xFF; + break; + case AMMBCommand::GetSystemFlags: + s_media_buffer[4] = 0; + s_media_buffer[5] = GDROM; + + // Enable development mode (Sega Boot) + // This also allows region free booting + s_media_buffer[6] = 1; + media_buffer_out_16[4] = 0; // Access Count + s_media_buffer[7] = 1; + break; + case AMMBCommand::GetMediaBoardSerial: + memcpy(s_media_buffer + 4, "A85E-01A62204904", 16); + break; + case AMMBCommand::Unknown_104: + s_media_buffer[4] = 1; + break; + default: + PanicAlertFmtT("Unhandled Media Board Command:{0:02x}", media_buffer_in_16[1]); + break; + } + + memcpy(memory.GetSpanForAddress(address).data(), s_media_buffer, length); + + memset(s_media_buffer + 0x20, 0, 0x20); + + ExpansionInterface::GenerateInterrupt(0x04); + return 0; + } + else + { + memcpy(s_media_buffer + dimmoffset, memory.GetSpanForAddress(address).data(), length); + } + return 0; + } + + if ((offset >= DIMMCommandVersion2_2) && (offset <= 0x89000200)) + { + u32 dimmoffset = offset - 0x89000000; + INFO_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: Write MEDIA BOARD COMM AREA (3) ({:08x})", + dimmoffset); + PrintMBBuffer(address, length); + + memcpy(s_media_buffer + dimmoffset, memory.GetSpanForAddress(address).data(), length); + + return 0; + } + + // Firmware Write + if ((offset >= FirmwareAddress) && (offset <= 0x84818000)) + { + u32 dimmoffset = offset - 0x84800000; + + INFO_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: Write Firmware ({:08x})", dimmoffset); + PrintMBBuffer(address, length); + return 0; + } + + // DIMM memory (8MB) + if ((offset >= DIMMMemory2) && (offset <= 0xFF800000)) + { + u32 dimmoffset = offset - 0xFF000000; + FileWriteData(&s_dimm, dimmoffset, 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: + if ((offset == 0) && (length == 0)) + { + // Recast for easier access + u32* media_buffer_in_32 = (u32*)(s_media_buffer + 0x20); + u16* media_buffer_in_16 = (u16*)(s_media_buffer + 0x20); + u32* media_buffer_out_32 = (u32*)(s_media_buffer); + u16* media_buffer_out_16 = (u16*)(s_media_buffer); + + memset(s_media_buffer, 0, 0x20); + + media_buffer_out_16[0] = media_buffer_in_16[0]; + + // Command + media_buffer_out_16[1] = media_buffer_in_16[1] | 0x8000; // Set command okay flag + + if (media_buffer_in_16[1]) + INFO_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: Execute command:{:03X}", media_buffer_in_16[1]); + + switch (static_cast(media_buffer_in_16[1])) + { + case AMMBCommand::Unknown_000: + media_buffer_out_32[1] = 1; + break; + case AMMBCommand::GetDIMMSize: + media_buffer_out_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; + + media_buffer_out_32[1] = status; + media_buffer_out_32[2] = progress; + if (progress < 100) + { + progress++; + } + else + { + status = LoadedGameProgram; + } + } + break; + // SegaBoot version: 3.11 + case AMMBCommand::GetSegaBootVersion: + // Version + media_buffer_out_16[2] = Common::swap16(0x1103); + // Unknown + media_buffer_out_16[3] = 1; + media_buffer_out_32[2] = 1; + media_buffer_out_32[4] = 0xFF; + break; + case AMMBCommand::GetSystemFlags: + // 1: GD-ROM + s_media_buffer[4] = 1; + s_media_buffer[5] = 1; + // Enable development mode (Sega Boot) + // This also allows region free booting + s_media_buffer[6] = 1; + media_buffer_out_16[4] = 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 + + // ERROR_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: 0x301: ({:08x})", *(u32*)(s_media_buffer+0x24) + // ); + + // Pointer to a memory address that is directly displayed on screen as a string + // ERROR_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: ({:08x})", *(u32*)(s_media_buffer+0x28) + // ); + + // On real system it shows the status about the DIMM/GD-ROM here + // We just show "TEST OK" + memory.Write_U32(0x54534554, media_buffer_in_32[4]); + memory.Write_U32(0x004B4F20, media_buffer_in_32[4] + 4); + + media_buffer_out_32[1] = media_buffer_in_32[1]; + break; + case AMMBCommand::Closesocket: + { + u32 fd = s_sockets[SocketCheck(media_buffer_in_32[2])]; + + int ret = closesocket(fd); + + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: closesocket( {}({}) ):{}\n", fd, + media_buffer_in_32[2], ret); + + s_sockets[media_buffer_in_32[2]] = SOCKET_ERROR; + + media_buffer_out_32[1] = ret; + s_last_error = SSC_SUCCESS; + } + break; + case AMMBCommand::Connect: + { + struct sockaddr_in addr; + + u32 fd = s_sockets[SocketCheck(media_buffer_in_32[2])]; + u32 off = media_buffer_in_32[3] - NetworkCommandAddress; + u32 len = media_buffer_in_32[4]; + + int ret = 0; + int err = 0; + + memcpy((void*)&addr, s_network_command_buffer + off, sizeof(struct sockaddr_in)); + + ret = NetDIMMConnect(fd, &addr, len); + + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: connect( {}({}), ({},{}:{}), {} ):{} ({})\n", fd, + media_buffer_in_32[2], addr.sin_family, inet_ntoa(addr.sin_addr), + Common::swap16(addr.sin_port), len, ret, err); + + s_media_buffer[1] = s_media_buffer[8]; + media_buffer_out_32[1] = ret; + } + break; + case AMMBCommand::Recv: + { + u32 fd = s_sockets[SocketCheck(media_buffer_in_32[2])]; + u32 off = media_buffer_in_32[3]; + u32 len = media_buffer_in_32[4]; + + if (len >= sizeof(s_network_buffer)) + { + len = sizeof(s_network_buffer); + } + + int ret = 0; + 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); + } + + int err = 0; + + ret = recv(fd, buffer, len, 0); + err = WSAGetLastError(); + + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: recv( {}, 0x{:08x}, {} ):{} {}\n", fd, off, len, + ret, err); + + s_media_buffer[1] = s_media_buffer[8]; + media_buffer_out_32[1] = ret; + } + break; + case AMMBCommand::Send: + { + u32 fd = s_sockets[SocketCheck(media_buffer_in_32[2])]; + u32 off = media_buffer_in_32[3]; + u32 len = media_buffer_in_32[4]; + + int ret = 0; + + if (off >= NetworkBufferAddress1 && off < NetworkBufferAddress1 + sizeof(s_network_buffer)) + { + off -= NetworkBufferAddress1; + } + else + { + ERROR_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: send(error) unhandled destination:{:08x}\n", + off); + } + + ret = send(fd, (char*)(s_network_buffer + off), len, 0); + int err = WSAGetLastError(); + + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: send( {}({}), 0x{:08x}, {} ): {} {}\n", fd, + media_buffer_in_32[2], off, len, ret, err); + + s_media_buffer[1] = s_media_buffer[8]; + media_buffer_out_32[1] = ret; + } + break; + case AMMBCommand::Socket: + { + // Protocol is not sent + u32 af = media_buffer_in_32[2]; + u32 type = media_buffer_in_32[3]; + + SOCKET fd = socket_(af, type, IPPROTO_TCP); + + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: socket( {}, {}, 6 ):{}\n", af, type, fd); + + s_media_buffer[1] = 0; + media_buffer_out_32[1] = fd; + } + break; + case AMMBCommand::Select: + { + u32 nfds = s_sockets[SocketCheck(media_buffer_in_32[2] - 1)]; + + fd_set* readfds = nullptr; + fd_set* writefds = nullptr; + fd_set* exceptfds = nullptr; + timeval* timeout = nullptr; + + // Only one of 3, 4, 5 is ever set alongside 6 + if (media_buffer_in_32[3] && media_buffer_in_32[6]) + { + u32 ROffset = media_buffer_in_32[6] - NetworkCommandAddress; + + readfds = (fd_set*)(s_network_command_buffer + ROffset); + FD_ZERO(readfds); + FD_SET(nfds, readfds); + + timeout = + (timeval*)(s_network_command_buffer + media_buffer_in_32[3] - NetworkCommandAddress); + } + else if (media_buffer_in_32[4] && media_buffer_in_32[6]) + { + u32 WOffset = media_buffer_in_32[6] - NetworkCommandAddress; + writefds = (fd_set*)(s_network_command_buffer + WOffset); + FD_ZERO(writefds); + FD_SET(nfds, writefds); + + timeout = + (timeval*)(s_network_command_buffer + media_buffer_in_32[4] - NetworkCommandAddress); + } + else if (media_buffer_in_32[5] && media_buffer_in_32[6]) + { + u32 EOffset = media_buffer_in_32[6] - NetworkCommandAddress; + exceptfds = (fd_set*)(s_network_command_buffer + EOffset); + FD_ZERO(exceptfds); + FD_SET(nfds, exceptfds); + + timeout = + (timeval*)(s_network_command_buffer + media_buffer_in_32[5] - NetworkCommandAddress); + } + + /* + 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; + } + + int ret = select(nfds + 1, readfds, writefds, exceptfds, timeout); + + int err = WSAGetLastError(); + + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, + "GC-AM: select( {}({}), 0x{:08x} 0x{:08x} 0x{:08x} 0x{:08x} ):{} {} \n", + nfds, media_buffer_in_32[2], media_buffer_in_32[3], media_buffer_in_32[4], + media_buffer_in_32[5], media_buffer_in_32[6], ret, err); + // hexdump( NetworkCMDBuffer, 0x40 ); + + s_media_buffer[1] = 0; + media_buffer_out_32[1] = ret; + } + break; + case AMMBCommand::SetSockOpt: + { + SOCKET fd = (SOCKET)(s_sockets[SocketCheck(media_buffer_in_32[2])]); + int level = (int)(media_buffer_in_32[3]); + int optname = (int)(media_buffer_in_32[4]); + const char* optval = + (char*)(s_network_command_buffer + media_buffer_in_32[5] - NetworkCommandAddress); + int optlen = (int)(media_buffer_in_32[6]); + + int ret = setsockopt(fd, level, optname, optval, optlen); + + int err = WSAGetLastError(); + + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, + "GC-AM: setsockopt( {:d}, {:04x}, {}, {:p}, {} ):{:d} ({})\n", fd, level, + optname, optval, optlen, ret, err); + + s_media_buffer[1] = s_media_buffer[8]; + media_buffer_out_32[1] = ret; + } + break; + case AMMBCommand::ModifyMyIPaddr: + { + u32 NetBufferOffset = media_buffer_in_32[2] - NetworkCommandAddress; + + char* IP = (char*)(s_network_command_buffer + NetBufferOffset); + + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: modifyMyIPaddr({})\n", IP); + } + break; + // Empty reply + case AMMBCommand::InitLink: + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: 0x601"); + break; + case AMMBCommand::Unknown_605: + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: 0x605"); + break; + case AMMBCommand::SetupLink: + { + struct sockaddr_in addra, addrb; + addra.sin_addr.s_addr = media_buffer_in_32[4]; + addrb.sin_addr.s_addr = media_buffer_in_32[5]; + + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: 0x606:"); + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: Size: ({}) ", media_buffer_in_16[2]); // size + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: Port: ({})", + Common::swap16(media_buffer_in_16[3])); // port + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM:LinkNum:({:02x})", + s_media_buffer[0x28]); // linknum + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: ({:02x})", s_media_buffer[0x29]); + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: ({:04x})", media_buffer_in_16[5]); + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: IP: ({})", inet_ntoa(addra.sin_addr)); // IP + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: IP: ({})", + inet_ntoa(addrb.sin_addr)); // Target IP + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: ({:08x})", + Common::swap32(media_buffer_in_32[6])); // some RAM address + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: ({:08x})", + Common::swap32(media_buffer_in_32[7])); // some RAM address + + media_buffer_out_32[1] = 0; + } + break; + // This sends a UDP packet to previously defined Target IP/Port + case AMMBCommand::SearchDevices: + { + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: 0x607: ({})", media_buffer_in_16[2]); + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: ({})", media_buffer_in_16[3]); + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: ({:08x})", media_buffer_in_32[2]); + + u8* Data = (u8*)(s_network_buffer + media_buffer_in_32[2] - 0x1FD00000); + + for (u32 i = 0; i < 0x20; i += 0x10) + { + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: {:08x} {:08x} {:08x} {:08x}", *(u32*)(Data + i), + *(u32*)(Data + i + 4), *(u32*)(Data + i + 8), *(u32*)(Data + i + 12)); + } + + media_buffer_out_32[1] = 0; + } + break; + case AMMBCommand::Unknown_608: + { + u32 IP = media_buffer_in_32[2]; + u16 Port = media_buffer_in_16[4]; + u16 Flag = media_buffer_in_16[5]; + + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: 0x608( {} {} {} )", IP, Port, Flag); + } + break; + case AMMBCommand::Unknown_614: + NOTICE_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: 0x614"); + break; + default: + ERROR_LOG_FMT(DVDINTERFACE_AMMB, "GC-AM: execute buffer UNKNOWN:{:03x}", + *(u16*)(s_media_buffer + 0x22)); + break; + } + + memset(s_media_buffer + 0x20, 0, 0x20); + return 0; + } + + PanicAlertFmtT("Unhandled Media Board Execute:{0:08x}", *(u16*)(s_media_buffer + 0x22)); + break; + default: + PanicAlertFmtT("Unhandled Media Board Command:{0:02x}", command); + break; + } + + return 0; +} + +u32 GetMediaType(void) +{ + switch (GetGameType()) + { + default: + case FZeroAX: + case VirtuaStriker3: + case VirtuaStriker4: + case GekitouProYakyuu: + case KeyOfAvalon: + return GDROM; + break; + + case MarioKartGP: + case MarioKartGP2: + case FZeroAXMonster: + return NAND; + break; + } + // Never reached +} + +u32 GetGameType(void) +{ + u64 game_id = 0; + + // Convert game ID into hex + if (strlen(SConfig::GetInstance().GetGameID().c_str()) > 4) + { + game_id = 0x30303030; + } + else + { + 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)) + { + // SBGG - F-ZERO AX + case 0x53424747: + return FZeroAX; + // SBHA - F-ZERO AX (Monster) + case 0x53424841: + return FZeroAXMonster; + // SBKJ/SBKP - MARIOKART ARCADE GP + case 0x53424B50: + case 0x53424B5A: + return MarioKartGP; + // SBNJ/SBNL - MARIOKART ARCADE GP2 + case 0x53424E4A: + case 0x53424E4C: + return MarioKartGP2; + // SBEJ/SBEY - Virtua Striker 2002 + case 0x5342454A: + case 0x53424559: + return VirtuaStriker3; + // SBLJ/SBLK/SBLL - VIRTUA STRIKER 4 Ver.2006 + case 0x53424C4A: + case 0x53424C4B: + case 0x53424C4C: + return VirtuaStriker4_2006; + // SBHJ/SBHN/SBHZ - VIRTUA STRIKER 4 VER.A + case 0x5342484A: + case 0x5342484E: + case 0x5342485A: + // SBJA/SBJJ - VIRTUA STRIKER 4 + case 0x53424A41: + case 0x53424A4A: + return VirtuaStriker4; + // SBFX/SBJN - Key of Avalon + case 0x53424658: + case 0x53424A4E: + return KeyOfAvalon; + // SBGX - Gekitou Pro Yakyuu (DIMM Upgrade 3.17) + case 0x53424758: + 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 0x4753424A: + case 0x47313255: + case 0x52454C53: + case 0x52454c4a: + return VirtuaStriker3; + // S000 - Firmware update + case 0x53303030: + return FirmwareUpdate; + } + // never reached +} +bool GetTestMenu(void) +{ + return s_test_menu; +} +void Shutdown(void) +{ + s_netcfg.Close(); + s_netctrl.Close(); + s_extra.Close(); + s_backup.Close(); + s_dimm.Close(); + + if (s_dimm_disc) + { + delete[] s_dimm_disc; + s_dimm_disc = nullptr; + } + + // 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..16de079c8f --- /dev/null +++ b/Source/Core/Core/HW/DVD/AMMediaboard.h @@ -0,0 +1,234 @@ +// Copyright 2017 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 + 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); +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(void); +u32 GetMediaType(void); +bool GetTestMenu(void); +void Shutdown(void); +}; // namespace AMMediaboard diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index 1c6ea07aa0..bda62ca303 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -27,6 +27,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" @@ -282,6 +283,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 @@ -311,7 +322,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,12 +708,16 @@ bool DVDInterface::CheckReadPreconditions() SetDriveError(DriveError::MotorStopped); return false; } - if (m_drive_state == DriveState::DiscIdNotRead) - { - ERROR_LOG_FMT(DVDINTERFACE, "Disc id not read."); - SetDriveError(DriveError::NoDiscID); - return false; - } + + // SegaBoot doesn't read the Disc ID + if (!m_system.IsTriforce()) + if (m_drive_state == DriveState::DiscIdNotRead) + { + ERROR_LOG_FMT(DVDINTERFACE, "Disc id not read."); + SetDriveError(DriveError::NoDiscID); + return false; + } + return true; } @@ -753,12 +768,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 a9200ac85a..4da3f5402c 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.cpp @@ -2,20 +2,132 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "Core/HW/EXI/EXI_DeviceBaseboard.h" +#include +#include +#include +#include +#include -#include "Common/Assert.h" -#include "Common/ChunkFile.h" +#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" + +bool g_interrupt_set = false; +u32 g_irq_timer = 0; +u32 g_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(); + + g_interrupt_set = true; + g_irq_timer = 0; + g_irq_status = flag; + + system.GetExpansionInterface().UpdateInterrupts(); +} + +CEXIBaseboard::CEXIBaseboard(Core::System& system) : IEXIDevice(system), m_position(0) +{ + std::string backup_Filename(File::GetUserPath(D_TRIUSER_IDX) + "tribackup_" + + SConfig::GetInstance().GetGameID().c_str() + ".bin"); + + 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 = File::GetUserPath(D_TRIUSER_IDX) + "tribackup_tmp_" + std::to_string(rand()) + + SConfig::GetInstance().GetGameID().c_str() + ".bin"; + + 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() != 0) + { + 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; } @@ -25,33 +137,199 @@ bool CEXIBaseboard::IsPresent() const return true; } -void CEXIBaseboard::TransferByte(u8& byte) +bool CEXIBaseboard::IsInterruptSet() { - if (m_position == 0) + if (g_interrupt_set) { - m_command = byte; + DEBUG_LOG_FMT(SP1, "AM-BB: IRQ"); + if (++g_irq_timer > 12) + g_interrupt_set = false; + return 1; } else { - switch (m_command) + return 0; + } +} + +void CEXIBaseboard::DMAWrite(u32 addr, u32 size) +{ + auto& system = Core::System::GetInstance(); + auto& memory = system.GetMemory(); + + NOTICE_LOG_FMT(SP1, "AM-BB: COMMAND: Backup DMA Write: {:08x} {:x}", addr, size); + + m_backup.Seek(m_backoffset, File::SeekOrigin::Begin); + + m_backup.WriteBytes(memory.GetSpanForAddress(addr).data(), size); + + m_backup.Flush(); +} + +void CEXIBaseboard::DMARead(u32 addr, u32 size) +{ + auto& system = Core::System::GetInstance(); + auto& memory = system.GetMemory(); + + NOTICE_LOG_FMT(SP1, "AM-BB: COMMAND: Backup DMA Read: {:08x} {:x}", addr, size); + + m_backup.Seek(m_backoffset, 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: - { - static constexpr std::array ID = {0x06, 0x04, 0x10, 0x00}; - byte = ID[(m_position - 2) & 3]; - break; + if (checksum & bit) + checksum ^= check; + check >>= 1; + bit >>= 1; } - default: - ERROR_LOG_FMT(EXPANSIONINTERFACE, "EXI BASEBOARD: Unhandled command {:#x} {:#x}", m_command, - m_position); + + 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]) + { + case BackupOffsetSet: + m_backoffset = (m_command[1] << 8) | m_command[2]; + DEBUG_LOG_FMT(SP1, "AM-BB: COMMAND: BackupOffsetSet:{:04x}", m_backoffset); + m_backup.Seek(m_backoffset, File::SeekOrigin::Begin); + _byte = 0x01; + break; + case BackupWrite: + DEBUG_LOG_FMT(SP1, "AM-BB: COMMAND: BackupWrite:{:04x}-{:02x}", m_backoffset, 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_backoffset); + _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, g_irq_status); + _byte = 0x04; + break; + case WriteISR: + NOTICE_LOG_FMT(SP1, "AM-BB: COMMAND: WriteISR :{:02x} {:02x}", m_command[1], m_command[2]); + g_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)) + { + g_interrupt_set = true; + g_irq_timer = 0; + g_irq_status = 0x02; + } + if ((m_command[1] == 2) && (m_command[2] == 1)) + { + g_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 = g_irq_status; + g_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(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(g_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..ccc8b45a8f 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceBaseboard.h @@ -3,25 +3,65 @@ #pragma once +#include +#include +#include +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "Common/Flag.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); - void SetCS(int cs) override; + explicit CEXIBaseboard(Core::System& system); + virtual ~CEXIBaseboard(); + + void SetCS(int _iCS) 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_backoffset; + 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..d53823026b 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[] = {PAD_SWITCH_TEST, PAD_SWITCH_SERVICE, PAD_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 e0708605db..11fca7fc10 100644 --- a/Source/Core/Core/HW/Memmap.h +++ b/Source/Core/Core/HW/Memmap.h @@ -115,7 +115,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 8ea3e75d02..c826cf345b 100644 --- a/Source/Core/Core/HW/SI/SI_Device.cpp +++ b/Source/Core/Core/HW/SI/SI_Device.cpp @@ -16,6 +16,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" @@ -139,6 +140,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; @@ -181,6 +183,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 ebab7e3235..ea022fa5fa 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..10c5261f43 --- /dev/null +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp @@ -0,0 +1,2374 @@ +// Copyright 2017 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// #pragma warning(disable : 4189) + +#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; + u8 hdr[3] = {0xE0, (u8)node, 0}; + m_csum = 0; + AddData(hdr, 3, 1); +} + +void JVSIOMessage::AddData(const u8* dst, size_t len, int sync = 0) +{ + while (len--) + { + int 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, size_t len) +{ + AddData((const u8*)data, len); +} + +void JVSIOMessage::AddData(const char* data) +{ + AddData(data, strlen(data)); +} + +void JVSIOMessage::AddData(u32 n) +{ + u8 cs = n; + AddData(&cs, 1); +} + +void JVSIOMessage::End() +{ + 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; +} + +static u8 last[2][0x80]; +static u32 lastptr[2]; +/* + Reply has to be delayed due a bug in the parser +*/ +static void swap_buffers(u8* buffer, u32* buffer_length) +{ + 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}; + +// AM-Baseboard device on SI +CSIDevice_AMBaseboard::CSIDevice_AMBaseboard(Core::System& system, SIDevices device, + int device_number) + : ISIDevice(system, device, device_number) +{ + memset(m_coin, 0, sizeof(m_coin)); + + // Setup IC-card + m_ic_card_state = 0x20; + m_ic_card_status = 0; + 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 = 0; + + 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 = 1; // Open + m_card_state_call_count = 0; + + // Serial + m_wheelinit = 0; + + m_motorinit = 0; + m_motorforce_x = 0; + + m_fzdx_seatbelt = 1; + m_fzdx_motion_stop = 0; + m_fzdx_sensor_right = 0; + m_fzdx_sensor_left = 0; + + m_rx_reply = 0xF0; + + m_fzcc_seatbelt = 1; + m_fzcc_sensor = 0; + m_fzcc_emergency = 0; + m_fzcc_service = 0; + + memset(m_motorreply, 0, sizeof(m_motorreply)); +} + +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); + + u16 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 + { + 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; + + memset(data_out, 0, sizeof(data_out)); + data_out[data_offset++] = 1; + data_out[data_offset++] = 1; + + u8* data_in = buffer + 2; + u8* data_in_end = buffer + buffer[buffer_position] + 2; + + while (data_in < data_in_end) + { + u32 gcam_command = *data_in++; + switch (GCAMCommand(gcam_command)) + { + case GCAMCommand::StatusSwitches: + { + u8 status = *data_in++; + DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x10, {:02x} (READ STATUS&SWITCHES)", + status); + + GCPadStatus PadStatus; + PadStatus = Pad::GetStatus(ISIDevice::m_device_number); + data_out[data_offset++] = gcam_command; + data_out[data_offset++] = 0x2; + + /* 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++, + *data_in++); + data_out[data_offset++] = gcam_command; + data_out[data_offset++] = 0x00; + break; + case GCAMCommand::Unknown_14: + NOTICE_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x14, {:02x} {:02x}", *data_in++, + *data_in++); + data_out[data_offset++] = gcam_command; + data_out[data_offset++] = 0x00; + 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++, *data_in++, *data_in++, *data_in++, *data_in++); + u8 string[] = "\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"; + data_out[data_offset++] = gcam_command; + data_out[data_offset++] = 0x14; + + for (int i = 0; i < 0x14; ++i) + data_out[data_offset++] = string[i]; + } + 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++, + *data_in++); + data_out[data_offset++] = gcam_command; + data_out[data_offset++] = 0x00; + break; + case GCAMCommand::Unknown_24: + DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 0x24, {:02x} {:02x}", *data_in++, + *data_in++); + data_out[data_offset++] = gcam_command; + data_out[data_offset++] = 0x00; + 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_wheelinit) + { + case 0: + data_out[data_offset++] = 'E'; // Error + data_out[data_offset++] = '0'; + data_out[data_offset++] = '0'; + m_wheelinit++; + 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_wheelinit++; + } + 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) + { + 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; + + u16 page = m_ic_write_buffer[5]; + 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: + { + 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: + { + 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: + { + 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: + { + u16 page = Common::swap16(*(u16*)(data_in + 6)); + u16 count = Common::swap16(*(u16*)(data_in + 8)); + + 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: + { + u16 pksize = length; + u16 size = Common::swap16(*(u16*)(data_in + 2)); + u16 page = Common::swap16(*(u16*)(data_in + 6)); + 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 + u32 serial_command = Common::swap32(*(u32*)(data_in + command_offset)); + serial_command ^= 0x80000000; + if (AMMediaboard::GetGameType() == FZeroAX || + AMMediaboard::GetGameType() == FZeroAXMonster) + { + INFO_LOG_FMT(SERIALINTERFACE_AMBB, + "GC-AM: Command 0x31 (MOTOR) Length:{:02x} Command:{:06x}({:02x})", + length, serial_command >> 8, serial_command & 0xFF); + } + 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_motorreply[command_offset + 2] = 0; + m_motorreply[command_offset + 3] = 0; + + // Error + m_motorreply[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_motorforce_x = -((s16)serial_command & 0xFF00); + } + else // Right + { + m_motorforce_x = (serial_command - 0x4000) & 0xFF00; + } + + m_motorforce_x *= 2; + + // FFB + if (m_motorinit == 2) + { + if (serial_interface.GetDeviceType(1) == SerialInterface::SIDEVICE_GC_STEERING) + { + GCPadStatus PadStatus; + PadStatus = Pad::GetStatus(1); + if (PadStatus.isConnected) + { + ControlState mapped_strength = (double)(m_motorforce_x >> 8); + mapped_strength /= 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_motorinit = 2; + break; + // Reset + case 0x7F: + m_motorinit = 1; + memset(m_motorreply, 0, sizeof(m_motorreply)); + break; + } + + // Checksum + m_motorreply[command_offset + 5] = m_motorreply[command_offset + 2] ^ + m_motorreply[command_offset + 3] ^ + m_motorreply[command_offset + 4]; + } + } + + if (length == 0) + { + data_out[data_offset++] = gcam_command; + data_out[data_offset++] = 0x00; + } + else + { + if (m_motorinit) + { + // Motor + m_motorreply[0] = gcam_command; + m_motorreply[1] = length; // Same out as in size + + memcpy(data_out + data_offset, m_motorreply, m_motorreply[1] + 2); + data_offset += m_motorreply[1] + 2; + } + } + + data_in += length; + break; + } + case GCAMCommand::SerialB: + { + DEBUG_LOG_FMT(SERIALINTERFACE_AMBB, "GC-AM: Command 32 (CARD-Interface)"); + 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 ReadLength = m_card_read_length - m_card_read; + + if (AMMediaboard::GetGameType() == FZeroAX) + { + if (ReadLength > 0x2F) + ReadLength = 0x2F; + } + + data_out[data_offset++] = ReadLength; // 0x2F (max size per packet) + + memcpy(data_out + data_offset, m_card_read_packet + m_card_read, ReadLength); + + data_offset += ReadLength; + m_card_read += ReadLength; + + if (m_card_read >= m_card_read_length) + m_card_read_length = 0; + + data_in += length; + break; + } + + data_out[data_offset++] = gcam_command; + u32 command_length_offset = data_offset; + data_out[data_offset++] = 0x00; // len + + data_out[data_offset++] = 0x02; // + 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 + + u32 i; + data_out[data_offset] = 0; // 0x07 + for (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) + { + std::string card_filename(File::GetUserPath(D_TRIUSER_IDX) + "tricard_" + + SConfig::GetInstance().GetGameID().c_str() + + ".bin"); + + 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 = 1; + } + } + + 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) + { + std::string card_filename(File::GetUserPath(D_TRIUSER_IDX) + "tricard_" + + SConfig::GetInstance().GetGameID().c_str() + + ".bin"); + + 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: + { + u8 mode = m_card_buffer[6]; + u8 bitmode = m_card_buffer[7]; + 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; + + std::string card_filename(File::GetUserPath(D_TRIUSER_IDX) + "tricard_" + + SConfig::GetInstance().GetGameID().c_str() + + ".bin"); + + 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 = 1; + } + + 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 + + u32 i; + for (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: + { + u8 mode = m_card_buffer[6]; + u8 bitmode = m_card_buffer[7]; + 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); + + 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 = 0; + } + // Open + else if (m_card_buffer[6] == 0x31) + { + m_card_shutter = 1; + } + 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; + + u8* frame = &data_in[0]; + 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]; + 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 + 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: + { + int player_count = *jvs_io++; + int 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; + PadStatus = Pad::GetStatus(0); + + // Test button + if (PadStatus.switches & PAD_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 (int i = 0; i < player_count; ++i) + { + unsigned char player_data[3] = {0, 0, 0}; + + // Service button + if (PadStatus.switches & PAD_SWITCH_SERVICE) + player_data[0] |= 0x40; + + switch (AMMediaboard::GetGameType()) + { + // Controller configuration for F-Zero AX (DX) + case FZeroAX: + PadStatus = Pad::GetStatus(0); + 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_Y) + 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_A) + player_data[0] |= 0x20; + // Paddle right + if (PadStatus.button & PAD_BUTTON_B) + 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: + PadStatus = Pad::GetStatus(0); + 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_Y) + 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_A) + player_data[0] |= 0x20; + // Paddle right + if (PadStatus.button & PAD_BUTTON_B) + 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; + // Long Pass + if (PadStatus.button & PAD_BUTTON_X) + player_data[0] |= 0x01; + // Short Pass + if (PadStatus.button & PAD_BUTTON_B) + player_data[1] |= 0x80; + // Shoot + if (PadStatus.button & PAD_BUTTON_A) + 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_Y) + player_data[0] |= 0x02; + // Shoot + if (PadStatus.button & PAD_BUTTON_A) + player_data[1] |= 0x80; + // Dash + if (PadStatus.button & PAD_BUTTON_B) + 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: + { + PadStatus = Pad::GetStatus(0); + // Start + if (PadStatus.button & PAD_BUTTON_START) + player_data[0] |= 0x80; + // Service button + if (PadStatus.button & PAD_BUTTON_X) + player_data[0] |= 0x40; + // 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: + { + PadStatus = Pad::GetStatus(0); + // 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 ^= 0x8000; + } + } + break; + } + + for (int j = 0; j < player_byte_count; ++j) + message.AddData(player_data[j]); + } + break; + } + case JVSIOCommand::CoinInput: + { + int slots = *jvs_io++; + message.AddData(StatusOkay); + for (int i = 0; i < slots; i++) + { + GCPadStatus PadStatus; + PadStatus = Pad::GetStatus(i); + if ((PadStatus.switches & PAD_SWITCH_COIN) && !m_coin_pressed[i]) + { + m_coin[i]++; + } + m_coin_pressed[i] = PadStatus.switches & PAD_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); + + int analogs = *jvs_io++; + GCPadStatus PadStatus; + GCPadStatus PadStatus2; + 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_motorinit == 1) + { + if (m_motorforce_x > 0) + { + message.AddData(0x80 - (m_motorforce_x >> 8)); + } + else + { + message.AddData((m_motorforce_x >> 8)); + } + message.AddData((u8)0); + + message.AddData(PadStatus.stickY); + message.AddData((u8)0); + } + else + { + message.AddData(PadStatus.stickX); + 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: + { + PadStatus2 = Pad::GetStatus(1); + + message.AddData(PadStatus.stickY); + message.AddData((u8)0); + message.AddData(PadStatus.stickX); + message.AddData((u8)0); + + message.AddData(PadStatus2.stickY); + message.AddData((u8)0); + message.AddData(PadStatus2.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: + { + int channel = *jvs_io++; + + GCPadStatus PadStatus; + 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: + { + u32 slot = *jvs_io++; + u8 coinh = *jvs_io++; + 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: + { + u32 bytes = *jvs_io++; + + if (bytes) + { + message.AddData(StatusOkay); + + // The lamps are controlled via this + if (AMMediaboard::GetGameType() == MarioKartGP) + { + 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: + { + int slot = *jvs_io++; + m_coin[slot] += (*jvs_io++ << 8) | *jvs_io++; + message.AddData(StatusOkay); + DEBUG_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0x35, CoinAddOutput: {}", slot); + break; + } + case JVSIOCommand::NAMCOCommand: + { + int cmd_ = *jvs_io++; + if (cmd_ == 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}", cmd_); + } + break; + } + case JVSIOCommand::Reset: + if (*jvs_io++ == 0xD9) + { + NOTICE_LOG_FMT(SERIALINTERFACE_JVSIO, "JVS-IO: Command 0xF0, Reset"); + delay = 0; + m_wheelinit = 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; + + u8* buf = message.m_msg; + 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..7ca6aaca8e --- /dev/null +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.h @@ -0,0 +1,304 @@ +// Copyright 2017 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, m_last_start, m_csum; + u8 m_msg[0x80]; + + JVSIOMessage(); + void Start(int node); + void AddData(const u8* dst, size_t len, int sync); + void AddData(const void* data, size_t len); + void AddData(const char* data); + void AddData(u32 n); + void End(); +}; // end class JVSIOMessage + +// 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 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; + /* + 0 - OK + 8000 - no card + 800E - ??? + other- bad card + */ + 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; + u32 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; + u32 m_card_shutter; + u32 m_card_state_call_count; + u8 m_card_offset; + + u32 m_wheelinit; + + u32 m_motorinit; + u8 m_motorreply[64]; + s16 m_motorforce_x; + + // F-Zero AX (DX) + u32 m_fzdx_seatbelt; + u32 m_fzdx_motion_stop; + u32 m_fzdx_sensor_right; + u32 m_fzdx_sensor_left; + u8 m_rx_reply; + + // F-Zero AX (CyCraft) + u32 m_fzcc_seatbelt; + u32 m_fzcc_sensor; + u32 m_fzcc_emergency; + u32 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 788d5295cc..cdbbd85032 100644 --- a/Source/Core/Core/HW/VideoInterface.cpp +++ b/Source/Core/Core/HW/VideoInterface.cpp @@ -163,7 +163,19 @@ 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; + + if (region == DiscIO::Region::NTSC_J) + { + m_dtv_status.ntsc_j = false; + } + + /* + Triforce IPL requires the DTV NTSC-J flag to be set. + */ + if (m_system.IsTriforce()) + { + m_dtv_status.ntsc_j = true; + } 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 3f4344ad88..bfda17a715 100644 --- a/Source/Core/Core/NetPlayClient.cpp +++ b/Source/Core/Core/NetPlayClient.cpp @@ -54,6 +54,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" @@ -1891,6 +1892,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); @@ -1898,8 +1901,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); @@ -1917,7 +1918,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 { @@ -2780,6 +2782,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); @@ -2852,3 +2864,12 @@ int SerialInterface::CSIDevice_GCController::NetPlay_InGamePadToLocalPad(int num return numPAD; } +int SerialInterface::CSIDevice_AMBaseboard::NetPlay_InGamePadToLocalPad(int numPAD) +{ + std::lock_guard lk(NetPlay::crit_netplay_client); + + if (NetPlay::netplay_client) + return NetPlay::netplay_client->InGamePadToLocalPad(numPAD); + + return numPAD; +} diff --git a/Source/Core/Core/TitleDatabase.cpp b/Source/Core/Core/TitleDatabase.cpp index 4609cfddb1..5cc521995d 100644 --- a/Source/Core/Core/TitleDatabase.cpp +++ b/Source/Core/Core/TitleDatabase.cpp @@ -94,32 +94,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; @@ -152,12 +128,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..d488692438 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,7 @@ 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) }; // Languages 0 - 9 match Nintendo's Wii language numbering. @@ -66,7 +66,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 a7273b0f0e..838ae28984 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 b32ddc6d17..0d6029ccf0 100644 --- a/Source/Core/DiscIO/VolumeDisc.cpp +++ b/Source/Core/DiscIO/VolumeDisc.cpp @@ -20,6 +20,20 @@ std::string VolumeDisc::GetGameID(const Partition& partition) const { char id[6]; + // Triforce games have their Game ID stored in the boot.id file + const FileSystem* file_system = GetFileSystem(partition); + if (file_system) + { + std::unique_ptr file_info = file_system->FindFileInfo("boot.id"); + if (file_info && !file_info->IsDirectory()) + { + if (Read(file_info->GetOffset() + 0x30, sizeof(id), reinterpret_cast(id), partition)) + { + return DecodeString(id); + } + } + } + if (!Read(0, sizeof(id), reinterpret_cast(id), partition)) return std::string(); @@ -67,6 +81,28 @@ 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 + const FileSystem* file_system = GetFileSystem(partition); + if (file_system) + { + std::unique_ptr file_info = file_system->FindFileInfo("boot.id"); + if (file_info && !file_info->IsDirectory()) + { + u8* bootid_buffer = new u8[file_info->GetTotalSize()]; + if (Read(file_info->GetOffset(), file_info->GetTotalSize(), bootid_buffer, partition)) + { + memcpy(name, bootid_buffer + 0x80, 0x20); + + delete[] bootid_buffer; + + return DecodeString(name); + } + // Fall back to normal title from header + delete[] bootid_buffer; + } + } + if (!Read(0x20, sizeof(name), reinterpret_cast(&name), partition)) return std::string(); diff --git a/Source/Core/DiscIO/VolumeGC.cpp b/Source/Core/DiscIO/VolumeGC.cpp index 8cc255570a..8d43ba02df 100644 --- a/Source/Core/DiscIO/VolumeGC.cpp +++ b/Source/Core/DiscIO/VolumeGC.cpp @@ -29,6 +29,8 @@ namespace DiscIO { +Region g_triforce_region; + VolumeGC::VolumeGC(std::unique_ptr reader) : m_reader(std::move(reader)), m_is_triforce(false) { @@ -40,6 +42,7 @@ VolumeGC::VolumeGC(std::unique_ptr reader) }; m_converted_banner = [this] { return LoadBannerFile(); }; + g_triforce_region = Region::Unknown; constexpr u32 BTID_MAGIC = 0x44495442; auto tmp_fs = GetFileSystem(PARTITION_NONE); @@ -54,7 +57,22 @@ VolumeGC::VolumeGC(std::unique_ptr reader) if (file_size >= 4 && triforce_header.magic == BTID_MAGIC) { m_is_triforce = true; - m_triforce_id = triforce_header.id; + + // Load region from the file + switch (triforce_header.region) + { + default: + case 0x02: // JAPAN + case 0x08: // ASIA + g_triforce_region = Region::NTSC_J; + break; + case 0x0E: // USA + g_triforce_region = Region::NTSC_U; + break; + case 0x0C: // EXPORT + g_triforce_region = Region::PAL; + break; + } } } } @@ -93,16 +111,11 @@ 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 (g_triforce_region != Region::Unknown) + return g_triforce_region; + return RegionCodeToRegion(m_reader->ReadSwapped(0x458)); } @@ -187,6 +200,14 @@ 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..bd4d64fd86 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; @@ -82,6 +81,13 @@ private: u32 magic; // "BTID" u32 padding[11]; std::array id; + u32 padding_b; + u8 region; + u8 padding_c[0x27]; + std::array maker; + std::array name; + u32 padding_d[0x10]; + char credits_text[8][32]; }; struct ConvertedGCBanner @@ -113,7 +119,6 @@ private: std::unique_ptr m_reader; bool m_is_triforce; - std::array m_triforce_id; }; } // namespace DiscIO diff --git a/Source/Core/DolphinLib.vcxproj b/Source/Core/DolphinLib.vcxproj index 0d4925c47e..4d9ba4b553 100644 --- a/Source/Core/DolphinLib.vcxproj +++ b/Source/Core/DolphinLib.vcxproj @@ -70,20 +70,24 @@ - + + + + + + + + + - - - + - + ]]> @@ -91,10 +95,6 @@ - + \ No newline at end of file diff --git a/Source/Core/DolphinQt/Config/GamecubeControllersWidget.cpp b/Source/Core/DolphinQt/Config/GamecubeControllersWidget.cpp index f45b51e9ee..2401c63cdc 100644 --- a/Source/Core/DolphinQt/Config/GamecubeControllersWidget.cpp +++ b/Source/Core/DolphinQt/Config/GamecubeControllersWidget.cpp @@ -43,6 +43,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) @@ -133,6 +134,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 53785d3cd0..2d9f32a130 100644 --- a/Source/Core/DolphinQt/Config/InfoWidget.cpp +++ b/Source/Core/DolphinQt/Config/InfoWidget.cpp @@ -123,12 +123,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 d3c9f8ccfc..6b9316fc5d 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/ControllerEmu/Setting/NumericSetting.h" #include "InputCommon/InputConfig.h" @@ -23,8 +27,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 e134902115..b4676c06c3 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp @@ -24,6 +24,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" @@ -437,8 +440,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 488ca8ecd6..820a6f11bd 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -1145,6 +1145,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 6f21030294..45f5ec5401 100644 --- a/Source/Core/DolphinQt/MenuBar.cpp +++ b/Source/Core/DolphinQt/MenuBar.cpp @@ -310,6 +310,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(); @@ -1105,6 +1108,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 934772e72c..92836179ba 100644 --- a/Source/Core/DolphinQt/MenuBar.h +++ b/Source/Core/DolphinQt/MenuBar.h @@ -217,6 +217,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 568230b3e0..164b426d38 100644 --- a/Source/Core/DolphinQt/Settings/GameCubePane.cpp +++ b/Source/Core/DolphinQt/Settings/GameCubePane.cpp @@ -138,15 +138,10 @@ void GameCubePane::CreateWidgets() } // Add SP1 devices - for (const auto device : { - EXIDeviceType::None, - EXIDeviceType::Dummy, - EXIDeviceType::Ethernet, - EXIDeviceType::EthernetXLink, - EXIDeviceType::EthernetTapServer, - EXIDeviceType::EthernetBuiltIn, - EXIDeviceType::ModemTapServer, - }) + for (const auto device : + {EXIDeviceType::None, EXIDeviceType::Dummy, EXIDeviceType::Ethernet, + EXIDeviceType::EthernetXLink, EXIDeviceType::EthernetTapServer, + EXIDeviceType::EthernetBuiltIn, EXIDeviceType::ModemTapServer, EXIDeviceType::Baseboard}) { m_slot_combos[ExpansionInterface::Slot::SP1]->addItem(tr(fmt::format("{:n}", device).c_str()), static_cast(device)); @@ -414,6 +409,10 @@ void GameCubePane::OnConfigPressed(ExpansionInterface::Slot slot) dialog.exec(); return; } + case ExpansionInterface::EXIDeviceType::Baseboard: + { + return; + } default: PanicAlertFmt("Unknown settings pressed for {}", device); return; diff --git a/Source/Core/InputCommon/GCPadStatus.h b/Source/Core/InputCommon/GCPadStatus.h index 74849e5594..262ee5adea 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 +{ + PAD_SWITCH_TEST = 0x0001, + PAD_SWITCH_SERVICE = 0x0002, + PAD_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 d548c55fbb..65ab4d8569 100644 --- a/Source/Core/UICommon/GameFile.cpp +++ b/Source/Core/UICommon/GameFile.cpp @@ -131,7 +131,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); @@ -312,7 +311,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); @@ -501,8 +499,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; } @@ -529,6 +526,15 @@ const std::string& GameFile::GetMaker(Variant variant) const if (!maker.empty()) return maker; + // Triforce games use a default header, so all have RELSAB as the ID + // The actual information is stored within the boot.id file + + // TODO: use maker name to set company IDs + if (m_game_id[0] == 'S' && m_game_id[1] == 'B') + { + return DiscIO::GetCompanyFromID("6E"); // SEGA + } + if (m_game_id.size() >= 6) return DiscIO::GetCompanyFromID(m_maker_id); 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 f72f47c11e..9a09349d84 100644 --- a/Source/Core/UICommon/GameFileCache.cpp +++ b/Source/Core/UICommon/GameFileCache.cpp @@ -26,7 +26,7 @@ namespace UICommon { -static constexpr u32 CACHE_REVISION = 26; // Last changed in PR 10084 +static constexpr u32 CACHE_REVISION = 27; // Last changed in PR 10084 std::vector FindAllGamePaths(const std::vector& directories_to_scan, bool recursive_scan) diff --git a/Source/Core/UICommon/UICommon.cpp b/Source/Core/UICommon/UICommon.cpp index 4f55660bcc..5e6f9ac60f 100644 --- a/Source/Core/UICommon/UICommon.cpp +++ b/Source/Core/UICommon/UICommon.cpp @@ -269,6 +269,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_MAILLOGS_IDX)); diff --git a/Source/dolphin-emu.sln b/Source/dolphin-emu.sln index f1183c287f..36f9c87c3a 100644 --- a/Source/dolphin-emu.sln +++ b/Source/dolphin-emu.sln @@ -25,7 +25,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Languages", "..\Languages\L EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LZO", "..\Externals\LZO\LZO.vcxproj", "{AB993F38-C31D-4897-B139-A620C42BC565}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lz4", "..\Externals\LZ4\LZ4.vcxproj", "{9092C5CC-3E71-41B3-BF68-4A7BDD8A5476}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LZ4", "..\Externals\lz4\LZ4.vcxproj", "{9092C5CC-3E71-41B3-BF68-4A7BDD8A5476}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "miniupnpc", "..\Externals\miniupnpc\miniupnpc.vcxproj", "{31643FDB-1BB8-4965-9DE7-000FC88D35AE}" EndProject @@ -186,7 +186,6 @@ Global {31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Debug|ARM64.ActiveCfg = Debug|ARM64 {31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Debug|ARM64.Build.0 = Debug|ARM64 {31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Debug|x64.ActiveCfg = Debug|x64 - {31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Debug|x64.Build.0 = Debug|x64 {31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Release|ARM64.ActiveCfg = Release|ARM64 {31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Release|ARM64.Build.0 = Release|ARM64 {31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Release|x64.ActiveCfg = Release|x64