fifa17-recon: the -4 rule is literally the ASCII prefix "RS4:"

The "4-byte header" that precedes every response class name in .rdata, which cost six
failed class-to-deserializer resolutions before anyone noticed the offset, is not a
length prefix or a refcount. It is the string RS4:. The full literal is
RS4:FutXServerResponse, and searching for the bare class name lands four bytes in.

Verified directly on three classes:
  FutDestroyMatchServerResponse           name@0x18021d694  header = b'RS4:'
  FutGetDraftCurrentStateServerResponse   name@0x180224204  header = b'RS4:'
  FutStickerBookStats2ServerResponse      name@0x1802220cc  header = b'RS4:'

Found by a verification agent that had been instructed to distrust the rule. It did,
and came back with the reason rather than the offset. A magic constant you have to
remember is a rule you will eventually get wrong; a prefix you can read is not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
This commit is contained in:
funman300
2026-08-04 13:48:14 -07:00
parent 24bbc32da5
commit 397d46f174
+15 -6
View File
@@ -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.