paavohtl Claude Opus 4.6 (1M context) commited on
Commit
8fee659
·
1 Parent(s): 5a60ed0

refactor: add MapClass struct, use typed pointer in replay map loading

Browse files

Create MapClass (0x29628 bytes) with vtable pointer and terrain_flag
field. Define MapClassVtable with typed destructor slot.

Replace raw u32/pointer arithmetic in replay map loading with typed
MapClass pointers — constructor, load, vtable destructor calls, and
terrain_flag field access all use the correct type. Eliminates manual
vtable offset calculations.

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

crates/openwa-core/src/engine/map_class.rs ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /// MapClass — landscape/terrain map object (0x29628 bytes).
2
+ ///
3
+ /// Used by the replay loader to parse map data from playback.thm files.
4
+ /// Allocated with CRT malloc (0x29628 bytes), constructed via
5
+ /// `MapClass__Constructor` (0x447E80), loaded via `MapClass__Load`
6
+ /// (0x44A9A0), then map info is copied to game state via
7
+ /// `MapClass__CopyInfo` (0x449B60, usercall ESI=this).
8
+ ///
9
+ /// After use, released via vtable[1] (thiscall destructor with free flag).
10
+ ///
11
+ /// PARTIAL: Only the vtable and terrain_flag field are known.
12
+ #[repr(C)]
13
+ pub struct MapClass {
14
+ /// 0x00000: Vtable pointer.
15
+ pub vtable: *const MapClassVtable,
16
+ /// 0x00004-0x29617: Unknown (map data, terrain grid, etc.).
17
+ pub _unknown_004: [u8; 0x29618 - 4],
18
+ /// 0x29618: Terrain type flag. Zero = cavern terrain (SETZ in replay loader).
19
+ pub terrain_flag: u8,
20
+ /// 0x29619-0x29627: Unknown trailing bytes.
21
+ pub _unknown_29619: [u8; 0x29628 - 0x29619],
22
+ }
23
+
24
+ const _: () = assert!(core::mem::size_of::<MapClass>() == 0x29628);
25
+
26
+ /// MapClass vtable (partial — only known slots).
27
+ #[repr(C)]
28
+ pub struct MapClassVtable {
29
+ /// Slot 0: Unknown virtual method.
30
+ pub slot_0: usize,
31
+ /// Slot 1: Destructor — thiscall(this, free_flag). Frees the object if flag & 1.
32
+ pub destructor: unsafe extern "thiscall" fn(*mut MapClass, i32),
33
+ }
crates/openwa-core/src/engine/mod.rs CHANGED
@@ -1,4 +1,5 @@
1
  pub mod ddgame;
 
2
  pub mod replay;
3
  pub mod ddgame_wrapper;
4
  pub mod game_info;
 
1
  pub mod ddgame;
2
+ pub mod map_class;
3
  pub mod replay;
4
  pub mod ddgame_wrapper;
5
  pub mod game_info;
crates/openwa-wormkit/src/replacements/replay.rs CHANGED
@@ -7,6 +7,7 @@ use crate::hook;
7
  use crate::log_line;
8
  use openwa_core::address::va;
9
  use openwa_core::engine::game_info::GameInfo;
 
10
  use openwa_core::engine::replay::{self, ReplayError, ReplayStream, ReplayTeamEntry};
11
  use openwa_core::rebase::rb;
12
  use openwa_core::wa_alloc::{wa_free, wa_malloc};
