paavohtl Claude Opus 4.6 (1M context) commited on
Commit
05452c7
·
1 Parent(s): 4902606

refactor: port BufferObject ctor, replace more bridges with typed calls

Browse files

- Port DualBufferObject__Constructor (0x545FD0) to Rust: the original
function was usercall with an implicit EDI register carrying the
second buffer's capacity. Traced the disassembly to find the capacity
is 0x2DC or 0x46C depending on game_info version fields.

- Replace PaletteContext__Init bridges in ddgame_constructor.rs with
the Rust palette_context_init() ported in the previous commit.

- Replace call_display_gfx_dirty_rect raw vtable dispatch with typed
DisplayBitGrid::fill_rect_raw(), using named struct field access
instead of raw pointer offset arithmetic.

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

crates/openwa-core/src/engine/ddgame.rs CHANGED
@@ -59,8 +59,8 @@ pub struct DDGame {
59
  pub game_info: *mut GameInfo,
60
  /// 0x028: Network game object (param_9 to DDGameWrapper constructor).
61
  pub net_game: *mut u8,
62
- /// 0x02C: Secondary GfxDir object (0x70C bytes, conditional on GfxHandler 1)
63
- pub secondary_gfxdir: *mut u8,
64
  /// 0x030: Gradient image pointer
65
  pub gradient_image: *mut u8,
66
  /// 0x034: Gradient image 2 pointer
 
59
  pub game_info: *mut GameInfo,
60
  /// 0x028: Network game object (param_9 to DDGameWrapper constructor).
61
  pub net_game: *mut u8,
62
+ /// 0x02C: Secondary PaletteContext (0x70C bytes, conditional on secondary GfxDir)
63
+ pub secondary_palette_ctx: *mut crate::render::palette::PaletteContext,
64
  /// 0x030: Gradient image pointer
65
  pub gradient_image: *mut u8,
66
  /// 0x034: Gradient image 2 pointer
crates/openwa-core/src/engine/ddgame_constructor.rs CHANGED
@@ -598,15 +598,14 @@ unsafe fn init_graphics_and_resources(
598
  );
599
  }
600
 
