refactor: add TeamIndexMap, RenderEntry, NetBridge structs to DDGame
Browse filesAdd three new typed structs replacing raw pointer arithmetic:
- TeamIndexMap (0x64 bytes): entries[i16;16] + count + gap + terminator.
Three instances at DDGame+0x7650 replace 6 fragmented byte arrays.
- RenderEntry (0x14 bytes): active flag + unknown data.
14 entries at DDGame+0x73B0 replace opaque _unknown_73b0 blob.
- NetBridge (0x2C bytes): ddgame back-pointer + net_config bytes.
Created for online games only, replaces ddgame_secondary raw pointer.
ddgame_init_render_indices now takes *mut DDGame and uses pure field
access. DDGameWrapper.ddgame_secondary renamed to net_bridge with
typed *mut NetBridge pointer.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crates/openwa-core/src/engine/ddgame.rs
CHANGED
|
@@ -8,6 +8,7 @@ use crate::display::gradient::compute_complex_gradient;
|
|
| 8 |
use crate::display::palette::Palette;
|
| 9 |
use crate::engine::ddgame_wrapper::DDGameWrapper;
|
| 10 |
use crate::engine::game_info::GameInfo;
|
|
|
|
| 11 |
use crate::input::keyboard::DDKeyboard;
|
| 12 |
use crate::rebase::rb;
|
| 13 |
// Re-export public GfxHandler functions so existing `engine::ddgame::*` imports keep working.
|
|
@@ -222,8 +223,10 @@ pub struct DDGame {
|
|
| 222 |
pub viewport_width_2: i32,
|
| 223 |
/// 0x738C: Viewport height max/duplicate (Fixed-point).
|
| 224 |
pub viewport_height_2: i32,
|
| 225 |
-
/// 0x7390-
|
| 226 |
-
pub _unknown_7390: [u8;
|
|
|
|
|
|
|
| 227 |
|
| 228 |
/// 0x73A0: Camera X position (Fixed-point, e.g. 393.0).
|
| 229 |
pub camera_x: i32,
|
|
@@ -234,22 +237,16 @@ pub struct DDGame {
|
|
| 234 |
/// 0x73AC: Camera target Y (Fixed-point).
|
| 235 |
pub camera_target_y: i32,
|
| 236 |
|
| 237 |
-
/// 0x73B0
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
///
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
///
|
| 244 |
-
|
| 245 |
-
///
|
| 246 |
-
pub
|
| 247 |
-
/// 0x76F0-0x7717: Unknown
|
| 248 |
-
pub _unknown_76f0: [u8; 0x7718 - 0x76F0],
|
| 249 |
-
/// 0x7718-0x7757: Team index mapping table 3 (similar pattern, slightly different end).
|
| 250 |
-
pub team_index_map_3: [u8; 0x7758 - 0x7718],
|
| 251 |
-
/// 0x7758-0x777B: Unknown
|
| 252 |
-
pub _unknown_7758: [u8; 0x777C - 0x7758],
|
| 253 |
/// 0x777C: Level width output (written by PCLandscape constructor param 10).
|
| 254 |
pub level_width_raw: u32,
|
| 255 |
/// 0x7780: Level height output (written by PCLandscape constructor param 11).
|
|
@@ -402,8 +399,8 @@ pub unsafe fn ddgame_init_fields(ddgame: *mut DDGame) {
|
|
| 402 |
(*ddgame).init_field_64d8 = 0;
|
| 403 |
(*ddgame).init_field_72a4 = 0;
|
| 404 |
|
| 405 |
-
// InitRenderIndices — original sets ESI = ddgame + 0x72D8
|
| 406 |
-
ddgame_init_render_indices(
|
| 407 |
|
| 408 |
// Zero x and y of each screen coordinate entry (4 entries each)
|
| 409 |
for entry in &mut (*ddgame).screen_coords {
|
|
@@ -419,50 +416,28 @@ pub unsafe fn ddgame_init_fields(ddgame: *mut DDGame) {
|
|
| 419 |
/// Pure Rust implementation of DDGame__InitRenderIndices (0x526080).
|
| 420 |
///
|
| 421 |
/// Convention: usercall(ESI=base_ptr), plain RET.
|
|
|
|
| 422 |
///
|
| 423 |
-
///
|
| 424 |
-
/// InitFields calls this with ESI = ddgame + 0x72D8.
|
| 425 |
-
/// All offsets are relative to whatever ESI points to.
|
| 426 |
-
///
|
| 427 |
-
/// Absolute DDGame offsets (base = ddgame+0x72D8):
|
| 428 |
-
/// - base+0xC4 = ddgame+0x739C
|
| 429 |
-
/// - base+0xD8 = ddgame+0x73B0 (eh_vector_constructor_iterator region)
|
| 430 |
-
/// - base+0x378 = ddgame+0x7650 (team_index_map_1)
|
| 431 |
-
/// - base+0x3DC = ddgame+0x76B4 (team_index_map_2)
|
| 432 |
-
/// - base+0x440 = ddgame+0x7718 (team_index_map_3)
|
| 433 |
///
|
| 434 |
/// # Safety
|
| 435 |
-
/// `
|
| 436 |
-
pub unsafe fn ddgame_init_render_indices(
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
// eh_vector_constructor_iterator equivalent:
|
| 440 |
-
// FUN_525F40 is fastcall { *param_1 = 0; }
|
| 441 |
-
// 14 entries at stride 0x14 starting from +0xD8
|
| 442 |
-
for i in 0..14usize {
|
| 443 |
-
*(base.add(0xD8 + i * 0x14) as *mut u32) = 0;
|
| 444 |
-
}
|
| 445 |
-
|
| 446 |
-
// Identity permutation 1: base+0x378 (= ddgame+0x7650), 16 entries (i16)
|
| 447 |
-
for i in 0..16i16 {
|
| 448 |
-
*(base.add(0x378 + i as usize * 2) as *mut i16) = i;
|
| 449 |
-
}
|
| 450 |
-
*(base.add(0x398) as *mut u16) = 0x10;
|
| 451 |
-
*(base.add(0x3DA) as *mut u16) = 0;
|
| 452 |
|
| 453 |
-
//
|
| 454 |
-
for
|
| 455 |
-
|
| 456 |
}
|
| 457 |
-
*(base.add(0x3FC) as *mut u16) = 0x10;
|
| 458 |
-
*(base.add(0x43E) as *mut u16) = 0;
|
| 459 |
|
| 460 |
-
//
|
| 461 |
-
for
|
| 462 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 463 |
}
|
| 464 |
-
*(base.add(0x4A2) as *mut u16) = 0;
|
| 465 |
-
*(base.add(0x460) as *mut u16) = 0x10;
|
| 466 |
}
|
| 467 |
|
| 468 |
// BitGrid__Init moved to crate::task::bit_grid
|
|
@@ -692,18 +667,17 @@ pub unsafe fn create_ddgame(
|
|
| 692 |
(*ddgame).sound_available = if is_headless { 0 } else { 1 };
|
| 693 |
(*ddgame).field_7efc = 1;
|
| 694 |
|
| 695 |
-
// ── 7.
|
| 696 |
-
(*wrapper).
|
| 697 |
|
| 698 |
-
// ── 8. Conditional network object (game_version == -2) ──
|
| 699 |
if (*game_info).game_version == -2 {
|
| 700 |
-
let
|
| 701 |
-
|
| 702 |
-
|
| 703 |
-
|
| 704 |
-
(*wrapper).
|
| 705 |
if network_ecx != 0 {
|
| 706 |
-
*((network_ecx as *mut u8).add(0x18) as *mut *mut
|
| 707 |
}
|
| 708 |
}
|
| 709 |
|
|
@@ -1535,6 +1509,39 @@ pub struct CoordEntry {
|
|
| 1535 |
|
| 1536 |
const _: () = assert!(core::mem::size_of::<CoordEntry>() == 0x10);
|
| 1537 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1538 |
// ============================================================
|
| 1539 |
// Sound queue entry — 16 entries at DDGame + 0x7F00
|
| 1540 |
// ============================================================
|
|
|
|
| 8 |
use crate::display::palette::Palette;
|
| 9 |
use crate::engine::ddgame_wrapper::DDGameWrapper;
|
| 10 |
use crate::engine::game_info::GameInfo;
|
| 11 |
+
use crate::engine::net_bridge::NetBridge;
|
| 12 |
use crate::input::keyboard::DDKeyboard;
|
| 13 |
use crate::rebase::rb;
|
| 14 |
// Re-export public GfxHandler functions so existing `engine::ddgame::*` imports keep working.
|
|
|
|
| 223 |
pub viewport_width_2: i32,
|
| 224 |
/// 0x738C: Viewport height max/duplicate (Fixed-point).
|
| 225 |
pub viewport_height_2: i32,
|
| 226 |
+
/// 0x7390-0x739B: Unknown
|
| 227 |
+
pub _unknown_7390: [u8; 0x739C - 0x7390],
|
| 228 |
+
/// 0x739C: Render state flag (zeroed by InitRenderIndices).
|
| 229 |
+
pub render_state_flag: u32,
|
| 230 |
|
| 231 |
/// 0x73A0: Camera X position (Fixed-point, e.g. 393.0).
|
| 232 |
pub camera_x: i32,
|
|
|
|
| 237 |
/// 0x73AC: Camera target Y (Fixed-point).
|
| 238 |
pub camera_target_y: i32,
|
| 239 |
|
| 240 |
+
/// 0x73B0: Render entry table (14 entries × 0x14 bytes).
|
| 241 |
+
/// First u32 of each entry zeroed by InitRenderIndices.
|
| 242 |
+
pub render_entries: [RenderEntry; 14],
|
| 243 |
+
/// 0x74C8-0x764F: Unknown
|
| 244 |
+
pub _unknown_74c8: [u8; 0x7650 - 0x74C8],
|
| 245 |
+
|
| 246 |
+
/// 0x7650: Team index permutation maps (3 × 0x64 bytes).
|
| 247 |
+
/// Used for team-to-slot mapping (render order, turn order, display order).
|
| 248 |
+
/// Initialized as identity permutations [0..15] with count=16.
|
| 249 |
+
pub team_index_maps: [TeamIndexMap; 3],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 250 |
/// 0x777C: Level width output (written by PCLandscape constructor param 10).
|
| 251 |
pub level_width_raw: u32,
|
| 252 |
/// 0x7780: Level height output (written by PCLandscape constructor param 11).
|
|
|
|
| 399 |
(*ddgame).init_field_64d8 = 0;
|
| 400 |
(*ddgame).init_field_72a4 = 0;
|
| 401 |
|
| 402 |
+
// InitRenderIndices — original sets ESI = ddgame + 0x72D8, now uses typed DDGame ptr
|
| 403 |
+
ddgame_init_render_indices(ddgame);
|
| 404 |
|
| 405 |
// Zero x and y of each screen coordinate entry (4 entries each)
|
| 406 |
for entry in &mut (*ddgame).screen_coords {
|
|
|
|
| 416 |
/// Pure Rust implementation of DDGame__InitRenderIndices (0x526080).
|
| 417 |
///
|
| 418 |
/// Convention: usercall(ESI=base_ptr), plain RET.
|
| 419 |
+
/// Initialize render state flag, render entry table, and team index maps.
|
| 420 |
///
|
| 421 |
+
/// Original: called with ESI = ddgame + 0x72D8, but now uses typed DDGame fields.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 422 |
///
|
| 423 |
/// # Safety
|
| 424 |
+
/// `ddgame` must point to a valid DDGame allocation.
|
| 425 |
+
pub unsafe fn ddgame_init_render_indices(ddgame: *mut DDGame) {
|
| 426 |
+
(*ddgame).render_state_flag = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 427 |
|
| 428 |
+
// Zero the active flag of each render entry (eh_vector_constructor_iterator).
|
| 429 |
+
for entry in &mut (*ddgame).render_entries {
|
| 430 |
+
entry.active = 0;
|
| 431 |
}
|
|
|
|
|
|
|
| 432 |
|
| 433 |
+
// Initialize all three team index maps as identity permutations.
|
| 434 |
+
for map in &mut (*ddgame).team_index_maps {
|
| 435 |
+
for i in 0..16i16 {
|
| 436 |
+
map.entries[i as usize] = i;
|
| 437 |
+
}
|
| 438 |
+
map.count = 0x10;
|
| 439 |
+
map.terminator = 0;
|
| 440 |
}
|
|
|
|
|
|
|
| 441 |
}
|
| 442 |
|
| 443 |
// BitGrid__Init moved to crate::task::bit_grid
|
|
|
|
| 667 |
(*ddgame).sound_available = if is_headless { 0 } else { 1 };
|
| 668 |
(*ddgame).field_7efc = 1;
|
| 669 |
|
| 670 |
+
// ── 7. Network bridge (online games only) ──
|
| 671 |
+
(*wrapper).net_bridge = core::ptr::null_mut();
|
| 672 |
|
|
|
|
| 673 |
if (*game_info).game_version == -2 {
|
| 674 |
+
let bridge = wa_malloc_zeroed(core::mem::size_of::<NetBridge>() as u32) as *mut NetBridge;
|
| 675 |
+
(*bridge).ddgame = ddgame;
|
| 676 |
+
(*bridge).net_config_1 = (*game_info).net_config_1;
|
| 677 |
+
(*bridge).net_config_2 = (*game_info).net_config_2;
|
| 678 |
+
(*wrapper).net_bridge = bridge;
|
| 679 |
if network_ecx != 0 {
|
| 680 |
+
*((network_ecx as *mut u8).add(0x18) as *mut *mut NetBridge) = bridge;
|
| 681 |
}
|
| 682 |
}
|
| 683 |
|
|
|
|
| 1509 |
|
| 1510 |
const _: () = assert!(core::mem::size_of::<CoordEntry>() == 0x10);
|
| 1511 |
|
| 1512 |
+
/// Team index permutation map (0x64 = 100 bytes).
|
| 1513 |
+
///
|
| 1514 |
+
/// Used for mapping team indices to rendering/turn slots. Three instances
|
| 1515 |
+
/// live in DDGame at offsets 0x7650, 0x76B4, 0x7718 (stride 0x64).
|
| 1516 |
+
/// Initialized as identity permutations [0,1,2,...,15] with count=16.
|
| 1517 |
+
#[repr(C)]
|
| 1518 |
+
#[derive(Clone, Copy)]
|
| 1519 |
+
pub struct TeamIndexMap {
|
| 1520 |
+
/// 16 team index entries (identity permutation on init).
|
| 1521 |
+
pub entries: [i16; 16],
|
| 1522 |
+
/// Number of active entries (initialized to 16).
|
| 1523 |
+
pub count: u16,
|
| 1524 |
+
/// Gap — unknown purpose, not initialized by constructor.
|
| 1525 |
+
pub _gap: [u8; 64],
|
| 1526 |
+
/// Terminator (initialized to 0).
|
| 1527 |
+
pub terminator: u16,
|
| 1528 |
+
}
|
| 1529 |
+
const _: () = assert!(core::mem::size_of::<TeamIndexMap>() == 0x64);
|
| 1530 |
+
|
| 1531 |
+
/// Render table entry (0x14 = 20 bytes).
|
| 1532 |
+
///
|
| 1533 |
+
/// 14 entries live at DDGame+0x73B0 (stride 0x14). Only the first u32
|
| 1534 |
+
/// is zeroed during construction; the rest is uninitialized/unknown.
|
| 1535 |
+
#[repr(C)]
|
| 1536 |
+
#[derive(Clone, Copy)]
|
| 1537 |
+
pub struct RenderEntry {
|
| 1538 |
+
/// Active/state flag (zeroed on init).
|
| 1539 |
+
pub active: u32,
|
| 1540 |
+
/// Unknown data.
|
| 1541 |
+
pub _unknown: [u8; 16],
|
| 1542 |
+
}
|
| 1543 |
+
const _: () = assert!(core::mem::size_of::<RenderEntry>() == 0x14);
|
| 1544 |
+
|
| 1545 |
// ============================================================
|
| 1546 |
// Sound queue entry — 16 entries at DDGame + 0x7F00
|
| 1547 |
// ============================================================
|
crates/openwa-core/src/engine/ddgame_wrapper.rs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
use crate::audio::dssound::DSSound;
|
| 2 |
use crate::display::dd_display::DDDisplay;
|
| 3 |
use crate::engine::ddgame::DDGame;
|
|
|
|
| 4 |
use crate::render::landscape::PCLandscape;
|
| 5 |
|
| 6 |
/// Speech name table entry size (0x40 = 64 bytes, null-terminated C string).
|
|
@@ -27,8 +28,8 @@ pub struct DDGameWrapper {
|
|
| 27 |
pub _unknown_004: [u8; 0x484],
|
| 28 |
/// 0x488: Pointer to DDGame allocation (DWORD index 0x122)
|
| 29 |
pub ddgame: *mut DDGame,
|
| 30 |
-
/// 0x48C:
|
| 31 |
-
pub
|
| 32 |
/// 0x490-0x4BF: Unknown
|
| 33 |
pub _unknown_490: [u8; 0x30],
|
| 34 |
/// 0x4C0: Primary GfxDir — main sprite archive (Gfx.dir / Gfx0.dir / Gfx1.dir).
|
|
|
|
| 1 |
use crate::audio::dssound::DSSound;
|
| 2 |
use crate::display::dd_display::DDDisplay;
|
| 3 |
use crate::engine::ddgame::DDGame;
|
| 4 |
+
use crate::engine::net_bridge::NetBridge;
|
| 5 |
use crate::render::landscape::PCLandscape;
|
| 6 |
|
| 7 |
/// Speech name table entry size (0x40 = 64 bytes, null-terminated C string).
|
|
|
|
| 28 |
pub _unknown_004: [u8; 0x484],
|
| 29 |
/// 0x488: Pointer to DDGame allocation (DWORD index 0x122)
|
| 30 |
pub ddgame: *mut DDGame,
|
| 31 |
+
/// 0x48C: Network bridge object (0x2C bytes). Only set for online games (game_version == -2).
|
| 32 |
+
pub net_bridge: *mut NetBridge,
|
| 33 |
/// 0x490-0x4BF: Unknown
|
| 34 |
pub _unknown_490: [u8; 0x30],
|
| 35 |
/// 0x4C0: Primary GfxDir — main sprite archive (Gfx.dir / Gfx0.dir / Gfx1.dir).
|
crates/openwa-core/src/engine/mod.rs
CHANGED
|
@@ -5,11 +5,13 @@ pub mod game_info;
|
|
| 5 |
pub mod game_session;
|
| 6 |
pub mod game_state_init;
|
| 7 |
pub mod game_timer;
|
|
|
|
| 8 |
pub mod net_wrapper;
|
| 9 |
|
| 10 |
pub use ddgame::{
|
| 11 |
-
DDGame, FullTeamBlock, SoundQueueEntry, TeamArenaRef, TeamArenaState,
|
| 12 |
-
TeamBlockSlot0, WormEntry, GAME_PHASE_NORMAL_MIN,
|
|
|
|
| 13 |
};
|
| 14 |
pub use ddgame_wrapper::DDGameWrapper;
|
| 15 |
pub use game_info::GameInfo;
|
|
|
|
| 5 |
pub mod game_session;
|
| 6 |
pub mod game_state_init;
|
| 7 |
pub mod game_timer;
|
| 8 |
+
pub mod net_bridge;
|
| 9 |
pub mod net_wrapper;
|
| 10 |
|
| 11 |
pub use ddgame::{
|
| 12 |
+
DDGame, FullTeamBlock, RenderEntry, SoundQueueEntry, TeamArenaRef, TeamArenaState,
|
| 13 |
+
TeamBlockHeader, TeamBlockSlot0, TeamIndexMap, WormEntry, GAME_PHASE_NORMAL_MIN,
|
| 14 |
+
GAME_PHASE_SUDDEN_DEATH,
|
| 15 |
};
|
| 16 |
pub use ddgame_wrapper::DDGameWrapper;
|
| 17 |
pub use game_info::GameInfo;
|
crates/openwa-core/src/engine/net_bridge.rs
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use crate::engine::ddgame::DDGame;
|
| 2 |
+
|
| 3 |
+
/// NetBridge — lightweight adapter between DDGame and the network layer (0x2C bytes).
|
| 4 |
+
///
|
| 5 |
+
/// Only created for online games (`game_version == -2`). Allocated during
|
| 6 |
+
/// `DDGame__Constructor` and stored at `DDGameWrapper+0x48C`.
|
| 7 |
+
/// Also linked into the network context object (constructor param 7, via ECX)
|
| 8 |
+
/// at offset +0x18.
|
| 9 |
+
///
|
| 10 |
+
/// Not to be confused with `DDNetGameWrapper` (same size, different purpose) —
|
| 11 |
+
/// that one is constructed by `GameEngine__InitHardware` and stored at
|
| 12 |
+
/// `GameSession+0xC0`.
|
| 13 |
+
#[repr(C)]
|
| 14 |
+
pub struct NetBridge {
|
| 15 |
+
/// 0x00: Back-pointer to the owning DDGame instance.
|
| 16 |
+
pub ddgame: *mut DDGame,
|
| 17 |
+
/// 0x04-0x27: Unknown (zero-filled on construction).
|
| 18 |
+
pub _unknown_04: [u8; 0x24],
|
| 19 |
+
/// 0x28: Network config byte 1 (from GameInfo+0xD944).
|
| 20 |
+
pub net_config_1: u8,
|
| 21 |
+
/// 0x29: Network config byte 2 (from GameInfo+0xD946).
|
| 22 |
+
pub net_config_2: u8,
|
| 23 |
+
/// 0x2A-0x2B: Padding (zero-filled).
|
| 24 |
+
pub _pad_2a: [u8; 2],
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
const _: () = assert!(core::mem::size_of::<NetBridge>() == 0x2C);
|
crates/openwa-wormkit/src/replacements/ddgame_init.rs
CHANGED
|
@@ -46,7 +46,9 @@ hook::usercall_trampoline!(
|
|
| 46 |
);
|
| 47 |
|
| 48 |
extern "cdecl" fn impl_init_render_indices(base: u32) -> u32 {
|
| 49 |
-
|
|
|
|
|
|
|
| 50 |
base // Original: MOV EAX, ESI; RET
|
| 51 |
}
|
| 52 |
|
|
|
|
| 46 |
);
|
| 47 |
|
| 48 |
extern "cdecl" fn impl_init_render_indices(base: u32) -> u32 {
|
| 49 |
+
// ESI = ddgame + 0x72D8; recover the DDGame pointer
|
| 50 |
+
let ddgame = (base - 0x72D8) as *mut DDGame;
|
| 51 |
+
unsafe { ddgame_init_render_indices(ddgame) }
|
| 52 |
base // Original: MOV EAX, ESI; RET
|
| 53 |
}
|
| 54 |
|