paavohtl Claude Opus 4.6 (1M context) commited on
Commit
6795996
Β·
1 Parent(s): f2d43fe

refactor: improve naming, typing, and readability in hardware_init

Browse files

- Fix misleading param names: bridge _hwnd→_param3, construct_ddgame_wrapper
gfx→keyboard, music→streaming_audio, network→input_ctrl
- Add GameSession fields: init_flag (0x24), fullscreen_flag (0x28)
- Expand Palette struct with _field_004 and 0x28 size assert
- Type WABox allocations: DisplayGfx, StreamingAudio, DDNetGameWrapper,
GameStats ctors use typed pointers instead of *mut u8
- Rename d778_val→crosshair_threshold, document game_info+4 offset
- Add PLAY_VSLOT constant for IDirectSoundBuffer vtable call
- Remove redundant write_bytes for DDKeyboard (covered by WABox zeroing)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

crates/openwa-core/src/game_session.rs CHANGED
@@ -29,7 +29,11 @@ pub struct GameSession {
29
  pub _unknown_004: [u8; 4],
30
  /// 0x008: main window HWND, stored from `hWnd` in `GameSession__Run`
31
  pub hwnd: u32,
32
- pub _unknown_00c: [u8; 0x20],
 
 
 
 
33
  /// 0x02C: set to 1 on init (role TBD)
34
  pub flag_2c: u32,
35
  pub _unknown_030: [u8; 8],
 
29
  pub _unknown_004: [u8; 4],
30
  /// 0x008: main window HWND, stored from `hWnd` in `GameSession__Run`
31
  pub hwnd: u32,
32
+ pub _unknown_00c: [u8; 0x18],
33
+ /// 0x024: Always set to 1 by GameEngine__InitHardware.
34
+ pub init_flag: u32,
35
+ /// 0x028: Fullscreen flag β€” `(GameInfo.home_lock != 0) as u32`.
36
+ pub fullscreen_flag: u32,
37
  /// 0x02C: set to 1 on init (role TBD)
38
  pub flag_2c: u32,
39
  pub _unknown_030: [u8; 8],
crates/openwa-core/src/palette.rs CHANGED
@@ -2,10 +2,17 @@
2
  ///
3
  /// Vtable: 0x66A2E4.
4
  /// Passed as "palette" parameter to DDGame__Constructor, stored at DDGame+0x010.
 
5
  ///
6
- /// OPAQUE: Size and fields not yet determined. Only vtable pointer defined.
7
  #[repr(C)]
8
  pub struct Palette {
9
  /// 0x000: Vtable pointer (0x66A2E4)
10
  pub vtable: *mut u8,
 
 
 
 
11
  }
 
 
 
2
  ///
3
  /// Vtable: 0x66A2E4.
4
  /// Passed as "palette" parameter to DDGame__Constructor, stored at DDGame+0x010.
5
+ /// Size: 0x28 bytes.
6
  ///
7
+ /// PARTIAL: Only vtable and first init field known.
8
  #[repr(C)]
9
  pub struct Palette {
10
  /// 0x000: Vtable pointer (0x66A2E4)
11
  pub vtable: *mut u8,
12
+ /// 0x004: Initialized to 0xFFFFFFFF during inline construction.
13
+ pub _field_004: u32,
14
+ /// 0x008-0x027: Unknown
15
+ pub _unknown_008: [u8; 0x20],
16
  }
17
+
18
+ const _: () = assert!(core::mem::size_of::<Palette>() == 0x28);
crates/openwa-wormkit/src/replacements/game_session.rs CHANGED
@@ -98,15 +98,15 @@ unsafe extern "stdcall" fn ddgame_constructor_call(
98
  _this: *mut DDGameWrapper,
99
  _display: *mut DDDisplay,
100
  _sound: *mut DSSound,
101
- _gfx: *mut u8,
102
  _palette: *mut Palette,
103
- _music: *mut u8,
104
  _timer_obj: *mut u8,
105
  _net_game: *mut u8,
106
  _game_info: *mut u8,
107
  ) -> *mut u8 {
108
  core::arch::naked_asm!(
109
- "movl {ecx_val}, %ecx", // ECX = network (implicit param for DDGame::ctor)
110
  "jmpl *({fn})", // tail-jump; DDGame::ctor's RET 0x24 returns to caller
111
  ecx_val = sym DDGAME_CTOR_ECX,
112
  fn = sym DDGAME_CTOR_ADDR,
@@ -125,10 +125,10 @@ unsafe extern "cdecl" fn ctor_impl(
125
  this: *mut DDGameWrapper,
126
  display: *mut DDDisplay,
127
  sound: *mut DSSound,
128
- gfx: *mut u8,
129
  palette: *mut Palette,
130
- music: *mut u8,
131
- network: *mut u8,
132
  ) -> *mut DDGameWrapper {
133
  let game_info = GAME_INFO as *mut u8;
134
 
@@ -147,10 +147,10 @@ unsafe extern "cdecl" fn ctor_impl(
147
  let timer_obj = (*session).timer_obj;
148
  let net_game = (*session).net_game;
149
 
150
- // Store network as the implicit ECX for DDGame::ctor, then tail-jump-call.
151
- DDGAME_CTOR_ECX = network as u32;
152
  ddgame_constructor_call(
153
- this, display, sound, gfx, palette, music,
154
  timer_obj, net_game, game_info,
155
  );
156
 
@@ -219,13 +219,13 @@ pub(crate) unsafe fn construct_ddgame_wrapper(
219
  this: *mut DDGameWrapper,
220
  display: *mut DDDisplay,
221
  sound: *mut DSSound,
222
- gfx: *mut u8,
223
  palette: *mut Palette,
224
- music: *mut u8,
225
- network: *mut u8,
226
  ) -> *mut DDGameWrapper {
227
  GAME_INFO = game_info as u32;
228
- ctor_impl(this, display, sound, gfx, palette, music, network)
229
  }
230
 
231
  pub fn install() -> Result<(), String> {
 
98
  _this: *mut DDGameWrapper,
99
  _display: *mut DDDisplay,
100
  _sound: *mut DSSound,
101
+ _keyboard: *mut u8,
102
  _palette: *mut Palette,
103
+ _streaming_audio: *mut u8,
104
  _timer_obj: *mut u8,
105
  _net_game: *mut u8,
106
  _game_info: *mut u8,
107
  ) -> *mut u8 {
108
  core::arch::naked_asm!(
109
+ "movl {ecx_val}, %ecx", // ECX = input_ctrl (implicit param for DDGame::ctor)
110
  "jmpl *({fn})", // tail-jump; DDGame::ctor's RET 0x24 returns to caller
111
  ecx_val = sym DDGAME_CTOR_ECX,
112
  fn = sym DDGAME_CTOR_ADDR,
 
125
  this: *mut DDGameWrapper,
126
  display: *mut DDDisplay,
127
  sound: *mut DSSound,
128
+ keyboard: *mut u8,
129
  palette: *mut Palette,
130
+ streaming_audio: *mut u8,
131
+ input_ctrl: *mut u8,
132
  ) -> *mut DDGameWrapper {
133
  let game_info = GAME_INFO as *mut u8;
134
 
 
147
  let timer_obj = (*session).timer_obj;
148
  let net_game = (*session).net_game;
149
 
150
+ // Store input_ctrl as the implicit ECX for DDGame::ctor, then tail-jump-call.
151
+ DDGAME_CTOR_ECX = input_ctrl as u32;
152
  ddgame_constructor_call(
153
+ this, display, sound, keyboard, palette, streaming_audio,
154
  timer_obj, net_game, game_info,
155
  );
156
 
 
219
  this: *mut DDGameWrapper,
220
  display: *mut DDDisplay,
221
  sound: *mut DSSound,
222
+ keyboard: *mut u8,
223
  palette: *mut Palette,
224
+ streaming_audio: *mut u8,
225
+ input_ctrl: *mut u8,
226
  ) -> *mut DDGameWrapper {
227
  GAME_INFO = game_info as u32;
228
+ ctor_impl(this, display, sound, keyboard, palette, streaming_audio, input_ctrl)
229
  }
230
 
231
  pub fn install() -> Result<(), String> {
crates/openwa-wormkit/src/replacements/hardware_init.rs CHANGED
@@ -53,6 +53,9 @@ use openwa_core::palette::Palette;
53
  use openwa_core::input_ctrl::InputCtrl;
54
  use openwa_core::game_timer::GameTimer;
55
  use openwa_core::game_stats::GameStats;
 
 
 
56
  use openwa_core::wa_alloc::WABox;
57
  use crate::hook;
58
  use crate::log_line;
@@ -90,16 +93,16 @@ static mut DSSOUND_INIT_EAX: u32 = 0;
90
  // so the callee sees its actual stack args at [esp+4] / [esp+8] etc. (not
91
  // displaced by an extra return address).
92
 
93
- /// Timer constructor: `usercall(ESI=timer_ptr, EAX=d778_val)`, plain RET.
94
  /// Returns whatever EAX holds after the call.
95
  #[unsafe(naked)]
96
- unsafe extern "cdecl" fn call_timer_ctor(_timer_ptr: *mut GameTimer, _d778_val: u32) -> u32 {
97
  core::arch::naked_asm!(
98
- // [esp+0]=bridge_ret, [esp+4]=timer_ptr, [esp+8]=d778_val
99
  "pushl %esi",
100
  // [esp+0]=old_esi, [esp+4]=bridge_ret, [esp+8]=timer_ptr, [esp+c]=d778_val
101
  "movl 8(%esp), %esi", // ESI = timer_ptr
102
- "movl 0xc(%esp), %eax", // EAX = d778_val
103
  "calll *({fn})", // FUN_0053E950: plain RET (no stack args)
104
  "popl %esi",
105
  "retl", // cdecl; caller cleans 2 Γ— u32
@@ -166,9 +169,9 @@ unsafe extern "stdcall" fn call_dssound_init_buffers(
166
  #[unsafe(naked)]
167
  unsafe extern "stdcall" fn call_input_ctrl_init(
168
  _game_info_p4: *mut u8,
169
- _hwnd: u32,
170
  _param3: u32,
171
- _d778_val: u32,
 
172
  ) -> u32 {
173
  core::arch::naked_asm!(
174
  // [esp+0]=bridge_ret, [esp+4]=gip4, [esp+8]=hwnd, [esp+c]=p3, [esp+10]=d778
@@ -255,7 +258,7 @@ unsafe extern "cdecl" fn impl_init_hardware(
255
  let _ = log_line("[hardware_init] GameEngine::InitHardware");
256
  let session = *(rb(va::G_GAME_SESSION) as *const *mut GameSession);
257
  let gi = &mut *game_info;
258
- let d778_val = gi.crosshair_overflow_threshold as u32;
259
 
260
  // ── Input controller (if param4 != 0) ────────────────────────────────────
261
  if param4 == 0 {
@@ -266,13 +269,10 @@ unsafe extern "cdecl" fn impl_init_hardware(
266
  (*ctrl).vtable = rb(va::INPUT_CTRL_VTABLE) as *mut u8;
267
  (*session).input_ctrl = ctrl as *mut u8;
268
 
 
 
269
  INPUT_CTRL_ESI = ctrl as u32;
270
- let ok = call_input_ctrl_init(
271
- (game_info as *mut u8).add(4),
272
- param3,
273
- param4,
274
- d778_val,
275
- );
276
  if ok == 0 {
277
  let vtbl = *(ctrl as *const *const usize);
278
  let dtor: unsafe extern "thiscall" fn(*mut u8, u32) =
@@ -285,17 +285,17 @@ unsafe extern "cdecl" fn impl_init_hardware(
285
 
286
  // ── Timer object (ALWAYS) ─────────────────────────────────────────────────
287
  let timer = WABox::<GameTimer>::alloc(0x30, 0x30).leak();
288
- call_timer_ctor(timer, d778_val);
289
  (*session).timer_obj = timer as *mut u8;
290
 
291
  let headless = gi.headless_mode != 0;
292
 
293
  if !headless {
294
  // ── DisplayGfx ───────────────────────────────────────────────────────
295
- let displaygfx_ctor: unsafe extern "stdcall" fn(*mut u8) -> *mut u8 =
296
  core::mem::transmute(rb(va::DISPLAYGFX_CTOR) as usize);
297
- let display_gfx = displaygfx_ctor(WABox::<u8>::alloc(0x24E28, 0x24E08).leak());
298
- (*session).display_gfx = display_gfx;
299
 
300
  // ── DDDisplay::Init retry loop ────────────────────────────────────────
301
  let flags = gi.display_flags;
@@ -303,7 +303,7 @@ unsafe extern "cdecl" fn impl_init_hardware(
303
  let h0 = gi.display_height;
304
 
305
  DDISPLAY_INIT_ECX = h0;
306
- let mut init_ok = call_ddisplay_init(display_gfx, hwnd, w0, flags) != 0;
307
 
308
  if !init_ok {
309
  let fallbacks: [(u32, u32); 3] = [
@@ -315,7 +315,7 @@ unsafe extern "cdecl" fn impl_init_hardware(
315
  gi.display_width = w;
316
  gi.display_height = h;
317
  DDISPLAY_INIT_ECX = h;
318
- if call_ddisplay_init(display_gfx, hwnd, w, flags) != 0 {
319
  init_ok = true;
320
  break;
321
  }
@@ -363,12 +363,12 @@ unsafe extern "cdecl" fn impl_init_hardware(
363
  }
364
 
365
  // ── DDKeyboard (inline construction) ──────────────────────────────────
 
 
366
  let kb = WABox::<DDKeyboard>::alloc(0x33C, 0x31C).leak();
367
  (*kb).vtable = rb(va::DDKEYBOARD_VTABLE) as *mut u8;
368
  (*kb).game_info_input_ptr = &raw mut gi.input_state_f918 as u32;
369
  (*kb)._field_008 = 1;
370
- core::ptr::write_bytes((*kb).key_state.as_mut_ptr(), 0, 0x100);
371
- core::ptr::write_bytes((*kb).prev_state.as_mut_ptr(), 0, 0x100);
372
  (*kb)._field_014 = 0;
373
  (*kb)._field_018 = 0;
374
  (*session).keyboard = kb;
@@ -376,7 +376,7 @@ unsafe extern "cdecl" fn impl_init_hardware(
376
  // ── Palette (inline construction) ─────────────────────────────────────
377
  let pal = WABox::<Palette>::alloc(0x28, 0).leak();
378
  (*pal).vtable = rb(va::PALETTE_VTABLE_MAYBE) as *mut u8;
379
- *((pal as *mut u8).add(4) as *mut u32) = 0xFFFF_FFFF;
380
  (*session).palette = pal;
381
 
382
  // ── DSSound ───────────────────────────────────────────────────────────
@@ -403,11 +403,12 @@ unsafe extern "cdecl" fn impl_init_hardware(
403
  );
404
  if hr2 == 0 {
405
  // IDirectSoundBuffer::Play(this, 0, 0, DSBPLAY_LOOPING=1)
 
406
  let pds = (*snd).primary_buffer as *const *const usize;
407
  let vtbl = *pds;
408
  let play: unsafe extern "stdcall" fn(
409
  *const *const usize, u32, u32, u32,
410
- ) -> i32 = core::mem::transmute(*vtbl.add(0x30 / 4));
411
  let hr3 = play(pds, 0, 0, 1);
412
  if hr3 == 0 {
413
  (*snd).init_success = 1;
@@ -418,25 +419,22 @@ unsafe extern "cdecl" fn impl_init_hardware(
418
 
419
  // ── Streaming audio ───────────────────────────────────────────────────
420
  (*session).streaming_audio = core::ptr::null_mut();
421
- if !(*session).sound.is_null() {
422
- if gi.speech_enabled != 0 {
423
- let stream_mem = WABox::<u8>::alloc(0x354, 0x334).leak();
424
- let snd = (*session).sound;
425
- let ids = (*snd).direct_sound;
426
- call_streaming_audio_ctor(
427
- stream_mem,
428
- ids,
429
- gi.streaming_audio_config.as_mut_ptr(),
430
- );
431
- (*session).streaming_audio = stream_mem;
432
- }
433
  }
434
  } else {
435
  // ── Headless / stats mode ─────────────────────────────────────────────
436
  let stats = WABox::<GameStats>::alloc(0x3560, 0x3560).leak();
437
- let gamestats_ctor: unsafe extern "stdcall" fn(*mut u8) -> *mut u8 =
438
  core::mem::transmute(rb(va::GAMESTATS_CTOR) as usize);
439
- gamestats_ctor(stats as *mut u8);
440
  (*stats).vtable = rb(va::GAMESTATS_VTABLE) as *mut u8;
441
  (*session).display_gfx = stats as *mut u8;
442
  (*session).keyboard = core::ptr::null_mut();
@@ -445,10 +443,9 @@ unsafe extern "cdecl" fn impl_init_hardware(
445
  (*session).streaming_audio = core::ptr::null_mut();
446
  }
447
 
448
- // ── Windowed/fullscreen flag β†’ session+0x28, and session+0x24 = 1 ���─────────
449
- let fullscreen_word = gi.home_lock;
450
- *((session as *mut u8).add(0x28) as *mut u32) = (fullscreen_word != 0) as u32;
451
- *((session as *mut u8).add(0x24) as *mut u32) = 1;
452
 
453
  // ── DDGameWrapper (ALWAYS) ────────────────────────────────────────────────
454
  let wrapper = game_session::construct_ddgame_wrapper(
@@ -488,9 +485,9 @@ unsafe extern "cdecl" fn impl_init_hardware(
488
  }
489
 
490
  // ── DDNetGameWrapper (ALWAYS) ─────────────────────────────────────────────
491
- let net_ctor: unsafe extern "stdcall" fn(*mut u8) -> *mut u8 =
492
  core::mem::transmute(rb(va::DDNETGAME_WRAPPER_CTOR) as usize);
493
- (*session).net_game = net_ctor(WABox::<u8>::alloc(0x2C, 0).leak());
494
 
495
  let _ = log_line("[hardware_init] GameEngine::InitHardware done");
496
  1
 
53
  use openwa_core::input_ctrl::InputCtrl;
54
  use openwa_core::game_timer::GameTimer;
55
  use openwa_core::game_stats::GameStats;
56
+ use openwa_core::display_gfx::DisplayGfx;
57
+ use openwa_core::streaming_audio::StreamingAudio;
58
+ use openwa_core::ddnetgame_wrapper::DDNetGameWrapper;
59
  use openwa_core::wa_alloc::WABox;
60
  use crate::hook;
61
  use crate::log_line;
 
93
  // so the callee sees its actual stack args at [esp+4] / [esp+8] etc. (not
94
  // displaced by an extra return address).
95
 
96
+ /// Timer constructor: `usercall(ESI=timer_ptr, EAX=crosshair_threshold)`, plain RET.
97
  /// Returns whatever EAX holds after the call.
98
  #[unsafe(naked)]
99
+ unsafe extern "cdecl" fn call_timer_ctor(_timer_ptr: *mut GameTimer, _crosshair_threshold: u32) -> u32 {
100
  core::arch::naked_asm!(
101
+ // [esp+0]=bridge_ret, [esp+4]=timer_ptr, [esp+8]=crosshair_threshold
102
  "pushl %esi",
103
  // [esp+0]=old_esi, [esp+4]=bridge_ret, [esp+8]=timer_ptr, [esp+c]=d778_val
104
  "movl 8(%esp), %esi", // ESI = timer_ptr
105
+ "movl 0xc(%esp), %eax", // EAX = crosshair_threshold
106
  "calll *({fn})", // FUN_0053E950: plain RET (no stack args)
107
  "popl %esi",
108
  "retl", // cdecl; caller cleans 2 Γ— u32
 
169
  #[unsafe(naked)]
170
  unsafe extern "stdcall" fn call_input_ctrl_init(
171
  _game_info_p4: *mut u8,
 
172
  _param3: u32,
173
+ _param4: u32,
174
+ _crosshair_threshold: u32,
175
  ) -> u32 {
176
  core::arch::naked_asm!(
177
  // [esp+0]=bridge_ret, [esp+4]=gip4, [esp+8]=hwnd, [esp+c]=p3, [esp+10]=d778
 
258
  let _ = log_line("[hardware_init] GameEngine::InitHardware");
259
  let session = *(rb(va::G_GAME_SESSION) as *const *mut GameSession);
260
  let gi = &mut *game_info;
261
+ let crosshair_threshold = gi.crosshair_overflow_threshold as u32;
262
 
263
  // ── Input controller (if param4 != 0) ────────────────────────────────────
264
  if param4 == 0 {
 
269
  (*ctrl).vtable = rb(va::INPUT_CTRL_VTABLE) as *mut u8;
270
  (*session).input_ctrl = ctrl as *mut u8;
271
 
272
+ // Original passes GameInfo+4 (skips first DWORD of unknown padding).
273
+ let game_info_plus_4 = (game_info as *mut u8).add(4);
274
  INPUT_CTRL_ESI = ctrl as u32;
275
+ let ok = call_input_ctrl_init(game_info_plus_4, param3, param4, crosshair_threshold);
 
 
 
 
 
276
  if ok == 0 {
277
  let vtbl = *(ctrl as *const *const usize);
278
  let dtor: unsafe extern "thiscall" fn(*mut u8, u32) =
 
285
 
286
  // ── Timer object (ALWAYS) ─────────────────────────────────────────────────
287
  let timer = WABox::<GameTimer>::alloc(0x30, 0x30).leak();
288
+ call_timer_ctor(timer, crosshair_threshold);
289
  (*session).timer_obj = timer as *mut u8;
290
 
291
  let headless = gi.headless_mode != 0;
292
 
293
  if !headless {
294
  // ── DisplayGfx ───────────────────────────────────────────────────────
295
+ let displaygfx_ctor: unsafe extern "stdcall" fn(*mut DisplayGfx) -> *mut DisplayGfx =
296
  core::mem::transmute(rb(va::DISPLAYGFX_CTOR) as usize);
297
+ let display_gfx = displaygfx_ctor(WABox::<DisplayGfx>::alloc(0x24E28, 0x24E08).leak());
298
+ (*session).display_gfx = display_gfx as *mut u8;
299
 
300
  // ── DDDisplay::Init retry loop ────────────────────────────────────────
301
  let flags = gi.display_flags;
 
303
  let h0 = gi.display_height;
304
 
305
  DDISPLAY_INIT_ECX = h0;
306
+ let mut init_ok = call_ddisplay_init(display_gfx as *mut u8, hwnd, w0, flags) != 0;
307
 
308
  if !init_ok {
309
  let fallbacks: [(u32, u32); 3] = [
 
315
  gi.display_width = w;
316
  gi.display_height = h;
317
  DDISPLAY_INIT_ECX = h;
318
+ if call_ddisplay_init(display_gfx as *mut u8, hwnd, w, flags) != 0 {
319
  init_ok = true;
320
  break;
321
  }
 
363
  }
364
 
365
  // ── DDKeyboard (inline construction) ──────────────────────────────────
366
+ // WABox zeroes the first 0x31C bytes, covering key_state (+0x11C) and
367
+ // prev_state (+0x21C) β€” no separate zeroing needed.
368
  let kb = WABox::<DDKeyboard>::alloc(0x33C, 0x31C).leak();
369
  (*kb).vtable = rb(va::DDKEYBOARD_VTABLE) as *mut u8;
370
  (*kb).game_info_input_ptr = &raw mut gi.input_state_f918 as u32;
371
  (*kb)._field_008 = 1;
 
 
372
  (*kb)._field_014 = 0;
373
  (*kb)._field_018 = 0;
374
  (*session).keyboard = kb;
 
376
  // ── Palette (inline construction) ─────────────────────────────────────
377
  let pal = WABox::<Palette>::alloc(0x28, 0).leak();
378
  (*pal).vtable = rb(va::PALETTE_VTABLE_MAYBE) as *mut u8;
379
+ (*pal)._field_004 = 0xFFFF_FFFF;
380
  (*session).palette = pal;
381
 
382
  // ── DSSound ───────────────────────────────────────────────────────────
 
403
  );
404
  if hr2 == 0 {
405
  // IDirectSoundBuffer::Play(this, 0, 0, DSBPLAY_LOOPING=1)
406
+ const PLAY_VSLOT: usize = 0x30 / 4; // IDirectSoundBuffer vtable slot 12
407
  let pds = (*snd).primary_buffer as *const *const usize;
408
  let vtbl = *pds;
409
  let play: unsafe extern "stdcall" fn(
410
  *const *const usize, u32, u32, u32,
411
+ ) -> i32 = core::mem::transmute(*vtbl.add(PLAY_VSLOT));
412
  let hr3 = play(pds, 0, 0, 1);
413
  if hr3 == 0 {
414
  (*snd).init_success = 1;
 
419
 
420
  // ── Streaming audio ───────────────────────────────────────────────────
421
  (*session).streaming_audio = core::ptr::null_mut();
422
+ if !(*session).sound.is_null() && gi.speech_enabled != 0 {
423
+ let stream = WABox::<StreamingAudio>::alloc(0x354, 0x334).leak();
424
+ let ids = (*(*session).sound).direct_sound;
425
+ call_streaming_audio_ctor(
426
+ stream as *mut u8,
427
+ ids,
428
+ gi.streaming_audio_config.as_mut_ptr(),
429
+ );
430
+ (*session).streaming_audio = stream as *mut u8;
 
 
 
431
  }
432
  } else {
433
  // ── Headless / stats mode ─────────────────────────────────────────────
434
  let stats = WABox::<GameStats>::alloc(0x3560, 0x3560).leak();
435
+ let gamestats_ctor: unsafe extern "stdcall" fn(*mut GameStats) -> *mut GameStats =
436
  core::mem::transmute(rb(va::GAMESTATS_CTOR) as usize);
437
+ gamestats_ctor(stats);
438
  (*stats).vtable = rb(va::GAMESTATS_VTABLE) as *mut u8;
439
  (*session).display_gfx = stats as *mut u8;
440
  (*session).keyboard = core::ptr::null_mut();
 
443
  (*session).streaming_audio = core::ptr::null_mut();
444
  }
445
 
446
+ // ── Session flags ─────────────────────────────────────────────────────────
447
+ (*session).init_flag = 1;
448
+ (*session).fullscreen_flag = (gi.home_lock != 0) as u32;
 
449
 
450
  // ── DDGameWrapper (ALWAYS) ────────────────────────────────────────────────
451
  let wrapper = game_session::construct_ddgame_wrapper(
 
485
  }
486
 
487
  // ── DDNetGameWrapper (ALWAYS) ─────────────────────────────────────────────
488
+ let net_ctor: unsafe extern "stdcall" fn(*mut DDNetGameWrapper) -> *mut DDNetGameWrapper =
489
  core::mem::transmute(rb(va::DDNETGAME_WRAPPER_CTOR) as usize);
490
+ (*session).net_game = net_ctor(WABox::<DDNetGameWrapper>::alloc(0x2C, 0).leak()) as *mut u8;
491
 
492
  let _ = log_line("[hardware_init] GameEngine::InitHardware done");
493
  1