File size: 11,742 Bytes
3f42fbe
04e939e
8790ac2
86781eb
5c850f4
8790ac2
d439705
123d0d1
f572faf
085ae61
 
 
 
 
4d4fb5b
ed63829
4d4fb5b
 
ed63829
 
 
4d4fb5b
ed63829
 
d439705
 
 
123d0d1
ed63829
 
d439705
ed63829
f2fa062
d439705
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314b694
 
 
 
d439705
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314b694
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d439705
 
 
 
 
 
 
 
c0e3602
 
 
 
 
 
d439705
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314b694
 
 
 
 
 
d439705
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4d4fb5b
f572faf
86781eb
 
d439705
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7cb030a
4f85ac3
7cb030a
4f85ac3
4d4fb5b
ed63829
4d4fb5b
1117dc5
88857ea
 
323b7e5
 
bee20b7
 
 
 
 
 
d439705
 
085ae61
 
 
 
 
 
 
4d4fb5b
 
902aa11
 
ed63829
 
902aa11
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
use crate::asset::gfx_dir::GfxDir;
use crate::audio::dssound::DSSound;
use crate::engine::ddgame::DDGame;
use crate::engine::net_bridge::NetBridge;
use crate::render::display::gfx::DisplayGfx;
use crate::render::landscape::PCLandscape;
use crate::render::palette::PaletteContext;
use crate::FieldRegistry;

/// Speech name table entry size (0x40 = 64 bytes, null-terminated C string).
pub const SPEECH_NAME_ENTRY_SIZE: usize = 0x40;
/// Maximum number of speech name entries.
pub const SPEECH_NAME_TABLE_LEN: usize = 360;

/// DDGameWrapper β€” large wrapper around DDGame.
///
/// Created by DDGameWrapper__Constructor (0x56DEF0).
/// Holds the DDGame pointer, graphics handlers, landscape, and display state.
///
/// Vtable: 0x66A30C
///
/// Note: Ghidra shows DWORD-indexed offsets (param_2[0x122] etc.).
/// Byte offset = dword_index * 4.
///
/// PARTIAL: Only confirmed fields are defined. Many fields in the
/// `_unknown_*` blobs are accessed via pointer arithmetic in game_state_init.rs
/// (team arrays at 0x054/0x128/0x22C, alliance data at 0x350/0x3AC, etc.).
#[derive(FieldRegistry)]
#[repr(C)]
pub struct DDGameWrapper {
    // ===== 0x000: Vtable =====
    /// 0x000: Vtable pointer (0x66A30C)
    pub vtable: *mut u8,
    /// 0x004: Unknown (4 bytes gap)
    pub _unknown_004: u32,

    // ===== 0x008-0x050: Sub-object pointers (allocated by InitGameState) =====
    /// 0x008: CTaskTurnGame or CTaskGameState pointer
    pub task_turn_game: *mut u8,
    /// 0x00C: Main serialization BufferObject
    pub main_buffer: *mut u8,
    /// 0x010: Unknown pointer
    pub _field_010: u32,
    /// 0x014: Render BufferObject B (capacity 0x10000)
    pub render_buffer_b: *mut u8,
    /// 0x018: DisplayGfx layer A
    pub display_gfx_a: *mut u8,
    /// 0x01C: DisplayGfx layer B
    pub display_gfx_b: *mut u8,
    /// 0x020: DisplayGfx layer C
    pub display_gfx_c: *mut u8,
    /// 0x024: DisplayGfx main (ConstructFull result)
    pub display_gfx_main: *mut u8,
    /// 0x028: RingBuffer B (capacity 0x2000)
    pub ring_buffer_b: *mut u8,
    /// 0x02C: DisplayGfx D
    pub display_gfx_d: *mut u8,
    /// 0x030: Camera object A (0x3D4 bytes)
    pub camera_a: *mut u8,
    /// 0x034: DisplayGfx E
    pub display_gfx_e: *mut u8,
    /// 0x038: Camera object B (0x3D4 bytes)
    pub camera_b: *mut u8,
    /// 0x03C: RingBuffer A (capacity 0x2000)
    pub ring_buffer_a: *mut u8,
    /// 0x040: Render BufferObject A (capacity 0x10000)
    pub render_buffer_a: *mut u8,
    /// 0x044: Network RingBuffer (conditional on online mode)
    pub network_ring_buffer: *mut u8,
    /// 0x048: State serialization BufferObject
    pub state_buffer: *mut u8,
    /// 0x04C: Statistics object (0xB94 bytes)
    pub statistics: *mut u8,
    /// 0x050: RingBuffer C (capacity 0x1000)
    pub ring_buffer_c: *mut u8,

