refactor: add GameInfo fields, replace pointer arithmetic
Browse filesAdd typed fields to GameInfo:
- game_version (0xD778): renamed from crosshair_overflow_threshold
(it's a game mode value, not a crosshair threshold)
- net_config_1/2 (0xD944/0xD946): network config bytes
- landscape_data_path (0xDAAC): path data for PCLandscape
Replace raw pointer arithmetic in create_ddgame/init_graphics_and_resources:
- (*game_info).headless_mode instead of *(ptr+0xF914)
- (*game_info).game_version instead of *(ptr+0xD778)
- (*game_info).display_flags instead of *(ptr+0xF374)
- (*game_info).landscape_data_path.as_ptr() instead of ptr+0xDAAC
- (*game_info).net_config_1/2 instead of *(ptr+0xD944/D946)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crates/openwa-core/src/engine/ddgame.rs
CHANGED
|
@@ -470,7 +470,7 @@ pub unsafe fn create_ddgame(
|
|
| 470 |
*(rb(va::G_GAME_INFO) as *mut *mut GameInfo) = game_info;
|
| 471 |
|
| 472 |
// ββ 6. Sound available + always-1 flags ββ
|
| 473 |
-
let is_headless =
|
| 474 |
// Defer sound_available β keep at 0 during construction.
|
| 475 |
// DDGame__LoadWeaponSprites checks this flag; when set, it calls
|
| 476 |
// FUN_572E30 which pumps PeekMessage/DispatchMessage. That can
|
|
@@ -482,15 +482,13 @@ pub unsafe fn create_ddgame(
|
|
| 482 |
// ββ 7. DDGameWrapper+0x48C init ββ
|
| 483 |
(*wrapper).ddgame_secondary = core::ptr::null_mut();
|
| 484 |
|
| 485 |
-
// ββ 8. Conditional network object (
|
| 486 |
-
|
| 487 |
-
if d778 == -2 {
|
| 488 |
let net_obj = wa_malloc(0x2C);
|
| 489 |
core::ptr::write_bytes(net_obj, 0, 0x2C);
|
| 490 |
*(net_obj as *mut *mut DDGame) = ddgame;
|
| 491 |
-
|
| 492 |
-
*net_obj.add(
|
| 493 |
-
*net_obj.add(0x29) = *gi.add(0xD946);
|
| 494 |
(*wrapper).ddgame_secondary = net_obj;
|
| 495 |
if network_ecx != 0 {
|
| 496 |
*((network_ecx as *mut u8).add(0x18) as *mut *mut u8) = net_obj;
|
|
@@ -538,8 +536,8 @@ unsafe fn init_graphics_and_resources(
|
|
| 538 |
(*wrapper)._field_4c4 = core::ptr::null_mut();
|
| 539 |
|
| 540 |
// Build path list (order depends on headless + GameInfo+0xF374)
|
| 541 |
-
let f914 =
|
| 542 |
-
let f374 =
|
| 543 |
let paths: [&[u8]; 3] = if f914 != 0 {
|
| 544 |
if f374 == 0 {
|
| 545 |
[b"data\\Gfx\\Gfx.dir\0", b"data\\Gfx\\Gfx0.dir\0", b"data\\Gfx\\Gfx1.dir\0"]
|
|
@@ -569,7 +567,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 569 |
(*wrapper).gfx_mode = if gfx_loaded_idx.wrapping_sub(headless_offset) < 2 { 1 } else { 0 };
|
| 570 |
|
| 571 |
// ββ GfxHandler #2 (conditional) ββ
|
| 572 |
-
let d778 =
|
| 573 |
let threshold = if (*wrapper).gfx_mode != 0 {
|
| 574 |
(0x23u32.wrapping_sub(2)) as i32
|
| 575 |
} else {
|
|
@@ -684,10 +682,10 @@ unsafe fn init_graphics_and_resources(
|
|
| 684 |
*(gfxdir2.add(0x708) as *mut u16) = 0;
|
| 685 |
(*ddgame).secondary_gfxdir = gfxdir2;
|
| 686 |
// GFX_HANDLER_LOAD_SPRITES: stdcall(wrapper, ddgame+0x7308, game_info+0xF374, 0), RET 0x10
|
| 687 |
-
|
| 688 |
let f: unsafe extern "stdcall" fn(*mut DDGameWrapper, *mut u8, u32, u32) =
|
| 689 |
core::mem::transmute(rb(va::GFX_HANDLER_LOAD_SPRITES) as usize);
|
| 690 |
-
f(wrapper, (ddgame as *mut u8).add(0x7308),
|
| 691 |
}
|
| 692 |
|
| 693 |
// ββ DDGameWrapper field inits ββ
|
|
@@ -769,7 +767,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 769 |
*mut u8, *mut u8, // 10=&ddgame+0x777C, 11=&ddgame+0x7780
|
| 770 |
) -> *mut u8 = core::mem::transmute(rb(va::PC_LANDSCAPE_CONSTRUCTOR) as usize);
|
| 771 |
|
| 772 |
-
|
| 773 |
// Param 8 is a stack local in the original's 4KB frame.
|
| 774 |
// Heap-allocate a generous buffer since we don't know the exact size.
|
| 775 |
let stack_local_8 = wa_malloc(0x1000);
|
|
@@ -779,7 +777,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 779 |
ddgame,
|
| 780 |
gfx_resource,
|
| 781 |
(*wrapper).display, // param 4: display (NOT wrapper!)
|
| 782 |
-
|
| 783 |
landscape_byte_buf, // param 6
|
| 784 |
(*wrapper).gfx_mode, // param 7
|
| 785 |
stack_local_8, // param 8
|
|
@@ -1015,8 +1013,8 @@ unsafe fn init_graphics_and_resources(
|
|
| 1015 |
);
|
| 1016 |
|
| 1017 |
// Set global flag based on game version
|
| 1018 |
-
let
|
| 1019 |
-
*(rb(0x6AF050) as *mut u32) = if
|
| 1020 |
|
| 1021 |
// Load resources for layer 1 with different table
|
| 1022 |
load_resource_list(
|
|
|
|
| 470 |
*(rb(va::G_GAME_INFO) as *mut *mut GameInfo) = game_info;
|
| 471 |
|
| 472 |
// ββ 6. Sound available + always-1 flags ββ
|
| 473 |
+
let is_headless = (*game_info).headless_mode != 0;
|
| 474 |
// Defer sound_available β keep at 0 during construction.
|
| 475 |
// DDGame__LoadWeaponSprites checks this flag; when set, it calls
|
| 476 |
// FUN_572E30 which pumps PeekMessage/DispatchMessage. That can
|
|
|
|
| 482 |
// ββ 7. DDGameWrapper+0x48C init ββ
|
| 483 |
(*wrapper).ddgame_secondary = core::ptr::null_mut();
|
| 484 |
|
| 485 |
+
// ββ 8. Conditional network object (game_version == -2) ββ
|
| 486 |
+
if (*game_info).game_version == -2 {
|
|
|
|
| 487 |
let net_obj = wa_malloc(0x2C);
|
| 488 |
core::ptr::write_bytes(net_obj, 0, 0x2C);
|
| 489 |
*(net_obj as *mut *mut DDGame) = ddgame;
|
| 490 |
+
*net_obj.add(0x28) = (*game_info).net_config_1;
|
| 491 |
+
*net_obj.add(0x29) = (*game_info).net_config_2;
|
|
|
|
| 492 |
(*wrapper).ddgame_secondary = net_obj;
|
| 493 |
if network_ecx != 0 {
|
| 494 |
*((network_ecx as *mut u8).add(0x18) as *mut *mut u8) = net_obj;
|
|
|
|
| 536 |
(*wrapper)._field_4c4 = core::ptr::null_mut();
|
| 537 |
|
| 538 |
// Build path list (order depends on headless + GameInfo+0xF374)
|
| 539 |
+
let f914 = (*game_info).headless_mode as i32;
|
| 540 |
+
let f374 = (*game_info).display_flags as i32;
|
| 541 |
let paths: [&[u8]; 3] = if f914 != 0 {
|
| 542 |
if f374 == 0 {
|
| 543 |
[b"data\\Gfx\\Gfx.dir\0", b"data\\Gfx\\Gfx0.dir\0", b"data\\Gfx\\Gfx1.dir\0"]
|
|
|
|
| 567 |
(*wrapper).gfx_mode = if gfx_loaded_idx.wrapping_sub(headless_offset) < 2 { 1 } else { 0 };
|
| 568 |
|
| 569 |
// ββ GfxHandler #2 (conditional) ββ
|
| 570 |
+
let d778 = (*game_info).game_version;
|
| 571 |
let threshold = if (*wrapper).gfx_mode != 0 {
|
| 572 |
(0x23u32.wrapping_sub(2)) as i32
|
| 573 |
} else {
|
|
|
|
| 682 |
*(gfxdir2.add(0x708) as *mut u16) = 0;
|
| 683 |
(*ddgame).secondary_gfxdir = gfxdir2;
|
| 684 |
// GFX_HANDLER_LOAD_SPRITES: stdcall(wrapper, ddgame+0x7308, game_info+0xF374, 0), RET 0x10
|
| 685 |
+
|
| 686 |
let f: unsafe extern "stdcall" fn(*mut DDGameWrapper, *mut u8, u32, u32) =
|
| 687 |
core::mem::transmute(rb(va::GFX_HANDLER_LOAD_SPRITES) as usize);
|
| 688 |
+
f(wrapper, (ddgame as *mut u8).add(0x7308), (*game_info).display_flags, 0);
|
| 689 |
}
|
| 690 |
|
| 691 |
// ββ DDGameWrapper field inits ββ
|
|
|
|
| 767 |
*mut u8, *mut u8, // 10=&ddgame+0x777C, 11=&ddgame+0x7780
|
| 768 |
) -> *mut u8 = core::mem::transmute(rb(va::PC_LANDSCAPE_CONSTRUCTOR) as usize);
|
| 769 |
|
| 770 |
+
|
| 771 |
// Param 8 is a stack local in the original's 4KB frame.
|
| 772 |
// Heap-allocate a generous buffer since we don't know the exact size.
|
| 773 |
let stack_local_8 = wa_malloc(0x1000);
|
|
|
|
| 777 |
ddgame,
|
| 778 |
gfx_resource,
|
| 779 |
(*wrapper).display, // param 4: display (NOT wrapper!)
|
| 780 |
+
(*game_info).landscape_data_path.as_ptr(), // param 5
|
| 781 |
landscape_byte_buf, // param 6
|
| 782 |
(*wrapper).gfx_mode, // param 7
|
| 783 |
stack_local_8, // param 8
|
|
|
|
| 1013 |
);
|
| 1014 |
|
| 1015 |
// Set global flag based on game version
|
| 1016 |
+
let gv = (*(*ddgame).game_info).game_version;
|
| 1017 |
+
*(rb(0x6AF050) as *mut u32) = if gv < 8 { 0 } else { 0x10 };
|
| 1018 |
|
| 1019 |
// Load resources for layer 1 with different table
|
| 1020 |
load_resource_list(
|
crates/openwa-core/src/engine/game_info.rs
CHANGED
|
@@ -21,12 +21,23 @@ pub struct GameInfo {
|
|
| 21 |
/// 0x044D-0xD777: Unknown
|
| 22 |
pub _unknown_044d: [u8; 0xD778 - 0x44D],
|
| 23 |
|
| 24 |
-
/// 0xD778:
|
| 25 |
-
///
|
| 26 |
-
|
|
|
|
| 27 |
|
| 28 |
-
/// 0xD77C-
|
| 29 |
-
pub _unknown_d77c: [u8;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
/// 0xD9E0: Streaming audio config data (path config passed to streaming audio ctor).
|
| 32 |
/// Address of this field is passed as a pointer parameter.
|
|
@@ -36,8 +47,12 @@ pub struct GameInfo {
|
|
| 36 |
/// If nonzero, streaming audio subsystem is created in InitHardware.
|
| 37 |
pub speech_enabled: u8,
|
| 38 |
|
| 39 |
-
/// 0xDAA5-
|
| 40 |
-
pub _unknown_daa5: [u8;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
// --- Cluster 1: data paths ---
|
| 43 |
|
|
|
|
| 21 |
/// 0x044D-0xD777: Unknown
|
| 22 |
pub _unknown_044d: [u8; 0xD778 - 0x44D],
|
| 23 |
|
| 24 |
+
/// 0xD778: Game version/mode. Compared against various thresholds:
|
| 25 |
+
/// -2 = network game, -3..0 = different game modes, 8+ = new versions.
|
| 26 |
+
/// Used by DDGame constructor for conditional initialization.
|
| 27 |
+
pub game_version: i32,
|
| 28 |
|
| 29 |
+
/// 0xD77C-0xD943: Unknown
|
| 30 |
+
pub _unknown_d77c: [u8; 0xD944 - 0xD77C],
|
| 31 |
+
|
| 32 |
+
/// 0xD944: Network config byte 1 (copied to network object+0x28).
|
| 33 |
+
pub net_config_1: u8,
|
| 34 |
+
/// 0xD945: Unknown
|
| 35 |
+
pub _unknown_d945: u8,
|
| 36 |
+
/// 0xD946: Network config byte 2 (copied to network object+0x29).
|
| 37 |
+
pub net_config_2: u8,
|
| 38 |
+
|
| 39 |
+
/// 0xD947-0xD9DF: Unknown
|
| 40 |
+
pub _unknown_d947: [u8; 0xD9E0 - 0xD947],
|
| 41 |
|
| 42 |
/// 0xD9E0: Streaming audio config data (path config passed to streaming audio ctor).
|
| 43 |
/// Address of this field is passed as a pointer parameter.
|
|
|
|
| 47 |
/// If nonzero, streaming audio subsystem is created in InitHardware.
|
| 48 |
pub speech_enabled: u8,
|
| 49 |
|
| 50 |
+
/// 0xDAA5-0xDAAB: Unknown
|
| 51 |
+
pub _unknown_daa5: [u8; 0xDAAC - 0xDAA5],
|
| 52 |
+
|
| 53 |
+
/// 0xDAAC: Landscape data path (passed to PCLandscape constructor).
|
| 54 |
+
/// Points to a path string used for loading level terrain data.
|
| 55 |
+
pub landscape_data_path: [u8; 0xDAE8 - 0xDAAC],
|
| 56 |
|
| 57 |
// --- Cluster 1: data paths ---
|
| 58 |
|
crates/openwa-wormkit/src/replacements/hardware_init.rs
CHANGED
|
@@ -252,7 +252,7 @@ unsafe extern "cdecl" fn impl_init_hardware(
|
|
| 252 |
let _ = log_line("[hardware_init] GameEngine::InitHardware");
|
| 253 |
let session = *(rb(va::G_GAME_SESSION) as *const *mut GameSession);
|
| 254 |
let gi = &mut *game_info;
|
| 255 |
-
let crosshair_threshold = gi.
|
| 256 |
|
| 257 |
// ββ Input controller (if param4 != 0) ββββββββββββββββββββββββββββββββββββ
|
| 258 |
if param4 == 0 {
|
|
|
|
| 252 |
let _ = log_line("[hardware_init] GameEngine::InitHardware");
|
| 253 |
let session = *(rb(va::G_GAME_SESSION) as *const *mut GameSession);
|
| 254 |
let gi = &mut *game_info;
|
| 255 |
+
let crosshair_threshold = gi.game_version as u32;
|
| 256 |
|
| 257 |
// ββ Input controller (if param4 != 0) ββββββββββββββββββββββββββββββββββββ
|
| 258 |
if param4 == 0 {
|
crates/openwa-wormkit/src/replacements/render.rs
CHANGED
|
@@ -529,7 +529,7 @@ unsafe extern "cdecl" fn draw_crosshair_line_impl(task_ptr: u32) {
|
|
| 529 |
let mut overflowed = false;
|
| 530 |
let mut clamp_factor = 0i32;
|
| 531 |
|
| 532 |
-
let threshold = (*ddgame.game_info).
|
| 533 |
|
| 534 |
if threshold > 0x11E {
|
| 535 |
// Check X overflow: sin > 0 but endpoint wrapped below start
|
|
|
|
| 529 |
let mut overflowed = false;
|
| 530 |
let mut clamp_factor = 0i32;
|
| 531 |
|
| 532 |
+
let threshold = (*ddgame.game_info).game_version;
|
| 533 |
|
| 534 |
if threshold > 0x11E {
|
| 535 |
// Check X overflow: sin > 0 but endpoint wrapped below start
|