socram8888 / tonyhax

PS1 savegame exploit
Do What The F*ck You Want To Public License
436 stars 25 forks source link

Kings Field SLPS 00017 Locks Up After Starting Text #153

Closed alex-free closed 7 months ago

alex-free commented 9 months ago

Before opening this kind of issue, please ensure:

tonyhax version v1.4.5 Installation method:WLaunchELF and Boot CD (Reproducible in emulation as well with DuckStation) Entry point game: Crash Warped USA/Boot CD Console model:39001 Integrity check:Yes BIOS version5.0 Target game: SLPS 00017 http://redump.org/disc/7072/

Bug explanation: Locks up after starting exe. Happens in International fork as well and is reproducible in emulation. This is a very early launch Japanese PSX.EXE (no SYSTEM.CNF) game.

Initially reported to my fork but I can reproduce: https://github.com/alex-free/tonyhax/issues/43

alex-free commented 9 months ago

Does not occur on the USA release: https://github.com/alex-free/tonyhax/issues/43#issuecomment-1738405183

alex-free commented 9 months ago

OKAY INTERESTING SYSTEM.CNF for USA release says this

BOOT = cdrom:\SLUS_001.58;1 TCB = 4 EVENT = 16 STACK = 801f8000

Tonyhax/Tonyhax International for PS-EXE games does this:

BOOT = cdrom:\PSX.EXE;1 TCB = 4 EVENT = 10 STACK = 801fff00

It can't be this easy, can it? Just test for this exact PS-EXE via checksum or whatever and change the values?

alex-free commented 9 months ago

Okay, not that simple: https://github.com/alex-free/tonyhax/issues/43#issuecomment-1738424651 also tried the above and it didn't work in any combination

socram8888 commented 9 months ago

I'm super busy IRL with other job-related projects so I can't look into this, but as an idea of what I did for Pepsiman and other games with edge cases.

What I'd do generally in these cases that can be replicated in an emulator, is setting a breakpoint right at the entry point of the game, save the RAM to a file, and then diff the changes in status between a cold boot from the BIOS and one with tonyhax.

alex-free commented 9 months ago

So it looks like PSX.EXE is a one sector long pre loader that then starts OPEN.EXE/GAME.EXE. Also it's kinda funny that they just put LICENSEJ.DAT on disc as an actual file (as well as having it at the proper sector 4 in the system area). Is it me or is that EXE header a bit weird?

psx exe psx-exe-sector-loader open exe game exe international

alex-free commented 9 months ago

Dissasembly via no $ emu for PSX.EXE kf-psx-exe.txt

This seems a bit strange, since the only console this would have been tested with would be CEX-1000 (and also this is reported to not work on pre-retail DTL-H2000 devboard):

80010228 80010000 movbs r1,[card_write_test(port)]

From https://problemkaputt.de/psx-spx.htm A(AFh) - card_write_test(port) ;not supported by old CEX-1000 version Resets the card changed flag. For some strange reason, this flag isn't automatically reset after reading the flag, instead, the flag is reset upon sector writes. To do that, this function issues a dummy write to sector 3Fh.

B(50h) - allow_new_card() Normally any memory card read/write functions fail if the BIOS senses the card change flag to be set. Calling this function tells the BIOS to ignore the card change flag on the next read/write operation (the function is internally used when loading the "MC" ID from sector 0, and when calling the card_write_test function to acknowledge the

alex-free commented 9 months ago

I DID IT I HAVE A FIX INCOMING

alex-free commented 9 months ago

Replace

debug_write("Reading executable header");
int32_t exe_fd = FileOpen(bootfile, FILE_READ);
if (exe_fd <= 0) {
    debug_write("Open error %d", GetLastError());
    return;
}

read = FileRead(exe_fd, data_buffer, 2048);

if (read != 2048) {
    debug_write("Read error %d", GetLastError());
    return;
}

with:

debug_write("Reading executable header");
int32_t exe_fd = FileOpen(bootfile, FILE_READ);
if (exe_fd <= 0) {
    debug_write("Open error %d", GetLastError());
    return;
}

read = FileRead(exe_fd, data_buffer, 2048);

if (read != 2048) {
    debug_write("Read error %d", GetLastError());
    return;
}

if(no_system_cnf) {
    for(int i = 0; i < 8; i++) {
        data_buffer[0x30 + i] = 0;
    }
}

in secondary.c. I have a bool set that applies this when the bootfile = PSX.EXE. The PSX EXE header of PSX.EXE in King's Field is quite strange to begin with, I was looking at no $ docs and this is what he says:

nocashpsxexe

So the problematic area is 0x30/0x34 : 030h Initial SP/R29 & FP/R30 Base (usually 801FFFF0h) (or 0=None) 034h Initial SP/R29 & FP/R30 Offs (usually 0, added to above Base)

See here:

Note: In bootfiles, SP is usually 801FFFF0h (ie. not 801FFF00h as in system.cnf). When SP is 0, the unmodified caller's stack is used. In most cases (except when manually calling DoExecute), the stack values in the exeheader seem to be ignored though (eg. replaced by the SYSTEM.CNF value)

I guess since there is no SYSTEM.CNF, the stack values in the EXE header are actually being used when we call doexecute but the real BIOS boot sequence does ignore them regardless of that.

alex-free commented 9 months ago

https://github.com/alex-free/tonyhax/issues/43#issuecomment-1742008063

alex-free commented 9 months ago

This is what I added as comments in my fork:

/* 
King's Field Japan (SLPS_00017) is a very early PSX.EXE Japan game. While the PSX.EXE is having 0x34/0x38 contain SP values, these are apparently garbage and not being used in reality by a real BIOS boot. These ARE being used by both our exec calls below however so we need to zero out that garbage so it is not used, which finally allows King's Feild (and most likely other PSX.EXE games) to boot correctly.

From No $ PSX SPX:

030h      Initial SP/R29 & FP/R30 Base (usually 801FFFF0h) (or 0=None)
034h      Initial SP/R29 & FP/R30 Offs (usually 0, added to above Base)

Note: In bootfiles, SP is usually 801FFFF0h (ie. not 801FFF00h as in system.cnf). When SP is 0, the unmodified caller's stack is used. In most cases (except when manually calling DoExecute), the stack values in the exeheader seem to be ignored though (eg. replaced by the SYSTEM.CNF value)
*/
if(no_system_cnf) {
    for(int i = 0; i < 8; i++) {
        data_buffer[0x30 + i] = 0;
    }
}
alex-free commented 7 months ago

@socram8888 here is the actual BIOS code (well openbios equivalent) that overwrites it : https://github.com/grumpycoders/pcsx-redux/blob/a072e38d78c12a4ce1dadf951d9cdfd7ea59220b/src/mips/openbios/main/main.c#L380-L381

Re: https://github.com/socram8888/tonyhax/issues/155#issuecomment-1793237441

Big thanks to Nicholas Noble for finding the actual bugged line of code

socram8888 commented 7 months ago

Many thanks! Will patch tonyhax to overwrite those entries like stock does!

socram8888 commented 7 months ago

I think d2036da6ba52bd42069b5876824a67f721a2f5f0 should fix it. It mimicks the stock boot ROM at least.