From 994fd7d90036ff3e76469eacbd0a65674c867ec3 Mon Sep 17 00:00:00 2001 From: crediar Date: Fri, 18 Jul 2025 19:56:36 +0200 Subject: [PATCH] Improved Triforce IPL compatibility and simplify logic changes Refactored AMMediaboard.cpp to remove legacy memory patching, introducing cryptographic key handling for Triforce IPL commands Enhanced DVDInterface.cpp to ensure cover closure during initialization for Triforce IPL Adjusted VideoInterface.cpp region flags for Triforce IPL compatibility Added detailed comments to clarify new logic and removed outdated code Added loading of region for Triforce games from boot.id Prevent test menu access without SegaBoot present Changed insert coin button to X --- Source/Core/Core/Boot/Boot.cpp | 3 +- Source/Core/Core/HW/DVD/AMMediaboard.cpp | 68 ++++++++----------- Source/Core/Core/HW/DVD/AMMediaboard.h | 1 + Source/Core/Core/HW/DVD/DVDInterface.cpp | 3 + .../Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp | 18 +++-- Source/Core/Core/HW/VideoInterface.cpp | 11 ++- Source/Core/DiscIO/VolumeGC.cpp | 32 +++++++++ 7 files changed, 90 insertions(+), 46 deletions(-) diff --git a/Source/Core/Core/Boot/Boot.cpp b/Source/Core/Core/Boot/Boot.cpp index 3f7bf3a140..31fb207f61 100644 --- a/Source/Core/Core/Boot/Boot.cpp +++ b/Source/Core/Core/Boot/Boot.cpp @@ -375,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; diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.cpp b/Source/Core/Core/HW/DVD/AMMediaboard.cpp index f9c704c468..2cab83379f 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.cpp +++ b/Source/Core/Core/HW/DVD/AMMediaboard.cpp @@ -83,6 +83,7 @@ namespace AMMediaboard static bool s_firmwaremap = false; static bool s_segaboot = 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; @@ -186,6 +187,7 @@ void Init(void) s_segaboot = false; s_firmwaremap = false; + s_test_menu = false; s_last_error = SSC_SUCCESS; @@ -230,6 +232,8 @@ void Init(void) u64 length = std::min(sega_boot.GetSize(), sizeof(s_firmware)); sega_boot.ReadBytes(s_firmware, length); + + s_test_menu = true; } u8* InitDIMM(u32 size) @@ -305,7 +309,7 @@ static s32 NetDIMMConnect(int fd, struct sockaddr_in* addr, int len) { addr->sin_addr.s_addr = inet_addr("127.0.0.1"); /* - BUG: An invalid family value is used + BUG: An invalid family value is being used */ addr->sin_family = htons(AF_INET); s_namco_cam = fd; @@ -395,44 +399,10 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32 address, u32 length) { auto& system = Core::System::GetInstance(); auto& memory = system.GetMemory(); - auto& ppc_state = system.GetPPCState(); - auto& jit_interface = system.GetJitInterface(); - - /* - The triforce IPL sends these commands first - 01010000 00000101 00000000 - 01010000 00000000 0000ffff - */ - if (s_GCAM_key_a == 0) - { - /* - Since it is currently unknown how the seed is created - we have to patch out the crypto. - */ - if (memory.Read_U32(0x8131ecf4)) - { - memory.Write_U32(0, 0x8131ecf4); - memory.Write_U32(0, 0x8131ecf8); - memory.Write_U32(0, 0x8131ecfC); - memory.Write_U32(0, 0x8131ebe0); - memory.Write_U32(0, 0x8131ed6c); - memory.Write_U32(0, 0x8131ed70); - memory.Write_U32(0, 0x8131ed74); - - memory.Write_U32(0x4E800020, 0x813025C8); - memory.Write_U32(0x4E800020, 0x81302674); - - ppc_state.iCache.Invalidate(memory, jit_interface, 0x813025C8); - ppc_state.iCache.Invalidate(memory, jit_interface, 0x81302674); - - HLE::Patch(system, 0x813048B8, "OSReport"); - HLE::Patch(system, 0x8130095C, "OSReport"); // Apploader - } - } DICMDBUF[0] ^= s_GCAM_key_a; DICMDBUF[1] ^= s_GCAM_key_b; - // length ^= s_GCAM_key_c; // DMA length is always plain + DICMDBUF[2] ^= s_GCAM_key_c; u32 seed = DICMDBUF[0] >> 16; @@ -440,6 +410,22 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32 address, u32 length) s_GCAM_key_b *= seed; s_GCAM_key_c *= seed; + /* + Key setup for Triforce IPL: + These RAM offset always hold the keys for the next command and since it sends two dummy + commands before a real read we can just use the key from RAM without missing any real commands. + */ + 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)); + } + } + DICMDBUF[0] <<= 24; DICMDBUF[1] <<= 2; @@ -669,7 +655,6 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32 address, u32 length) // Empty reply case AMMBCommand::Unknown_103: break; - // Network Commands case AMMBCommand::Accept: { u32 fd = s_sockets[SocketCheck(media_buffer_32[2])]; @@ -710,8 +695,8 @@ u32 ExecuteCommand(std::array& DICMDBUF, u32 address, u32 length) *(u32*)(&addr.sin_addr) = Common::swap32(*(u32*)(&addr.sin_addr)); /* - Triforce games usually use hardcoded IPs - This is replaced to listen to the ANY address instead + 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; @@ -1825,7 +1810,10 @@ u32 GetGameType(void) } // never reached } - +bool GetTestMenu(void) +{ + return s_test_menu; +} void Shutdown(void) { if (s_netcfg) diff --git a/Source/Core/Core/HW/DVD/AMMediaboard.h b/Source/Core/Core/HW/DVD/AMMediaboard.h index dd32a5e0c0..192a551331 100644 --- a/Source/Core/Core/HW/DVD/AMMediaboard.h +++ b/Source/Core/Core/HW/DVD/AMMediaboard.h @@ -212,5 +212,6 @@ void InitKeys(u32 KeyA, u32 KeyB, u32 KeyC); u32 ExecuteCommand(std::array& DICMDBUF, 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 0d43dcbc67..2ffe2855e2 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -291,6 +291,9 @@ void DVDInterface::Init() if (m_enable_gcam) { AMMediaboard::Init(); + + // The Trifoce IPL expects the cover to be closed + m_DICVR.Hex = 0; } } diff --git a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp index c82d70b730..97306c212c 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceAMBaseboard.cpp @@ -1544,10 +1544,20 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) GCPadStatus PadStatus; PadStatus = Pad::GetStatus(0); - + // Test button if (PadStatus.button & PAD_TRIGGER_Z) - message.addData(0x80); + { + // 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); @@ -1833,11 +1843,11 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length) { GCPadStatus PadStatus; PadStatus = Pad::GetStatus(i); - if ((PadStatus.button & PAD_TRIGGER_Z) && !m_coin_pressed[i]) + if ((PadStatus.button & PAD_BUTTON_X) && !m_coin_pressed[i]) { m_coin[i]++; } - m_coin_pressed[i] = PadStatus.button & PAD_TRIGGER_Z; + m_coin_pressed[i] = PadStatus.button & PAD_BUTTON_X; message.addData((m_coin[i] >> 8) & 0x3f); message.addData(m_coin[i] & 0xff); } diff --git a/Source/Core/Core/HW/VideoInterface.cpp b/Source/Core/Core/HW/VideoInterface.cpp index e1e822f08e..1cdfe6279d 100644 --- a/Source/Core/Core/HW/VideoInterface.cpp +++ b/Source/Core/Core/HW/VideoInterface.cpp @@ -163,7 +163,16 @@ void VideoInterfaceManager::Preset(bool _bNTSC) // Say component cable is plugged m_dtv_status.component_plugged = Config::Get(Config::SYSCONF_PROGRESSIVE_SCAN); - m_dtv_status.ntsc_j = region == DiscIO::Region::NTSC_J; + + // Triforce IPL requires this to be set + if (region == DiscIO::Region::DEV || region == DiscIO::Region::NTSC_J) + { + m_dtv_status.ntsc_j = true; + } + else + { + m_dtv_status.ntsc_j = false; + } m_fb_width.Hex = 0; m_border_hblank.Hex = 0; diff --git a/Source/Core/DiscIO/VolumeGC.cpp b/Source/Core/DiscIO/VolumeGC.cpp index 8cc255570a..a557ced9f1 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); @@ -103,6 +106,9 @@ std::string VolumeGC::GetTriforceID() const Region VolumeGC::GetRegion() const { + if (g_triforce_region != Region::Unknown) + return g_triforce_region; + return RegionCodeToRegion(m_reader->ReadSwapped(0x458)); } @@ -187,6 +193,32 @@ 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. + Check for boot.id and then just skip loading the banner. + */ + u8 bootid[0x1E0]; + const u64 bootid_size = ReadFile(*this, PARTITION_NONE, "boot.id", reinterpret_cast(&bootid), sizeof(bootid)); + if (bootid_size) + { + // Load region from the file + switch (bootid[0x38]) + { + 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; + } + + return {}; + } GCBanner banner_file; const u64 file_size = ReadFile(*this, PARTITION_NONE, "opening.bnr", reinterpret_cast(&banner_file), sizeof(GCBanner));