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));