refactor: unify BitGrid to display::bitgrid, clean up naming
Browse filesMove BitGrid from task::bit_grid to display::bitgrid as its canonical
home. Delete the old module. Rename fields to match known semantics:
_unused_04 β external_buffer, _unused_1c/20 β clip_left/clip_top,
width_dup/height_dup β clip_right/clip_bottom.
Convert bit_grid_init to BitGrid::init with *mut BitGrid parameter.
Replace magic vtable address with va::BIT_GRID_VTABLE constant.
Remove redundant double memset. Remove dead TaskStateMachine class.
Rename DDGame::display_gfx β display_bitgrid (it's a BitGrid, not
DisplayGfx). Rename Sprite::display_gfx β bitgrid_vtable. Clean up
tsm variable names and stale re-exports throughout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- crates/openwa-core/src/address.rs +3 -5
- crates/openwa-core/src/display/bitgrid.rs +93 -16
- crates/openwa-core/src/display/gfx.rs +1 -1
- crates/openwa-core/src/display/gradient.rs +1 -1
- crates/openwa-core/src/engine/ddgame.rs +11 -22
- crates/openwa-core/src/engine/ddgame_constructor.rs +18 -20
- crates/openwa-core/src/engine/team_arena.rs +3 -0
- crates/openwa-core/src/engine/team_ops.rs +4 -2
- crates/openwa-core/src/game/weapon_fire.rs +4 -10
- crates/openwa-core/src/render/sprite.rs +3 -2
- crates/openwa-core/src/task/bit_grid.rs +0 -67
- crates/openwa-core/src/task/mod.rs +0 -1
- crates/openwa-core/src/wa_alloc.rs +19 -0
- crates/openwa-dll/src/replacements/ddgame_init.rs +16 -11
- crates/openwa-dll/src/replacements/game_session.rs +2 -1
- crates/openwa-dll/src/replacements/replay_test.rs +2 -1
- crates/openwa-dll/src/replacements/sprite.rs +2 -2
- crates/openwa-dll/src/replacements/team.rs +1 -2
- crates/openwa-dll/src/replacements/weapon.rs +1 -2
- crates/openwa-dll/src/snapshot.rs +3 -1
crates/openwa-core/src/address.rs
CHANGED
|
@@ -182,10 +182,8 @@ pub mod va {
|
|
| 182 |
fn/Usercall INPUT_CTRL_INIT = 0x0058_C0D0;
|
| 183 |
}
|
| 184 |
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
vtable TASK_STATE_MACHINE_VTABLE = 0x0066_4118;
|
| 188 |
-
}
|
| 189 |
|
| 190 |
class "OpenGLCPU" {
|
| 191 |
/// OpenGLCPU vtable (0x48-byte object)
|
|
@@ -230,7 +228,7 @@ pub mod va {
|
|
| 230 |
class "BitGrid" {
|
| 231 |
/// BitGrid base vtable
|
| 232 |
vtable BIT_GRID_VTABLE = 0x0066_40EC;
|
| 233 |
-
/// BitGrid variant vtable (
|
| 234 |
vtable BIT_GRID_VARIANT_VTABLE = 0x0066_4118;
|
| 235 |
/// BitGrid init
|
| 236 |
fn BIT_GRID_INIT = 0x004F_6370;
|
|
|
|
| 182 |
fn/Usercall INPUT_CTRL_INIT = 0x0058_C0D0;
|
| 183 |
}
|
| 184 |
|
| 185 |
+
// TaskStateMachine was our old name for BitGrid before we understood it.
|
| 186 |
+
// Vtable 0x664118 is now BIT_GRID_VARIANT_VTABLE in the BitGrid class block.
|
|
|
|
|
|
|
| 187 |
|
| 188 |
class "OpenGLCPU" {
|
| 189 |
/// OpenGLCPU vtable (0x48-byte object)
|
|
|
|
| 228 |
class "BitGrid" {
|
| 229 |
/// BitGrid base vtable
|
| 230 |
vtable BIT_GRID_VTABLE = 0x0066_40EC;
|
| 231 |
+
/// BitGrid variant vtable (used for DDGame+0x380 collision grid)
|
| 232 |
vtable BIT_GRID_VARIANT_VTABLE = 0x0066_4118;
|
| 233 |
/// BitGrid init
|
| 234 |
fn BIT_GRID_INIT = 0x004F_6370;
|
crates/openwa-core/src/display/bitgrid.rs
CHANGED
|
@@ -1,29 +1,108 @@
|
|
| 1 |
-
//! BitGrid
|
| 2 |
//!
|
| 3 |
-
//!
|
| 4 |
-
//!
|
| 5 |
-
//!
|
|
|
|
|
|
|
|
|
|
| 6 |
//!
|
| 7 |
-
//!
|
| 8 |
-
//! base 0x2C). The first 0x2C bytes share the same layout; the extra 0x20 bytes
|
| 9 |
-
//! hold display-layer-specific state.
|
| 10 |
//!
|
| 11 |
-
//!
|
| 12 |
-
//!
|
| 13 |
-
//! Some fields have different semantics in display-layer context:
|
| 14 |
//! - `cells_per_unit` (+0x0C) β bit depth (8 for 8bpp)
|
| 15 |
-
//! - `width/height` (+0x14/+0x18) β unused
|
| 16 |
-
//! - `_unused_1c/
|
| 17 |
//! - `width_dup/height_dup` (+0x24/+0x28) β clip_right / clip_bottom
|
| 18 |
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
/// BitGrid display-layer vtable (0x664144, 8 slots).
|
| 22 |
///
|
| 23 |
/// Pixel buffer operations for 8-bit paletted rendering layers.
|
| 24 |
/// All pixel addressing uses: `data[y * row_stride + x]`
|
| 25 |
///
|
| 26 |
-
///
|
| 27 |
/// - Layer 0 at DisplayGfx+0x3D9C
|
| 28 |
/// - Layer 1 at DisplayGfx+0x3DA0
|
| 29 |
/// - Layer 2 at DisplayGfx+0x3DA4 (uses BitGrid__Init for extended setup)
|
|
@@ -42,8 +121,6 @@ pub struct BitGridDisplayVtable {
|
|
| 42 |
#[slot(3)]
|
| 43 |
pub destructor: fn(this: *mut BitGrid, flags: u8) -> *mut BitGrid,
|
| 44 |
/// get pixel (clipped) β returns 0 if (x, y) is outside clip rect (0x4F9140, RET 0x8)
|
| 45 |
-
///
|
| 46 |
-
/// Clip rect: `_unused_1c`..`width_dup` (x), `_unused_20`..`height_dup` (y)
|
| 47 |
#[slot(4)]
|
| 48 |
pub get_pixel_clipped: fn(this: *mut BitGrid, x: i32, y: i32) -> u8,
|
| 49 |
/// put pixel (clipped) β no-op if (x, y) is outside clip rect (0x4F9180, RET 0xC)
|
|
|
|
| 1 |
+
//! BitGrid β 2D pixel/bit buffer used for rendering layers and spatial queries.
|
| 2 |
//!
|
| 3 |
+
//! Two vtables:
|
| 4 |
+
//! - **Base** (0x6640EC): Bitfield grid for spatial queries (collision, visibility).
|
| 5 |
+
//! Initialized by `BitGrid::init` (0x4F6370). Size: 0x2C bytes.
|
| 6 |
+
//! - **Display layer** (0x664144, 8 slots): Pixel buffer with drawing primitives.
|
| 7 |
+
//! Used as rendering layers in DisplayGfx (+0x3D9C/+0x3DA0/+0x3DA4).
|
| 8 |
+
//! Allocated at 0x4C bytes; first 0x2C bytes share the base layout.
|
| 9 |
//!
|
| 10 |
+
//! ## Field reinterpretation (display-layer context)
|
|
|
|
|
|
|
| 11 |
//!
|
| 12 |
+
//! Some fields have different semantics when used as a display layer:
|
|
|
|
|
|
|
| 13 |
//! - `cells_per_unit` (+0x0C) β bit depth (8 for 8bpp)
|
| 14 |
+
//! - `width/height` (+0x14/+0x18) β unused
|
| 15 |
+
//! - `_unused_1c/_unused_20` (+0x1C/+0x20) β clip_left / clip_top
|
| 16 |
//! - `width_dup/height_dup` (+0x24/+0x28) β clip_right / clip_bottom
|
| 17 |
|
| 18 |
+
use crate::address::va;
|
| 19 |
+
use crate::rebase::rb;
|
| 20 |
+
use crate::wa_alloc::wa_malloc;
|
| 21 |
+
use crate::FieldRegistry;
|
| 22 |
+
|
| 23 |
+
/// BitGrid β 2D pixel/bit buffer.
|
| 24 |
+
///
|
| 25 |
+
/// Base vtable at 0x6640EC (bitfield spatial grid).
|
| 26 |
+
/// Display-layer vtable at 0x664144 (pixel drawing).
|
| 27 |
+
/// Size: 0x2C bytes (base), 0x4C bytes (display layer).
|
| 28 |
+
#[derive(FieldRegistry)]
|
| 29 |
+
#[repr(C)]
|
| 30 |
+
pub struct BitGrid {
|
| 31 |
+
pub vtable: u32,
|
| 32 |
+
/// 0x04: External buffer flag. 0 = BitGrid owns data (frees on destroy),
|
| 33 |
+
/// nonzero = external ownership.
|
| 34 |
+
pub external_buffer: u32,
|
| 35 |
+
/// 0x08: Pixel/bit data pointer
|
| 36 |
+
pub data: *mut u8,
|
| 37 |
+
/// 0x0C: Cells per unit (spatial) / bit depth (display, typically 8)
|
| 38 |
+
pub cells_per_unit: u32,
|
| 39 |
+
/// 0x10: Row stride in bytes
|
| 40 |
+
pub row_stride: u32,
|
| 41 |
+
/// 0x14: Width in pixels/cells
|
| 42 |
+
pub width: u32,
|
| 43 |
+
/// 0x18: Height in pixels/cells
|
| 44 |
+
pub height: u32,
|
| 45 |
+
/// 0x1C: Clip left / x minimum (display), init 0 (spatial)
|
| 46 |
+
pub clip_left: u32,
|
| 47 |
+
/// 0x20: Clip top / y minimum (display), init 0 (spatial)
|
| 48 |
+
pub clip_top: u32,
|
| 49 |
+
/// 0x24: Clip right / x maximum (display), init = width (spatial)
|
| 50 |
+
pub clip_right: u32,
|
| 51 |
+
/// 0x28: Clip bottom / y maximum (display), init = height (spatial)
|
| 52 |
+
pub clip_bottom: u32,
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
const _: () = assert!(core::mem::size_of::<BitGrid>() == 0x2C);
|
| 56 |
+
|
| 57 |
+
impl BitGrid {
|
| 58 |
+
/// Pure Rust implementation of BitGrid__Init (0x4F6370).
|
| 59 |
+
///
|
| 60 |
+
/// Allocates a bit-per-cell grid buffer. `cells_per_unit` is typically 1.
|
| 61 |
+
/// `width` and `height` are pixel dimensions. The buffer is a row-major
|
| 62 |
+
/// bitfield with rows aligned to 4 bytes.
|
| 63 |
+
///
|
| 64 |
+
/// # Safety
|
| 65 |
+
/// `this` must point to a zero-filled allocation of at least 0x2C bytes.
|
| 66 |
+
pub unsafe fn init(this: *mut BitGrid, cells_per_unit: u32, width: u32, height: u32) {
|
| 67 |
+
let bits = cells_per_unit.wrapping_mul(width).wrapping_add(7) as i32;
|
| 68 |
+
let row_stride = ((bits >> 3) + 3) & !3;
|
| 69 |
+
let total_size = row_stride as u32 * height;
|
| 70 |
+
|
| 71 |
+
let alloc_size = ((total_size + 3) & !3) + 0x20;
|
| 72 |
+
let buffer = wa_malloc(alloc_size);
|
| 73 |
+
|
| 74 |
+
if buffer.is_null() {
|
| 75 |
+
return;
|
| 76 |
+
}
|
| 77 |
+
if total_size as usize > alloc_size as usize {
|
| 78 |
+
return;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
// Note: the original calls memset twice on the same buffer (possibly a
|
| 82 |
+
// debug/paranoia pattern from MSVC 2005). We only do it once since the
|
| 83 |
+
// second write is a no-op that modern compilers would eliminate anyway.
|
| 84 |
+
core::ptr::write_bytes(buffer, 0, total_size as usize);
|
| 85 |
+
|
| 86 |
+
(*this).vtable = rb(va::BIT_GRID_VTABLE);
|
| 87 |
+
(*this).external_buffer = 0;
|
| 88 |
+
(*this).data = buffer;
|
| 89 |
+
(*this).cells_per_unit = cells_per_unit;
|
| 90 |
+
(*this).row_stride = row_stride as u32;
|
| 91 |
+
(*this).width = width;
|
| 92 |
+
(*this).height = height;
|
| 93 |
+
(*this).clip_left = 0;
|
| 94 |
+
(*this).clip_top = 0;
|
| 95 |
+
(*this).clip_right = width;
|
| 96 |
+
(*this).clip_bottom = height;
|
| 97 |
+
}
|
| 98 |
+
}
|
| 99 |
|
| 100 |
/// BitGrid display-layer vtable (0x664144, 8 slots).
|
| 101 |
///
|
| 102 |
/// Pixel buffer operations for 8-bit paletted rendering layers.
|
| 103 |
/// All pixel addressing uses: `data[y * row_stride + x]`
|
| 104 |
///
|
| 105 |
+
/// DDDisplay creates 3 layer objects in DDDisplay__Init:
|
| 106 |
/// - Layer 0 at DisplayGfx+0x3D9C
|
| 107 |
/// - Layer 1 at DisplayGfx+0x3DA0
|
| 108 |
/// - Layer 2 at DisplayGfx+0x3DA4 (uses BitGrid__Init for extended setup)
|
|
|
|
| 121 |
#[slot(3)]
|
| 122 |
pub destructor: fn(this: *mut BitGrid, flags: u8) -> *mut BitGrid,
|
| 123 |
/// get pixel (clipped) β returns 0 if (x, y) is outside clip rect (0x4F9140, RET 0x8)
|
|
|
|
|
|
|
| 124 |
#[slot(4)]
|
| 125 |
pub get_pixel_clipped: fn(this: *mut BitGrid, x: i32, y: i32) -> u8,
|
| 126 |
/// put pixel (clipped) β no-op if (x, y) is outside clip rect (0x4F9180, RET 0xC)
|
crates/openwa-core/src/display/gfx.rs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
use super::base::DisplayBase;
|
| 2 |
-
use
|
| 3 |
use core::ffi::c_void;
|
| 4 |
|
| 5 |
/// DisplayGfx β full display/graphics subsystem (derived from DisplayBase).
|
|
|
|
| 1 |
use super::base::DisplayBase;
|
| 2 |
+
use super::bitgrid::BitGrid;
|
| 3 |
use core::ffi::c_void;
|
| 4 |
|
| 5 |
/// DisplayGfx β full display/graphics subsystem (derived from DisplayBase).
|
crates/openwa-core/src/display/gradient.rs
CHANGED
|
@@ -6,10 +6,10 @@
|
|
| 6 |
//! may have subtle color differences due to the tangled decompiler output.
|
| 7 |
|
| 8 |
use crate::address::va;
|
|
|
|
| 9 |
use crate::engine::ddgame::DDGame;
|
| 10 |
use crate::rebase::rb;
|
| 11 |
use crate::render::gfx_dir::call_gfx_find_and_load;
|
| 12 |
-
use crate::task::bit_grid::BitGrid;
|
| 13 |
use crate::wa_alloc::{wa_free, wa_malloc};
|
| 14 |
|
| 15 |
/// Palette context for gradient color mapping.
|
|
|
|
| 6 |
//! may have subtle color differences due to the tangled decompiler output.
|
| 7 |
|
| 8 |
use crate::address::va;
|
| 9 |
+
use crate::display::bitgrid::BitGrid;
|
| 10 |
use crate::engine::ddgame::DDGame;
|
| 11 |
use crate::rebase::rb;
|
| 12 |
use crate::render::gfx_dir::call_gfx_find_and_load;
|
|
|
|
| 13 |
use crate::wa_alloc::{wa_free, wa_malloc};
|
| 14 |
|
| 15 |
/// Palette context for gradient color mapping.
|
crates/openwa-core/src/engine/ddgame.rs
CHANGED
|
@@ -4,7 +4,11 @@ use crate::audio::music::Music;
|
|
| 4 |
use crate::audio::speech::SpeechSlotTable;
|
| 5 |
use crate::display::dd_display::DDDisplay;
|
| 6 |
use crate::display::palette::Palette;
|
|
|
|
| 7 |
use crate::engine::game_info::GameInfo;
|
|
|
|
|
|
|
|
|
|
| 8 |
use crate::fixed::Fixed;
|
| 9 |
use crate::game::weapon::WeaponTable;
|
| 10 |
use crate::input::keyboard::DDKeyboard;
|
|
@@ -13,22 +17,6 @@ use crate::render::queue::RenderQueue;
|
|
| 13 |
use crate::render::turn_order::TurnOrderWidget;
|
| 14 |
use crate::FieldRegistry;
|
| 15 |
|
| 16 |
-
// Re-export constructor items so existing `engine::ddgame::*` imports keep working.
|
| 17 |
-
pub use super::ddgame_constructor::{
|
| 18 |
-
bit_grid_init, create_ddgame, ddgame_init_fields, ddgame_init_render_indices,
|
| 19 |
-
display_layer_color_init, gfx_dir_find_entry, gfx_resource_create, init_constructor_addrs,
|
| 20 |
-
ON_DDGAME_ALLOC,
|
| 21 |
-
};
|
| 22 |
-
// Re-export public GfxHandler functions so existing `engine::ddgame::*` imports keep working.
|
| 23 |
-
pub use crate::render::gfx_dir::gfx_dir_load_dir;
|
| 24 |
-
|
| 25 |
-
// Re-export all team_arena types so existing `engine::ddgame::*` imports keep working.
|
| 26 |
-
pub use crate::engine::team_arena::{
|
| 27 |
-
worm, CoordEntry, CoordList, CoordListEntry, RenderEntry, SoundQueueEntry, TeamArenaRef,
|
| 28 |
-
TeamArenaState, TeamBlock, TeamHeader, TeamIndexMap, TeamSlot0, TeamWeaponSlots, WeaponSlots,
|
| 29 |
-
WormEntry, GAME_PHASE_NORMAL_MIN, GAME_PHASE_SUDDEN_DEATH,
|
| 30 |
-
};
|
| 31 |
-
|
| 32 |
/// DDGame β the main game engine object.
|
| 33 |
///
|
| 34 |
/// This is a massive ~39KB struct (0x98D8 bytes) that owns all major subsystems:
|
|
@@ -81,15 +69,16 @@ pub struct DDGame {
|
|
| 81 |
pub arrow_sprites: [*mut u8; 32],
|
| 82 |
/// 0x0B8-0x134: Arrow GfxDir pointers (32 entries, conditional)
|
| 83 |
pub arrow_gfxdirs: [*mut u8; 32],
|
| 84 |
-
/// 0x138:
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
///
|
|
|
|
| 88 |
/// Not initialized in DDGame__Constructor β filled during gameplay with
|
| 89 |
/// weapon sprites, effect images, cursor graphics, etc.
|
| 90 |
pub sprite_cache: [*mut u8; 145],
|
| 91 |
/// 0x380: BitGrid pointer (vtable 0x664118, 0x2C bytes)
|
| 92 |
-
pub bit_grid: *mut
|
| 93 |
/// 0x384-0x467: Additional sprite/image object slots.
|
| 94 |
/// Same vtable 0x664144 as sprite_cache. ~20 entries populated at runtime.
|
| 95 |
pub sprite_cache_2: [*mut u8; 57],
|
|
@@ -590,7 +579,7 @@ impl DDGame {
|
|
| 590 |
// DDGame constructor code (create_ddgame, init helpers, usercall bridges)
|
| 591 |
// has been moved to ddgame_constructor.rs.
|
| 592 |
|
| 593 |
-
// BitGrid__Init
|
| 594 |
// Re-exported via ddgame_constructor.rs
|
| 595 |
/// Well-known byte offsets into DDGame, for use with raw pointer access.
|
| 596 |
///
|
|
|
|
| 4 |
use crate::audio::speech::SpeechSlotTable;
|
| 5 |
use crate::display::dd_display::DDDisplay;
|
| 6 |
use crate::display::palette::Palette;
|
| 7 |
+
use crate::display::BitGrid;
|
| 8 |
use crate::engine::game_info::GameInfo;
|
| 9 |
+
use crate::engine::{
|
| 10 |
+
CoordEntry, CoordList, RenderEntry, SoundQueueEntry, TeamArenaState, TeamIndexMap,
|
| 11 |
+
};
|
| 12 |
use crate::fixed::Fixed;
|
| 13 |
use crate::game::weapon::WeaponTable;
|
| 14 |
use crate::input::keyboard::DDKeyboard;
|
|
|
|
| 17 |
use crate::render::turn_order::TurnOrderWidget;
|
| 18 |
use crate::FieldRegistry;
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
/// DDGame β the main game engine object.
|
| 21 |
///
|
| 22 |
/// This is a massive ~39KB struct (0x98D8 bytes) that owns all major subsystems:
|
|
|
|
| 69 |
pub arrow_sprites: [*mut u8; 32],
|
| 70 |
/// 0x0B8-0x134: Arrow GfxDir pointers (32 entries, conditional)
|
| 71 |
pub arrow_gfxdirs: [*mut u8; 32],
|
| 72 |
+
/// 0x138: Primary display BitGrid (vtable 0x664144, 8bpp pixel buffer).
|
| 73 |
+
/// Allocated as 0x4C bytes, initialized with BitGrid::init(8, 0x100, 0x1E0).
|
| 74 |
+
pub display_bitgrid: *mut BitGrid,
|
| 75 |
+
/// 0x13C-0x37F: Sprite/image BitGrid cache (145 pointer slots).
|
| 76 |
+
/// All populated entries have vtable 0x664144 (same class as `display_bitgrid`).
|
| 77 |
/// Not initialized in DDGame__Constructor β filled during gameplay with
|
| 78 |
/// weapon sprites, effect images, cursor graphics, etc.
|
| 79 |
pub sprite_cache: [*mut u8; 145],
|
| 80 |
/// 0x380: BitGrid pointer (vtable 0x664118, 0x2C bytes)
|
| 81 |
+
pub bit_grid: *mut BitGrid,
|
| 82 |
/// 0x384-0x467: Additional sprite/image object slots.
|
| 83 |
/// Same vtable 0x664144 as sprite_cache. ~20 entries populated at runtime.
|
| 84 |
pub sprite_cache_2: [*mut u8; 57],
|
|
|
|
| 579 |
// DDGame constructor code (create_ddgame, init helpers, usercall bridges)
|
| 580 |
// has been moved to ddgame_constructor.rs.
|
| 581 |
|
| 582 |
+
// BitGrid__Init lives in crate::display::bitgrid
|
| 583 |
// Re-exported via ddgame_constructor.rs
|
| 584 |
/// Well-known byte offsets into DDGame, for use with raw pointer access.
|
| 585 |
///
|
crates/openwa-core/src/engine/ddgame_constructor.rs
CHANGED
|
@@ -12,6 +12,7 @@ use crate::address::va;
|
|
| 12 |
use crate::audio::active_sound::ActiveSoundTable;
|
| 13 |
use crate::audio::dssound::DSSound;
|
| 14 |
use crate::audio::music::Music;
|
|
|
|
| 15 |
use crate::display::dd_display::DDDisplay;
|
| 16 |
use crate::display::gradient::compute_complex_gradient;
|
| 17 |
use crate::display::palette::Palette;
|
|
@@ -26,12 +27,8 @@ use crate::render::gfx_dir::{
|
|
| 26 |
};
|
| 27 |
pub use crate::render::gfx_dir::{gfx_dir_find_entry, gfx_resource_create};
|
| 28 |
use crate::render::landscape::PCLandscape;
|
| 29 |
-
use crate::task::bit_grid::BitGrid;
|
| 30 |
use crate::wa_alloc::{wa_malloc, wa_malloc_zeroed};
|
| 31 |
|
| 32 |
-
// Re-export bit_grid_init so existing `engine::ddgame::bit_grid_init` paths keep working.
|
| 33 |
-
pub use crate::task::bit_grid::bit_grid_init;
|
| 34 |
-
|
| 35 |
// ============================================================
|
| 36 |
// Pure Rust implementations of DDGame sub-functions
|
| 37 |
// ============================================================
|
|
@@ -708,16 +705,16 @@ unsafe fn init_graphics_and_resources(
|
|
| 708 |
}
|
| 709 |
};
|
| 710 |
|
| 711 |
-
// ββ BitGrid at DDGame+0x380
|
| 712 |
-
// Pure Rust: allocate object, call bit_grid_init, override vtable.
|
| 713 |
{
|
| 714 |
-
|
| 715 |
-
|
|
|
|
| 716 |
if !bit_grid.is_null() {
|
| 717 |
let width = (*ddgame).level_width;
|
| 718 |
let height = (*ddgame).level_height;
|
| 719 |
-
|
| 720 |
-
|
| 721 |
}
|
| 722 |
(*ddgame).bit_grid = bit_grid;
|
| 723 |
}
|
|
@@ -803,9 +800,9 @@ unsafe fn init_graphics_and_resources(
|
|
| 803 |
// right/bottom = dim - margin
|
| 804 |
// Result: this[5]=this[6]=10 for sprites >20px (the 10px inset).
|
| 805 |
if !sprite.is_null() {
|
| 806 |
-
let
|
| 807 |
-
let sprite_w =
|
| 808 |
-
let sprite_h =
|
| 809 |
let margin_w = (sprite_w / 2 - 10).max(0);
|
| 810 |
let margin_h = (sprite_h / 2 - 10).max(0);
|
| 811 |
|
|
@@ -846,15 +843,16 @@ unsafe fn init_graphics_and_resources(
|
|
| 846 |
release(gfx_resource, 1);
|
| 847 |
}
|
| 848 |
|
| 849 |
-
// ββ
|
| 850 |
{
|
| 851 |
-
|
| 852 |
-
|
| 853 |
-
|
| 854 |
-
|
| 855 |
-
|
|
|
|
| 856 |
}
|
| 857 |
-
(*ddgame).
|
| 858 |
}
|
| 859 |
|
| 860 |
// ββ CoordList at DDGame+0x50C (capacity 600, 0x12C0 buffer) ββ
|
|
|
|
| 12 |
use crate::audio::active_sound::ActiveSoundTable;
|
| 13 |
use crate::audio::dssound::DSSound;
|
| 14 |
use crate::audio::music::Music;
|
| 15 |
+
use crate::display::bitgrid::BitGrid;
|
| 16 |
use crate::display::dd_display::DDDisplay;
|
| 17 |
use crate::display::gradient::compute_complex_gradient;
|
| 18 |
use crate::display::palette::Palette;
|
|
|
|
| 27 |
};
|
| 28 |
pub use crate::render::gfx_dir::{gfx_dir_find_entry, gfx_resource_create};
|
| 29 |
use crate::render::landscape::PCLandscape;
|
|
|
|
| 30 |
use crate::wa_alloc::{wa_malloc, wa_malloc_zeroed};
|
| 31 |
|
|
|
|
|
|
|
|
|
|
| 32 |
// ============================================================
|
| 33 |
// Pure Rust implementations of DDGame sub-functions
|
| 34 |
// ============================================================
|
|
|
|
| 705 |
}
|
| 706 |
};
|
| 707 |
|
| 708 |
+
// ββ BitGrid at DDGame+0x380 ββ
|
|
|
|
| 709 |
{
|
| 710 |
+
use crate::wa_alloc::wa_malloc_struct_zeroed;
|
| 711 |
+
|
| 712 |
+
let bit_grid = wa_malloc_struct_zeroed::<BitGrid>();
|
| 713 |
if !bit_grid.is_null() {
|
| 714 |
let width = (*ddgame).level_width;
|
| 715 |
let height = (*ddgame).level_height;
|
| 716 |
+
BitGrid::init(bit_grid as *mut BitGrid, 1, width, height);
|
| 717 |
+
(*bit_grid).vtable = rb(va::BIT_GRID_VARIANT_VTABLE);
|
| 718 |
}
|
| 719 |
(*ddgame).bit_grid = bit_grid;
|
| 720 |
}
|
|
|
|
| 800 |
// right/bottom = dim - margin
|
| 801 |
// Result: this[5]=this[6]=10 for sprites >20px (the 10px inset).
|
| 802 |
if !sprite.is_null() {
|
| 803 |
+
let grid = &*(sprite as *const BitGrid);
|
| 804 |
+
let sprite_w = grid.width as i32;
|
| 805 |
+
let sprite_h = grid.height as i32;
|
| 806 |
let margin_w = (sprite_w / 2 - 10).max(0);
|
| 807 |
let margin_h = (sprite_h / 2 - 10).max(0);
|
| 808 |
|
|
|
|
| 843 |
release(gfx_resource, 1);
|
| 844 |
}
|
| 845 |
|
| 846 |
+
// ββ Display BitGrid at DDGame+0x138 ββ
|
| 847 |
{
|
| 848 |
+
use crate::wa_alloc::wa_malloc_struct_zeroed;
|
| 849 |
+
|
| 850 |
+
let grid = wa_malloc_struct_zeroed::<BitGrid>();
|
| 851 |
+
if !grid.is_null() {
|
| 852 |
+
BitGrid::init(grid, 8, 0x100, 0x1E0);
|
| 853 |
+
*(grid as *mut u32) = rb(va::BIT_GRID_DISPLAY_VTABLE);
|
| 854 |
}
|
| 855 |
+
(*ddgame).display_bitgrid = grid as *mut BitGrid;
|
| 856 |
}
|
| 857 |
|
| 858 |
// ββ CoordList at DDGame+0x50C (capacity 600, 0x12C0 buffer) ββ
|
crates/openwa-core/src/engine/team_arena.rs
CHANGED
|
@@ -143,6 +143,7 @@ pub struct WormEntry {
|
|
| 143 |
/// 0x00: Worm state machine state.
|
| 144 |
/// Values: 0x65=idle, 0x67=active/selected, 0x68=active variant.
|
| 145 |
/// Special states {0x80..0x85, 0x89} = dying/drowning/special animation.
|
|
|
|
| 146 |
pub state: u32,
|
| 147 |
/// 0x04: Unknown counter. Incremented by Freeze (+10).
|
| 148 |
pub effect_counter_04_Maybe: i32,
|
|
@@ -310,6 +311,7 @@ pub struct TeamArenaState {
|
|
| 310 |
/// 0x2C10: Unknown padding
|
| 311 |
pub _pad_2c10: [u8; 0x18],
|
| 312 |
/// 0x2C28: Game phase counter (>=484 = sudden death, >=-2 = normal game)
|
|
|
|
| 313 |
pub game_phase: i32,
|
| 314 |
|
| 315 |
// === Alliance tracking (set by CountTeamsByAlliance + SetActiveWorm_Maybe) ===
|
|
@@ -332,6 +334,7 @@ pub struct TeamArenaState {
|
|
| 332 |
const _: () = assert!(core::mem::size_of::<TeamArenaState>() == 0x2C48);
|
| 333 |
|
| 334 |
/// Worm state constants and helpers.
|
|
|
|
| 335 |
pub mod worm {
|
| 336 |
// --- Known state values (runtime-validated via validator dumps) ---
|
| 337 |
|
|
|
|
| 143 |
/// 0x00: Worm state machine state.
|
| 144 |
/// Values: 0x65=idle, 0x67=active/selected, 0x68=active variant.
|
| 145 |
/// Special states {0x80..0x85, 0x89} = dying/drowning/special animation.
|
| 146 |
+
/// TODO: Update this to use WormState enum
|
| 147 |
pub state: u32,
|
| 148 |
/// 0x04: Unknown counter. Incremented by Freeze (+10).
|
| 149 |
pub effect_counter_04_Maybe: i32,
|
|
|
|
| 311 |
/// 0x2C10: Unknown padding
|
| 312 |
pub _pad_2c10: [u8; 0x18],
|
| 313 |
/// 0x2C28: Game phase counter (>=484 = sudden death, >=-2 = normal game)
|
| 314 |
+
/// TODO: Convert this to GamePhase enum
|
| 315 |
pub game_phase: i32,
|
| 316 |
|
| 317 |
// === Alliance tracking (set by CountTeamsByAlliance + SetActiveWorm_Maybe) ===
|
|
|
|
| 334 |
const _: () = assert!(core::mem::size_of::<TeamArenaState>() == 0x2C48);
|
| 335 |
|
| 336 |
/// Worm state constants and helpers.
|
| 337 |
+
/// TODO: Replace this module with the WormState enum
|
| 338 |
pub mod worm {
|
| 339 |
// --- Known state values (runtime-validated via validator dumps) ---
|
| 340 |
|
crates/openwa-core/src/engine/team_ops.rs
CHANGED
|
@@ -13,7 +13,9 @@
|
|
| 13 |
//! - CheckAnyWormState0x8b (0x522970)
|
| 14 |
//! - SetActiveWorm_Maybe (0x522500)
|
| 15 |
|
| 16 |
-
use
|
|
|
|
|
|
|
| 17 |
|
| 18 |
/// Count teams by alliance membership β port of 0x522030.
|
| 19 |
///
|
|
@@ -67,7 +69,7 @@ pub unsafe fn is_worm_in_special_state(
|
|
| 67 |
worm_index: u32,
|
| 68 |
arena: TeamArenaRef,
|
| 69 |
) -> u32 {
|
| 70 |
-
if
|
| 71 |
arena
|
| 72 |
.team_worm(team_index as usize, worm_index as usize)
|
| 73 |
.state,
|
|
|
|
| 13 |
//! - CheckAnyWormState0x8b (0x522970)
|
| 14 |
//! - SetActiveWorm_Maybe (0x522500)
|
| 15 |
|
| 16 |
+
use crate::engine::team_arena::worm;
|
| 17 |
+
|
| 18 |
+
use super::team_arena::TeamArenaRef;
|
| 19 |
|
| 20 |
/// Count teams by alliance membership β port of 0x522030.
|
| 21 |
///
|
|
|
|
| 69 |
worm_index: u32,
|
| 70 |
arena: TeamArenaRef,
|
| 71 |
) -> u32 {
|
| 72 |
+
if worm::is_special_state(
|
| 73 |
arena
|
| 74 |
.team_worm(team_index as usize, worm_index as usize)
|
| 75 |
.state,
|
crates/openwa-core/src/game/weapon_fire.rs
CHANGED
|
@@ -11,7 +11,7 @@
|
|
| 11 |
|
| 12 |
use crate::address::va;
|
| 13 |
use crate::audio::{KnownSoundId, SoundId};
|
| 14 |
-
use crate::engine::
|
| 15 |
use crate::fixed::Fixed;
|
| 16 |
use crate::game::weapon::{WeaponEntry, WeaponFireParams, WeaponSpawnData};
|
| 17 |
use crate::game::Weapon;
|
|
@@ -93,15 +93,13 @@ pub unsafe fn get_ammo(team_index: u32, arena: TeamArenaRef, weapon_id: u32) ->
|
|
| 93 |
}
|
| 94 |
// In sudden death (phase >= 484), delayed weapons return 0
|
| 95 |
// unless it's Teleport (weapon 0x28)
|
| 96 |
-
if state.game_phase >=
|
| 97 |
-
&& weapon_id != Weapon::Teleport as u32
|
| 98 |
-
{
|
| 99 |
return 0;
|
| 100 |
}
|
| 101 |
}
|
| 102 |
|
| 103 |
// SelectWorm (0x3B) requires >1 alive worm on the team
|
| 104 |
-
if state.game_phase >=
|
| 105 |
&& weapon_id == Weapon::SelectWorm as u32
|
| 106 |
&& count_alive_worms(team_index, arena) == 0
|
| 107 |
{
|
|
@@ -465,7 +463,6 @@ unsafe fn fire_select_worm(worm: *mut CTaskWorm) {
|
|
| 465 |
/// Bit position comes from weapon entry's fire_params.
|
| 466 |
/// In game_version > 0x1C: toggles (set/clear). Otherwise: always sets.
|
| 467 |
unsafe fn fire_skip_go(worm: *const CTaskWorm, entry: *const WeaponEntry) {
|
| 468 |
-
use crate::engine::ddgame::TeamArenaRef;
|
| 469 |
let ddgame = CTask::ddgame_raw(worm as *const CTask);
|
| 470 |
let game_version = (*(*ddgame).game_info).game_version;
|
| 471 |
let team_index = (*worm).team_index as usize;
|
|
@@ -489,8 +486,6 @@ unsafe fn fire_skip_go(worm: *const CTaskWorm, entry: *const WeaponEntry) {
|
|
| 489 |
/// Sends message 0x29 (TaskMessage::Freeze) to CTaskTurnGame, then increments
|
| 490 |
/// WormEntry.turn_action_counter_Maybe by 14 (0x0E).
|
| 491 |
unsafe fn fire_freeze(worm: *mut CTaskWorm) {
|
| 492 |
-
use crate::engine::ddgame::TeamArenaRef;
|
| 493 |
-
|
| 494 |
fire_send_team_message(worm, 0x29);
|
| 495 |
|
| 496 |
let ddgame = CTask::ddgame_raw(worm as *const CTask);
|
|
@@ -514,7 +509,6 @@ unsafe fn fire_freeze(worm: *mut CTaskWorm) {
|
|
| 514 |
/// - version >= 5 && worm state == 0x78 && version < 8: call vtable
|
| 515 |
/// - otherwise: skip
|
| 516 |
unsafe fn fire_mail_mine_mole(worm: *mut CTaskWorm) {
|
| 517 |
-
use crate::engine::ddgame::TeamArenaRef;
|
| 518 |
let ddgame = CTask::ddgame_raw(worm as *const CTask);
|
| 519 |
let version = (*ddgame).version_flag_4;
|
| 520 |
let worm_state = (*worm).state();
|
|
@@ -989,7 +983,7 @@ unsafe fn fire_girder(worm: *mut CTaskWorm) {
|
|
| 989 |
girder_visual(landscape, girder_x >> 16, girder_y >> 16, sprite1, sprite2);
|
| 990 |
|
| 991 |
// Increment WormEntry counters
|
| 992 |
-
let arena =
|
| 993 |
let team_index = (*worm).team_index as usize;
|
| 994 |
let worm_index = (*worm).worm_index as usize;
|
| 995 |
let entry = arena.team_worm_mut(team_index, worm_index);
|
|
|
|
| 11 |
|
| 12 |
use crate::address::va;
|
| 13 |
use crate::audio::{KnownSoundId, SoundId};
|
| 14 |
+
use crate::engine::{TeamArenaRef, GAME_PHASE_NORMAL_MIN, GAME_PHASE_SUDDEN_DEATH};
|
| 15 |
use crate::fixed::Fixed;
|
| 16 |
use crate::game::weapon::{WeaponEntry, WeaponFireParams, WeaponSpawnData};
|
| 17 |
use crate::game::Weapon;
|
|
|
|
| 93 |
}
|
| 94 |
// In sudden death (phase >= 484), delayed weapons return 0
|
| 95 |
// unless it's Teleport (weapon 0x28)
|
| 96 |
+
if state.game_phase >= GAME_PHASE_SUDDEN_DEATH && weapon_id != Weapon::Teleport as u32 {
|
|
|
|
|
|
|
| 97 |
return 0;
|
| 98 |
}
|
| 99 |
}
|
| 100 |
|
| 101 |
// SelectWorm (0x3B) requires >1 alive worm on the team
|
| 102 |
+
if state.game_phase >= GAME_PHASE_NORMAL_MIN
|
| 103 |
&& weapon_id == Weapon::SelectWorm as u32
|
| 104 |
&& count_alive_worms(team_index, arena) == 0
|
| 105 |
{
|
|
|
|
| 463 |
/// Bit position comes from weapon entry's fire_params.
|
| 464 |
/// In game_version > 0x1C: toggles (set/clear). Otherwise: always sets.
|
| 465 |
unsafe fn fire_skip_go(worm: *const CTaskWorm, entry: *const WeaponEntry) {
|
|
|
|
| 466 |
let ddgame = CTask::ddgame_raw(worm as *const CTask);
|
| 467 |
let game_version = (*(*ddgame).game_info).game_version;
|
| 468 |
let team_index = (*worm).team_index as usize;
|
|
|
|
| 486 |
/// Sends message 0x29 (TaskMessage::Freeze) to CTaskTurnGame, then increments
|
| 487 |
/// WormEntry.turn_action_counter_Maybe by 14 (0x0E).
|
| 488 |
unsafe fn fire_freeze(worm: *mut CTaskWorm) {
|
|
|
|
|
|
|
| 489 |
fire_send_team_message(worm, 0x29);
|
| 490 |
|
| 491 |
let ddgame = CTask::ddgame_raw(worm as *const CTask);
|
|
|
|
| 509 |
/// - version >= 5 && worm state == 0x78 && version < 8: call vtable
|
| 510 |
/// - otherwise: skip
|
| 511 |
unsafe fn fire_mail_mine_mole(worm: *mut CTaskWorm) {
|
|
|
|
| 512 |
let ddgame = CTask::ddgame_raw(worm as *const CTask);
|
| 513 |
let version = (*ddgame).version_flag_4;
|
| 514 |
let worm_state = (*worm).state();
|
|
|
|
| 983 |
girder_visual(landscape, girder_x >> 16, girder_y >> 16, sprite1, sprite2);
|
| 984 |
|
| 985 |
// Increment WormEntry counters
|
| 986 |
+
let arena = TeamArenaRef::from_ptr(&raw mut (*ddgame).team_arena);
|
| 987 |
let team_index = (*worm).team_index as usize;
|
| 988 |
let worm_index = (*worm).worm_index as usize;
|
| 989 |
let entry = arena.team_worm_mut(team_index, worm_index);
|
crates/openwa-core/src/render/sprite.rs
CHANGED
|
@@ -795,8 +795,9 @@ pub struct Sprite {
|
|
| 795 |
pub secondary_frame_count: u16,
|
| 796 |
/// 0x32: Padding
|
| 797 |
pub _pad_32: u16,
|
| 798 |
-
/// 0x34:
|
| 799 |
-
|
|
|
|
| 800 |
/// 0x38-0x5F: Unknown fields
|
| 801 |
pub _unknown_38: [u8; 0x28],
|
| 802 |
/// 0x60: Pointer to raw frame header data
|
|
|
|
| 795 |
pub secondary_frame_count: u16,
|
| 796 |
/// 0x32: Padding
|
| 797 |
pub _pad_32: u16,
|
| 798 |
+
/// 0x34: Embedded BitGrid vtable pointer (0x664144, set by ConstructSprite).
|
| 799 |
+
/// Part of a BitGrid sub-object within the Sprite.
|
| 800 |
+
pub bitgrid_vtable: *mut u8,
|
| 801 |
/// 0x38-0x5F: Unknown fields
|
| 802 |
pub _unknown_38: [u8; 0x28],
|
| 803 |
/// 0x60: Pointer to raw frame header data
|
crates/openwa-core/src/task/bit_grid.rs
DELETED
|
@@ -1,67 +0,0 @@
|
|
| 1 |
-
//! BitGrid initialization β bitfield grid buffer for spatial queries.
|
| 2 |
-
|
| 3 |
-
use crate::rebase::rb;
|
| 4 |
-
use crate::wa_alloc::wa_malloc;
|
| 5 |
-
use crate::FieldRegistry;
|
| 6 |
-
|
| 7 |
-
/// Bitfield grid buffer used for spatial collision/visibility queries.
|
| 8 |
-
/// Vtable at 0x6640EC (shared with DisplayGfx base).
|
| 9 |
-
/// Size: 0x2C bytes (11 u32 fields).
|
| 10 |
-
#[derive(FieldRegistry)]
|
| 11 |
-
#[repr(C)]
|
| 12 |
-
pub struct BitGrid {
|
| 13 |
-
pub vtable: u32,
|
| 14 |
-
pub _unused_04: u32,
|
| 15 |
-
pub data: *mut u8,
|
| 16 |
-
pub cells_per_unit: u32,
|
| 17 |
-
pub row_stride: u32,
|
| 18 |
-
pub width: u32,
|
| 19 |
-
pub height: u32,
|
| 20 |
-
pub _unused_1c: u32,
|
| 21 |
-
pub _unused_20: u32,
|
| 22 |
-
pub width_dup: u32,
|
| 23 |
-
pub height_dup: u32,
|
| 24 |
-
}
|
| 25 |
-
|
| 26 |
-
const _: () = assert!(core::mem::size_of::<BitGrid>() == 0x2C);
|
| 27 |
-
|
| 28 |
-
/// Pure Rust implementation of BitGrid__Init (0x4F6370).
|
| 29 |
-
///
|
| 30 |
-
/// Allocates a bit-per-cell grid buffer. `cells_per_unit` is typically 1.
|
| 31 |
-
/// `width` and `height` are pixel dimensions. The buffer is a row-major
|
| 32 |
-
/// bitfield with rows aligned to 4 bytes.
|
| 33 |
-
///
|
| 34 |
-
/// # Safety
|
| 35 |
-
/// `object` must point to a zero-filled allocation of at least 0x2C bytes.
|
| 36 |
-
pub unsafe fn bit_grid_init(object: *mut u8, cells_per_unit: u32, width: u32, height: u32) {
|
| 37 |
-
let bits = cells_per_unit.wrapping_mul(width).wrapping_add(7) as i32;
|
| 38 |
-
let row_stride = ((bits >> 3) + 3) & !3;
|
| 39 |
-
let total_size = row_stride as u32 * height;
|
| 40 |
-
|
| 41 |
-
let alloc_size = ((total_size + 3) & !3) + 0x20;
|
| 42 |
-
let buffer = wa_malloc(alloc_size);
|
| 43 |
-
|
| 44 |
-
if buffer.is_null() {
|
| 45 |
-
return;
|
| 46 |
-
}
|
| 47 |
-
if total_size as usize > alloc_size as usize {
|
| 48 |
-
return;
|
| 49 |
-
}
|
| 50 |
-
|
| 51 |
-
// Memset twice (matches original β likely redundant but exact match)
|
| 52 |
-
core::ptr::write_bytes(buffer, 0, total_size as usize);
|
| 53 |
-
core::ptr::write_bytes(buffer, 0, total_size as usize);
|
| 54 |
-
|
| 55 |
-
let tsm = &mut *(object as *mut BitGrid);
|
| 56 |
-
tsm.vtable = rb(0x6640EC);
|
| 57 |
-
tsm._unused_04 = 0;
|
| 58 |
-
tsm.data = buffer;
|
| 59 |
-
tsm.cells_per_unit = cells_per_unit;
|
| 60 |
-
tsm.row_stride = row_stride as u32;
|
| 61 |
-
tsm.width = width;
|
| 62 |
-
tsm.height = height;
|
| 63 |
-
tsm._unused_1c = 0;
|
| 64 |
-
tsm._unused_20 = 0;
|
| 65 |
-
tsm.width_dup = width;
|
| 66 |
-
tsm.height_dup = height;
|
| 67 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
crates/openwa-core/src/task/mod.rs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
| 1 |
pub mod arrow;
|
| 2 |
pub mod base;
|
| 3 |
-
pub mod bit_grid;
|
| 4 |
pub mod cloud;
|
| 5 |
pub mod filter;
|
| 6 |
pub mod fire;
|
|
|
|
| 1 |
pub mod arrow;
|
| 2 |
pub mod base;
|
|
|
|
| 3 |
pub mod cloud;
|
| 4 |
pub mod filter;
|
| 5 |
pub mod fire;
|
crates/openwa-core/src/wa_alloc.rs
CHANGED
|
@@ -13,6 +13,25 @@ pub unsafe fn wa_malloc(size: u32) -> *mut u8 {
|
|
| 13 |
f(size)
|
| 14 |
}
|
| 15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
/// Allocate `size` bytes from WA's CRT heap and zero-initialize them.
|
| 17 |
///
|
| 18 |
/// Equivalent to `wa_malloc(size)` followed by `write_bytes(ptr, 0, size)`.
|
|
|
|
| 13 |
f(size)
|
| 14 |
}
|
| 15 |
|
| 16 |
+
/// Allocate space for a `T` on WA's heap and return a pointer to it.
|
| 17 |
+
///
|
| 18 |
+
/// Equivalent to `wa_malloc(size_of::<T>())` with a cast. The allocation is uninitialized.
|
| 19 |
+
///
|
| 20 |
+
/// # Safety
|
| 21 |
+
/// Must only be called from within the WA.exe process (game or injected DLL).
|
| 22 |
+
pub unsafe fn wa_malloc_struct<T>() -> *mut T {
|
| 23 |
+
wa_malloc(core::mem::size_of::<T>() as u32) as *mut T
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
/// Allocate space for a `T` on WA's heap, zero-initialize it, and return a pointer to it.
|
| 27 |
+
/// Equivalent to `wa_malloc_zeroed(size_of::<T>())` with a cast. The allocation is zero-initialized.
|
| 28 |
+
///
|
| 29 |
+
/// # Safety
|
| 30 |
+
/// Must only be called from within the WA.exe process (game or injected DLL).
|
| 31 |
+
pub unsafe fn wa_malloc_struct_zeroed<T>() -> *mut T {
|
| 32 |
+
wa_malloc_zeroed(core::mem::size_of::<T>() as u32) as *mut T
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
/// Allocate `size` bytes from WA's CRT heap and zero-initialize them.
|
| 36 |
///
|
| 37 |
/// Equivalent to `wa_malloc(size)` followed by `write_bytes(ptr, 0, size)`.
|
crates/openwa-dll/src/replacements/ddgame_init.rs
CHANGED
|
@@ -14,15 +14,15 @@
|
|
| 14 |
use crate::hook;
|
| 15 |
use core::ffi::c_char;
|
| 16 |
use openwa_core::address::va;
|
| 17 |
-
use openwa_core::
|
| 18 |
-
bit_grid_init, ddgame_init_fields, ddgame_init_render_indices, display_layer_color_init,
|
| 19 |
-
gfx_dir_find_entry, gfx_dir_load_dir, gfx_resource_create, DDGame,
|
| 20 |
-
};
|
| 21 |
use openwa_core::engine::game_state_init::{
|
| 22 |
check_weapon_avail, init_alliance_data, init_landscape_flags, init_team_scoring,
|
| 23 |
init_turn_state, is_super_weapon, ring_buffer_init, sprite_gfx_table_init,
|
| 24 |
};
|
| 25 |
-
use openwa_core::engine::
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
// βββ DDGame__InitFields (0x526120) ββββββββββββββββββββββββββββββββββββββββββ
|
| 28 |
|
|
@@ -54,13 +54,18 @@ extern "cdecl" fn impl_init_render_indices(base: u32) -> u32 {
|
|
| 54 |
|
| 55 |
// βββ BitGrid__Init (0x4F6370) ββββββββββββββββββββββββββββββββββββββ
|
| 56 |
|
| 57 |
-
extern "cdecl" fn
|
| 58 |
-
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
}
|
| 61 |
|
| 62 |
#[unsafe(naked)]
|
| 63 |
-
unsafe extern "C" fn
|
| 64 |
core::arch::naked_asm!(
|
| 65 |
"push edx",
|
| 66 |
"push [esp+8]", // width (stack param)
|
|
@@ -71,7 +76,7 @@ unsafe extern "C" fn tsm_init_trampoline() {
|
|
| 71 |
"add esp, 16",
|
| 72 |
"pop edx",
|
| 73 |
"ret 0x4",
|
| 74 |
-
impl_fn = sym
|
| 75 |
);
|
| 76 |
}
|
| 77 |
|
|
@@ -180,7 +185,7 @@ pub fn install() -> Result<(), String> {
|
|
| 180 |
hook::install(
|
| 181 |
"BitGrid__Init",
|
| 182 |
va::BIT_GRID_INIT,
|
| 183 |
-
|
| 184 |
)?;
|
| 185 |
|
| 186 |
hook::install(
|
|
|
|
| 14 |
use crate::hook;
|
| 15 |
use core::ffi::c_char;
|
| 16 |
use openwa_core::address::va;
|
| 17 |
+
use openwa_core::display::BitGrid;
|
|
|
|
|
|
|
|
|
|
| 18 |
use openwa_core::engine::game_state_init::{
|
| 19 |
check_weapon_avail, init_alliance_data, init_landscape_flags, init_team_scoring,
|
| 20 |
init_turn_state, is_super_weapon, ring_buffer_init, sprite_gfx_table_init,
|
| 21 |
};
|
| 22 |
+
use openwa_core::engine::{
|
| 23 |
+
ddgame_init_fields, ddgame_init_render_indices, display_layer_color_init, DDGame, DDGameWrapper,
|
| 24 |
+
};
|
| 25 |
+
use openwa_core::render::gfx_dir::{gfx_dir_find_entry, gfx_dir_load_dir, gfx_resource_create};
|
| 26 |
|
| 27 |
// βββ DDGame__InitFields (0x526120) ββββββββββββββββββββββββββββββββββββββββββ
|
| 28 |
|
|
|
|
| 54 |
|
| 55 |
// βββ BitGrid__Init (0x4F6370) ββββββββββββββββββββββββββββββββββββββ
|
| 56 |
|
| 57 |
+
extern "cdecl" fn impl_bitgrid_init(
|
| 58 |
+
bit_grid: *mut BitGrid,
|
| 59 |
+
cells_per_unit: u32,
|
| 60 |
+
height: u32,
|
| 61 |
+
width: u32,
|
| 62 |
+
) -> *mut BitGrid {
|
| 63 |
+
unsafe { BitGrid::init(bit_grid, cells_per_unit, width, height) }
|
| 64 |
+
bit_grid // Original: MOV EAX, ESI; RET 0x4
|
| 65 |
}
|
| 66 |
|
| 67 |
#[unsafe(naked)]
|
| 68 |
+
unsafe extern "C" fn bitgrid_init_trampoline() {
|
| 69 |
core::arch::naked_asm!(
|
| 70 |
"push edx",
|
| 71 |
"push [esp+8]", // width (stack param)
|
|
|
|
| 76 |
"add esp, 16",
|
| 77 |
"pop edx",
|
| 78 |
"ret 0x4",
|
| 79 |
+
impl_fn = sym impl_bitgrid_init,
|
| 80 |
);
|
| 81 |
}
|
| 82 |
|
|
|
|
| 185 |
hook::install(
|
| 186 |
"BitGrid__Init",
|
| 187 |
va::BIT_GRID_INIT,
|
| 188 |
+
bitgrid_init_trampoline as *const (),
|
| 189 |
)?;
|
| 190 |
|
| 191 |
hook::install(
|
crates/openwa-dll/src/replacements/game_session.rs
CHANGED
|
@@ -17,7 +17,8 @@ use crate::log_line;
|
|
| 17 |
use openwa_core::address::va;
|
| 18 |
use openwa_core::audio::DSSound;
|
| 19 |
use openwa_core::display::{DDDisplay, Palette};
|
| 20 |
-
use openwa_core::engine::
|
|
|
|
| 21 |
use openwa_core::engine::{DDGameWrapper, GameInfo, GameSession};
|
| 22 |
use openwa_core::rebase::rb;
|
| 23 |
|
|
|
|
| 17 |
use openwa_core::address::va;
|
| 18 |
use openwa_core::audio::DSSound;
|
| 19 |
use openwa_core::display::{DDDisplay, Palette};
|
| 20 |
+
use openwa_core::engine::create_ddgame;
|
| 21 |
+
use openwa_core::engine::init_constructor_addrs;
|
| 22 |
use openwa_core::engine::{DDGameWrapper, GameInfo, GameSession};
|
| 23 |
use openwa_core::rebase::rb;
|
| 24 |
|
crates/openwa-dll/src/replacements/replay_test.rs
CHANGED
|
@@ -13,9 +13,10 @@
|
|
| 13 |
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, Ordering};
|
| 14 |
|
| 15 |
use crate::log_line;
|
| 16 |
-
use openwa_core::engine::ddgame::
|
| 17 |
use openwa_core::engine::game_session;
|
| 18 |
use openwa_core::engine::DDGame;
|
|
|
|
| 19 |
|
| 20 |
// ---------------------------------------------------------------------------
|
| 21 |
// Fast-forward
|
|
|
|
| 13 |
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, Ordering};
|
| 14 |
|
| 15 |
use crate::log_line;
|
| 16 |
+
use openwa_core::engine::ddgame::offsets;
|
| 17 |
use openwa_core::engine::game_session;
|
| 18 |
use openwa_core::engine::DDGame;
|
| 19 |
+
use openwa_core::engine::TeamArenaRef;
|
| 20 |
|
| 21 |
// ---------------------------------------------------------------------------
|
| 22 |
// Fast-forward
|
crates/openwa-dll/src/replacements/sprite.rs
CHANGED
|
@@ -24,9 +24,9 @@ unsafe extern "cdecl" fn construct_sprite_impl(sprite: *mut Sprite, context: *mu
|
|
| 24 |
|
| 25 |
(*sprite).vtable = rb(va::SPRITE_VTABLE) as *mut u8;
|
| 26 |
(*sprite).context_ptr = context;
|
| 27 |
-
(*sprite).
|
| 28 |
|
| 29 |
-
//
|
| 30 |
let p = sprite as *mut u8;
|
| 31 |
*(p.add(0x38) as *mut u32) = 1;
|
| 32 |
*(p.add(0x40) as *mut u32) = 8;
|
|
|
|
| 24 |
|
| 25 |
(*sprite).vtable = rb(va::SPRITE_VTABLE) as *mut u8;
|
| 26 |
(*sprite).context_ptr = context;
|
| 27 |
+
(*sprite).bitgrid_vtable = rb(va::BIT_GRID_DISPLAY_VTABLE) as *mut u8;
|
| 28 |
|
| 29 |
+
// BitGrid sub-object fields (within _unknown_38)
|
| 30 |
let p = sprite as *mut u8;
|
| 31 |
*(p.add(0x38) as *mut u32) = 1;
|
| 32 |
*(p.add(0x40) as *mut u32) = 8;
|
crates/openwa-dll/src/replacements/team.rs
CHANGED
|
@@ -3,9 +3,8 @@
|
|
| 3 |
//! Thin hook shim β all game logic lives in `openwa_core::engine::team_ops`.
|
| 4 |
//! This file contains only usercall trampolines and hook installation.
|
| 5 |
|
| 6 |
-
use openwa_core::address::va;
|
| 7 |
-
use openwa_core::engine::ddgame::TeamArenaRef;
|
| 8 |
use openwa_core::engine::team_ops;
|
|
|
|
| 9 |
|
| 10 |
use crate::hook::{self, usercall_trampoline};
|
| 11 |
|
|
|
|
| 3 |
//! Thin hook shim β all game logic lives in `openwa_core::engine::team_ops`.
|
| 4 |
//! This file contains only usercall trampolines and hook installation.
|
| 5 |
|
|
|
|
|
|
|
| 6 |
use openwa_core::engine::team_ops;
|
| 7 |
+
use openwa_core::{address::va, engine::TeamArenaRef};
|
| 8 |
|
| 9 |
use crate::hook::{self, usercall_trampoline};
|
| 10 |
|
crates/openwa-dll/src/replacements/weapon.rs
CHANGED
|
@@ -12,12 +12,11 @@
|
|
| 12 |
|
| 13 |
use core::sync::atomic::{AtomicU32, Ordering};
|
| 14 |
|
| 15 |
-
use openwa_core::address::va;
|
| 16 |
-
use openwa_core::engine::ddgame::TeamArenaRef;
|
| 17 |
use openwa_core::game::weapon::WeaponFireParams;
|
| 18 |
use openwa_core::game::weapon_fire;
|
| 19 |
use openwa_core::log::log_line;
|
| 20 |
use openwa_core::task::worm::CTaskWorm;
|
|
|
|
| 21 |
|
| 22 |
use crate::hook::{self, usercall_trampoline};
|
| 23 |
|
|
|
|
| 12 |
|
| 13 |
use core::sync::atomic::{AtomicU32, Ordering};
|
| 14 |
|
|
|
|
|
|
|
| 15 |
use openwa_core::game::weapon::WeaponFireParams;
|
| 16 |
use openwa_core::game::weapon_fire;
|
| 17 |
use openwa_core::log::log_line;
|
| 18 |
use openwa_core::task::worm::CTaskWorm;
|
| 19 |
+
use openwa_core::{address::va, engine::TeamArenaRef};
|
| 20 |
|
| 21 |
use crate::hook::{self, usercall_trampoline};
|
| 22 |
|
crates/openwa-dll/src/snapshot.rs
CHANGED
|
@@ -19,6 +19,8 @@ use openwa_core::task::{CTask, CTaskBfsIter, CTaskMissile, CTaskWorm};
|
|
| 19 |
/// Must be called from the DLL while the game is paused (frame breakpoint).
|
| 20 |
#[cfg(target_arch = "x86")]
|
| 21 |
pub unsafe fn capture() -> String {
|
|
|
|
|
|
|
| 22 |
let mut out = String::with_capacity(128 * 1024);
|
| 23 |
|
| 24 |
let session_ptr = rb(va::G_GAME_SESSION) as *const *mut GameSession;
|
|
@@ -60,7 +62,7 @@ pub unsafe fn capture() -> String {
|
|
| 60 |
// ββ Team blocks + worm entries ββ
|
| 61 |
let team_count = (*ddgame).team_arena.team_count as usize;
|
| 62 |
let _ = writeln!(out, "[Teams] count={}", team_count);
|
| 63 |
-
let arena =
|
| 64 |
let blocks = arena.blocks();
|
| 65 |
for t in 0..team_count {
|
| 66 |
let header = arena.team_header(t);
|
|
|
|
| 19 |
/// Must be called from the DLL while the game is paused (frame breakpoint).
|
| 20 |
#[cfg(target_arch = "x86")]
|
| 21 |
pub unsafe fn capture() -> String {
|
| 22 |
+
use openwa_core::engine::TeamArenaRef;
|
| 23 |
+
|
| 24 |
let mut out = String::with_capacity(128 * 1024);
|
| 25 |
|
| 26 |
let session_ptr = rb(va::G_GAME_SESSION) as *const *mut GameSession;
|
|
|
|
| 62 |
// ββ Team blocks + worm entries ββ
|
| 63 |
let team_count = (*ddgame).team_arena.team_count as usize;
|
| 64 |
let _ = writeln!(out, "[Teams] count={}", team_count);
|
| 65 |
+
let arena = TeamArenaRef::from_ptr(&raw mut (*ddgame).team_arena);
|
| 66 |
let blocks = arena.blocks();
|
| 67 |
for t in 0..team_count {
|
| 68 |
let header = arena.team_header(t);
|