Skip to content

Commit c49aaa3

Browse files
authored
Implement/match LegoRacers::FUN_0042bd00 (isledecomp#82)
1 parent 3730f8d commit c49aaa3

5 files changed

Lines changed: 56 additions & 4 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ Matching these three functions provides high confidence that the class header (s
223223
- **Vtable-set-at-dtor-start is orthogonal to virtual-dtor.** MSVC emits `*this = &own_vftable` as the first instruction of a destructor whenever the class has ANY virtual methods — to ensure virtual calls during destruction resolve to this class's implementations, not a derived class's. This emission happens for both virtual and non-virtual dtors in polymorphic classes. The vtable set at dtor entry does NOT prove the dtor is virtual; check the vtable for an SDD slot (or lack thereof) to determine virtuality.
224224
- **Declaring sub-objects as typed members auto-emits ctor/dtor calls in the outer class.** Prefer typed members (`SubClass m_member;`) over `undefined` byte arrays when you know the sub-object's class. MSVC then auto-emits the sub-ctor call in the outer ctor (at the member's offset, in declaration order) and the sub-dtor call in the outer dtor (reverse order). If the sub-class's ctor/dtor is annotated at the correct original address (even as a STUB), reccmp resolves the outer's call targets and the outer ctor/dtor can reach 100% — verifying the sub-object's offset without implementing its body. This is the primary tool for verifying class layout.
225225
- **Double vtable set in a ctor reveals inheritance.** A ctor body like `*this = off_BASE; <sub_ctor_calls>; *this = off_DERIVED; <body>` is MSVC's signature for a derived class. The first vtable is the (inlined) base constructor; the second is the derived class's own vtable. Use this to identify inheritance chains from the binary: if you see two `mov [ecx], &vtable_addr` writes in a ctor, the class inherits from the class whose vtable is set first.
226+
- **Walking-pointer base reveals true struct layout.** When iterating an array of structs inside a member, MSVC 6.0 /O2 emits `lea <reg>, [this + K]` to seed the walking pointer, where `K` is the byte offset from `this` to the **center of the struct's hottest access region** — typically the middle of the first triplet of member accesses inside the loop body. If reccmp shows the original's seed is `[ebp + K0]` and yours is `[ebp + K0 + Δ]` (with every subsequent `[reg+disp]` also shifted by `Δ`), the *struct base* in your declaration is wrong by `Δ` bytes: the true struct starts `Δ` bytes earlier (or later) than you've declared, with corresponding shifts to every member offset. Example: a pointer array accessed at indices `{0, 1, 2, 9, 10, 11}` — MSVC centers the walking pointer at `&array[1]`. If that lands at absolute address `slot_base + 32`, the array starts at `slot_base + 28` (matches a declaration where ptrs begin at struct offset 28). But if MSVC's center is 4 bytes higher than the original's, the true layout has the ptr array starting at struct offset 32, not 28 — meaning there are 4 more "header" bytes before the flag than you thought, and the array's containing member starts 4 bytes earlier in the outer class. This is often the only way to pin down the struct's origin when no other function exposes the pre-flag bytes: **the displacement delta IS the struct-boundary correction**. Shift your outer-class array declaration backward by `Δ` and pad each struct's head with `Δ` extra `undefined` bytes to restore size.
226227

227228
## COMDAT Folding Across Targets
228229

LEGORacers/include/ironflame0x944.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class IronFlame0x944 : public CrimsonForge0x800 {
7070

7171
LegoU32 GetGolBackendType() { return m_golBackendType; }
7272
void SetGolBackendType(LegoU32 p_golBackendType) { m_golBackendType = p_golBackendType; }
73+
GolExport* GetGolExport() { return m_golExport; }
7374
HWND GetHwnd() { return m_hWnd; }
7475
LegoU32 GetWindowMode() { return m_windowMode; }
7576
LegoBool32 IsDisabled() { return m_disabled; }

LEGORacers/include/legoracers.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "cobaltmist0x30.h"
66
#include "decomp.h"
77
#include "ironflame0x944.h"
8+
#include "scarletnova0x5c.h"
89
#include "soundmanager.h"
910
#include "types.h"
1011

@@ -66,7 +67,9 @@ class LegoRacers : public CactusInterface0x4 {
6667
undefined m_unk0xaf8[0xafc - 0xaf8]; // 0xaf8
6768
LegoChar m_unk0xafc[8]; // 0xafc
6869
LegoChar m_unk0xb04[8]; // 0xb04
69-
undefined m_unk0xb0c[0xde8 - 0xb0c]; // 0xb0c
70+
undefined m_unk0xb0c[0xbc4 - 0xb0c]; // 0xb0c
71+
ScarletNova0x5c m_unk0xbc4[5]; // 0xbc4
72+
undefined m_unk0xd90[0xde8 - 0xd90]; // 0xd90
7073
undefined4 m_unk0xde8; // 0xde8
7174
undefined m_unk0xdec[0xe54 - 0xdec]; // 0xdec
7275
undefined4 m_unk0xe54; // 0xe54
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#ifndef SCARLETNOVA0X5C_H
2+
#define SCARLETNOVA0X5C_H
3+
4+
#include "decomp.h"
5+
#include "types.h"
6+
7+
// SIZE 0x5c
8+
class ScarletNova0x5c {
9+
public:
10+
undefined m_unk0x00[4]; // 0x00
11+
LegoBool m_flag; // 0x04
12+
undefined m_unk0x05[0x20 - 0x05]; // 0x05
13+
undefined4* m_unk0x20[12]; // 0x20
14+
undefined m_unk0x50[0x5c - 0x50]; // 0x50
15+
};
16+
17+
#endif // SCARLETNOVA0X5C_H

LEGORacers/src/legoracers.cpp

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,41 @@ void LegoRacers::Run()
125125
ResetDisplay();
126126
}
127127

128-
// STUB: LEGORACERS 0x0042bd00
128+
// FUNCTION: LEGORACERS 0x0042bd00
129129
void LegoRacers::FUN_0042bd00()
130130
{
131-
// TODO
132-
STUB(0x42bd00);
131+
GolExport* golExport = m_unk0xac0->GetGolExport();
132+
133+
for (LegoU32 i = 0; i < m_unk0xde8; i++) {
134+
ScarletNova0x5c& slot = m_unk0xbc4[i];
135+
136+
if (slot.m_flag) {
137+
if (slot.m_unk0x20[0]) {
138+
golExport->VTable0x48(slot.m_unk0x20[0]);
139+
slot.m_unk0x20[0] = NULL;
140+
}
141+
if (slot.m_unk0x20[1]) {
142+
golExport->VTable0x44(slot.m_unk0x20[1]);
143+
slot.m_unk0x20[1] = NULL;
144+
}
145+
if (slot.m_unk0x20[2]) {
146+
golExport->VTable0x40(slot.m_unk0x20[2]);
147+
slot.m_unk0x20[2] = NULL;
148+
}
149+
if (slot.m_unk0x20[9]) {
150+
golExport->VTable0x48(slot.m_unk0x20[9]);
151+
slot.m_unk0x20[9] = NULL;
152+
}
153+
if (slot.m_unk0x20[10]) {
154+
golExport->VTable0x44(slot.m_unk0x20[10]);
155+
slot.m_unk0x20[10] = NULL;
156+
}
157+
if (slot.m_unk0x20[11]) {
158+
golExport->VTable0x40(slot.m_unk0x20[11]);
159+
slot.m_unk0x20[11] = NULL;
160+
}
161+
}
162+
}
133163
}
134164

135165
// FUNCTION: LEGORACERS 0x0042bdc0

0 commit comments

Comments
 (0)