    // ===== 0x054-0x087: Per-team CTask pointers =====
    /// 0x054: Per-team CTask pointers (13 slots). Zeroed by InitGameState.
    /// Sub-fields +0x08..+0x18 cleared if non-null during InitTeamScoring.
    pub team_task_ptrs: [*mut u8; 13],

    // ===== 0x088-0x090: PaletteContext pointers =====
    /// 0x088: PaletteContext A (allocated 0x72C bytes)
    pub palette_ctx_a: *mut PaletteContext,
    /// 0x08C: PaletteContext B (allocated 0x72C bytes)
    pub palette_ctx_b: *mut PaletteContext,
    /// 0x090: PaletteContext C (allocated 0x72C bytes)
    pub palette_ctx_c: *mut PaletteContext,

    // ===== 0x094-0x0F3: State fields =====
    /// 0x094: Unknown gap
    pub _unknown_094: u32,
    /// 0x098-0x0CF: State block (14 u32s zeroed by InitGameState)
    pub _zeroed_098: [u32; 14],
    /// 0x0D0-0x0DF: Unknown
    pub _unknown_0d0: [u8; 0x10],
    /// 0x0E0: State flag (zeroed by InitGameState)
    pub _field_0e0: u32,
    /// 0x0E4-0x0EB: Unknown
    pub _unknown_0e4: [u8; 8],
    /// 0x0EC: State flag
    pub _field_0ec: u32,
    /// 0x0F0: Init flag (set to 1 by InitGameState early)
    pub init_flag: u32,

    // ===== 0x0F4-0x25F: Team scoring arrays (7 parallel arrays of 13 u32s) =====
    /// 0x0F4: Team score array 0 β€” zeroed by InitTeamScoring.
    pub team_score_array_0: [u32; 13],
    /// 0x128: Starting team marker β€” 1 for starting team, 0 for others.
    pub team_starting_marker: [u32; 13],
    /// 0x15C: Team scoring A β€” initialized to scoring_param_a Γ— 50.
    pub team_scoring_a: [u32; 13],
    /// 0x190: Team score array 3 β€” zeroed by InitTeamScoring.
    pub team_score_array_3: [u32; 13],
    /// 0x1C4: Team scoring B β€” initialized to scoring_param_b Γ— 50.
    pub team_scoring_b: [u32; 13],
    /// 0x1F8: Team scoring C β€” initialized to scoring_param_b Γ— 50.
    pub team_scoring_c: [u32; 13],
    /// 0x22C: Team activity flags β€” -1 (normal), -2 (training), 0 (inactive), 1 (starting).
    pub team_activity_flags: [u32; 13],

    // ===== 0x260-0x267: Game config =====
    /// 0x260: Health display precision (initialized to 500)
    pub health_precision: i32,
    /// 0x264: Zeroed by InitGameState
    pub _field_264: u32,

    // ===== 0x268-0x273: Sync checksums (already defined) =====
    /// 0x268: Network sync checksum A (written by GameFrameChecksumProcessor)
    pub sync_checksum_a: u32,
    /// 0x26C: Network sync checksum B (written by GameFrameChecksumProcessor)
    pub sync_checksum_b: u32,
    /// 0x270: Checksum validity flag (set to 1 after computation)
    pub checksum_valid: u32,

