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
|
| 63 |
-
pub
|
| 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
|
| 602 |
if !(*wrapper).secondary_gfx_dir.is_null() {
|
| 603 |
-
let
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
|
| 608 |
-
|
| 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 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
| 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).
|
| 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::
|
| 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 |
-
|
|
|
|
| 1041 |
let num_teams = (*game_info).num_teams_alloc as u32;
|
| 1042 |
let num_objects = (*game_info).object_slot_count;
|
| 1043 |
-
let
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1044 |
|
| 1045 |
-
|
| 1046 |
-
|
| 1047 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 1267 |
-
|
| 1268 |
-
|
| 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 |
-
///
|
| 1300 |
-
unsafe fn
|
| 1301 |
if gfx.is_null() {
|
| 1302 |
return;
|
| 1303 |
}
|
| 1304 |
-
let
|
| 1305 |
-
let height =
|
| 1306 |
-
let width = *gfx32.add(5);
|
| 1307 |
if width <= 0 || height <= 0 {
|
| 1308 |
return;
|
| 1309 |
}
|
| 1310 |
-
|
|
|
|
|
|
|
| 1311 |
return;
|
| 1312 |
}
|
| 1313 |
-
let
|
| 1314 |
-
let
|
| 1315 |
-
if
|
| 1316 |
return;
|
| 1317 |
}
|
| 1318 |
|
| 1319 |
-
let x1 =
|
| 1320 |
-
let y1 =
|
| 1321 |
-
let x2 =
|
| 1322 |
-
|
| 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
|
| 1333 |
-
|
| 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).
|