@@ -542,47 +543,36 @@ unsafe fn parse_and_write_v2plus(
542
  // For positive sub-version (our test case), we need to:
543
  // Map loading: construct map object, load playback.thm, copy info, release.
544
  if (*gi).replay_map_type >= 1 {
545
- // Alloc: PUSH 0x29628; CALL 005C0AB8; ADD ESP,4 — cdecl(size)
546
- let alloc: unsafe extern "cdecl" fn(u32) -> u32 =
547
  core::mem::transmute(rb(va::WA_CRT_MALLOC));
548
- let buf = alloc(0x29628);
549
- let map_obj = if buf != 0 {
550
- // Construct: PUSH 1; PUSH buf; CALL 00447E80 — stdcall(buf, 1)
551
- let construct: unsafe extern "stdcall" fn(u32, i32) -> *mut u32 =
552
  core::mem::transmute(rb(va::MAP_CLASS_CONSTRUCTOR));
553
  construct(buf, 1)
554
  } else {
555
  ptr::null_mut()
556
  };
557
 
558
- // Load: PUSH 0; PUSH path; PUSH map_obj; CALL 0044A9A0 — stdcall(3)
559
- let load: unsafe extern "stdcall" fn(*mut u32, *const u8, i32) -> i32 =
560
  core::mem::transmute(rb(va::MAP_CLASS_LOAD));
561
- let ok = load(map_obj, b"data\\playback.thm\0".as_ptr(), 0);
562
 
563
  if ok == 0 {
564
- // Load failed — release map_obj and return error
565
- if !map_obj.is_null() {
566
- let vtable = *map_obj;
567
- let release: unsafe extern "thiscall" fn(*mut u32, i32) =
568
- core::mem::transmute(*(vtable as *const u32).add(1));
569
- release(map_obj, 1);
570
  }
571
  return Err(ReplayError::MapLoadFailure);
572
  }
573
 
574
- // FUN_00449B60: usercall(ESI=map_obj). Copies map info to game state.
575
- call_usercall_esi(map_obj as u32, rb(va::MAP_COPY_INFO));
576
 
577
- // Terrain flag: CMP [ESI+0x29618],0; SETZ
578
- (*gi).terrain_flag =
579
- (*(map_obj as *const u8).add(0x29618) == 0) as u8;
580
 
581
- // Release: PUSH 1; MOV ECX,ESI; CALL [vtable+4] — thiscall(this, 1)
582
- let vtable = *map_obj;
583
- let release: unsafe extern "thiscall" fn(*mut u32, i32) =
584
- core::mem::transmute(*(vtable as *const u32).add(1));
585
- release(map_obj, 1);
586
  }
587
 
588
  // Log output is written by the caller after map loading completes.
 
7
  use crate::log_line;
8
  use openwa_core::address::va;
9
  use openwa_core::engine::game_info::GameInfo;
10
+ use openwa_core::engine::map_class::MapClass;
11
  use openwa_core::engine::replay::{self, ReplayError, ReplayStream, ReplayTeamEntry};
12
  use openwa_core::rebase::rb;
13
  use openwa_core::wa_alloc::{wa_free, wa_malloc};
 
543
  // For positive sub-version (our test case), we need to:
544
  // Map loading: construct map object, load playback.thm, copy info, release.
545
  if (*gi).replay_map_type >= 1 {
546
+ let alloc: unsafe extern "cdecl" fn(u32) -> *mut MapClass =
 
547
  core::mem::transmute(rb(va::WA_CRT_MALLOC));
548
+ let buf = alloc(core::mem::size_of::<MapClass>() as u32);
549
+ let map = if !buf.is_null() {
550
+ let construct: unsafe extern "stdcall" fn(*mut MapClass, i32) -> *mut MapClass =
 
551
  core::mem::transmute(rb(va::MAP_CLASS_CONSTRUCTOR));
552
  construct(buf, 1)
553
  } else {
554
  ptr::null_mut()
555
  };
556
 
557
+ let load: unsafe extern "stdcall" fn(*mut MapClass, *const u8, i32) -> i32 =
 
558
  core::mem::transmute(rb(va::MAP_CLASS_LOAD));
559
+ let ok = load(map, b"data\\playback.thm\0".as_ptr(), 0);
560
 
561
  if ok == 0 {
562
+ if !map.is_null() {
563
+ ((*(*map).vtable).destructor)(map, 1);
 
 
 
 
564
  }
565
  return Err(ReplayError::MapLoadFailure);
566
  }
567
 
568
+ // Copy map info to game state (usercall ESI=map)
569
+ call_usercall_esi(map as u32, rb(va::MAP_COPY_INFO));
570
 
571
+ // Terrain flag: zero = cavern terrain
572
+ (*gi).terrain_flag = ((*map).terrain_flag == 0) as u8;
 
573
 
574
+ // Release map object
575
+ ((*(*map).vtable).destructor)(map, 1);
 
 
 
576
  }
577
 
578
  // Log output is written by the caller after map loading completes.