refactor: port IMG_Decode and rename GfxResource to IMG
Browse filesPort IMG_Decode (0x4F5F80) and DisplayGfx__Constructor (0x4F5E80) to
pure Rust, eliminating the last transmute bridges in the asset loading
pipeline. img_decode returns Option<DecodedBitGrid> with Display/Collision
discrimination; img_decode_cached always returns *mut DisplayBitGrid.
- Add asset/img.rs with img_decode and img_decode_cached
- Replace transmute bridges in gfx_dir.rs, ddgame_constructor.rs
- Hook IMG_DECODE and IMG_DECODE_CACHED for remaining WA callers
- Type DDGame fields: gradient_image, gradient_image_2, arrow_sprites
as *mut BitGrid instead of *mut u8
- Rename GfxResource__Create_Maybe β IMG__LoadFromDir (Ghidra + Rust)
- Rename DisplayGfx__Constructor_Maybe β IMG__DecodeCached (Ghidra + VA)
- Propagate Option<DecodedBitGrid> through call_gfx_find_and_load
and call_gfx_load_and_wrap
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- crates/openwa-core/src/address.rs +4 -4
- crates/openwa-core/src/asset/gfx_dir.rs +48 -54
- crates/openwa-core/src/asset/img.rs +293 -0
- crates/openwa-core/src/asset/mod.rs +1 -0
- crates/openwa-core/src/engine/ddgame.rs +7 -7
- crates/openwa-core/src/engine/ddgame_constructor.rs +55 -47
- crates/openwa-core/src/render/display/gradient.rs +12 -10
- crates/openwa-dll/src/replacements/ddgame_init.rs +10 -10
- crates/openwa-dll/src/replacements/gfx_dir.rs +55 -4
|
@@ -249,8 +249,8 @@ pub mod va {
|
|
| 249 |
class "DisplayGfx" {
|
| 250 |
/// DisplayGfx constructor
|
| 251 |
ctor/Stdcall DISPLAYGFX_CTOR = 0x0056_9C10;
|
| 252 |
-
///
|
| 253 |
-
|
| 254 |
/// DisplayGfx construct full (5 params)
|
| 255 |
fn/Stdcall DISPLAYGFX_CONSTRUCT_FULL = 0x0056_3FC0;
|
| 256 |
/// DisplayGfx init team palette display objects
|
|
@@ -524,8 +524,8 @@ pub mod va {
|
|
| 524 |
fn PALETTE_ANIMATE_MAYBE = 0x0053_3A80;
|
| 525 |
fn LOAD_SPRITE = 0x0052_3400;
|
| 526 |
fn OPENGL_INIT = 0x0059_F000;
|
| 527 |
-
///
|
| 528 |
-
fn
|
| 529 |
/// SpriteGfxTable__Init
|
| 530 |
fn/Fastcall SPRITE_GFX_TABLE_INIT = 0x0054_1620;
|
| 531 |
/// RingBuffer__Init
|
|
|
|
| 249 |
class "DisplayGfx" {
|
| 250 |
/// DisplayGfx constructor
|
| 251 |
ctor/Stdcall DISPLAYGFX_CTOR = 0x0056_9C10;
|
| 252 |
+
/// IMG__DecodeCached: decode cached raw image buffer into DisplayBitGrid
|
| 253 |
+
fn/Stdcall IMG_DECODE_CACHED = 0x004F_5E80;
|
| 254 |
/// DisplayGfx construct full (5 params)
|
| 255 |
fn/Stdcall DISPLAYGFX_CONSTRUCT_FULL = 0x0056_3FC0;
|
| 256 |
/// DisplayGfx init team palette display objects
|
|
|
|
| 524 |
fn PALETTE_ANIMATE_MAYBE = 0x0053_3A80;
|
| 525 |
fn LOAD_SPRITE = 0x0052_3400;
|
| 526 |
fn OPENGL_INIT = 0x0059_F000;
|
| 527 |
+
/// IMG__LoadFromDir: look up + decode IMG resource from a .dir archive
|
| 528 |
+
fn IMG_LOAD_FROM_DIR = 0x004F_6300;
|
| 529 |
/// SpriteGfxTable__Init
|
| 530 |
fn/Fastcall SPRITE_GFX_TABLE_INIT = 0x0054_1620;
|
| 531 |
/// RingBuffer__Init
|
|
@@ -4,11 +4,19 @@
|
|
| 4 |
//! `Gfx0.dir`). It provides nameβresource lookup via a 1024-bucket hash table
|
| 5 |
//! and delegates file I/O and caching to vtable methods.
|
| 6 |
|
|
|
|
|
|
|
|
|
|
| 7 |
use openwa_core::vtable;
|
| 8 |
|
| 9 |
use crate::address::va;
|
|
|
|
|
|
|
|
|
|
| 10 |
use crate::rebase::rb;
|
| 11 |
use crate::wa_alloc::{wa_malloc, wa_malloc_struct_zeroed};
|
|
|
|
|
|
|
| 12 |
|
| 13 |
/// Cache slot within a GfxDir (0x10 bytes).
|
| 14 |
///
|
|
@@ -143,10 +151,7 @@ unsafe fn gfx_dir_hash(name: *const u8) -> u32 {
|
|
| 143 |
/// `gfx_dir` must be a valid GfxHandler with initialized hash table at +0x04.
|
| 144 |
/// `name` must be a valid null-terminated C string.
|
| 145 |
#[cfg(target_arch = "x86")]
|
| 146 |
-
pub unsafe fn gfx_dir_find_entry(
|
| 147 |
-
name: *const core::ffi::c_char,
|
| 148 |
-
gfx_dir: *const GfxDir,
|
| 149 |
-
) -> *mut u8 {
|
| 150 |
let mut current_name = name as *const u8;
|
| 151 |
|
| 152 |
loop {
|
|
@@ -215,7 +220,7 @@ pub unsafe fn gfx_dir_find_entry(
|
|
| 215 |
}
|
| 216 |
|
| 217 |
if match_found {
|
| 218 |
-
return entry
|
| 219 |
}
|
| 220 |
|
| 221 |
entry = (*entry).next;
|
|
@@ -448,94 +453,86 @@ pub unsafe extern "thiscall" fn gfx_dir_release(this: *mut GfxDir, flags: u32) {
|
|
| 448 |
}
|
| 449 |
}
|
| 450 |
|
| 451 |
-
/// Pure Rust implementation of
|
| 452 |
///
|
| 453 |
-
/// Convention: usercall(ECX=gfx_dir, EAX=name) + 1 stack(
|
| 454 |
///
|
| 455 |
-
/// Looks up `name` in the
|
| 456 |
-
///
|
|
|
|
| 457 |
///
|
| 458 |
/// # Safety
|
| 459 |
/// All pointers must be valid WA objects.
|
| 460 |
#[cfg(target_arch = "x86")]
|
| 461 |
-
pub unsafe fn
|
| 462 |
gfx_dir: *mut GfxDir,
|
| 463 |
-
name: *const
|
| 464 |
-
|
| 465 |
-
) -> *mut
|
| 466 |
-
|
|
|
|
|
|
|
| 467 |
let entry = gfx_dir_find_entry(name, gfx_dir);
|
| 468 |
if !entry.is_null() {
|
| 469 |
-
|
| 470 |
-
let entry_val = (*(entry as *const GfxDirEntry)).value;
|
| 471 |
let cached = GfxDir::load_cached_raw(gfx_dir, entry_val);
|
| 472 |
if !cached.is_null() {
|
| 473 |
-
|
| 474 |
-
let ctor: unsafe extern "stdcall" fn(*mut u8) -> *mut u8 =
|
| 475 |
-
core::mem::transmute(rb(va::DISPLAYGFX_CONSTRUCTOR) as usize);
|
| 476 |
-
return ctor(cached);
|
| 477 |
}
|
| 478 |
}
|
| 479 |
|
| 480 |
-
// 2. Fallback: LoadImage β IMG_Decode
|
| 481 |
let raw_image = gfx_dir_load_image(gfx_dir, name);
|
| 482 |
if raw_image.is_null() {
|
| 483 |
return core::ptr::null_mut();
|
| 484 |
}
|
| 485 |
|
| 486 |
-
|
| 487 |
-
|
| 488 |
-
core::
|
| 489 |
-
|
| 490 |
-
|
| 491 |
GfxDirStream::destroy_raw(raw_image);
|
| 492 |
|
| 493 |
result
|
| 494 |
}
|
| 495 |
|
| 496 |
/// Helper: find entry in GfxDir and load image, or load directly.
|
| 497 |
-
/// Returns a DisplayGfx/sprite pointer or null.
|
| 498 |
#[cfg(target_arch = "x86")]
|
| 499 |
pub(crate) unsafe fn call_gfx_find_and_load(
|
| 500 |
gfx_dir: *mut GfxDir,
|
| 501 |
-
name: &
|
| 502 |
-
|
| 503 |
-
) ->
|
| 504 |
-
let name_ptr = name.as_ptr()
|
| 505 |
-
let entry = gfx_dir_find_entry(name_ptr
|
| 506 |
|
| 507 |
if !entry.is_null() {
|
| 508 |
-
|
| 509 |
-
let cached = GfxDir::load_cached_raw(gfx_dir, (*(entry as *const GfxDirEntry)).value);
|
| 510 |
if !cached.is_null() {
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
return
|
| 516 |
}
|
| 517 |
}
|
| 518 |
|
| 519 |
// Fallback: load image directly
|
| 520 |
-
call_gfx_load_and_wrap(gfx_dir, name_ptr.cast(),
|
| 521 |
}
|
| 522 |
|
| 523 |
-
/// Helper: load image via GfxDir__LoadImage +
|
| 524 |
-
/// Used by arrow sprite loop when GfxDir__FindEntry returns null.
|
| 525 |
#[cfg(target_arch = "x86")]
|
| 526 |
pub(crate) unsafe fn call_gfx_load_and_wrap(
|
| 527 |
gfx_dir: *mut GfxDir,
|
| 528 |
-
name: *const
|
| 529 |
-
|
| 530 |
-
) ->
|
| 531 |
let image = gfx_dir_load_image(gfx_dir, name);
|
| 532 |
if image.is_null() {
|
| 533 |
-
return
|
| 534 |
}
|
| 535 |
-
|
| 536 |
-
let f: unsafe extern "stdcall" fn(*mut u8, *mut u8, u32) -> *mut u8 =
|
| 537 |
-
core::mem::transmute(rb(va::IMG_DECODE) as usize);
|
| 538 |
-
let result = f(display_ctx as *mut u8, image as *mut u8, 1);
|
| 539 |
GfxDirStream::destroy_raw(image);
|
| 540 |
result
|
| 541 |
}
|
|
@@ -806,10 +803,7 @@ pub unsafe extern "thiscall" fn gfx_dir_stream_read(
|
|
| 806 |
/// creates a GfxDirStream for sequential reading, and returns it.
|
| 807 |
/// Returns null if no free slots or name not found.
|
| 808 |
#[cfg(target_arch = "x86")]
|
| 809 |
-
pub unsafe fn gfx_dir_load_image(
|
| 810 |
-
gfx_dir: *mut GfxDir,
|
| 811 |
-
name: *const core::ffi::c_char,
|
| 812 |
-
) -> *mut GfxDirStream {
|
| 813 |
// Check if there are free cache slots
|
| 814 |
if (*gfx_dir).slot_count == 0 {
|
| 815 |
return core::ptr::null_mut();
|
|
|
|
| 4 |
//! `Gfx0.dir`). It provides nameβresource lookup via a 1024-bucket hash table
|
| 5 |
//! and delegates file I/O and caching to vtable methods.
|
| 6 |
|
| 7 |
+
#[cfg(target_arch = "x86")]
|
| 8 |
+
use core::ffi::{c_char, CStr};
|
| 9 |
+
|
| 10 |
use openwa_core::vtable;
|
| 11 |
|
| 12 |
use crate::address::va;
|
| 13 |
+
#[cfg(target_arch = "x86")]
|
| 14 |
+
use crate::asset::img::DecodedBitGrid;
|
| 15 |
+
use crate::bitgrid::BitGrid;
|
| 16 |
use crate::rebase::rb;
|
| 17 |
use crate::wa_alloc::{wa_malloc, wa_malloc_struct_zeroed};
|
| 18 |
+
#[cfg(target_arch = "x86")]
|
| 19 |
+
use crate::{asset, render::palette::PaletteContext};
|
| 20 |
|
| 21 |
/// Cache slot within a GfxDir (0x10 bytes).
|
| 22 |
///
|
|
|
|
| 151 |
/// `gfx_dir` must be a valid GfxHandler with initialized hash table at +0x04.
|
| 152 |
/// `name` must be a valid null-terminated C string.
|
| 153 |
#[cfg(target_arch = "x86")]
|
| 154 |
+
pub unsafe fn gfx_dir_find_entry(name: *const c_char, gfx_dir: *const GfxDir) -> *mut GfxDirEntry {
|
|
|
|
|
|
|
|
|
|
| 155 |
let mut current_name = name as *const u8;
|
| 156 |
|
| 157 |
loop {
|
|
|
|
| 220 |
}
|
| 221 |
|
| 222 |
if match_found {
|
| 223 |
+
return entry;
|
| 224 |
}
|
| 225 |
|
| 226 |
entry = (*entry).next;
|
|
|
|
| 453 |
}
|
| 454 |
}
|
| 455 |
|
| 456 |
+
/// Pure Rust implementation of IMG__LoadFromDir (0x4F6300).
|
| 457 |
///
|
| 458 |
+
/// Convention: usercall(ECX=gfx_dir, EAX=name) + 1 stack(palette_ctx), RET 0x4.
|
| 459 |
///
|
| 460 |
+
/// Looks up `name` in the GfxDir archive's hash table. If found in cache,
|
| 461 |
+
/// decodes via `img_decode_cached`. Otherwise loads from the archive stream
|
| 462 |
+
/// and decodes via `img_decode`.
|
| 463 |
///
|
| 464 |
/// # Safety
|
| 465 |
/// All pointers must be valid WA objects.
|
| 466 |
#[cfg(target_arch = "x86")]
|
| 467 |
+
pub unsafe fn img_load_from_dir(
|
| 468 |
gfx_dir: *mut GfxDir,
|
| 469 |
+
name: *const c_char,
|
| 470 |
+
palette_ctx: *mut PaletteContext,
|
| 471 |
+
) -> *mut BitGrid {
|
| 472 |
+
use crate::asset::img::{img_decode, img_decode_cached};
|
| 473 |
+
|
| 474 |
+
// 1. Try FindEntry β cached load β decode from raw buffer
|
| 475 |
let entry = gfx_dir_find_entry(name, gfx_dir);
|
| 476 |
if !entry.is_null() {
|
| 477 |
+
let entry_val = (*entry).value;
|
|
|
|
| 478 |
let cached = GfxDir::load_cached_raw(gfx_dir, entry_val);
|
| 479 |
if !cached.is_null() {
|
| 480 |
+
return img_decode_cached(palette_ctx, cached) as *mut BitGrid;
|
|
|
|
|
|
|
|
|
|
| 481 |
}
|
| 482 |
}
|
| 483 |
|
| 484 |
+
// 2. Fallback: LoadImage β IMG_Decode from stream
|
| 485 |
let raw_image = gfx_dir_load_image(gfx_dir, name);
|
| 486 |
if raw_image.is_null() {
|
| 487 |
return core::ptr::null_mut();
|
| 488 |
}
|
| 489 |
|
| 490 |
+
let result = match img_decode(palette_ctx, raw_image, 1) {
|
| 491 |
+
Some(decoded) => decoded.as_bitgrid_ptr(),
|
| 492 |
+
None => core::ptr::null_mut(),
|
| 493 |
+
};
|
|
|
|
| 494 |
GfxDirStream::destroy_raw(raw_image);
|
| 495 |
|
| 496 |
result
|
| 497 |
}
|
| 498 |
|
| 499 |
/// Helper: find entry in GfxDir and load image, or load directly.
|
|
|
|
| 500 |
#[cfg(target_arch = "x86")]
|
| 501 |
pub(crate) unsafe fn call_gfx_find_and_load(
|
| 502 |
gfx_dir: *mut GfxDir,
|
| 503 |
+
name: &CStr,
|
| 504 |
+
palette_ctx: *mut PaletteContext,
|
| 505 |
+
) -> Option<DecodedBitGrid> {
|
| 506 |
+
let name_ptr = name.as_ptr();
|
| 507 |
+
let entry = gfx_dir_find_entry(name_ptr, gfx_dir);
|
| 508 |
|
| 509 |
if !entry.is_null() {
|
| 510 |
+
let cached = GfxDir::load_cached_raw(gfx_dir, (*entry).value);
|
|
|
|
| 511 |
if !cached.is_null() {
|
| 512 |
+
let grid = asset::img::img_decode_cached(palette_ctx, cached);
|
| 513 |
+
if !grid.is_null() {
|
| 514 |
+
return Some(DecodedBitGrid::Display(grid));
|
| 515 |
+
}
|
| 516 |
+
return None;
|
| 517 |
}
|
| 518 |
}
|
| 519 |
|
| 520 |
// Fallback: load image directly
|
| 521 |
+
call_gfx_load_and_wrap(gfx_dir, name_ptr.cast(), palette_ctx)
|
| 522 |
}
|
| 523 |
|
| 524 |
+
/// Helper: load image via GfxDir__LoadImage + IMG_Decode.
|
|
|
|
| 525 |
#[cfg(target_arch = "x86")]
|
| 526 |
pub(crate) unsafe fn call_gfx_load_and_wrap(
|
| 527 |
gfx_dir: *mut GfxDir,
|
| 528 |
+
name: *const c_char,
|
| 529 |
+
palette_ctx: *mut PaletteContext,
|
| 530 |
+
) -> Option<DecodedBitGrid> {
|
| 531 |
let image = gfx_dir_load_image(gfx_dir, name);
|
| 532 |
if image.is_null() {
|
| 533 |
+
return None;
|
| 534 |
}
|
| 535 |
+
let result = asset::img::img_decode(palette_ctx, image, 1);
|
|
|
|
|
|
|
|
|
|
| 536 |
GfxDirStream::destroy_raw(image);
|
| 537 |
result
|
| 538 |
}
|
|
|
|
| 803 |
/// creates a GfxDirStream for sequential reading, and returns it.
|
| 804 |
/// Returns null if no free slots or name not found.
|
| 805 |
#[cfg(target_arch = "x86")]
|
| 806 |
+
pub unsafe fn gfx_dir_load_image(gfx_dir: *mut GfxDir, name: *const c_char) -> *mut GfxDirStream {
|
|
|
|
|
|
|
|
|
|
| 807 |
// Check if there are free cache slots
|
| 808 |
if (*gfx_dir).slot_count == 0 {
|
| 809 |
return core::ptr::null_mut();
|
|
@@ -0,0 +1,293 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//! IMG format decoder β `.img` image files in `.dir` archives.
|
| 2 |
+
//!
|
| 3 |
+
//! WA uses a custom "IMG" format for static graphics (gradients, masks,
|
| 4 |
+
//! HUD elements). Format: magic "IMG\x1A", header with flags, optional
|
| 5 |
+
//! palette, width/height, then raw or LZSS-compressed pixel data.
|
| 6 |
+
//!
|
| 7 |
+
//! Two entry points:
|
| 8 |
+
//! - `img_decode` β reads from a `GfxDirStream` (stream-based path)
|
| 9 |
+
//! - `img_decode_cached` β reads from a raw memory buffer (cached path)
|
| 10 |
+
|
| 11 |
+
use crate::asset::gfx_dir::GfxDirStream;
|
| 12 |
+
use crate::bitgrid::{
|
| 13 |
+
BitGrid, CollisionBitGrid, DisplayBitGrid, BIT_GRID_COLLISION_VTABLE, BIT_GRID_DISPLAY_VTABLE,
|
| 14 |
+
};
|
| 15 |
+
use crate::rebase::rb;
|
| 16 |
+
use crate::render::palette::{palette_map_color, remap_pixels_through_lut, PaletteContext};
|
| 17 |
+
use crate::render::sprite::lzss::sprite_lzss_decode;
|
| 18 |
+
use crate::wa_alloc::wa_malloc_zeroed;
|
| 19 |
+
|
| 20 |
+
/// IMG file magic: "IMG\x1A" as little-endian u32.
|
| 21 |
+
const IMG_MAGIC: u32 = 0x1A47_4D49;
|
| 22 |
+
|
| 23 |
+
/// IMG header flags.
|
| 24 |
+
const FLAG_HAS_PALETTE: u16 = 0x8000;
|
| 25 |
+
const FLAG_LZSS_COMPRESSED: u16 = 0x4000;
|
| 26 |
+
const FLAG_EXTENDED_HEADER: u16 = 0x3FF6;
|
| 27 |
+
|
| 28 |
+
// βββ Global sprite palette byte counter (0x7A0870) ββββββββββββββββββββββββββ
|
| 29 |
+
// IMG_Decode and DisplayGfx__Constructor both add palette_count * 3 to this.
|
| 30 |
+
crate::define_addresses! {
|
| 31 |
+
global G_SPRITE_PALETTE_BYTES = 0x007A_0870;
|
| 32 |
+
/// Global temp buffer for LZSS decompression (large static array in WA.exe)
|
| 33 |
+
global G_LZSS_TEMP_BUFFER = 0x006B_39C8;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
pub enum DecodedBitGrid {
|
| 37 |
+
Display(*mut DisplayBitGrid),
|
| 38 |
+
Collision(*mut CollisionBitGrid),
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
impl DecodedBitGrid {
|
| 42 |
+
/// Get the underlying pointer as `*mut BitGrid` (common base type).
|
| 43 |
+
/// Valid because all BitGrid variants share the same `#[repr(C)]` layout.
|
| 44 |
+
pub fn as_bitgrid_ptr(self) -> *mut BitGrid {
|
| 45 |
+
match self {
|
| 46 |
+
DecodedBitGrid::Display(p) => p as *mut BitGrid,
|
| 47 |
+
DecodedBitGrid::Collision(p) => p as *mut BitGrid,
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
/// Pure Rust implementation of IMG_Decode (0x4F5F80).
|
| 53 |
+
///
|
| 54 |
+
/// Reads an IMG image from a `GfxDirStream`, decodes it into a BitGrid
|
| 55 |
+
/// (DisplayBitGrid for 8bpp or CollisionBitGrid for 1bpp).
|
| 56 |
+
///
|
| 57 |
+
/// Convention: stdcall(palette_ctx, stream, align_flag), RET 0xC.
|
| 58 |
+
///
|
| 59 |
+
/// # Safety
|
| 60 |
+
/// All pointers must be valid WA objects. Must be called from within WA.exe.
|
| 61 |
+
#[cfg(target_arch = "x86")]
|
| 62 |
+
pub unsafe fn img_decode(
|
| 63 |
+
palette_ctx: *mut PaletteContext,
|
| 64 |
+
stream: *mut GfxDirStream,
|
| 65 |
+
align_flag: i32,
|
| 66 |
+
) -> Option<DecodedBitGrid> {
|
| 67 |
+
// ββ 1. Read and validate magic ββ
|
| 68 |
+
let mut magic: u32 = 0;
|
| 69 |
+
GfxDirStream::read_raw(stream, &raw mut magic as *mut u8, 4);
|
| 70 |
+
if magic != IMG_MAGIC {
|
| 71 |
+
return None;
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
// ββ 2. Read data_size (used for LZSS remaining calculation) ββ
|
| 75 |
+
let mut data_size: u32 = 0;
|
| 76 |
+
GfxDirStream::read_raw(stream, &raw mut data_size as *mut u8, 4);
|
| 77 |
+
|
| 78 |
+
// ββ 3. Read flags ββ
|
| 79 |
+
let mut flags: u16 = 0;
|
| 80 |
+
GfxDirStream::read_raw(stream, &raw mut flags as *mut u8, 2);
|
| 81 |
+
|
| 82 |
+
// ββ 4. Handle extended header (rare) ββ
|
| 83 |
+
if (flags & FLAG_EXTENDED_HEADER) != 0 {
|
| 84 |
+
// Seek back before flags, skip bytes until null terminator, re-read flags
|
| 85 |
+
let pos = GfxDirStream::bytes_consumed_raw(stream);
|
| 86 |
+
GfxDirStream::seek_raw(stream, pos.wrapping_sub(2));
|
| 87 |
+
loop {
|
| 88 |
+
let mut b: u8 = 0;
|
| 89 |
+
GfxDirStream::read_raw(stream, &raw mut b, 1);
|
| 90 |
+
if b == 0 {
|
| 91 |
+
break;
|
| 92 |
+
}
|
| 93 |
+
}
|
| 94 |
+
GfxDirStream::read_raw(stream, &raw mut flags as *mut u8, 2);
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
// ββ 5. Build palette LUT ββ
|
| 98 |
+
let mut palette_lut = [0u8; 256];
|
| 99 |
+
if (flags & FLAG_HAS_PALETTE) != 0 {
|
| 100 |
+
let mut palette_count: u16 = 0;
|
| 101 |
+
GfxDirStream::read_raw(stream, &raw mut palette_count as *mut u8, 2);
|
| 102 |
+
|
| 103 |
+
// Read RGB triplets into temp buffer
|
| 104 |
+
let rgb_byte_count = palette_count as u32 * 3;
|
| 105 |
+
let mut rgb_buf = [0u8; 256 * 3]; // max 256 entries * 3 bytes
|
| 106 |
+
GfxDirStream::read_raw(stream, rgb_buf.as_mut_ptr(), rgb_byte_count);
|
| 107 |
+
|
| 108 |
+
// Map each RGB triplet through PaletteContext to build LUT
|
| 109 |
+
// lut[0] = 0 (transparent), lut[1+i] = MapColor(RGB[i])
|
| 110 |
+
for i in 0..palette_count as usize {
|
| 111 |
+
// Read as u32 from the RGB bytes (only low 3 bytes matter)
|
| 112 |
+
let r = rgb_buf[i * 3] as u32;
|
| 113 |
+
let g = rgb_buf[i * 3 + 1] as u32;
|
| 114 |
+
let b = rgb_buf[i * 3 + 2] as u32;
|
| 115 |
+
let rgb = r | (g << 8) | (b << 16);
|
| 116 |
+
palette_lut[i + 1] = palette_map_color(palette_ctx, rgb) as u8;
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
// Update global palette byte counter
|
| 120 |
+
let counter = rb(G_SPRITE_PALETTE_BYTES) as *mut u32;
|
| 121 |
+
*counter = (*counter).wrapping_add(rgb_byte_count);
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
// ββ 6. Read width and height ββ
|
| 125 |
+
let mut width: u16 = 0;
|
| 126 |
+
let mut height: u16 = 0;
|
| 127 |
+
GfxDirStream::read_raw(stream, &raw mut width as *mut u8, 2);
|
| 128 |
+
GfxDirStream::read_raw(stream, &raw mut height as *mut u8, 2);
|
| 129 |
+
|
| 130 |
+
// ββ 7. Determine bpp from flags low byte ββ
|
| 131 |
+
let bpp = (flags & 0xFF) as u8;
|
| 132 |
+
if bpp != 1 && bpp != 8 {
|
| 133 |
+
return None;
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
// ββ 8. Allocate BitGrid (0x4C bytes, matching original allocation size) ββ
|
| 137 |
+
let grid = wa_malloc_zeroed(0x4C) as *mut BitGrid;
|
| 138 |
+
if grid.is_null() {
|
| 139 |
+
return None;
|
| 140 |
+
}
|
| 141 |
+
BitGrid::init(grid, bpp as u32, width as u32, height as u32);
|
| 142 |
+
|
| 143 |
+
// Override vtable based on bpp
|
| 144 |
+
if bpp == 8 {
|
| 145 |
+
(*(grid as *mut DisplayBitGrid)).vtable =
|
| 146 |
+
rb(BIT_GRID_DISPLAY_VTABLE) as *const crate::bitgrid::BitGridDisplayVtable;
|
| 147 |
+
} else {
|
| 148 |
+
(*(grid as *mut CollisionBitGrid)).vtable =
|
| 149 |
+
rb(BIT_GRID_COLLISION_VTABLE) as *const crate::bitgrid::BitGridCollisionVtable;
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
let pixel_data = (*grid).data;
|
| 153 |
+
let row_stride = (*grid).row_stride;
|
| 154 |
+
|
| 155 |
+
// ββ 9. Alignment padding ββ
|
| 156 |
+
if align_flag != 0 {
|
| 157 |
+
loop {
|
| 158 |
+
let pos = GfxDirStream::bytes_consumed_raw(stream);
|
| 159 |
+
if (pos & 3) == 0 {
|
| 160 |
+
break;
|
| 161 |
+
}
|
| 162 |
+
let mut dummy: u8 = 0;
|
| 163 |
+
GfxDirStream::read_raw(stream, &raw mut dummy, 1);
|
| 164 |
+
}
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
// ββ 10. Read pixel data ββ
|
| 168 |
+
if (flags & FLAG_LZSS_COMPRESSED) != 0 {
|
| 169 |
+
// LZSS path: read remaining stream data into temp buffer, then decompress.
|
| 170 |
+
// sprite_lzss_decode applies the palette LUT during decompression,
|
| 171 |
+
// so no separate remap step is needed afterward.
|
| 172 |
+
let pos = GfxDirStream::bytes_consumed_raw(stream);
|
| 173 |
+
let remaining = data_size.wrapping_sub(pos);
|
| 174 |
+
|
| 175 |
+
// Use WA's global LZSS temp buffer
|
| 176 |
+
let lzss_buffer = rb(G_LZSS_TEMP_BUFFER) as *mut u8;
|
| 177 |
+
GfxDirStream::read_raw(stream, lzss_buffer, remaining);
|
| 178 |
+
|
| 179 |
+
sprite_lzss_decode(pixel_data, lzss_buffer, palette_lut.as_ptr());
|
| 180 |
+
} else {
|
| 181 |
+
// Row-by-row path: read raw pixel data
|
| 182 |
+
let bits_per_row = width as u32 * bpp as u32;
|
| 183 |
+
let bytes_per_row = (bits_per_row + 7) / 8;
|
| 184 |
+
|
| 185 |
+
for row in 0..height as u32 {
|
| 186 |
+
let row_ptr = pixel_data.add((row * row_stride) as usize);
|
| 187 |
+
GfxDirStream::read_raw(stream, row_ptr, bytes_per_row);
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
// Remap through palette LUT (only for non-LZSS, since LZSS applies the LUT inline)
|
| 191 |
+
if (flags & FLAG_HAS_PALETTE) != 0 && bpp == 8 {
|
| 192 |
+
let dwords_per_row = row_stride / 4;
|
| 193 |
+
remap_pixels_through_lut(
|
| 194 |
+
pixel_data,
|
| 195 |
+
row_stride,
|
| 196 |
+
palette_lut.as_ptr(),
|
| 197 |
+
dwords_per_row,
|
| 198 |
+
height as u32,
|
| 199 |
+
);
|
| 200 |
+
}
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
if bpp == 8 {
|
| 204 |
+
Some(DecodedBitGrid::Display(grid as *mut DisplayBitGrid))
|
| 205 |
+
} else {
|
| 206 |
+
Some(DecodedBitGrid::Collision(grid as *mut CollisionBitGrid))
|
| 207 |
+
}
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
/// Pure Rust implementation of DisplayGfx__Constructor (0x4F5E80).
|
| 211 |
+
///
|
| 212 |
+
/// Reads from a raw memory buffer (cached `.dir` entry). Always creates an
|
| 213 |
+
/// 8bpp DisplayBitGrid. Format: raw_image+0x0A has palette_count, RGB data,
|
| 214 |
+
/// width, height, then pixel data.
|
| 215 |
+
///
|
| 216 |
+
/// In the original WA code, the PaletteContext is passed implicitly via EBX
|
| 217 |
+
/// (callee-saved register from the caller). Our Rust API takes it explicitly.
|
| 218 |
+
///
|
| 219 |
+
/// # Safety
|
| 220 |
+
/// `raw_image` must be a valid pointer to cached image data.
|
| 221 |
+
/// `palette_ctx` must be a valid PaletteContext pointer (from the caller).
|
| 222 |
+
#[cfg(target_arch = "x86")]
|
| 223 |
+
pub unsafe fn img_decode_cached(
|
| 224 |
+
palette_ctx: *mut PaletteContext,
|
| 225 |
+
raw_image: *mut u8,
|
| 226 |
+
) -> *mut DisplayBitGrid {
|
| 227 |
+
use crate::wa_alloc::wa_malloc_struct_zeroed;
|
| 228 |
+
|
| 229 |
+
let mut ptr = raw_image.add(0x0A);
|
| 230 |
+
|
| 231 |
+
// ββ 1. Read palette count and build LUT ββ
|
| 232 |
+
let palette_count = *(ptr as *const u16) as usize;
|
| 233 |
+
ptr = ptr.add(2);
|
| 234 |
+
|
| 235 |
+
let mut palette_lut = [0u8; 256];
|
| 236 |
+
// palette_lut[0] = 0 (transparent, already zero)
|
| 237 |
+
|
| 238 |
+
for i in 0..palette_count {
|
| 239 |
+
// Read 3 RGB bytes as a u32 (4th byte is junk but MapColor only uses low 24 bits)
|
| 240 |
+
let rgb = *(ptr as *const u32);
|
| 241 |
+
palette_lut[i + 1] = palette_map_color(palette_ctx, rgb) as u8;
|
| 242 |
+
ptr = ptr.add(3);
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
// Update global palette byte counter
|
| 246 |
+
let counter = rb(G_SPRITE_PALETTE_BYTES) as *mut u32;
|
| 247 |
+
*counter = (*counter).wrapping_add(palette_count as u32 * 3);
|
| 248 |
+
|
| 249 |
+
// ββ 2. Read width and height ββ
|
| 250 |
+
let width = *(ptr as *const u16) as u32;
|
| 251 |
+
let height = *(ptr.add(2) as *const u16) as u32;
|
| 252 |
+
ptr = ptr.add(2); // advance past width (height pointer computed differently)
|
| 253 |
+
|
| 254 |
+
// ββ 3. Allocate DisplayBitGrid (0x4C bytes, 8bpp only) ββ
|
| 255 |
+
let grid = wa_malloc_struct_zeroed::<DisplayBitGrid>();
|
| 256 |
+
if grid.is_null() {
|
| 257 |
+
return core::ptr::null_mut();
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
// ββ 4. Compute pixel data pointer (4-byte aligned after width+height) ββ
|
| 261 |
+
// Original: (ptr - raw_image + 5) & ~3 + raw_image
|
| 262 |
+
// ptr is at raw_image + 0x0C + palette_count * 3 + 2
|
| 263 |
+
let offset = ptr.offset_from(raw_image) as usize + 5; // +5 accounts for remaining header
|
| 264 |
+
let aligned_offset = (offset & !3) as usize;
|
| 265 |
+
let pixel_data = raw_image.add(aligned_offset);
|
| 266 |
+
|
| 267 |
+
// ββ 5. Set up fields manually (matching original, not via BitGrid::init) ββ
|
| 268 |
+
(*grid).vtable = rb(BIT_GRID_DISPLAY_VTABLE) as *const crate::bitgrid::BitGridDisplayVtable;
|
| 269 |
+
(*grid).cells_per_unit = 8;
|
| 270 |
+
(*grid).width = width;
|
| 271 |
+
(*grid).height = height;
|
| 272 |
+
(*grid).row_stride = width; // row_stride = width for 8bpp uncompressed
|
| 273 |
+
(*grid).data = pixel_data;
|
| 274 |
+
(*grid).clip_left = 0;
|
| 275 |
+
(*grid).clip_top = 0;
|
| 276 |
+
(*grid).clip_right = width;
|
| 277 |
+
(*grid).clip_bottom = height;
|
| 278 |
+
(*grid).external_buffer = 1; // pixel data is owned by the cached buffer, not the grid
|
| 279 |
+
|
| 280 |
+
// ββ 6. Remap pixels through palette LUT ββ
|
| 281 |
+
if (*grid).cells_per_unit == 8 {
|
| 282 |
+
let dwords_per_row = width / 4;
|
| 283 |
+
remap_pixels_through_lut(
|
| 284 |
+
pixel_data,
|
| 285 |
+
width,
|
| 286 |
+
palette_lut.as_ptr(),
|
| 287 |
+
dwords_per_row,
|
| 288 |
+
height,
|
| 289 |
+
);
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
grid
|
| 293 |
+
}
|
|
@@ -1 +1,2 @@
|
|
| 1 |
pub mod gfx_dir;
|
|
|
|
|
|
| 1 |
pub mod gfx_dir;
|
| 2 |
+
pub mod img;
|
|
@@ -3,7 +3,7 @@ use crate::audio::dssound::DSSound;
|
|
| 3 |
use crate::audio::music::Music;
|
| 4 |
use crate::audio::speech::SpeechSlotTable;
|
| 5 |
use crate::audio::SoundQueueEntry;
|
| 6 |
-
use crate::bitgrid::{CollisionBitGrid, DisplayBitGrid};
|
| 7 |
use crate::engine::game_info::GameInfo;
|
| 8 |
use crate::engine::{CoordEntry, CoordList, TeamArena, TeamIndexMap};
|
| 9 |
use crate::fixed::Fixed;
|
|
@@ -61,12 +61,12 @@ pub struct DDGame {
|
|
| 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
|
| 65 |
-
pub gradient_image: *mut
|
| 66 |
-
/// 0x034: Gradient image 2 pointer
|
| 67 |
-
pub gradient_image_2: *mut
|
| 68 |
-
/// 0x038-0x0B4: Arrow sprite
|
| 69 |
-
pub arrow_sprites: [*mut
|
| 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).
|
|
|
|
| 3 |
use crate::audio::music::Music;
|
| 4 |
use crate::audio::speech::SpeechSlotTable;
|
| 5 |
use crate::audio::SoundQueueEntry;
|
| 6 |
+
use crate::bitgrid::{BitGrid, CollisionBitGrid, DisplayBitGrid};
|
| 7 |
use crate::engine::game_info::GameInfo;
|
| 8 |
use crate::engine::{CoordEntry, CoordList, TeamArena, TeamIndexMap};
|
| 9 |
use crate::fixed::Fixed;
|
|
|
|
| 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 (decoded from gradient.img via IMG_Decode).
|
| 65 |
+
pub gradient_image: *mut BitGrid,
|
| 66 |
+
/// 0x034: Gradient image 2 pointer.
|
| 67 |
+
pub gradient_image_2: *mut BitGrid,
|
| 68 |
+
/// 0x038-0x0B4: Arrow sprite BitGrid pointers (32 entries, decoded from arrow*.img).
|
| 69 |
+
pub arrow_sprites: [*mut BitGrid; 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).
|
|
@@ -12,7 +12,8 @@ use crate::address::va;
|
|
| 12 |
use crate::asset::gfx_dir::{
|
| 13 |
call_gfx_find_and_load, call_gfx_load_and_wrap, gfx_dir_load_dir, GfxDir, GfxDirVtable,
|
| 14 |
};
|
| 15 |
-
pub use crate::asset::gfx_dir::{gfx_dir_find_entry,
|
|
|
|
| 16 |
use crate::audio::active_sound::ActiveSoundTable;
|
| 17 |
use crate::audio::dssound::DSSound;
|
| 18 |
use crate::audio::music::Music;
|
|
@@ -173,7 +174,7 @@ unsafe fn wa_init_version_flags(wrapper: *mut DDGameWrapper) {
|
|
| 173 |
/// GfxHandler__LoadSprites (0x570B50): usercall(ESI=layer_ctx) + stdcall(4 params).
|
| 174 |
///
|
| 175 |
/// ESI must hold the display layer context (from DisplayGfx::set_active_layer).
|
| 176 |
-
/// The function uses ESI for LoadSpriteFromVfs and
|
| 177 |
/// when param4 (gfx_dir) is non-null.
|
| 178 |
#[cfg(target_arch = "x86")]
|
| 179 |
#[unsafe(naked)]
|
|
@@ -253,7 +254,7 @@ unsafe fn load_effect_wavs(wrapper: *mut DDGameWrapper) {
|
|
| 253 |
unsafe fn wa_pc_landscape_ctor(
|
| 254 |
this: *mut u8,
|
| 255 |
ddgame: *mut DDGame,
|
| 256 |
-
gfx_resource: *mut
|
| 257 |
display: *mut DisplayGfx,
|
| 258 |
landscape_data: *const u8,
|
| 259 |
byte_output: *mut u8,
|
|
@@ -266,7 +267,7 @@ unsafe fn wa_pc_landscape_ctor(
|
|
| 266 |
let f: unsafe extern "stdcall" fn(
|
| 267 |
*mut u8,
|
| 268 |
*mut DDGame,
|
| 269 |
-
*mut
|
| 270 |
*mut DisplayGfx,
|
| 271 |
*const u8,
|
| 272 |
*mut u8,
|
|
@@ -291,12 +292,13 @@ unsafe fn wa_pc_landscape_ctor(
|
|
| 291 |
)
|
| 292 |
}
|
| 293 |
|
| 294 |
-
///
|
| 295 |
#[cfg(target_arch = "x86")]
|
| 296 |
-
unsafe fn wa_displaygfx_ctor(
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
|
|
|
| 300 |
}
|
| 301 |
|
| 302 |
/// DDGame__InitDisplayFinal (0x56A830): finalize display for non-headless mode.
|
|
@@ -545,27 +547,27 @@ unsafe fn init_graphics_and_resources(
|
|
| 545 |
|
| 546 |
// ββ Display vtable slot 5 (offset 0x14) ββ
|
| 547 |
// Original: CALL EAX (vtable[5]), saves return value in ESI for use as
|
| 548 |
-
// the `output` parameter in the color-entries
|
| 549 |
let layer_ctx = (*(*ddgame).display).set_active_layer(1);
|
| 550 |
|
| 551 |
// ββ GfxDir color entries DDGame+0x730C..0x732C ββ
|
| 552 |
-
// Original logic: if gfx_mode!=0, try
|
| 553 |
// If gfx_mode==0 OR resource creation fails, fall back to LoadSprites.
|
| 554 |
// The fallback's 4th param is primary_gfx_dir when gfx_mode==0, or 0 on resource fail.
|
| 555 |
if (*wrapper).gfx_mode != 0 {
|
| 556 |
-
let res =
|
| 557 |
(*wrapper).primary_gfx_dir,
|
| 558 |
rb(va::STR_COLOURS_IMG) as *const core::ffi::c_char,
|
| 559 |
layer_ctx,
|
| 560 |
);
|
| 561 |
if !res.is_null() {
|
| 562 |
let rvt = *(res as *const *const u32);
|
| 563 |
-
let get_color: unsafe extern "thiscall" fn(*mut
|
| 564 |
core::mem::transmute(*rvt.add(4));
|
| 565 |
for i in 0..9u32 {
|
| 566 |
(*ddgame).gfx_color_table[i as usize] = get_color(res, i, 0);
|
| 567 |
}
|
| 568 |
-
let release: unsafe extern "thiscall" fn(*mut
|
| 569 |
core::mem::transmute(*rvt.add(3));
|
| 570 |
release(res, 1);
|
| 571 |
} else {
|
|
@@ -638,16 +640,16 @@ unsafe fn init_graphics_and_resources(
|
|
| 638 |
// ββ GfxResource for masks.img ββ
|
| 639 |
// The original constructor uses a stack-local PaletteContext buffer (ESP+0x1A4)
|
| 640 |
// initialized at the start with (word=1, word=0xFE, PaletteContext__Init).
|
| 641 |
-
// This same buffer is reused as the output param for
|
| 642 |
// We replicate this initialization here. Size 0x900 matches the stack allocation.
|
| 643 |
-
let gfx_resource: *mut
|
| 644 |
{
|
| 645 |
let gfx_dir = (*wrapper).primary_gfx_dir;
|
| 646 |
let palette_ctx = wa_malloc_zeroed(0x900) as *mut crate::render::palette::PaletteContext;
|
| 647 |
(*palette_ctx).dirty_range_min = 1;
|
| 648 |
(*palette_ctx).dirty_range_max = 0xFE;
|
| 649 |
crate::render::palette::palette_context_init(palette_ctx);
|
| 650 |
-
gfx_resource =
|
| 651 |
gfx_dir,
|
| 652 |
rb(va::STR_MASKS_IMG) as *const core::ffi::c_char,
|
| 653 |
palette_ctx as *mut crate::render::palette::PaletteContext,
|
|
@@ -659,7 +661,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 659 |
let use_orig = std::env::var("OPENWA_USE_ORIG_CTOR").is_ok();
|
| 660 |
let tag = if use_orig { "orig" } else { "rust" };
|
| 661 |
// Dump GfxResource object + first sub-object
|
| 662 |
-
let gr_data = core::slice::from_raw_parts(gfx_resource, 0x100);
|
| 663 |
let _ = std::fs::write(format!("gfx_resource_{}.bin", tag), gr_data);
|
| 664 |
}
|
| 665 |
|
|
@@ -707,7 +709,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 707 |
// SpriteRegion__Constructor: fastcall(ECX, EDX) + 6 stack(this, p2, p3, p4, p5, p6), RET 0x18
|
| 708 |
{
|
| 709 |
// (array_index, ECX, EDX, p2, p3, p4, p5, p6=gfx_resource)
|
| 710 |
-
// p6 is the GfxResource pointer returned by
|
| 711 |
let gr = gfx_resource as u32;
|
| 712 |
let regions: [(usize, u32, u32, u32, u32, u32, u32, u32); 8] = [
|
| 713 |
(2, 0x37, 0x36, 0x2E, 0x24, 0x41, 0x2D, gr),
|
|
@@ -756,22 +758,27 @@ unsafe fn init_graphics_and_resources(
|
|
| 756 |
|
| 757 |
let entry = gfx_dir_find_entry(name_buf.as_ptr().cast(), gfx_dir);
|
| 758 |
|
| 759 |
-
let
|
| 760 |
-
if !entry.is_null() {
|
| 761 |
let entry_val = *(entry.add(4) as *const u32);
|
| 762 |
let cached = GfxDir::load_cached_raw(gfx_dir, entry_val);
|
| 763 |
if !cached.is_null() {
|
| 764 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 765 |
} else {
|
| 766 |
-
|
| 767 |
-
sprite = call_gfx_load_and_wrap(gfx_dir, name_buf.as_ptr().cast(), layer_ctx);
|
| 768 |
}
|
| 769 |
} else {
|
| 770 |
-
|
| 771 |
-
|
| 772 |
-
}
|
| 773 |
|
| 774 |
// Store arrow sprite at DDGame+0x38+i*4
|
|
|
|
|
|
|
|
|
|
| 775 |
(*ddgame).arrow_sprites[i as usize] = sprite;
|
| 776 |
|
| 777 |
// Calculate collision region dimensions from sprite.
|
|
@@ -780,9 +787,8 @@ unsafe fn init_graphics_and_resources(
|
|
| 780 |
// right/bottom = dim - margin
|
| 781 |
// Result: this[5]=this[6]=10 for sprites >20px (the 10px inset).
|
| 782 |
if !sprite.is_null() {
|
| 783 |
-
let
|
| 784 |
-
let
|
| 785 |
-
let sprite_h = grid.height as i32;
|
| 786 |
let margin_w = (sprite_w / 2 - 10).max(0);
|
| 787 |
let margin_h = (sprite_h / 2 - 10).max(0);
|
| 788 |
|
|
@@ -807,12 +813,12 @@ unsafe fn init_graphics_and_resources(
|
|
| 807 |
|
| 808 |
// Arrow GfxDir (conditional on secondary gfxdir)
|
| 809 |
if !(*ddgame).secondary_palette_ctx.is_null() {
|
| 810 |
-
let
|
| 811 |
*mut GfxDir,
|
| 812 |
*mut u8,
|
| 813 |
-
) -> *mut u8 = core::mem::transmute(rb(va::
|
| 814 |
(*ddgame).arrow_gfxdirs[i as usize] =
|
| 815 |
-
|
| 816 |
}
|
| 817 |
}
|
| 818 |
}
|
|
@@ -821,7 +827,8 @@ unsafe fn init_graphics_and_resources(
|
|
| 821 |
// vtable[3] = DisplayGfx__vmethod_3: thiscall(this, byte param_2), RET 4.
|
| 822 |
if !gfx_resource.is_null() {
|
| 823 |
let rvt = *(gfx_resource as *const *const u32);
|
| 824 |
-
let release: unsafe extern "thiscall" fn(*mut
|
|
|
|
| 825 |
release(gfx_resource, 1);
|
| 826 |
}
|
| 827 |
|
|
@@ -956,8 +963,10 @@ unsafe fn init_graphics_and_resources(
|
|
| 956 |
|
| 957 |
if s_var1 < 0x61 && level_height == 0x2B8 {
|
| 958 |
// Simple gradient: load gradient.img directly
|
| 959 |
-
|
| 960 |
-
|
|
|
|
|
|
|
| 961 |
} else {
|
| 962 |
compute_complex_gradient(ddgame, land_dir, layer3_ctx, s_var1);
|
| 963 |
}
|
|
@@ -965,19 +974,18 @@ unsafe fn init_graphics_and_resources(
|
|
| 965 |
// ββ Fill image β fill_pixel (0x7338) ββ
|
| 966 |
{
|
| 967 |
let layer2_ctx = (*(*ddgame).display).set_active_layer(2);
|
| 968 |
-
// In the original, fill.img uses piStack_126c which the decompiler
|
| 969 |
-
// shows was set from piVar3 (water_layer from landscape+0xB38).
|
| 970 |
let fill_sprite = call_gfx_find_and_load(water_dir, c"fill.img", layer2_ctx);
|
| 971 |
-
if
|
| 972 |
-
|
| 973 |
-
|
| 974 |
-
let
|
|
|
|
| 975 |
core::mem::transmute(*fill_vt.add(4));
|
| 976 |
-
(*ddgame).fill_pixel = get_pixel(
|
| 977 |
-
// Release
|
| 978 |
-
let release: unsafe extern "thiscall" fn(*mut
|
| 979 |
core::mem::transmute(*fill_vt.add(3));
|
| 980 |
-
release(
|
| 981 |
}
|
| 982 |
}
|
| 983 |
|
|
@@ -1006,7 +1014,7 @@ unsafe fn init_graphics_and_resources(
|
|
| 1006 |
if !obj.is_null() {
|
| 1007 |
(*obj).vtable = rb(va::BIT_GRID_BASE_VTABLE) as *const BitGridBaseVtable;
|
| 1008 |
// height = 0 β CTaskLand skips the gradient column loop
|
| 1009 |
-
(*ddgame).gradient_image = obj
|
| 1010 |
}
|
| 1011 |
}
|
| 1012 |
|
|
|
|
| 12 |
use crate::asset::gfx_dir::{
|
| 13 |
call_gfx_find_and_load, call_gfx_load_and_wrap, gfx_dir_load_dir, GfxDir, GfxDirVtable,
|
| 14 |
};
|
| 15 |
+
pub use crate::asset::gfx_dir::{gfx_dir_find_entry, img_load_from_dir};
|
| 16 |
+
use crate::asset::img::DecodedBitGrid;
|
| 17 |
use crate::audio::active_sound::ActiveSoundTable;
|
| 18 |
use crate::audio::dssound::DSSound;
|
| 19 |
use crate::audio::music::Music;
|
|
|
|
| 174 |
/// GfxHandler__LoadSprites (0x570B50): usercall(ESI=layer_ctx) + stdcall(4 params).
|
| 175 |
///
|
| 176 |
/// ESI must hold the display layer context (from DisplayGfx::set_active_layer).
|
| 177 |
+
/// The function uses ESI for LoadSpriteFromVfs and IMG__LoadFromDir
|
| 178 |
/// when param4 (gfx_dir) is non-null.
|
| 179 |
#[cfg(target_arch = "x86")]
|
| 180 |
#[unsafe(naked)]
|
|
|
|
| 254 |
unsafe fn wa_pc_landscape_ctor(
|
| 255 |
this: *mut u8,
|
| 256 |
ddgame: *mut DDGame,
|
| 257 |
+
gfx_resource: *mut crate::bitgrid::BitGrid,
|
| 258 |
display: *mut DisplayGfx,
|
| 259 |
landscape_data: *const u8,
|
| 260 |
byte_output: *mut u8,
|
|
|
|
| 267 |
let f: unsafe extern "stdcall" fn(
|
| 268 |
*mut u8,
|
| 269 |
*mut DDGame,
|
| 270 |
+
*mut crate::bitgrid::BitGrid,
|
| 271 |
*mut DisplayGfx,
|
| 272 |
*const u8,
|
| 273 |
*mut u8,
|
|
|
|
| 292 |
)
|
| 293 |
}
|
| 294 |
|
| 295 |
+
/// Decode a cached raw image buffer into a BitGrid (0x4F5E80).
|
| 296 |
#[cfg(target_arch = "x86")]
|
| 297 |
+
unsafe fn wa_displaygfx_ctor(
|
| 298 |
+
palette_ctx: *mut crate::render::palette::PaletteContext,
|
| 299 |
+
raw_image: *mut u8,
|
| 300 |
+
) -> *mut BitGrid {
|
| 301 |
+
crate::asset::img::img_decode_cached(palette_ctx, raw_image) as *mut BitGrid
|
| 302 |
}
|
| 303 |
|
| 304 |
/// DDGame__InitDisplayFinal (0x56A830): finalize display for non-headless mode.
|
|
|
|
| 547 |
|
| 548 |
// ββ Display vtable slot 5 (offset 0x14) ββ
|
| 549 |
// Original: CALL EAX (vtable[5]), saves return value in ESI for use as
|
| 550 |
+
// the `output` parameter in the color-entries IMG__LoadFromDir call below.
|
| 551 |
let layer_ctx = (*(*ddgame).display).set_active_layer(1);
|
| 552 |
|
| 553 |
// ββ GfxDir color entries DDGame+0x730C..0x732C ββ
|
| 554 |
+
// Original logic: if gfx_mode!=0, try IMG__LoadFromDir for colours.img.
|
| 555 |
// If gfx_mode==0 OR resource creation fails, fall back to LoadSprites.
|
| 556 |
// The fallback's 4th param is primary_gfx_dir when gfx_mode==0, or 0 on resource fail.
|
| 557 |
if (*wrapper).gfx_mode != 0 {
|
| 558 |
+
let res = img_load_from_dir(
|
| 559 |
(*wrapper).primary_gfx_dir,
|
| 560 |
rb(va::STR_COLOURS_IMG) as *const core::ffi::c_char,
|
| 561 |
layer_ctx,
|
| 562 |
);
|
| 563 |
if !res.is_null() {
|
| 564 |
let rvt = *(res as *const *const u32);
|
| 565 |
+
let get_color: unsafe extern "thiscall" fn(*mut BitGrid, u32, u32) -> u32 =
|
| 566 |
core::mem::transmute(*rvt.add(4));
|
| 567 |
for i in 0..9u32 {
|
| 568 |
(*ddgame).gfx_color_table[i as usize] = get_color(res, i, 0);
|
| 569 |
}
|
| 570 |
+
let release: unsafe extern "thiscall" fn(*mut BitGrid, u8) =
|
| 571 |
core::mem::transmute(*rvt.add(3));
|
| 572 |
release(res, 1);
|
| 573 |
} else {
|
|
|
|
| 640 |
// ββ GfxResource for masks.img ββ
|
| 641 |
// The original constructor uses a stack-local PaletteContext buffer (ESP+0x1A4)
|
| 642 |
// initialized at the start with (word=1, word=0xFE, PaletteContext__Init).
|
| 643 |
+
// This same buffer is reused as the output param for IMG__LoadFromDir(masks.img).
|
| 644 |
// We replicate this initialization here. Size 0x900 matches the stack allocation.
|
| 645 |
+
let gfx_resource: *mut crate::bitgrid::BitGrid;
|
| 646 |
{
|
| 647 |
let gfx_dir = (*wrapper).primary_gfx_dir;
|
| 648 |
let palette_ctx = wa_malloc_zeroed(0x900) as *mut crate::render::palette::PaletteContext;
|
| 649 |
(*palette_ctx).dirty_range_min = 1;
|
| 650 |
(*palette_ctx).dirty_range_max = 0xFE;
|
| 651 |
crate::render::palette::palette_context_init(palette_ctx);
|
| 652 |
+
gfx_resource = img_load_from_dir(
|
| 653 |
gfx_dir,
|
| 654 |
rb(va::STR_MASKS_IMG) as *const core::ffi::c_char,
|
| 655 |
palette_ctx as *mut crate::render::palette::PaletteContext,
|
|
|
|
| 661 |
let use_orig = std::env::var("OPENWA_USE_ORIG_CTOR").is_ok();
|
| 662 |
let tag = if use_orig { "orig" } else { "rust" };
|
| 663 |
// Dump GfxResource object + first sub-object
|
| 664 |
+
let gr_data = core::slice::from_raw_parts(gfx_resource as *const u8, 0x100);
|
| 665 |
let _ = std::fs::write(format!("gfx_resource_{}.bin", tag), gr_data);
|
| 666 |
}
|
| 667 |
|
|
|
|
| 709 |
// SpriteRegion__Constructor: fastcall(ECX, EDX) + 6 stack(this, p2, p3, p4, p5, p6), RET 0x18
|
| 710 |
{
|
| 711 |
// (array_index, ECX, EDX, p2, p3, p4, p5, p6=gfx_resource)
|
| 712 |
+
// p6 is the GfxResource pointer returned by IMG__LoadFromDir.
|
| 713 |
let gr = gfx_resource as u32;
|
| 714 |
let regions: [(usize, u32, u32, u32, u32, u32, u32, u32); 8] = [
|
| 715 |
(2, 0x37, 0x36, 0x2E, 0x24, 0x41, 0x2D, gr),
|
|
|
|
| 758 |
|
| 759 |
let entry = gfx_dir_find_entry(name_buf.as_ptr().cast(), gfx_dir);
|
| 760 |
|
| 761 |
+
let decoded = if !entry.is_null() {
|
|
|
|
| 762 |
let entry_val = *(entry.add(4) as *const u32);
|
| 763 |
let cached = GfxDir::load_cached_raw(gfx_dir, entry_val);
|
| 764 |
if !cached.is_null() {
|
| 765 |
+
let grid = wa_displaygfx_ctor(layer_ctx, cached);
|
| 766 |
+
if !grid.is_null() {
|
| 767 |
+
Some(DecodedBitGrid::Display(grid as *mut DisplayBitGrid))
|
| 768 |
+
} else {
|
| 769 |
+
None
|
| 770 |
+
}
|
| 771 |
} else {
|
| 772 |
+
call_gfx_load_and_wrap(gfx_dir, name_buf.as_ptr().cast(), layer_ctx)
|
|
|
|
| 773 |
}
|
| 774 |
} else {
|
| 775 |
+
call_gfx_load_and_wrap(gfx_dir, name_buf.as_ptr().cast(), layer_ctx)
|
| 776 |
+
};
|
|
|
|
| 777 |
|
| 778 |
// Store arrow sprite at DDGame+0x38+i*4
|
| 779 |
+
let sprite = decoded
|
| 780 |
+
.map(|d| d.as_bitgrid_ptr())
|
| 781 |
+
.unwrap_or(core::ptr::null_mut());
|
| 782 |
(*ddgame).arrow_sprites[i as usize] = sprite;
|
| 783 |
|
| 784 |
// Calculate collision region dimensions from sprite.
|
|
|
|
| 787 |
// right/bottom = dim - margin
|
| 788 |
// Result: this[5]=this[6]=10 for sprites >20px (the 10px inset).
|
| 789 |
if !sprite.is_null() {
|
| 790 |
+
let sprite_w = (*sprite).width as i32;
|
| 791 |
+
let sprite_h = (*sprite).height as i32;
|
|
|
|
| 792 |
let margin_w = (sprite_w / 2 - 10).max(0);
|
| 793 |
let margin_h = (sprite_h / 2 - 10).max(0);
|
| 794 |
|
|
|
|
| 813 |
|
| 814 |
// Arrow GfxDir (conditional on secondary gfxdir)
|
| 815 |
if !(*ddgame).secondary_palette_ctx.is_null() {
|
| 816 |
+
let wa_img_load_from_dir: unsafe extern "thiscall" fn(
|
| 817 |
*mut GfxDir,
|
| 818 |
*mut u8,
|
| 819 |
+
) -> *mut u8 = core::mem::transmute(rb(va::IMG_LOAD_FROM_DIR) as usize);
|
| 820 |
(*ddgame).arrow_gfxdirs[i as usize] =
|
| 821 |
+
wa_img_load_from_dir(gfx_dir, core::ptr::null_mut());
|
| 822 |
}
|
| 823 |
}
|
| 824 |
}
|
|
|
|
| 827 |
// vtable[3] = DisplayGfx__vmethod_3: thiscall(this, byte param_2), RET 4.
|
| 828 |
if !gfx_resource.is_null() {
|
| 829 |
let rvt = *(gfx_resource as *const *const u32);
|
| 830 |
+
let release: unsafe extern "thiscall" fn(*mut crate::bitgrid::BitGrid, u8) =
|
| 831 |
+
core::mem::transmute(*rvt.add(3));
|
| 832 |
release(gfx_resource, 1);
|
| 833 |
}
|
| 834 |
|
|
|
|
| 963 |
|
| 964 |
if s_var1 < 0x61 && level_height == 0x2B8 {
|
| 965 |
// Simple gradient: load gradient.img directly
|
| 966 |
+
(*ddgame).gradient_image =
|
| 967 |
+
call_gfx_find_and_load(land_dir, c"gradient.img", layer3_ctx)
|
| 968 |
+
.map(|d| d.as_bitgrid_ptr())
|
| 969 |
+
.unwrap_or(core::ptr::null_mut());
|
| 970 |
} else {
|
| 971 |
compute_complex_gradient(ddgame, land_dir, layer3_ctx, s_var1);
|
| 972 |
}
|
|
|
|
| 974 |
// ββ Fill image β fill_pixel (0x7338) ββ
|
| 975 |
{
|
| 976 |
let layer2_ctx = (*(*ddgame).display).set_active_layer(2);
|
|
|
|
|
|
|
| 977 |
let fill_sprite = call_gfx_find_and_load(water_dir, c"fill.img", layer2_ctx);
|
| 978 |
+
if let Some(decoded) = fill_sprite {
|
| 979 |
+
let ptr = decoded.as_bitgrid_ptr();
|
| 980 |
+
// Get pixel value: vtable[4](0, 0)
|
| 981 |
+
let fill_vt = *(ptr as *const *const u32);
|
| 982 |
+
let get_pixel: unsafe extern "thiscall" fn(*mut BitGrid, i32, i32) -> u32 =
|
| 983 |
core::mem::transmute(*fill_vt.add(4));
|
| 984 |
+
(*ddgame).fill_pixel = get_pixel(ptr, 0, 0);
|
| 985 |
+
// Release: vtable[3](this, flags=1)
|
| 986 |
+
let release: unsafe extern "thiscall" fn(*mut BitGrid, u8) =
|
| 987 |
core::mem::transmute(*fill_vt.add(3));
|
| 988 |
+
release(ptr, 1);
|
| 989 |
}
|
| 990 |
}
|
| 991 |
|
|
|
|
| 1014 |
if !obj.is_null() {
|
| 1015 |
(*obj).vtable = rb(va::BIT_GRID_BASE_VTABLE) as *const BitGridBaseVtable;
|
| 1016 |
// height = 0 β CTaskLand skips the gradient column loop
|
| 1017 |
+
(*ddgame).gradient_image = obj;
|
| 1018 |
}
|
| 1019 |
}
|
| 1020 |
|
|
@@ -117,18 +117,19 @@ pub(crate) unsafe fn compute_complex_gradient(
|
|
| 117 |
let mut palette = PaletteContext::new();
|
| 118 |
|
| 119 |
// Step 1: Load gradient.img
|
| 120 |
-
let
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
|
|
|
| 124 |
|
| 125 |
// Sprite height from BitGrid layout (shared vtable 0x6640EC)
|
| 126 |
-
let gradient_height = (*
|
| 127 |
if gradient_height <= 0 {
|
| 128 |
return;
|
| 129 |
}
|
| 130 |
|
| 131 |
-
let get_pixel: unsafe extern "thiscall" fn(*mut
|
| 132 |
core::mem::transmute(*(*(gradient_sprite as *const *const u32)).add(4));
|
| 133 |
|
| 134 |
// Step 2: Compute target rows and stretch gradient through palette
|
|
@@ -146,8 +147,9 @@ pub(crate) unsafe fn compute_complex_gradient(
|
|
| 146 |
|
| 147 |
// Step 3: If heights match, also set gradient_image_2
|
| 148 |
if target_rows == gradient_height {
|
| 149 |
-
|
| 150 |
-
|
|
|
|
| 151 |
}
|
| 152 |
|
| 153 |
// Step 4: Sample 7 anchor colors by averaging 8Γ2 pixel blocks.
|
|
@@ -184,7 +186,7 @@ pub(crate) unsafe fn compute_complex_gradient(
|
|
| 184 |
|
| 185 |
// Release the gradient sprite
|
| 186 |
let gvt = *(gradient_sprite as *const *const u32);
|
| 187 |
-
let release: unsafe extern "thiscall" fn(*mut
|
| 188 |
release(gradient_sprite, 1);
|
| 189 |
|
| 190 |
// Step 5: Establish initial/fallback color via palette closest-match.
|
|
@@ -256,5 +258,5 @@ pub(crate) unsafe fn compute_complex_gradient(
|
|
| 256 |
(*gfx_obj).row_stride = stride;
|
| 257 |
(*gfx_obj).width = 0;
|
| 258 |
(*gfx_obj).height = total_height as u32;
|
| 259 |
-
(*ddgame).gradient_image = gfx_obj
|
| 260 |
}
|
|
|
|
| 117 |
let mut palette = PaletteContext::new();
|
| 118 |
|
| 119 |
// Step 1: Load gradient.img
|
| 120 |
+
let gradient_decoded = call_gfx_find_and_load(land_dir, c"gradient.img", layer3_ctx);
|
| 121 |
+
let gradient_sprite = match gradient_decoded {
|
| 122 |
+
Some(d) => d.as_bitgrid_ptr(),
|
| 123 |
+
None => return,
|
| 124 |
+
};
|
| 125 |
|
| 126 |
// Sprite height from BitGrid layout (shared vtable 0x6640EC)
|
| 127 |
+
let gradient_height = (*gradient_sprite).height as i32;
|
| 128 |
if gradient_height <= 0 {
|
| 129 |
return;
|
| 130 |
}
|
| 131 |
|
| 132 |
+
let get_pixel: unsafe extern "thiscall" fn(*mut BitGrid, i32, i32) -> u32 =
|
| 133 |
core::mem::transmute(*(*(gradient_sprite as *const *const u32)).add(4));
|
| 134 |
|
| 135 |
// Step 2: Compute target rows and stretch gradient through palette
|
|
|
|
| 147 |
|
| 148 |
// Step 3: If heights match, also set gradient_image_2
|
| 149 |
if target_rows == gradient_height {
|
| 150 |
+
(*ddgame).gradient_image_2 = call_gfx_find_and_load(land_dir, c"gradient.img", layer3_ctx)
|
| 151 |
+
.map(|d| d.as_bitgrid_ptr())
|
| 152 |
+
.unwrap_or(core::ptr::null_mut());
|
| 153 |
}
|
| 154 |
|
| 155 |
// Step 4: Sample 7 anchor colors by averaging 8Γ2 pixel blocks.
|
|
|
|
| 186 |
|
| 187 |
// Release the gradient sprite
|
| 188 |
let gvt = *(gradient_sprite as *const *const u32);
|
| 189 |
+
let release: unsafe extern "thiscall" fn(*mut BitGrid, u8) = core::mem::transmute(*gvt.add(3));
|
| 190 |
release(gradient_sprite, 1);
|
| 191 |
|
| 192 |
// Step 5: Establish initial/fallback color via palette closest-match.
|
|
|
|
| 258 |
(*gfx_obj).row_stride = stride;
|
| 259 |
(*gfx_obj).width = 0;
|
| 260 |
(*gfx_obj).height = total_height as u32;
|
| 261 |
+
(*ddgame).gradient_image = gfx_obj;
|
| 262 |
}
|
|
@@ -8,14 +8,14 @@
|
|
| 8 |
//! - `DDGame__InitFields` (0x526120): usercall(EDI=ddgame), plain RET β EAX=ddgame
|
| 9 |
//! - `DDGame__InitRenderIndices` (0x526080): usercall(ESI=base), plain RET β EAX=base
|
| 10 |
//! - `BitGrid__Init` (0x4F6370): usercall(ESI,ECX,EDI) + 1 stack, RET 0x4
|
| 11 |
-
//! - `
|
| 12 |
//! - `FUN_570E20` (display layer init): usercall(ESI=wrapper), plain RET
|
| 13 |
|
| 14 |
use crate::hook;
|
| 15 |
use core::ffi::c_char;
|
| 16 |
use openwa_core::address::va;
|
| 17 |
use openwa_core::asset::gfx_dir::{
|
| 18 |
-
gfx_dir_find_entry, gfx_dir_load_dir,
|
| 19 |
};
|
| 20 |
use openwa_core::bitgrid::BitGrid;
|
| 21 |
use openwa_core::engine::game_state_init::{
|
|
@@ -95,9 +95,9 @@ extern "cdecl" fn impl_display_layer_init(wrapper: *mut DDGameWrapper) -> *mut D
|
|
| 95 |
wrapper
|
| 96 |
}
|
| 97 |
|
| 98 |
-
// βββ
|
| 99 |
|
| 100 |
-
extern "cdecl" fn
|
| 101 |
gfx_dir: *mut GfxDir,
|
| 102 |
name: *const c_char,
|
| 103 |
output: *mut u8,
|
|
@@ -106,12 +106,12 @@ extern "cdecl" fn impl_gfx_resource_create(
|
|
| 106 |
// WA caller always passes a `PaletteContext*` (verified at all known
|
| 107 |
// call sites β `set_active_layer`'s return value).
|
| 108 |
let output = output as *mut openwa_core::render::palette::PaletteContext;
|
| 109 |
-
let result = unsafe {
|
| 110 |
result as u32
|
| 111 |
}
|
| 112 |
|
| 113 |
#[unsafe(naked)]
|
| 114 |
-
unsafe extern "C" fn
|
| 115 |
core::arch::naked_asm!(
|
| 116 |
"push edx",
|
| 117 |
"push [esp+8]", // output (stack param)
|
|
@@ -121,7 +121,7 @@ unsafe extern "C" fn gfx_resource_create_trampoline() {
|
|
| 121 |
"add esp, 12",
|
| 122 |
"pop edx",
|
| 123 |
"ret 0x4",
|
| 124 |
-
impl_fn = sym
|
| 125 |
);
|
| 126 |
}
|
| 127 |
|
|
@@ -197,9 +197,9 @@ pub fn install() -> Result<(), String> {
|
|
| 197 |
)?;
|
| 198 |
|
| 199 |
hook::install(
|
| 200 |
-
"
|
| 201 |
-
va::
|
| 202 |
-
|
| 203 |
)?;
|
| 204 |
|
| 205 |
hook::install(
|
|
|
|
| 8 |
//! - `DDGame__InitFields` (0x526120): usercall(EDI=ddgame), plain RET β EAX=ddgame
|
| 9 |
//! - `DDGame__InitRenderIndices` (0x526080): usercall(ESI=base), plain RET β EAX=base
|
| 10 |
//! - `BitGrid__Init` (0x4F6370): usercall(ESI,ECX,EDI) + 1 stack, RET 0x4
|
| 11 |
+
//! - `IMG__LoadFromDir` (0x4F6300): usercall(ECX,EAX) + 1 stack, RET 0x4
|
| 12 |
//! - `FUN_570E20` (display layer init): usercall(ESI=wrapper), plain RET
|
| 13 |
|
| 14 |
use crate::hook;
|
| 15 |
use core::ffi::c_char;
|
| 16 |
use openwa_core::address::va;
|
| 17 |
use openwa_core::asset::gfx_dir::{
|
| 18 |
+
gfx_dir_find_entry, gfx_dir_load_dir, img_load_from_dir, GfxDir,
|
| 19 |
};
|
| 20 |
use openwa_core::bitgrid::BitGrid;
|
| 21 |
use openwa_core::engine::game_state_init::{
|
|
|
|
| 95 |
wrapper
|
| 96 |
}
|
| 97 |
|
| 98 |
+
// βββ IMG__LoadFromDir (0x4F6300) βββββββββββββββββββββββββββββββββββ
|
| 99 |
|
| 100 |
+
extern "cdecl" fn impl_img_load_from_dir(
|
| 101 |
gfx_dir: *mut GfxDir,
|
| 102 |
name: *const c_char,
|
| 103 |
output: *mut u8,
|
|
|
|
| 106 |
// WA caller always passes a `PaletteContext*` (verified at all known
|
| 107 |
// call sites β `set_active_layer`'s return value).
|
| 108 |
let output = output as *mut openwa_core::render::palette::PaletteContext;
|
| 109 |
+
let result = unsafe { img_load_from_dir(gfx_dir, name, output) };
|
| 110 |
result as u32
|
| 111 |
}
|
| 112 |
|
| 113 |
#[unsafe(naked)]
|
| 114 |
+
unsafe extern "C" fn img_load_from_dir_trampoline() {
|
| 115 |
core::arch::naked_asm!(
|
| 116 |
"push edx",
|
| 117 |
"push [esp+8]", // output (stack param)
|
|
|
|
| 121 |
"add esp, 12",
|
| 122 |
"pop edx",
|
| 123 |
"ret 0x4",
|
| 124 |
+
impl_fn = sym impl_img_load_from_dir,
|
| 125 |
);
|
| 126 |
}
|
| 127 |
|
|
|
|
| 197 |
)?;
|
| 198 |
|
| 199 |
hook::install(
|
| 200 |
+
"IMG__LoadFromDir",
|
| 201 |
+
va::IMG_LOAD_FROM_DIR,
|
| 202 |
+
img_load_from_dir_trampoline as *const (),
|
| 203 |
)?;
|
| 204 |
|
| 205 |
hook::install(
|
|
@@ -1,7 +1,6 @@
|
|
| 1 |
-
//! GfxDir
|
| 2 |
//!
|
| 3 |
-
//! Replaces
|
| 4 |
-
//! and hooks GfxDir__LoadImage for any remaining WA callers.
|
| 5 |
|
| 6 |
use core::ffi::c_char;
|
| 7 |
use openwa_core::address::va;
|
|
@@ -10,7 +9,10 @@ use openwa_core::asset::gfx_dir::{
|
|
| 10 |
gfx_dir_stream_bytes_consumed, gfx_dir_stream_destructor, gfx_dir_stream_get_total_size,
|
| 11 |
gfx_dir_stream_has_data, gfx_dir_stream_read, gfx_dir_stream_seek, GfxDir, GfxDirStream,
|
| 12 |
};
|
|
|
|
|
|
|
| 13 |
use openwa_core::log::log_line;
|
|
|
|
| 14 |
|
| 15 |
// βββ GfxDir__LoadImage (0x5666D0) βββββββββββββββββββββββββββββββββββββββββββ
|
| 16 |
// Convention: usercall(ESI=gfx_dir) + 1 stack(name), RET 0x4.
|
|
@@ -33,6 +35,47 @@ unsafe extern "C" fn load_image_trampoline() {
|
|
| 33 |
);
|
| 34 |
}
|
| 35 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
pub fn install() -> Result<(), String> {
|
| 37 |
use openwa_core::vtable_replace;
|
| 38 |
|
|
@@ -58,8 +101,16 @@ pub fn install() -> Result<(), String> {
|
|
| 58 |
va::GFX_DIR_LOAD_IMAGE,
|
| 59 |
load_image_trampoline as *const (),
|
| 60 |
)?;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
}
|
| 62 |
|
| 63 |
-
let _ = log_line("[GfxDir] All vtable methods + LoadImage replaced");
|
| 64 |
Ok(())
|
| 65 |
}
|
|
|
|
| 1 |
+
//! GfxDir, GfxDirStream, and IMG decoder replacements.
|
| 2 |
//!
|
| 3 |
+
//! Replaces vtable methods, LoadImage, IMG_Decode, and DisplayGfx__Constructor.
|
|
|
|
| 4 |
|
| 5 |
use core::ffi::c_char;
|
| 6 |
use openwa_core::address::va;
|
|
|
|
| 9 |
gfx_dir_stream_bytes_consumed, gfx_dir_stream_destructor, gfx_dir_stream_get_total_size,
|
| 10 |
gfx_dir_stream_has_data, gfx_dir_stream_read, gfx_dir_stream_seek, GfxDir, GfxDirStream,
|
| 11 |
};
|
| 12 |
+
use openwa_core::asset::img::{img_decode, img_decode_cached};
|
| 13 |
+
use openwa_core::bitgrid::BitGrid;
|
| 14 |
use openwa_core::log::log_line;
|
| 15 |
+
use openwa_core::render::palette::PaletteContext;
|
| 16 |
|
| 17 |
// βββ GfxDir__LoadImage (0x5666D0) βββββββββββββββββββββββββββββββββββββββββββ
|
| 18 |
// Convention: usercall(ESI=gfx_dir) + 1 stack(name), RET 0x4.
|
|
|
|
| 35 |
);
|
| 36 |
}
|
| 37 |
|
| 38 |
+
// βββ IMG_Decode (0x4F5F80) ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 39 |
+
// Convention: stdcall(palette_ctx, stream, align_flag), RET 0xC.
|
| 40 |
+
|
| 41 |
+
unsafe extern "stdcall" fn img_decode_hook(
|
| 42 |
+
palette_ctx: *mut PaletteContext,
|
| 43 |
+
stream: *mut GfxDirStream,
|
| 44 |
+
align_flag: i32,
|
| 45 |
+
) -> *mut BitGrid {
|
| 46 |
+
match img_decode(palette_ctx, stream, align_flag) {
|
| 47 |
+
Some(decoded) => decoded.as_bitgrid_ptr(),
|
| 48 |
+
None => core::ptr::null_mut(),
|
| 49 |
+
}
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
// βββ DisplayGfx__Constructor (0x4F5E80) βββββββββββββββββββββββββββββββββββββ
|
| 53 |
+
// Convention: stdcall(raw_image), RET 0x4.
|
| 54 |
+
// PaletteContext passed implicitly via EBX (callee-saved from caller).
|
| 55 |
+
|
| 56 |
+
extern "cdecl" fn impl_displaygfx_ctor(
|
| 57 |
+
palette_ctx: *mut PaletteContext,
|
| 58 |
+
raw_image: *mut u8,
|
| 59 |
+
) -> *mut u8 {
|
| 60 |
+
unsafe { img_decode_cached(palette_ctx, raw_image) as *mut u8 }
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
#[unsafe(naked)]
|
| 64 |
+
unsafe extern "C" fn displaygfx_ctor_trampoline() {
|
| 65 |
+
core::arch::naked_asm!(
|
| 66 |
+
"push edx",
|
| 67 |
+
"push ecx",
|
| 68 |
+
"push [esp+12]", // raw_image (stack param, shifted by 2 pushes)
|
| 69 |
+
"push ebx", // palette_ctx (EBX, callee-saved from caller)
|
| 70 |
+
"call {impl_fn}",
|
| 71 |
+
"add esp, 8",
|
| 72 |
+
"pop ecx",
|
| 73 |
+
"pop edx",
|
| 74 |
+
"ret 0x4", // callee cleans 1 stack param
|
| 75 |
+
impl_fn = sym impl_displaygfx_ctor,
|
| 76 |
+
);
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
pub fn install() -> Result<(), String> {
|
| 80 |
use openwa_core::vtable_replace;
|
| 81 |
|
|
|
|
| 101 |
va::GFX_DIR_LOAD_IMAGE,
|
| 102 |
load_image_trampoline as *const (),
|
| 103 |
)?;
|
| 104 |
+
|
| 105 |
+
crate::hook::install("IMG_Decode", va::IMG_DECODE, img_decode_hook as *const ())?;
|
| 106 |
+
|
| 107 |
+
crate::hook::install(
|
| 108 |
+
"DisplayGfx__Constructor",
|
| 109 |
+
va::IMG_DECODE_CACHED,
|
| 110 |
+
displaygfx_ctor_trampoline as *const (),
|
| 111 |
+
)?;
|
| 112 |
}
|
| 113 |
|
| 114 |
+
let _ = log_line("[GfxDir] All vtable methods + LoadImage + IMG_Decode replaced");
|
| 115 |
Ok(())
|
| 116 |
}
|