601
- // ── Secondary GfxDir object (DDGame+0x2C, conditional) ──
602
  if !(*wrapper).secondary_gfx_dir.is_null() {
603
- let gfxdir2 = wa_malloc_zeroed(0x70C);
604
- *(gfxdir2 as *mut u16) = 1;
605
- *(gfxdir2.add(2) as *mut u16) = 0x5A;
606
- // FUN_5411A0: usercall(EAX=gfxdir2), plain RET
607
- call_usercall_eax(gfxdir2 as *mut DDGameWrapper, rb(va::PALETTE_CONTEXT_INIT));
608
- *(gfxdir2.add(0x708) as *mut u16) = 0;
609
- (*ddgame).secondary_gfxdir = gfxdir2;
610
  // param4=0 so the ESI-dependent block is skipped; layer_ctx doesn't matter
611
  wa_load_sprites(
612
  wrapper,
@@ -653,15 +652,10 @@ unsafe fn init_graphics_and_resources(
653
  let gfx_resource: *mut u8;
654
  {
655
  let gfx_dir = (*wrapper).primary_gfx_dir;
656
- // Create PaletteContext the same way the original DDGame constructor does:
657
- // word[0]=1, word[1]=0xFE, then PaletteContext__Init (0x5411A0)
658
- let palette_ctx = wa_malloc_zeroed(0x900);
659
- *(palette_ctx as *mut u16) = 1;
660
- *(palette_ctx.add(2) as *mut u16) = 0xFE;
661
- call_usercall_eax(
662
- palette_ctx as *mut DDGameWrapper,
663
- rb(va::PALETTE_CONTEXT_INIT),
664
- );
665
  gfx_resource = gfx_resource_create(
666
  gfx_dir,
667
  rb(va::STR_MASKS_IMG) as *const core::ffi::c_char,
@@ -821,7 +815,7 @@ unsafe fn init_graphics_and_resources(
821
  }
822
 
823
  // Arrow GfxDir (conditional on secondary gfxdir)
824
- if !(*ddgame).secondary_gfxdir.is_null() {
825
  let gfx_resource_create: unsafe extern "thiscall" fn(
826
  *mut GfxDir,
827
  *mut u8,
 
598
  );
599
  }
600
 
601
+ // ── Secondary PaletteContext (DDGame+0x2C, conditional on secondary GfxDir) ──
602
  if !(*wrapper).secondary_gfx_dir.is_null() {
603
+ let palette_ctx = wa_malloc_zeroed(0x70C) as *mut crate::render::palette::PaletteContext;
604
+ (*palette_ctx).dirty_range_min = 1;
605
+ (*palette_ctx).dirty_range_max = 0x5A;
606
+ crate::render::palette::palette_context_init(palette_ctx);
607
+ (*palette_ctx).dirty = 0;
608
+ (*ddgame).secondary_palette_ctx = palette_ctx;
 
609
  // param4=0 so the ESI-dependent block is skipped; layer_ctx doesn't matter
610
  wa_load_sprites(
611
  wrapper,
 
652
  let gfx_resource: *mut u8;
653
  {
654
  let gfx_dir = (*wrapper).primary_gfx_dir;
655
+ let palette_ctx = wa_malloc_zeroed(0x900) as *mut crate::render::palette::PaletteContext;
656
+ (*palette_ctx).dirty_range_min = 1;
657
+ (*palette_ctx).dirty_range_max = 0xFE;
658
+ crate::render::palette::palette_context_init(palette_ctx);
 
 
 
 
 
659
  gfx_resource = gfx_resource_create(
660
  gfx_dir,
661
  rb(va::STR_MASKS_IMG) as *const core::ffi::c_char,
 
815
  }
816
 
817
  // Arrow GfxDir (conditional on secondary gfxdir)
818
+ if !(*ddgame).secondary_palette_ctx.is_null() {
819
  let gfx_resource_create: unsafe extern "thiscall" fn(
820
  *mut GfxDir,
821
  *mut u8,
crates/openwa-core/src/engine/game_state_init.rs CHANGED
@@ -5,6 +5,7 @@
5
  //! InitGameState itself is Rust or the original WA code.
6
 
7
  use crate::audio::dssound::DSSound;
 
8
  use crate::engine::ddgame::DDGame;
9
  use crate::engine::ddgame_wrapper::DDGameWrapper;
10
  use crate::engine::game_info::GameInfo;
@@ -1028,23 +1029,64 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
1028
 
1029
  /// Allocate a BufferObject (0x48 bytes) with size computed from game setup.
1030
  /// Used for main_buffer and state_buffer.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1031
  unsafe fn allocate_buffer_object(ddgame: *mut DDGame, game_info: *const GameInfo) -> *mut u8 {
1032
- use crate::address::va;
1033
- use crate::rebase::rb;
1034
- use crate::wa_alloc::wa_malloc_zeroed;
1035
 
1036
  let mem = wa_malloc_zeroed(0x48) as *mut u32;
1037
  if mem.is_null() {
1038
  return core::ptr::null_mut();
1039
  }
1040
- // Compute size: num_teams * 0x450 + 0x4F178 + num_objects * 0x70
 
1041
  let num_teams = (*game_info).num_teams_alloc as u32;
1042
  let num_objects = (*game_info).object_slot_count;
1043
- let size = num_teams * 0x450 + 0x4F178 + num_objects * 0x70;
 
 
 
 
 
 
 
 
 
1044
 
1045
- let ctor: unsafe extern "stdcall" fn(*mut u32, u32, u32) -> *mut u8 =
1046
- core::mem::transmute(rb(va::BUFFER_OBJECT_CONSTRUCTOR) as usize);
1047
- ctor(mem, size, ddgame as u32)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1048
  }
1049
 
1050
  /// Allocate a raw ring-buffer-like object with manual field initialization.
@@ -1263,9 +1305,9 @@ unsafe fn init_game_state_display(
1263
  }
1264
 
1265
  // Dirty rect / clipping calls on layers A, B, C
1266
- call_display_gfx_dirty_rect((*wrapper).display_gfx_a, ddgame);
1267
- call_display_gfx_dirty_rect((*wrapper).display_gfx_b, ddgame);
1268
- call_display_gfx_dirty_rect((*wrapper).display_gfx_c, ddgame);
1269
 
1270
  // ===== Layer 4 (wrapper+0x2C): BitGrid(8, 0x100, 0x154) — constant =====
1271
  (*wrapper).display_gfx_d = create_display_gfx_layer_sized(0x100, 0x154);
@@ -1296,43 +1338,34 @@ unsafe fn create_display_gfx_layer_sized(height: u32, width: u32) -> *mut u8 {
1296
  mem as *mut u8
1297
  }
1298
 
1299
- /// Call the DisplayGfx dirty rect/clipping vtable dispatch (slot 0).
1300
- unsafe fn call_display_gfx_dirty_rect(gfx: *mut u8, ddgame: *mut DDGame) {
1301
  if gfx.is_null() {
1302
  return;
1303
  }
1304
- let gfx32 = gfx as *mut u32;
1305
- let height = *gfx32.add(6);
1306
- let width = *gfx32.add(5);
1307
  if width <= 0 || height <= 0 {
1308
  return;
1309
  }
1310
- if *(gfx32.add(9)) <= 0 || *(gfx32.add(10)) <= 0 {
 
 
1311
  return;
1312
  }
1313
- let clip_x1 = *gfx32.add(7) as i32;
1314
- let clip_y1 = *gfx32.add(8) as i32;
1315
- if clip_x1 >= width as i32 || clip_y1 >= height as i32 {
1316
  return;
1317
  }
1318
 
1319
- let x1 = if clip_x1 > 0 { clip_x1 } else { 0 };
1320
- let y1 = if clip_y1 > 0 { clip_y1 } else { 0 };
1321
- let x2 = if (*gfx32.add(9) as i32) < width as i32 {
1322
- *gfx32.add(9) as i32
1323
- } else {
1324
- width as i32
1325
- };
1326
- let y2 = if (*gfx32.add(10) as i32) < height as i32 {
1327
- *gfx32.add(10) as i32
1328
- } else {
1329
- height as i32
1330
- };
1331
 
1332
- let vtable = *(gfx as *const *const u32);
1333
- let vmethod: unsafe extern "thiscall" fn(*mut u8, i32, i32, i32, i32, u32) =
1334
- core::mem::transmute(*vtable);
1335
- vmethod(gfx, x1, y1, x2, y2, (*ddgame).gfx_color_table[7]);
1336
  }
1337
 
1338
  /// Create a camera/display object (0x3D4 bytes).
 
5
  //! InitGameState itself is Rust or the original WA code.
6
 
7
  use crate::audio::dssound::DSSound;
8
+ use crate::bitgrid::DisplayBitGrid;
9
  use crate::engine::ddgame::DDGame;
10
  use crate::engine::ddgame_wrapper::DDGameWrapper;
11
  use crate::engine::game_info::GameInfo;
 
1029
 
1030
  /// Allocate a BufferObject (0x48 bytes) with size computed from game setup.
1031
  /// Used for main_buffer and state_buffer.
1032
+ ///
1033
+ /// Pure Rust port of BufferObject__Constructor (0x545FD0).
1034
+ /// The original function is usercall: stdcall(this, size, ddgame) with an implicit
1035
+ /// EDI register carrying the second buffer's capacity.
1036
+ ///
1037
+ /// ## Dual-buffer layout (0x48 = 18 × u32):
1038
+ /// - [0]: buf1 data ptr [5]: buf2 data ptr
1039
+ /// - [1]: buf1 capacity [6]: buf2 capacity
1040
+ /// - [2]: 0 [7]: 0
1041
+ /// - [3]: 0 [8]: 0
1042
+ /// - [4]: ddgame ptr [9]: ddgame ptr
1043
+ ///
1044
+ /// ## Second buffer capacity (originally passed via EDI):
1045
+ /// Base = 0x2DC. If game_info[0xD9B4] != 0 AND (game_info[0xD9B1] as i8) is
1046
+ /// outside range [2, 0x22], add 0x190 (total 0x46C).
1047
  unsafe fn allocate_buffer_object(ddgame: *mut DDGame, game_info: *const GameInfo) -> *mut u8 {
1048
+ use crate::wa_alloc::{wa_malloc, wa_malloc_zeroed};
 
 
1049
 
1050
  let mem = wa_malloc_zeroed(0x48) as *mut u32;
1051
  if mem.is_null() {
1052
  return core::ptr::null_mut();
1053
  }
1054
+
1055
+ // Primary buffer: size from game setup
1056
  let num_teams = (*game_info).num_teams_alloc as u32;
1057
  let num_objects = (*game_info).object_slot_count;
1058
+ let buf1_capacity = num_teams * 0x450 + 0x4F178 + num_objects * 0x70;
1059
+
1060
+ let buf1 = wa_malloc((buf1_capacity + 3 & !3) + 0x20);
1061
+ core::ptr::write_bytes(buf1, 0, buf1_capacity as usize);
1062
+
1063
+ *mem.add(0) = buf1 as u32;
1064
+ *mem.add(1) = buf1_capacity;
1065
+ *mem.add(2) = 0;
1066
+ *mem.add(3) = 0;
1067
+ *mem.add(4) = ddgame as u32;
1068
 
1069
+ // Secondary buffer: capacity from game_info version fields (originally EDI)
1070
+ let gi_raw = game_info as *const u8;
1071
+ let field_d9b4 = *gi_raw.add(0xD9B4);
1072
+ let field_d9b1 = *gi_raw.add(0xD9B1) as i8;
1073
+ let extra = if field_d9b4 != 0 && ((field_d9b1 as i32 - 2) as u32) >= 0x21 {
1074
+ 0x190u32
1075
+ } else {
1076
+ 0
1077
+ };
1078
+ let buf2_capacity = extra + 0x2DC;
1079
+
1080
+ let buf2 = wa_malloc((buf2_capacity + 3 & !3) + 0x20);
1081
+ core::ptr::write_bytes(buf2, 0, buf2_capacity as usize);
1082
+
1083
+ *mem.add(5) = buf2 as u32;
1084
+ *mem.add(6) = buf2_capacity;
1085
+ *mem.add(7) = 0;
1086
+ *mem.add(8) = 0;
1087
+ *mem.add(9) = ddgame as u32;
1088
+
1089
+ mem as *mut u8
1090
  }
1091
 
1092
  /// Allocate a raw ring-buffer-like object with manual field initialization.
 
1305
  }
1306
 
1307
  // Dirty rect / clipping calls on layers A, B, C
1308
+ fill_display_layer((*wrapper).display_gfx_a as *mut DisplayBitGrid, ddgame);
1309
+ fill_display_layer((*wrapper).display_gfx_b as *mut DisplayBitGrid, ddgame);
1310
+ fill_display_layer((*wrapper).display_gfx_c as *mut DisplayBitGrid, ddgame);
1311
 
1312
  // ===== Layer 4 (wrapper+0x2C): BitGrid(8, 0x100, 0x154) — constant =====
1313
  (*wrapper).display_gfx_d = create_display_gfx_layer_sized(0x100, 0x154);
 
1338
  mem as *mut u8
1339
  }
1340
 
1341
+ /// Fill a DisplayBitGrid layer with its background color, respecting clip bounds.
1342
+ unsafe fn fill_display_layer(gfx: *mut DisplayBitGrid, ddgame: *mut DDGame) {
1343
  if gfx.is_null() {
1344
  return;
1345
  }
1346
+ let width = (*gfx).width as i32;
1347
+ let height = (*gfx).height as i32;
 
1348
  if width <= 0 || height <= 0 {
1349
  return;
1350
  }
1351
+ let clip_right = (*gfx).clip_right as i32;
1352
+ let clip_bottom = (*gfx).clip_bottom as i32;
1353
+ if clip_right <= 0 || clip_bottom <= 0 {
1354
  return;
1355
  }
1356
+ let clip_left = (*gfx).clip_left as i32;
1357
+ let clip_top = (*gfx).clip_top as i32;
1358
+ if clip_left >= width || clip_top >= height {
1359
  return;
1360
  }
1361
 
1362
+ let x1 = clip_left.max(0);
1363
+ let y1 = clip_top.max(0);
1364
+ let x2 = clip_right.min(width);
1365
+ let y2 = clip_bottom.min(height);
 
 
 
 
 
 
 
 
1366
 
1367
+ let color = (*ddgame).gfx_color_table[7] as u8;
1368
+ DisplayBitGrid::fill_rect_raw(gfx, x1, y1, x2, y2, color);
 
 
1369
  }
1370
 
1371
  /// Create a camera/display object (0x3D4 bytes).