    // ===== 0x274-0x487: Game state fields =====
    /// 0x274: Initial state checksum (computed at end of InitGameState)
    pub initial_checksum: u32,
    /// 0x278-0x27F: Zeroed
    pub _field_278: u32,
    pub _field_27c: u32,
    /// 0x280-0x29C: Team render order indices (resolution-dependent, 8 entries)
    pub team_render_indices: [i32; 8],
    /// 0x2A0: Worm selection count
    pub worm_select_count: i32,
    /// 0x2A4: Worm selection count (alternate)
    pub worm_select_count_alt: i32,
    /// 0x2A8: Minimum number of active teams
    pub min_active_teams: i32,
    /// 0x2AC: Team count configuration (7 or 10)
    pub team_count_config: i32,
    /// 0x2B0: Maximum team render index (0xC or 0x10)
    pub max_team_render_index: i32,
    /// 0x2B4-0x2BB: Unknown.
    pub _unknown_2b4: [u8; 8],
    /// 0x2BC: Team score array 6 β€” set to 1 by InitTeamScoring.
    pub team_score_array_6: [u32; 13],
    /// 0x2F0-0x34F: Unknown.
    pub _unknown_2f0: [u8; 0x350 - 0x2F0],
    /// 0x350-0x383: Alliance bitmask arrays (13 Γ— u32, accessed by init_alliance_data)
    pub _alliance_bitmasks: [u32; 13],
    /// 0x384: Screen offset (computed from screen height and team count)
    pub screen_offset: i32,
    /// 0x388: Screen height for HUD
    pub screen_height_hud: i32,
    /// 0x38C-0x3AB: Team-to-slot mapping (first half, 8 entries, filled with team indices)
    pub team_to_slot_a: [i32; 8],
    /// 0x3AC-0x3EB: Slot-to-team reverse mapping (16 entries, sentinel -1 init)
    pub slot_to_team: [i32; 16],
    /// 0x3EC: Last entry of sentinel block
    pub _field_3ec: i32,
    /// 0x3F0: Game mode flag (initialized to 1)
    pub game_mode_flag: i32,
    /// 0x3F4: Sentinel -1
    pub _field_3f4: i32,
    /// 0x3F8: Sentinel -1
    pub _field_3f8: i32,
    /// 0x3FC: Zeroed
    pub _field_3fc: i32,
    /// 0x400: Game turn timer (max_team_render_index << 5)
    pub turn_timer_max: i32,
    /// 0x404: Zeroed
    pub _field_404: i32,
    /// 0x408: Game turn timer copy
    pub turn_timer_current: i32,
    /// 0x40C-0x413: Zeroed
    pub _field_40c: i32,
    pub _field_410: i32,
    /// 0x414: Config bool (from game_info)
    pub _field_414: u32,
    /// 0x418: Zeroed
    pub _field_418: i32,
    /// 0x41C: Unknown
    pub _unknown_41c: u32,
    /// 0x420: Turn percentage (from game_info, fixed-point)
    pub turn_percentage: i32,
    /// 0x424-0x44F: State fields (mix of zeroes and values)
    pub _field_424: i32,
    pub _field_428: i32,
    pub _field_42c: i32,
    pub _field_430: i32,
    /// 0x434: Zeroed
    pub _field_434: i32,
    /// 0x438: Sentinel -1
    pub _field_438: i32,
    /// 0x43C: Sentinel -1
    pub _field_43c: i32,
    /// 0x440: Zeroed
    pub _field_440: i32,
    /// 0x444: Zeroed
    pub _field_444: i32,
    /// 0x448: Zeroed
    pub _field_448: i32,
    /// 0x44C: Zeroed
    pub _field_44c: i32,
    /// 0x450-0x45B: Turn state fields (written by init_turn_state)
    pub _field_450: u32,
    pub _field_454: u32,
    pub _field_458: u32,
    /// 0x45C: Zeroed
    pub _field_45c: i32,
    /// 0x460: Zeroed
    pub _field_460: i32,
    /// 0x464: Zeroed
    pub _field_464: i32,
    /// 0x468: Sentinel -1
    pub _field_468: i32,
    /// 0x46C: Sentinel -1
    pub _field_46c: i32,
    /// 0x470: Sentinel -1
    pub _field_470: i32,
    /// 0x474: Zeroed
    pub _field_474: u32,
    /// 0x478: Zeroed
    pub _field_478: u32,
    /// 0x47C: Unknown
    pub _unknown_47c: u32,
    /// 0x480: Zeroed
    pub _field_480: u32,
    /// 0x484: State initialized flag (set to 1 at end of InitGameState)
    pub state_initialized: u32,

