You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -55,6 +59,15 @@ Functions in a compilation unit must be ordered by address (ascending).
55
59
// SIZE 0xc8ac8 — struct/class size assertion
56
60
```
57
61
62
+
**GLOBAL variables** have a pointer address and may point to initialized data. A `// GLOBAL:` annotation marks the address of the pointer variable itself. If the variable is a `char*` pointing to a string literal, the address of where the string data lives should be in `reccmp/lego-racers-ascii.csv`
63
+
64
+
```cpp
65
+
// GLOBAL: LEGORACERS 0x4be8d8 — address of the pointer variable
66
+
LegoChar* g_jamFile = "lego.jam";
67
+
```
68
+
69
+
When adding or modifying globals, always run `reccmp-datacmp` to verify the initial values match the original binary. Global variables with non-zero initial values (strings, pointers, constants) must be initialized to match.
70
+
58
71
**FOLDED functions:** MSVC 6.0's linker merges functions with identical compiled code (Identical COMDAT Folding). Multiple distinct functions end up sharing one address in the original binary. Annotate each with `// FUNCTION: MODULE 0xADDRESS FOLDED` where the address is the single copy the linker kept. All folded functions share the same address. Functions that fold together have the same signature and body (e.g. all empty `void` methods fold to one address, all empty `void(undefined4)` methods fold to a different address). FOLDED functions are exempt from the address-ascending ordering rule and do not need the `STUB()` anti-folding macro.
Member offset comments (`// 0xNN`) and vtable offset comments (`// vtable+0xNN`) are required.
85
98
99
+
**Gap members:** When declaring `undefined` arrays to fill gaps between typed members, use a subtraction expression rather than a hardcoded size. This makes the size self-documenting and avoids manual recalculation when members are added:
100
+
101
+
```cpp
102
+
undefined m_unk0x05[0x7dc - 0x05]; // 0x05 (gap until next member at 0x7dc)
103
+
undefined m_unk0x92c[0x944 - 0x92c]; // 0x92c (gap until end of class at 0x944)
104
+
```
105
+
86
106
**Overrides:** When a derived class overrides a virtual method from a base class, use `override` instead of `virtual` (e.g. `void VTable0x04() override;`). Include `compat.h` which defines `override` as empty for MSVC 6.0 compatibility.
87
107
88
108
## Naming Conventions
@@ -129,6 +149,8 @@ When a variable's type is dictated by an external interface (Windows API return
129
149
130
150
7.**Build with double `cmake --build`**, then compare: `reccmp-reccmp --target LEGORACERS --verbose 0xADDRESS --print-rec-addr`. Iterate until 100%.
131
151
152
+
7b. **Verify global data** with `reccmp-datacmp --target LEGORACERS --verbose --print-rec-addr`. Any `// GLOBAL:` variable with non-zero initial data must match the original binary. This catches uninitialized globals that should have string pointers, default values, etc.
153
+
132
154
8.**Validate vtables.** After adding or modifying virtual methods, always verify the vtable matches: `reccmp-reccmp --target LEGORACERS --verbose 0xVTABLE_ADDR --print-rec-addr`. Every virtual method declared in a class header must have a corresponding `// STUB:` annotation with its real address from the original binary. Read the vtable data from the original binary to find the function address at each vtable slot.
133
155
134
156
9.**Check for regressions.** After any change, re-verify all previously matched `// FUNCTION:` implementations that touch the same classes, especially functions that make virtual calls on modified classes.
@@ -160,6 +182,7 @@ Matching these three functions provides high confidence that the class header (s
160
182
161
183
-**Return types affect register allocation.** Changing a function from `void` to returning a pointer/int can significantly change which registers the compiler assigns to local variables and parameters. If register allocation doesn't match, check whether the original function returns a value (IDA's guessed return type is a hint).
162
184
-**Float literal assignments may compile to integer `mov`.** MSVC 6.0 with `/O2` optimizes `float_member = 1.0f` to `mov dword ptr [offset], 0x3F800000` (an integer immediate store) rather than `fld`/`fstp`. Using `LegoFloat` members with float literals still produces the correct integer stores.
185
+
-**Inline getters/setters produce matching code for cross-object member access.** When a function accesses internal members of a composed sub-object (e.g. `NeonCactus` accessing `IronFlame`'s private members), use public inline getters/setters on the sub-object class. MSVC 6.0 with `/O2` inlines these, producing identical code to direct member access — and can even improve register scheduling to match the original binary more closely than making members public.
163
186
-**Write human-readable code, not IDA pseudocode.** The decompiled code should look like it was written by a human programmer. Avoid `goto` patterns, raw integer bit patterns for floats, and other artifacts of decompilation. Use proper types, named variables, and clean control flow. Iterate with reccmp to ensure the clean version still matches.
0 commit comments