diff --git a/fifa17-recon/tools/ghidra_env.py b/fifa17-recon/tools/ghidra_env.py index f7946b6..8ea99bb 100644 --- a/fifa17-recon/tools/ghidra_env.py +++ b/fifa17-recon/tools/ghidra_env.py @@ -165,12 +165,21 @@ def vtable(a, n=64): def class_deser(cls): """FutXServerResponse class name -> [(deserializer, vtable, factory), ...]. - THE -4 RULE. A response class's name literal is preceded by a 4-BYTE HEADER, - and the factory's `lea r8,[rip+...]` points at THAT header, not at the text. - So the reference to look up is `name_addr - 4`. Six attempts at class->deser - resolution failed before this was noticed -- four of them returned zero - candidates and were nearly written up as "the class has no deserializer". - Ghidra does create the reference, so no manual instruction decoding is needed. + THE -4 RULE, AND WHAT IT ACTUALLY IS. A response class's name literal is + preceded by a 4-byte header, and the factory's `lea r8,[rip+...]` points at + THAT header, not at the text, so the reference to look up is `name_addr - 4`. + Six attempts at class->deser resolution failed before this was noticed; four of + them returned zero candidates and were nearly written up as "the class has no + deserializer". Ghidra does create the reference, so no manual instruction + decoding is needed. + + The "4-byte header" is not a length prefix or a refcount. It is literally the + ASCII string `RS4:`. The full literal is `RS4:FutXServerResponse`, and searching + for the bare class name lands four bytes into it. Knowing that, the rule stops + being a magic constant to remember and becomes obvious, and it also means you + can search for `RS4:` + the class name directly and skip the arithmetic. + (Established 2026-08-04 by a verification agent that had been told to distrust + the rule; it did, and found the reason instead of the offset.) From the factory, the object's vtable is the .rdata address it references whose first two qwords are functions; the deserializer is vtable slot +0x08.