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
This commit is contained in:
crediar 2025-07-18 19:56:36 +02:00
commit 994fd7d900
7 changed files with 90 additions and 46 deletions

View file

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

View file

@ -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<u64>(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<u32, 3>& 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<u32, 3>& 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<u32, 3>& 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<u32, 3>& 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)

View file

@ -212,5 +212,6 @@ void InitKeys(u32 KeyA, u32 KeyB, u32 KeyC);
u32 ExecuteCommand(std::array<u32, 3>& DICMDBUF, u32 Address, u32 Length);
u32 GetGameType(void);
u32 GetMediaType(void);
bool GetTestMenu(void);
void Shutdown(void);
}; // namespace AMMediaboard

View file

@ -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;
}
}

View file

@ -1547,7 +1547,17 @@ int CSIDevice_AMBaseboard::RunBuffer(u8* buffer, int request_length)
// Test button
if (PadStatus.button & PAD_TRIGGER_Z)
{
// 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);
}

View file

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

View file

@ -29,6 +29,8 @@
namespace DiscIO
{
Region g_triforce_region;
VolumeGC::VolumeGC(std::unique_ptr<BlobReader> reader)
: m_reader(std::move(reader)), m_is_triforce(false)
{
@ -40,6 +42,7 @@ VolumeGC::VolumeGC(std::unique_ptr<BlobReader> 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<u32>(0x458));
}
@ -187,6 +193,32 @@ std::array<u8, 20> 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<u8*>(&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<u8*>(&banner_file), sizeof(GCBanner));