mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-17 08:20:30 +02:00
docs: How to find and hash a game function for ReplaceTables
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
1f89313034
commit
c5ba9bead0
2 files changed
+60
-1
No files matched your search
@@ -13,7 +13,7 @@ for it:
|
||||
|---|---|
|
||||
| [docs/building.md](docs/building.md) | Build commands for every target (VS/MSBuild, CMake, UWP, legacy Android NDK, libretro), unit tests, pspautotests |
|
||||
| [docs/android-device-testing.md](docs/android-device-testing.md) | Running a build on a real Android device or a Quest over adb: installing the build you think you are, forcing activity recreates, measuring retained activities, VR |
|
||||
| [docs/debugging.md](docs/debugging.md) | Driving the WebSocket debugger and PPSSPPHeadless from a script, measuring a commercial game with headless, comparing binaries' speed (`Tools/headless_bench.py`), breakpoint reliability per CPU backend, debugging a game that works on hardware |
|
||||
| [docs/debugging.md](docs/debugging.md) | Driving the WebSocket debugger and PPSSPPHeadless from a script, measuring a commercial game with headless, comparing binaries' speed (`Tools/headless_bench.py`), breakpoint reliability per CPU backend, debugging a game that works on hardware, finding and hashing a game function (like its own `memcpy`) for `ReplaceTables` when hardware backends miss CPU writes to VRAM |
|
||||
| [docs/DebuggerThreading.md](docs/DebuggerThreading.md) | `Core_RunOnCPUThread` / `g_frameMutex` / shutdown-lock rules - required reading before touching debugger code |
|
||||
| [docs/HLEModules.md](docs/HLEModules.md) | Adding an HLE module or function, and the seven build files a new source file goes in |
|
||||
| [docs/translations.md](docs/translations.md) | Translating UI strings with Tools/langtool |
|
||||
|
||||
@@ -314,3 +314,62 @@ relocations, and immediately showed that the first fix attempt scored worse than
|
||||
Reach for that before trying to reason a fix out of a disassembly. A few dozen lines of Python over the ELF beats
|
||||
re-running the game.
|
||||
|
||||
## Finding a game function to replace by hash
|
||||
|
||||
The tell is a game that's right on the software renderer and stale on the hardware backends, usually a video
|
||||
that sticks on its first frame while the audio plays on. The game is writing pixels into VRAM with the CPU, and
|
||||
the hardware backends only see that when it goes through something we hook. Games ship their own `memcpy`,
|
||||
`memset` and so on, and we recognise known ones by a hash of their code: the `hardcodedHashes` table in
|
||||
`Core/MIPS/MIPSAnalyst.cpp` names them, and `Core/HLE/ReplaceTables.cpp` swaps in a replacement
|
||||
(`Replace_memcpy` hands VRAM copies to `gpu->PerformMemoryCopy`). SSX On Tour's videos were fixed this way.
|
||||
|
||||
Finding the function:
|
||||
|
||||
1. **Get to the right moment.** Confirm the thing is actually happening before breaking on it - SSX's
|
||||
videos only start 11.5s in, and a screenshot from earlier just shows the paper background. `memory.read` of
|
||||
the decode buffer at a few `cpu.runUntilTime` points tells you when it starts changing.
|
||||
2. **Break on the write**, under the interpreter (`-i`), since JIT memory breakpoints only work for constant
|
||||
addresses: `memory.breakpoint.add address=<a pixel mid-framebuffer> size=4 write=true enabled=true`, then
|
||||
`cpu.resume`. Pick a pixel in the middle of the frame, not the first one, which clears also hit.
|
||||
3. **`hle.backtrace`** gives the function's entry address, and `hle.func.list` its size. Addresses come back
|
||||
as decimal in JSON; convert carefully (`145311076` is `08a94564`, not `08ab1564`).
|
||||
4. **`memory.disasm` the whole function** and check that it really is what you're going to call it: argument
|
||||
order (`a0` dest, `a1` src, `a2` size for memcpy), and no side effects the replacement wouldn't have. A
|
||||
replacement that doesn't match the original is a much worse bug than the one it fixes.
|
||||
|
||||
Steps 2 and 3 as one script (the SSX run, so 12s in and a pixel at 240,136 of each 8888 stride-512
|
||||
framebuffer). Parameters take hex as well as decimal; replies are always decimal:
|
||||
|
||||
```
|
||||
cpu.runUntilTime us=12000000
|
||||
memory.breakpoint.add address=0x040443c0 size=4 write=true enabled=true
|
||||
memory.breakpoint.add address=0x040cc3c0 size=4 write=true enabled=true
|
||||
cpu.resume
|
||||
hle.backtrace
|
||||
:quit
|
||||
```
|
||||
|
||||
```bash
|
||||
Tools/wsdbg/target/release/wsdbg.exe --sync --compact --quiet --launch \
|
||||
Windows/x64/Release/PPSSPPHeadless.exe GAME.iso --memstick=memstick -i --graphics=vulkan \
|
||||
--timeout-wall=240 < script.txt 2>/dev/null | grep -v '^<- log'
|
||||
```
|
||||
|
||||
The first `hle.backtrace` frame is the writer: `entry` is the function's start, `code` the store that hit.
|
||||
Once a replacement is in place, the same script shows it working: the hit's `source` reads `GPUMemcpy/...`,
|
||||
its `pc` is the function's entry, and the backtrace starts at the caller.
|
||||
Follow up with `memory.disasm address=<entry> count=64` in the same session (it needs the code loaded).
|
||||
|
||||
Getting the hash: `HashFunctions()` in `MIPSAnalyst.cpp` takes CityHash64 over the function's instructions
|
||||
with their immediates masked out, so it doesn't depend on where the function was loaded. The fastest way to
|
||||
read it is a temporary line there, then revert it:
|
||||
|
||||
```cpp
|
||||
if (f.start == 0x08a94500) printf("TMPHASH %016llx size %d\n", (unsigned long long)f.hash, (int)f.size);
|
||||
```
|
||||
|
||||
Add the entry to `hardcodedHashes` in hash order, with a comment naming the game. Then check that it took
|
||||
effect: with `--log --loglevel=4` the loader prints `Replaced memcpy at 08a94500 with hash 3966eef670a93781`.
|
||||
Check other regions of the game too. Since the hash doesn't depend on address, they usually match the same
|
||||
entry (SSX's Korean release has the function at `08a94200`).
|
||||
|
||||
Reference in new issue
Block a user