Skip to content

Commit ee36c0a

Browse files
madebrfoxtacles
andauthored
Implement GolDPExport::VTable0x08 (isledecomp#58)
Co-authored-by: Christian Semmler <mail@csemmler.com>
1 parent 5199d1e commit ee36c0a

25 files changed

Lines changed: 1014 additions & 136 deletions

CLAUDE.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,110 @@ Matching these three functions provides high confidence that the class header (s
199199
- **Use `sizeOfArray(m_member)` for array-size loop bounds.** When iterating a class member array, prefer `sizeOfArray(m_files)` over the hard-coded count (e.g. `20`). `sizeOfArray` is compile-time evaluated to the same immediate, so codegen is identical, but the source is self-documenting and robust to array resizes.
200200
- **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.
201201
- **Decomposing `if/else if` from asm.** A redundant-looking second conditional jump whose flags come from a much earlier `cmp` — e.g. `cmp ebp, 0xc ; jae label ; ... ; label: jne skip ; body ; skip:` — reveals an `else if` chain in the source. The correct form is usually `if (x < N) { main } else if (x == N) { body }`, NOT the reversed `if (x >= N) { body } else { main }` that IDA often shows. The `else if` branch body ends up physically at the end of the enclosing block, reached by a forward jump from the outer test. This restructuring often also unlocks different register allocation (e.g. pointer-walk vs index-form loops), because it changes how the compiler accounts for uses of the loop counter.
202+
- **Byte-loop hash accumulator pattern.** For a hash loop reading bytes from a string, prefer loading the byte into a local (`LegoChar c = p_str[i];`) and folding the shift directly into the accumulator (`acc += c << shift;`) rather than extracting a separate temp (`int v = p_str[i] << shift; acc += v;`). The temp pattern changes register allocation — the accumulator lands in a caller-saved register (`eax`) instead of callee-saved (`esi`) — which cascades to `this`/`p_str` register choices and stack-frame size.
203+
- **Loop-index variable scoping.** Declare `LegoU32 i;` before the first `for` loop, not inside the init. MSVC 6.0's legacy for-scoping lets `i` leak into the enclosing block (so a second `for (i = ...)` reusing the same variable compiles), but CI runs clang-tidy under modern ANSI scoping where the second loop sees `i` as undeclared. The compatible form is `LegoU32 i;` at function scope + `for (i = 0; ...; i++) { ... }`.
204+
205+
## COMDAT Folding Across Targets
206+
207+
A function in `common/src/` that is compiled into both `legoracers.exe` and `goldp.dll` can need different COMDAT fold groups in each target — e.g. a trivial `return 0;` function that folds with a group of empty STUBs in one target but stays independent in the other.
208+
209+
To achieve a different fold behavior per target, wrap the `STUB(0xADDRESS)` macro in `#ifdef BUILDING_GOL` and annotate both targets:
210+
211+
```cpp
212+
// FUNCTION: GOLDP 0xAAAAAAAA FOLDED
213+
// FUNCTION: LEGORACERS 0xBBBBBBBB
214+
LegoS32 Class::Method()
215+
{
216+
#ifdef BUILDING_GOL
217+
STUB(0xAAAAAAAA);
218+
#endif
219+
return c_successCode;
220+
}
221+
```
222+
223+
In GOLDP the `STUB(...)` write to `g_foldingDummyVariable` makes the body identical to other `STUB(0xAAAAAAAA)` stubs and they fold together; in LEGORACERS the macro is gone and the function compiles to the standalone `xor eax, eax; ret` that LEGORACERS matches at its own address.
224+
225+
## Vtable annotation syntax
226+
227+
Class-header annotations require the colon: `// VTABLE: MODULE 0xADDRESS`, not `// VTABLE MODULE 0xADDRESS`. reccmp silently ignores the colonless form, and vtable set sites inside destructors/constructors then show up in the diff as `<OFFSET2>` instead of the resolved `ClassName::vftable`, costing the 5–10% match. Same rule for `// FUNCTION:`, `// STUB:`, `// GLOBAL:`, `// SYNTHETIC:`, `// SIZE` (size is an exception — no colon, per the existing convention).
228+
229+
## Scalar Deleting Destructor Inlining
230+
231+
For small classes, MSVC 6.0 inlines the destructor body directly into the compiler-synthesized *scalar deleting destructor* (SDD) rather than calling the destructor. If the SDD is <40% match while the destructor itself is 100%, the asm likely shows the destructor body inlined (e.g. setting the vftable, deleting a member, clearing a field) immediately followed by the standard `test byte [esp+N], 1 / je / delete this / ret 4` tail — whereas your build emits a plain `call Class::~Class` before that tail.
232+
233+
To get the SDD to inline, move the destructor body into the class declaration in the header (implicit inline):
234+
235+
```cpp
236+
class Small {
237+
public:
238+
// FUNCTION: MODULE 0xADDR (keep the annotation on the inline definition)
239+
virtual ~Small()
240+
{
241+
if (m_data != NULL) {
242+
delete[] m_data;
243+
m_data = NULL;
244+
}
245+
}
246+
};
247+
```
248+
249+
This pushes every translation unit that includes the header toward inlining the destructor, which is exactly what you want inside the SDD. But it also causes *inlining at unwanted call sites* — notably, a containing class's destructor that must emit 5 distinct calls to `~Small()` for five `Small` members will inline the body five times instead, breaking that containing destructor's match.
250+
251+
Guard against that with `#pragma inline_depth(0)` around the containing destructor definition:
252+
253+
```cpp
254+
// TODO: Temporary workaround until we figure out how the original code was written.
255+
#pragma inline_depth(0)
256+
OuterClass::~OuterClass()
257+
{
258+
// compiler-emitted member destructor calls stay as calls
259+
}
260+
#pragma inline_depth()
261+
```
262+
263+
This keeps the SDD's inlined-destructor match AND the outer destructor's call-based match. The pragma only affects the single function definition it wraps.
264+
265+
**`#pragma inline_depth(0)` is a temporary workaround.** It is not how the original developers wrote their code — they got matching codegen without the pragma. Every use of this pragma carries a standard TODO comment:
266+
267+
```cpp
268+
// TODO: Temporary workaround until we figure out how the original code was written.
269+
```
270+
271+
The original likely used some combination of source-level choices (inline vs. out-of-line definition placement, header layout, compiler flags, build-time codegen settings) that biased MSVC 6.0's inliner the right way without needing a pragma. Prefer discovering that configuration over propagating the pragma. When you add a new `#pragma inline_depth(0)`, include the TODO comment verbatim so it's easy to grep for and revisit.
272+
273+
## Per-target Inline Definition Placement
274+
275+
A common-layer function (in `common/src/`) can need different inlining decisions between the LEGORACERS and GOLDP targets — e.g. a small `Init()` helper that the compiler inlines into all callers in one target but calls as a function in the other. If the asm shows the helper *expanded* inside caller A and *called* from caller B, split the definition by target:
276+
277+
```cpp
278+
// header
279+
class Class {
280+
#ifdef BUILDING_GOL
281+
// FUNCTION: GOLDP 0xADDR
282+
virtual ~Class()
283+
{
284+
// body, inlined by every TU that compiles for GOLDP — so the GOLDP SDD
285+
// and anything else that wants the inlined form picks it up.
286+
}
287+
#else
288+
virtual ~Class();
289+
#endif
290+
};
291+
```
292+
293+
```cpp
294+
// .cpp
295+
#ifndef BUILDING_GOL
296+
// FUNCTION: LEGORACERS 0xADDR
297+
Class::~Class()
298+
{
299+
// same body, but out-of-line in LEGORACERS — so LEGORACERS call sites
300+
// (constructor, SDD, other destructors) emit a real `call` and match.
301+
}
302+
#endif
303+
```
304+
305+
The function body is duplicated, which is the downside; both copies must stay in sync. In return you get per-target inline control without any pragmas. Apply the same pattern to any helper (not just destructors) when the targets disagree on inlining.
202306

203307
## Naming Members from Matched Code
204308

CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,13 @@ add_library(goldp SHARED
9393
GolDP/src/goldrawstate.cpp
9494
GolDP/src/goldrawdpstate.cpp
9595
GolDP/src/goldpexport.cpp
96+
GolDP/src/fluffygloomkins0x118.cpp
9697
GolDP/src/goldevicelist.cpp
9798
GolDP/src/main.cpp
9899
GolDP/src/pearldew0x0c.cpp
100+
GolDP/src/zoweeblubberworth0xf0.cpp
99101
GolDP/src/silverdune0x30.cpp
102+
GolDP/src/smallcocoon0xc.cpp
100103
GolDP/src/slatepeak0x58.cpp
101104
GolDP/src/azureridge0x38.cpp
102105
util/decomp.cpp

GolDP/include/azureridge0x38.h

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,8 @@ class AzureRidge0x38 : public SilverDune0x30 {
1818
undefined4 p_width,
1919
undefined4 p_height,
2020
undefined4 p_bpp
21-
); // vtable+0x30
22-
virtual void VTable0x34(); // vtable+0x34
23-
virtual void VTable0x38(undefined4); // vtable+0x38
24-
virtual void VTable0x3c() = 0; // vtable+0x3c
21+
); // vtable+0x30
22+
virtual void VTable0x34(); // vtable+0x34
2523

2624
// SYNTHETIC: GOLDP 0x1001d750
2725
// AzureRidge0x38::`scalar deleting destructor'
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#ifndef FLUFFYGLOOMKINS0X118_H
2+
#define FLUFFYGLOOMKINS0X118_H
3+
4+
#include "compat.h"
5+
#include "decomp.h"
6+
#include "zoweeblubberworth0xf0.h"
7+
8+
// SIZE 0x118
9+
// VTABLE: GOLDP 0x1005690c
10+
class FluffyGloomkins : public ZoweeBlubberworth0xf0 {
11+
public:
12+
FluffyGloomkins();
13+
~FluffyGloomkins() override;
14+
undefined4* VTable0x08(void) override; // vtable+0x08
15+
undefined4* VTable0x0c(void) override; // vtable+0x0c
16+
void VTable0x18(void) override; // vtable+0x1c
17+
void VTable0x1c(undefined4*) override; // vtable+0x1c
18+
void VTable0x20(undefined4) override; // vtable+0x20
19+
void VTable0x24(undefined4) override; // vtable+0x24
20+
void VTable0x28(undefined4*) override; // vtable+0x28
21+
undefined4* VTable0x2c(undefined4) override; // vtable+0x2c
22+
undefined4* VTable0x30(undefined4) override; // vtable+0x30
23+
undefined4* VTable0x34(undefined4) override; // vtable+0x34
24+
undefined4* VTable0x38(undefined4) override; // vtable+0x38
25+
undefined4* VTable0x3c(undefined4) override; // vtable+0x3c
26+
undefined4* VTable0x40(undefined4) override; // vtable+0x40
27+
undefined4* VTable0x44(undefined4) override; // vtable+0x44
28+
undefined4* VTable0x48(undefined4) override; // vtable+0x48
29+
undefined4* VTable0x4c(undefined4) override; // vtable+0x4c
30+
undefined4* VTable0x50(undefined4) override; // vtable+0x50
31+
32+
// SYNTHETIC: GOLDP 0x100171e0
33+
// FluffyGloomkins::`scalar deleting destructor'
34+
35+
private:
36+
void FUN_10017390();
37+
38+
undefined m_unk0xc0[0x118 - 0xf0];
39+
};
40+
41+
#endif // FLUFFYGLOOMKINS0X118_H

GolDP/include/gol.h

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "decomp.h"
55
#include "goldrawstate.h"
66
#include "types.h"
7+
#include "zoweeblubberworth0xf0.h"
78

89
#include <windows.h>
910

@@ -33,33 +34,33 @@ class GolExport {
3334
virtual ~GolExport() {} // vtable+0x00
3435

3536
public:
36-
virtual GolDrawState* VTable0x04() = 0; // vtable+0x04
37-
virtual undefined4* VTable0x08() = 0; // vtable+0x08
38-
virtual undefined4* VTable0x0c() = 0; // vtable+0x0c
39-
virtual undefined4* VTable0x10() = 0; // vtable+0x10
40-
virtual undefined4* VTable0x14() = 0; // vtable+0x14
41-
virtual undefined4* VTable0x18() = 0; // vtable+0x18
42-
virtual undefined4* VTable0x1c() = 0; // vtable+0x1c
43-
virtual undefined4* VTable0x20() = 0; // vtable+0x20
44-
virtual undefined4 VTable0x24() = 0; // vtable+0x24
45-
virtual undefined4* VTable0x28() = 0; // vtable+0x28
46-
virtual undefined4* VTable0x2c() = 0; // vtable+0x2c
47-
virtual undefined4* VTable0x30() = 0; // vtable+0x30
48-
virtual undefined4* VTable0x34() = 0; // vtable+0x34
49-
virtual undefined4* VTable0x38() = 0; // vtable+0x38
50-
virtual void VTable0x3c(undefined4*) = 0; // vtable+0x3c
51-
virtual void VTable0x40(undefined4*) = 0; // vtable+0x40
52-
virtual void VTable0x44(undefined4*) = 0; // vtable+0x44
53-
virtual void VTable0x48(undefined4*) = 0; // vtable+0x48
54-
virtual void VTable0x4c(undefined4*) = 0; // vtable+0x4c
55-
virtual void VTable0x50(undefined4*) = 0; // vtable+0x50
56-
virtual void VTable0x54(undefined4*) = 0; // vtable+0x54
57-
virtual void VTable0x58(undefined4*) = 0; // vtable+0x58
58-
virtual void VTable0x5c(undefined4*) = 0; // vtable+0x5c
59-
virtual void VTable0x60(undefined4*) = 0; // vtable+0x60
60-
virtual void VTable0x64(undefined4*) = 0; // vtable+0x64
61-
virtual void VTable0x68(undefined4*) = 0; // vtable+0x68
62-
virtual void VTable0x6c(undefined4*) = 0; // vtable+0x6c
37+
virtual GolDrawState* VTable0x04() = 0; // vtable+0x04
38+
virtual ZoweeBlubberworth0xf0* VTable0x08() = 0; // vtable+0x08
39+
virtual undefined4* VTable0x0c() = 0; // vtable+0x0c
40+
virtual undefined4* VTable0x10() = 0; // vtable+0x10
41+
virtual undefined4* VTable0x14() = 0; // vtable+0x14
42+
virtual undefined4* VTable0x18() = 0; // vtable+0x18
43+
virtual undefined4* VTable0x1c() = 0; // vtable+0x1c
44+
virtual undefined4* VTable0x20() = 0; // vtable+0x20
45+
virtual undefined4 VTable0x24() = 0; // vtable+0x24
46+
virtual undefined4* VTable0x28() = 0; // vtable+0x28
47+
virtual undefined4* VTable0x2c() = 0; // vtable+0x2c
48+
virtual undefined4* VTable0x30() = 0; // vtable+0x30
49+
virtual undefined4* VTable0x34() = 0; // vtable+0x34
50+
virtual undefined4* VTable0x38() = 0; // vtable+0x38
51+
virtual void VTable0x3c(undefined4*) = 0; // vtable+0x3c
52+
virtual void VTable0x40(undefined4*) = 0; // vtable+0x40
53+
virtual void VTable0x44(undefined4*) = 0; // vtable+0x44
54+
virtual void VTable0x48(undefined4*) = 0; // vtable+0x48
55+
virtual void VTable0x4c(undefined4*) = 0; // vtable+0x4c
56+
virtual void VTable0x50(undefined4*) = 0; // vtable+0x50
57+
virtual void VTable0x54(undefined4*) = 0; // vtable+0x54
58+
virtual void VTable0x58(undefined4*) = 0; // vtable+0x58
59+
virtual void VTable0x5c(undefined4*) = 0; // vtable+0x5c
60+
virtual void VTable0x60(undefined4*) = 0; // vtable+0x60
61+
virtual void VTable0x64(undefined4*) = 0; // vtable+0x64
62+
virtual void VTable0x68(undefined4*) = 0; // vtable+0x68
63+
virtual void VTable0x6c(undefined4*) = 0; // vtable+0x6c
6364
};
6465

6566
typedef GolExport* GolEntryCBFN(GolImport*);

GolDP/include/goldpexport.h

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,33 +15,33 @@ class GolDPExport : public GolExport {
1515
// SYNTHETIC: GOLDP 0x10007040
1616
// GolDPExport::`scalar deleting destructor'
1717

18-
GolDrawState* VTable0x04() override; // vtable+0x04
19-
undefined4* VTable0x08() override; // vtable+0x08
20-
undefined4* VTable0x0c() override; // vtable+0x0c
21-
undefined4* VTable0x10() override; // vtable+0x10
22-
undefined4* VTable0x14() override; // vtable+0x14
23-
undefined4* VTable0x18() override; // vtable+0x18
24-
undefined4* VTable0x1c() override; // vtable+0x1c
25-
undefined4* VTable0x20() override; // vtable+0x20
26-
undefined4 VTable0x24() override; // vtable+0x24
27-
undefined4* VTable0x28() override; // vtable+0x28
28-
undefined4* VTable0x2c() override; // vtable+0x2c
29-
undefined4* VTable0x30() override; // vtable+0x30
30-
undefined4* VTable0x34() override; // vtable+0x34
31-
undefined4* VTable0x38() override; // vtable+0x38
32-
void VTable0x3c(undefined4*) override; // vtable+0x3c
33-
void VTable0x40(undefined4*) override; // vtable+0x40
34-
void VTable0x44(undefined4*) override; // vtable+0x44
35-
void VTable0x48(undefined4*) override; // vtable+0x48
36-
void VTable0x4c(undefined4*) override; // vtable+0x4c
37-
void VTable0x50(undefined4*) override; // vtable+0x50
38-
void VTable0x54(undefined4*) override; // vtable+0x54
39-
void VTable0x58(undefined4*) override; // vtable+0x58
40-
void VTable0x5c(undefined4*) override; // vtable+0x5c
41-
void VTable0x60(undefined4*) override; // vtable+0x60
42-
void VTable0x64(undefined4*) override; // vtable+0x64
43-
void VTable0x68(undefined4*) override; // vtable+0x68
44-
void VTable0x6c(undefined4*) override; // vtable+0x6c
18+
GolDrawState* VTable0x04() override; // vtable+0x04
19+
ZoweeBlubberworth0xf0* VTable0x08() override; // vtable+0x08
20+
undefined4* VTable0x0c() override; // vtable+0x0c
21+
undefined4* VTable0x10() override; // vtable+0x10
22+
undefined4* VTable0x14() override; // vtable+0x14
23+
undefined4* VTable0x18() override; // vtable+0x18
24+
undefined4* VTable0x1c() override; // vtable+0x1c
25+
undefined4* VTable0x20() override; // vtable+0x20
26+
undefined4 VTable0x24() override; // vtable+0x24
27+
undefined4* VTable0x28() override; // vtable+0x28
28+
undefined4* VTable0x2c() override; // vtable+0x2c
29+
undefined4* VTable0x30() override; // vtable+0x30
30+
undefined4* VTable0x34() override; // vtable+0x34
31+
undefined4* VTable0x38() override; // vtable+0x38
32+
void VTable0x3c(undefined4*) override; // vtable+0x3c
33+
void VTable0x40(undefined4*) override; // vtable+0x40
34+
void VTable0x44(undefined4*) override; // vtable+0x44
35+
void VTable0x48(undefined4*) override; // vtable+0x48
36+
void VTable0x4c(undefined4*) override; // vtable+0x4c
37+
void VTable0x50(undefined4*) override; // vtable+0x50
38+
void VTable0x54(undefined4*) override; // vtable+0x54
39+
void VTable0x58(undefined4*) override; // vtable+0x58
40+
void VTable0x5c(undefined4*) override; // vtable+0x5c
41+
void VTable0x60(undefined4*) override; // vtable+0x60
42+
void VTable0x64(undefined4*) override; // vtable+0x64
43+
void VTable0x68(undefined4*) override; // vtable+0x68
44+
void VTable0x6c(undefined4*) override; // vtable+0x6c
4545

4646
private:
4747
GolDrawDPState m_state; // 0x04

GolDP/include/goldrawdpstate.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include "golcommondrawstate.h"
55
#include "goldevicelist.h"
6+
#include "slatepeak0x58.h"
67

78
#include <ddraw.h>
89

@@ -76,7 +77,8 @@ class GolDrawDPState : public GolCommonDrawState {
7677
GolDeviceList m_deviceList; // 0x2e4
7778
LegoChar* m_driverName; // 0x2f4
7879
LegoChar* m_deviceName; // 0x2f8
79-
undefined m_unk0x2fc[0xc8ac4 - 0x2fc]; // 0x2fc
80+
SlatePeak0x58 m_unk0x2fc; // 0x2fc
81+
undefined m_unk0x354[0xc8ac4 - 0x354]; // 0x354
8082
};
8183

8284
#endif // GOLDP_GOLDPSTATE_H

GolDP/include/silverdune0x30.h

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@ class SilverDune0x30 {
1313
// FUNCTION: GOLDP 0x1001cf60
1414
virtual ~SilverDune0x30() {} // vtable+0x00
1515

16-
virtual void VTable0x04(undefined4*, undefined4*, undefined4); // vtable+0x04
17-
virtual void VTable0x08(); // vtable+0x08
18-
virtual void VTable0x0c(undefined4*, undefined4*, undefined4); // vtable+0x0c
19-
virtual void VTable0x10(); // vtable+0x10
20-
virtual void VTable0x14(undefined4*); // vtable+0x14
21-
virtual void VTable0x18(); // vtable+0x18
22-
virtual undefined4 VTable0x1c(); // vtable+0x1c
23-
virtual void VTable0x20(undefined4, undefined4, undefined4); // vtable+0x20
24-
virtual void VTable0x24(undefined4, undefined4, undefined4, undefined4, undefined4*); // vtable+0x24
25-
virtual void VTable0x28(undefined4, undefined4, undefined4*); // vtable+0x28
26-
virtual void VTable0x2c(); // vtable+0x2c
16+
virtual void VTable0x04(undefined4*, undefined4*, undefined4); // vtable+0x04
17+
virtual void VTable0x08(); // vtable+0x08
18+
virtual void VTable0x0c(undefined4*, undefined4*, undefined4); // vtable+0x0c
19+
virtual void VTable0x10(); // vtable+0x10
20+
virtual void VTable0x14(undefined4*); // vtable+0x14
21+
virtual void VTable0x18(); // vtable+0x18
22+
virtual undefined4 VTable0x1c(); // vtable+0x1c
23+
virtual void VTable0x20(undefined4, undefined4, undefined4); // vtable+0x20
24+
virtual void VTable0x24(undefined4, undefined4, undefined4*, undefined4*, undefined4*); // vtable+0x24
25+
virtual void VTable0x28(undefined4, undefined4, undefined4*); // vtable+0x28
26+
virtual void VTable0x2c(); // vtable+0x2c
2727

2828
// SYNTHETIC: GOLDP 0x1001cf40
2929
// SilverDune0x30::`scalar deleting destructor'

0 commit comments

Comments
 (0)