thpatch / thcrap-tsa

Basic thcrap patches maintained by the Touhou Patch Center developers
https://www.thpatch.net
23 stars 13 forks source link

TH07/TH08/?????: Don't crash on score.dat corruptions #19

Open nmlgc opened 6 years ago

nmlgc commented 6 years ago

Here's a score.dat for TH08 that crashes the game on startup:

===
Exception c0000005 at 0x004A5CFF (Rxa5cff) (\th08.exe)

Registers:
EAX: 0x00000000 ECX: 0x00000005 EDX: 0x06B60DB8 EBX: 0x00000005
ESP: 0x0019FCD8 EBP: 0x0019FCE4 ESI: 0x00000001 EDI: 0x0A245EA8

Stack trace:
[09] 0x00448651 (Rx48651) (\th08.exe)
[08] 0x0045A881 (Rx5a881) (\th08.exe)
[07] 0x00459BC4 (Rx59bc4) (\th08.exe)
[06] 0x00458375 (Rx58375) (\th08.exe)
[05] 0x00441B9A (Rx41b9a) (\th08.exe)
[04] 0x004A631F (Rxa631f) (\th08.exe)
[03] 0x74FC8654 (Rx18654) (\KERNEL32.DLL)
[02] 0x77234B17 (Rx64b17) (\ntdll.dll)
[01] 0x77234AE7 (Rx64ae7) (\ntdll.dll)
===

Other games might be affected by similar ZUN bugs.

We probably can't restore those in case of a corruption, but it would be nice to print a more specific error, with the option of creating a new score file, rather than just crashing and forcing players to debug the issue themselves.

Bonus points for actually preventing the root cause.

DankRank commented 6 years ago

crash is at strncmp, presumably because of second argument (ESI: 0x00000001).

one level up the stack is 44858D (which I dubbed versioncmp). Looks like it's compares it's arguments against contents of th08_0100d.ver from the dat. The check always succeeds if pointer to file contents is NULL, but here it seems that the pointer is 0x00000001 in this case. That pointer is located at this + 0x360. Additionally, size of the file is stored at this + 0x35C.

another level up is 45A5E0 (score_load), which checks the score file and recreates it, if it has errors. The object used as this in versioncmp call is 17CE758.

Conclusion: pointer to th08_0100d.ver contents is at 17CEAB8. Something corrupts that memory.

Will do further investigation with a debugger later.

32th-System commented 3 years ago

The scorefile has a sort of checksum in it that is used to validate which version the scorefile was made in. The game will loop through th08_0100d.ver until it finds a line that matches the checksum in the scorefile. That's it, the game reaching the end of the file will not stop it from continuing to loop, then reading past the file and reaching invalid memory. https://github.com/thpatch/thcrap-tsa/commit/cc8f8dbf90add9eebc50a7366d9dd9f97af6af10 will even write a valid checksum if the one in the scorefile is invalid