    // ===== 0x488-0x4BF: Core pointers and flags =====
    /// 0x488: Pointer to DDGame allocation (DWORD index 0x122)
    pub ddgame: *mut DDGame,
    /// 0x48C: Network bridge object (0x2C bytes). Only set for online games (game_version == -2).
    pub net_bridge: *mut NetBridge,
    /// 0x490: Replay flag A (from game_info)
    pub replay_flag_a: u8,
    /// 0x491: Replay flag B (from game_info)
    pub replay_flag_b: u8,
    /// 0x492: Unknown byte
    pub _unknown_492: u8,
    /// 0x493: Zeroed by InitGameState
    pub _field_493: u8,
    /// 0x494: Zeroed by InitGameState
    pub _field_494: u32,
    /// 0x498: Zeroed by InitGameState
    pub _field_498: u32,
    /// 0x49C: Version counter (read by InitGameState, not written)
    pub _field_49c: u32,
    /// 0x4A0-0x4AB: Unknown
    pub _unknown_4a0: [u8; 0x0C],
    /// 0x4AC: State field
    pub _field_4ac: u32,
    /// 0x4B0: Sentinel -1
    pub _field_4b0: u32,
    /// 0x4B4-0x4BF: Unknown
    pub _unknown_4b4: [u8; 0x0C],

    // ===== 0x4C0-0x4E3: Graphics/display/sound pointers =====
    /// 0x4C0: Primary GfxDir β€” main sprite archive (Gfx.dir / Gfx0.dir / Gfx1.dir).
    pub primary_gfx_dir: *mut GfxDir,
    /// 0x4C4: Secondary GfxDir β€” supplemental sprites (GfxC_3_0.dir), conditional on game version.
    pub secondary_gfx_dir: *mut GfxDir,
    /// 0x4C8: Graphics mode flag (DWORD index 0x132)
    pub gfx_mode: u32,
    /// 0x4CC: PCLandscape object pointer (DWORD index 0x133)
    pub landscape: *mut PCLandscape,
    /// 0x4D0: DisplayGfx pointer (param2 of constructor)
    pub display: *mut DisplayGfx,
    /// 0x4D4: DSSound pointer (param3 of constructor)
    pub sound: *mut DSSound,
    /// 0x4D8: Loading progress counter (incremented per loading tick).
    pub loading_progress: u32,
    /// 0x4DC: Loading progress total (base 0x2AD + 0x38 per team + 0x7E overhead).
    pub loading_total: u32,
    /// 0x4E0: Last displayed loading percentage (init -100 to force first update).
    pub loading_last_pct: u32,

    // ===== 0x4E4-0x6F0F: Remaining fields =====
    /// 0x4E4-0x14E7: Unknown fields
    pub _unknown_4e4: [u8; 0x14E8 - 0x4E4],
    /// 0x14E8: Speech name table β€” 360 entries of 0x40-byte C strings.
    /// Used by DDGameWrapper__LoadSpeechWAV to deduplicate loaded WAVs.
    pub speech_name_table: [[u8; SPEECH_NAME_ENTRY_SIZE]; SPEECH_NAME_TABLE_LEN],
    /// 0x6EE8: Number of entries used in speech_name_table.
    pub speech_name_count: u32,
    /// 0x6EEC: Init 0 (DWORD index 0x1BBA)
    pub _field_6eec: u32,
    /// 0x6EF0-0x6F0F: Unknown trailing fields (not zeroed by constructor memset).
    pub _unknown_6ef0: [u8; 0x20],
}

const _: () = assert!(core::mem::size_of::<DDGameWrapper>() == 0x6F10);