Skip to content

Commit 19cf0ec

Browse files
committed
Merge remote-tracking branch 'origin/master' into HEAD
2 parents 09945f3 + 56b27f8 commit 19cf0ec

19 files changed

Lines changed: 949 additions & 44 deletions

CLAUDE.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ pip install -r tools/requirements.txt
2929
reccmp-reccmp --target LEGORACERS --print-rec-addr
3030
reccmp-reccmp --target GOLDP --verbose 0x100070b0 --print-rec-addr
3131

32+
# Compare global variable data values
33+
reccmp-datacmp --target LEGORACERS --verbose --print-rec-addr
34+
reccmp-datacmp --target GOLDP --verbose --print-rec-addr
35+
3236
# Progress SVGs
3337
reccmp-reccmp --target LEGORACERS --total 3986 --nolib -S LEGORACERSPROGRESS.SVG --svg-icon assets/legoracers.png
3438
reccmp-reccmp --target GOLDP --total 1071 --nolib -S GOLDPPROGRESS.SVG --svg-icon assets/goldp.png
@@ -55,6 +59,15 @@ Functions in a compilation unit must be ordered by address (ascending).
5559
// SIZE 0xc8ac8 — struct/class size assertion
5660
```
5761

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+
5871
**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.
5972

6073
## Class Pattern
@@ -83,6 +96,13 @@ DECOMP_SIZE_ASSERT(VelvetThunder0xc8ac8, 0xc8ac8)
8396
8497
Member offset comments (`// 0xNN`) and vtable offset comments (`// vtable+0xNN`) are required.
8598
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+
86106
**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.
87107

88108
## Naming Conventions
@@ -129,6 +149,8 @@ When a variable's type is dictated by an external interface (Windows API return
129149

130150
7. **Build with double `cmake --build`**, then compare: `reccmp-reccmp --target LEGORACERS --verbose 0xADDRESS --print-rec-addr`. Iterate until 100%.
131151

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+
132154
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.
133155

134156
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
160182

161183
- **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).
162184
- **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.
163186
- **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.
164187

165188
## Project Structure

CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ add_executable(legoracers WIN32
101101
LEGORacers/src/frostpetal0x34.cpp
102102
LEGORacers/src/emberdust0x28.cpp
103103
LEGORacers/src/cobaltmist0x30.cpp
104+
LEGORacers/src/tealpulse0x24.cpp
105+
LEGORacers/src/crimsonforge0x800.cpp
106+
LEGORacers/src/opalvault0xf0.cpp
104107
LEGORacers/src/silvernode0x50.cpp
105108
LEGORacers/racers.rc
106109
)

LEGORacers/include/amberwolf0x98.h

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// SIZE 0x98
1414
class AmberWolf0x98 : public IndigoStar0x18 {
1515
public:
16+
AmberWolf0x98();
1617
~AmberWolf0x98() override; // vtable+0x00
1718
undefined4 VTable0x04(undefined4) override; // vtable+0x04
1819
void VTable0x08() override; // vtable+0x08
@@ -30,17 +31,26 @@ class AmberWolf0x98 : public IndigoStar0x18 {
3031
// AmberWolf0x98::`scalar deleting destructor'
3132

3233
private:
33-
undefined4 m_unk0x18; // 0x18
34-
undefined m_unk0x1c[0x08]; // 0x1c
35-
IUnknown* m_unk0x24; // 0x24
36-
undefined m_unk0x28[0x08]; // 0x28
37-
IUnknown* m_unk0x30; // 0x30
38-
undefined m_unk0x34[0x08]; // 0x34
39-
ListHead m_unk0x3c; // 0x3c
40-
ListHead m_unk0x48; // 0x48
41-
undefined4 m_unk0x54; // 0x54
42-
undefined4 m_unk0x58; // 0x58
43-
undefined m_unk0x5c[0x3c]; // 0x5c
34+
undefined4 m_unk0x18; // 0x18
35+
undefined4 m_unk0x1c; // 0x1c
36+
undefined4 m_unk0x20; // 0x20
37+
IUnknown* m_unk0x24; // 0x24
38+
undefined4 m_unk0x28; // 0x28
39+
undefined4 m_unk0x2c; // 0x2c
40+
IUnknown* m_unk0x30; // 0x30
41+
undefined m_unk0x34; // 0x34
42+
undefined m_unk0x35; // 0x35
43+
undefined m_unk0x36[0x38 - 0x36]; // 0x36
44+
undefined4 m_unk0x38; // 0x38
45+
ListHead m_unk0x3c; // 0x3c
46+
ListHead m_unk0x48; // 0x48
47+
undefined4 m_unk0x54; // 0x54
48+
undefined4 m_unk0x58; // 0x58
49+
ListHead m_unk0x5c; // 0x5c
50+
ListHead m_unk0x68; // 0x68
51+
ListHead m_unk0x74; // 0x74
52+
ListHead m_unk0x80; // 0x80
53+
ListHead m_unk0x8c; // 0x8c
4454
};
4555

4656
#endif // AMBERWOLF0X98_H
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#ifndef CRIMSONFORGE0X800_H
2+
#define CRIMSONFORGE0X800_H
3+
4+
#include "decomp.h"
5+
#include "tealpulse0x24.h"
6+
#include "types.h"
7+
8+
// VTABLE: LEGORACERS 0x4af9a8
9+
// SIZE 0x800
10+
class CrimsonForge0x800 {
11+
public:
12+
CrimsonForge0x800();
13+
virtual void VTable0x00() = 0; // vtable+0x00
14+
virtual ~CrimsonForge0x800(); // vtable+0x04
15+
virtual void VTable0x08(); // vtable+0x08
16+
virtual void VTable0x0c(LegoChar*, LegoChar*) = 0; // vtable+0x0c
17+
virtual void VTable0x10() = 0; // vtable+0x10
18+
virtual void VTable0x14() = 0; // vtable+0x14
19+
virtual void VTable0x18() = 0; // vtable+0x18
20+
virtual void VTable0x1c() = 0; // vtable+0x1c
21+
virtual void VTable0x20() = 0; // vtable+0x20
22+
virtual void VTable0x24() = 0; // vtable+0x24
23+
virtual void VTable0x28(); // vtable+0x28
24+
virtual void VTable0x2c() = 0; // vtable+0x2c
25+
virtual void VTable0x30() = 0; // vtable+0x30
26+
virtual void VTable0x34() = 0; // vtable+0x34
27+
28+
// SYNTHETIC: LEGORACERS 0x4163b0
29+
// CrimsonForge0x800::`scalar deleting destructor'
30+
31+
LegoU8 GetUnk0x04() { return m_unk0x04; }
32+
TealPulse0x24& GetUnk0x7dc() { return m_unk0x7dc; }
33+
34+
protected:
35+
LegoU8 m_unk0x04; // 0x04
36+
undefined m_unk0x05[0x7dc - 0x05]; // 0x05
37+
TealPulse0x24 m_unk0x7dc; // 0x7dc
38+
};
39+
40+
#endif // CRIMSONFORGE0X800_H

LEGORacers/include/goldenoak0x128.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ class GoldenOak0x128 : public RubyHaze0x08, public ListLink {
2828
// GoldenOak0x128::`scalar deleting destructor'
2929

3030
private:
31-
IndigoStar0x18* m_unk0x10; // 0x10
32-
ListHead m_unk0x14; // 0x14
33-
undefined m_unk0x20; // 0x20
34-
undefined m_unk0x21[0x103]; // 0x21
35-
undefined4 m_unk0x124; // 0x124
31+
IndigoStar0x18* m_unk0x10; // 0x10
32+
ListHead m_unk0x14; // 0x14
33+
undefined m_unk0x20; // 0x20
34+
undefined m_unk0x21[0x124 - 0x21]; // 0x21
35+
undefined4 m_unk0x124; // 0x124
3636
};
3737

3838
#endif // GOLDENOAK0X128_H
Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,50 @@
11
#ifndef IRONFLAME0X944_H
22
#define IRONFLAME0X944_H
33

4+
#include "compat.h"
5+
#include "crimsonforge0x800.h"
46
#include "decomp.h"
7+
#include "opalvault0xf0.h"
8+
#include "types.h"
59

610
// VTABLE: LEGORACERS 0x4af9f4
711
// SIZE 0x944
8-
class IronFlame0x944 {
12+
class IronFlame0x944 : public CrimsonForge0x800 {
913
public:
10-
virtual void VTable0x00(); // vtable+0x00
11-
virtual ~IronFlame0x944(); // vtable+0x04
12-
virtual void VTable0x08(); // vtable+0x08
13-
virtual void VTable0x0c(); // vtable+0x0c
14-
virtual void VTable0x10(); // vtable+0x10
15-
virtual void VTable0x14(); // vtable+0x14
16-
virtual void VTable0x18(); // vtable+0x18
17-
virtual void VTable0x1c(); // vtable+0x1c
18-
virtual void VTable0x20(); // vtable+0x20
19-
virtual void VTable0x24(); // vtable+0x24
20-
virtual void VTable0x28(); // vtable+0x28
21-
virtual void VTable0x2c(); // vtable+0x2c
14+
IronFlame0x944();
15+
void VTable0x00() override; // vtable+0x00
16+
~IronFlame0x944() override; // vtable+0x04
17+
void VTable0x0c(LegoChar*, LegoChar*) override; // vtable+0x0c
18+
void VTable0x10() override; // vtable+0x10
19+
void VTable0x14() override; // vtable+0x14
20+
void VTable0x18() override; // vtable+0x18
21+
void VTable0x1c() override; // vtable+0x1c
22+
void VTable0x20() override; // vtable+0x20
23+
void VTable0x24() override; // vtable+0x24
24+
void VTable0x28() override; // vtable+0x28
25+
void VTable0x2c() override; // vtable+0x2c
26+
void VTable0x30() override; // vtable+0x30
27+
void VTable0x34() override; // vtable+0x34
2228

2329
// SYNTHETIC: LEGORACERS 0x416560
2430
// IronFlame0x944::`scalar deleting destructor'
2531

32+
undefined4 GetUnk0x928() { return m_unk0x928; }
33+
void SetUnk0x928(undefined4 p_unk0x928) { m_unk0x928 = p_unk0x928; }
34+
2635
private:
27-
undefined m_unk0x04[0x940]; // 0x04
36+
undefined4 m_unk0x800; // 0x800
37+
undefined m_unk0x804[0x830 - 0x804]; // 0x804
38+
undefined4 m_unk0x830; // 0x830
39+
OpalVault0xf0 m_unk0x834; // 0x834
40+
undefined4 m_unk0x924; // 0x924
41+
undefined4 m_unk0x928; // 0x928
42+
undefined4 m_unk0x92c; // 0x92c
43+
undefined4 m_unk0x930; // 0x930
44+
undefined4 m_unk0x934; // 0x934
45+
undefined4 m_unk0x938; // 0x938
46+
undefined4 m_unk0x93c; // 0x93c
47+
undefined4 m_unk0x940; // 0x940
2848
};
2949

3050
#endif // IRONFLAME0X944_H

LEGORacers/include/neoncactus0x1d6c.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,20 @@ class NeonCactus0x1d6c : public CactusInterface0x4 {
1717
// SYNTHETIC: LEGORACERS 0x42bb20
1818
// NeonCactus0x1d6c::`scalar deleting destructor'
1919

20-
LegoS32 FUN_0042bbb0(LegoS32 p_argc, LegoChar** p_argv);
20+
LegoS32 Init(LegoS32 p_argc, LegoChar** p_argv);
2121
void FUN_0042bc20();
2222
void FUN_0042bc40();
2323
void FUN_0042bd00();
2424
void FUN_0042be90();
25+
LegoS32 FUN_0042bee0(LegoS32 p_argc, LegoChar** p_argv);
2526

2627
private:
2728
IronFlame0x944 m_unk0x04; // 0x04
2829
AmberWolf0x98 m_unk0x948; // 0x948
2930
CobaltMist0x30 m_unk0x9e0; // 0x9e0
30-
undefined m_unk0xa10[0x1d6c - 0xa10]; // 0xa10
31+
undefined m_unk0xa10[0xa14 - 0xa10]; // 0xa10
32+
undefined4 m_unk0xa14; // 0xa14
33+
undefined m_unk0xa18[0x1d6c - 0xa18]; // 0xa18
3134
};
3235

3336
#endif // NEONCACTUS0X1D6C_H

LEGORacers/include/opalvault0xf0.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#ifndef OPALVAULT0XF0_H
2+
#define OPALVAULT0XF0_H
3+
4+
#include "decomp.h"
5+
#include "types.h"
6+
7+
// VTABLE: LEGORACERS 0x4b1224
8+
// SIZE 0xf0
9+
class OpalVault0xf0 {
10+
public:
11+
OpalVault0xf0();
12+
virtual void VTable0x00(); // vtable+0x00
13+
virtual ~OpalVault0xf0(); // vtable+0x04
14+
virtual void VTable0x08(); // vtable+0x08
15+
virtual void VTable0x0c(); // vtable+0x0c
16+
virtual void VTable0x10(); // vtable+0x10
17+
virtual void VTable0x14(); // vtable+0x14
18+
virtual void VTable0x18(); // vtable+0x18
19+
virtual void VTable0x1c(); // vtable+0x1c
20+
21+
// SYNTHETIC: LEGORACERS 0x450350
22+
// OpalVault0xf0::`scalar deleting destructor'
23+
24+
private:
25+
undefined m_unk0x04[0xf0 - 0x04]; // 0x04
26+
};
27+
28+
#endif // OPALVAULT0XF0_H

LEGORacers/include/tealpulse0x24.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#ifndef TEALPULSE0X24_H
2+
#define TEALPULSE0X24_H
3+
4+
#include "decomp.h"
5+
#include "types.h"
6+
7+
// VTABLE: LEGORACERS 0x4b0f28
8+
// SIZE 0x24
9+
class TealPulse0x24 {
10+
public:
11+
TealPulse0x24();
12+
~TealPulse0x24();
13+
14+
virtual void VTable0x00(); // vtable+0x00
15+
virtual undefined4* VTable0x04(undefined4*, const LegoChar*); // vtable+0x04
16+
virtual void VTable0x08(undefined4, undefined4); // vtable+0x08
17+
virtual void VTable0x0c(); // vtable+0x0c
18+
19+
void FUN_0044c5a0();
20+
21+
private:
22+
undefined4 m_unk0x04; // 0x04
23+
undefined4 m_unk0x08; // 0x08
24+
undefined4 m_unk0x0c; // 0x0c
25+
undefined4 m_unk0x10; // 0x10
26+
undefined4 m_unk0x14; // 0x14
27+
undefined4 m_unk0x18; // 0x18
28+
undefined4 m_unk0x1c; // 0x1c
29+
undefined m_unk0x20; // 0x20
30+
};
31+
32+
#endif // TEALPULSE0X24_H

LEGORacers/src/amberwolf0x98.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,23 @@
99

1010
DECOMP_SIZE_ASSERT(AmberWolf0x98, 0x98)
1111

12+
// FUNCTION: LEGORACERS 0x4185d0
13+
AmberWolf0x98::AmberWolf0x98()
14+
{
15+
m_unk0x28 = 2;
16+
m_unk0x34 = 2;
17+
m_unk0x20 = 0;
18+
m_unk0x1c = 0;
19+
m_unk0x24 = NULL;
20+
m_unk0x2c = 0;
21+
m_unk0x30 = NULL;
22+
m_unk0x38 = 22050;
23+
m_unk0x35 = 16;
24+
m_unk0x18 = 0;
25+
m_unk0x58 = 0;
26+
m_unk0x54 = 0;
27+
}
28+
1229
// FUNCTION: LEGORACERS 0x4186a0
1330
AmberWolf0x98::~AmberWolf0x98()
1431
{

0 commit comments

Comments
 (0)