paavohtl Claude Opus 4.6 (1M context) commited on
Commit
693004d
·
1 Parent(s): 88857ea

feat: port DisplayGfx slots 4 (set_layer_color) and 6 (get_sprite_info)

Browse files

Port two more DisplayGfx vtable methods to pure Rust (20/38 total):

- Slot 4 (set_layer_color): palette slot allocator scans slot_table for
consecutive free entries, allocates and initializes a PaletteContext
with the claimed palette range.
- Slot 6 (get_sprite_info): dispatches to Sprite or SpriteBank paths,
returning sprite metadata (data, flags, width) via output pointers.
Fixed vtable signature from 3 to 4 stack params (RET 0x10).

Type safety improvements:
- Define SpriteBank (0x17C), SpriteBankFrame (0xC) structs
- Add typed SpriteVtable (8 slots) and SpriteBankVtable (3 slots)
- Rename DisplayBase fields: sprite_array_0/1/2 → sprite_palettes/
sprite_ptrs/sprite_banks; _field_312c/352c → slot_table_guard/sentinel
- Move Sprite/SpriteBank/PaletteContext addresses to home modules
- Move SpriteId enum to dedicated sprite_id module
- Delete obsolete dd_display_sprite_offsets and sprite_vtable modules
- Label 7 functions + STR_SPRITE in Ghidra

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

crates/openwa-core/src/address.rs CHANGED
@@ -86,6 +86,16 @@ pub mod va {
86
  pub use crate::input::controller::INPUT_CTRL_VTABLE;
87
  pub use crate::task::game_task::SOUND_EMITTER_VTABLE;
88
 
 
 
 
 
 
 
 
 
 
 
89
  crate::define_addresses! {
90
  class "DDGameWrapper" {
91
  /// DDGameWrapper vtable
@@ -200,21 +210,6 @@ pub mod va {
200
  vtable WATER_EFFECT_VTABLE = 0x0066_B268;
201
  }
202
 
203
- class "Sprite" {
204
- /// Sprite vtable (0x70-byte objects, 8 entries)
205
- vtable SPRITE_VTABLE = 0x0066_418C;
206
- /// ConstructSprite — usercall EAX=sprite_ptr, ECX=context_ptr
207
- ctor/Usercall CONSTRUCT_SPRITE = 0x004F_AA30;
208
- /// Sprite destructor — thiscall, vtable slot 0
209
- fn/Thiscall DESTROY_SPRITE = 0x004F_AA80;
210
- /// LoadSpriteFromVfs
211
- fn/Usercall LOAD_SPRITE_FROM_VFS = 0x004F_AAF0;
212
- /// ProcessSprite — parses .spr binary format
213
- fn/Usercall PROCESS_SPRITE = 0x004F_AB80;
214
- // Note: vtable 0x664144 is BitGrid display-layer vtable,
215
- // now defined via #[vtable] macro as BITGRID_DISPLAY_VTABLE.
216
- }
217
-
218
  class "GfxHandler" {
219
  /// GfxHandler vtable
220
  vtable GFX_DIR_VTABLE = 0x0066_B280;
@@ -228,6 +223,11 @@ pub mod va {
228
  fn GFX_DIR_LOAD_IMAGE = 0x0056_66D0;
229
  }
230
 
 
 
 
 
 
231
  class "DisplayGfx" {
232
  /// DisplayGfx constructor
233
  ctor/Stdcall DISPLAYGFX_CTOR = 0x0056_9C10;
@@ -237,8 +237,13 @@ pub mod va {
237
  fn/Stdcall DISPLAYGFX_CONSTRUCT_FULL = 0x0056_3FC0;
238
  /// DisplayGfx init team palette display objects
239
  fn/Stdcall DISPLAY_GFX_INIT_TEAM_PALETTE_DISPLAY = 0x0057_03E0;
 
 
240
  }
241
 
 
 
 
242
  }
243
 
244
  // Backward-compat aliases (not registered separately — same address)
@@ -473,10 +478,6 @@ pub mod va {
473
  fn OPENGL_INIT = 0x0059_F000;
474
  /// GfxResource__Create_Maybe
475
  fn GFX_RESOURCE_CREATE = 0x004F_6300;
476
- /// PaletteContext__Init
477
- fn/Usercall PALETTE_CONTEXT_INIT = 0x0054_11A0;
478
- /// PaletteContext__MapColor — thiscall(palette_ctx, rgb_u32), returns nearest palette index
479
- fn/Thiscall PALETTE_CONTEXT_MAP_COLOR = 0x0054_12B0;
480
  /// SpriteGfxTable__Init
481
  fn/Fastcall SPRITE_GFX_TABLE_INIT = 0x0054_1620;
482
  /// RingBuffer__Init
 
86
  pub use crate::input::controller::INPUT_CTRL_VTABLE;
87
  pub use crate::task::game_task::SOUND_EMITTER_VTABLE;
88
 
89
+ // Sprite, SpriteBank, PaletteContext — defined alongside their structs
90
+ pub use crate::render::palette::{
91
+ PALETTE_CONTEXT_INIT, PALETTE_CONTEXT_INIT_RANGE, PALETTE_CONTEXT_MAP_COLOR,
92
+ };
93
+ pub use crate::render::sprite::SPRITE_BANK_VTABLE;
94
+ pub use crate::render::sprite::{
95
+ CONSTRUCT_SPRITE, DESTROY_SPRITE, LOAD_SPRITE_FROM_VFS, PROCESS_SPRITE,
96
+ SPRITE_BANK_GET_INFO, SPRITE_GET_INFO, SPRITE_VTABLE,
97
+ };
98
+
99
  crate::define_addresses! {
100
  class "DDGameWrapper" {
101
  /// DDGameWrapper vtable
 
210
  vtable WATER_EFFECT_VTABLE = 0x0066_B268;
211
  }
212
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  class "GfxHandler" {
214
  /// GfxHandler vtable
215
  vtable GFX_DIR_VTABLE = 0x0066_B280;
 
223
  fn GFX_DIR_LOAD_IMAGE = 0x0056_66D0;
224
  }
225
 
226
+ class "DisplayBase" {
227
+ /// DisplayBase__AllocPaletteSlots — usercall EAX=count, 1 stack(this)
228
+ fn/Usercall DISPLAY_BASE_ALLOC_PALETTE_SLOTS = 0x0052_3190;
229
+ }
230
+
231
  class "DisplayGfx" {
232
  /// DisplayGfx constructor
233
  ctor/Stdcall DISPLAYGFX_CTOR = 0x0056_9C10;
 
237
  fn/Stdcall DISPLAYGFX_CONSTRUCT_FULL = 0x0056_3FC0;
238
  /// DisplayGfx init team palette display objects
239
  fn/Stdcall DISPLAY_GFX_INIT_TEAM_PALETTE_DISPLAY = 0x0057_03E0;
240
+ /// DisplayGfx__LoadSpriteEx (vtable slot 30) — thiscall
241
+ fn/Thiscall DISPLAY_GFX_LOAD_SPRITE_EX = 0x0052_3310;
242
  }
243
 
244
+ /// "sprite" type-tag string in .rdata — returned by Sprite/SpriteBank GetInfo
245
+ global STR_SPRITE = 0x0066_4170;
246
+
247
  }
248
 
249
  // Backward-compat aliases (not registered separately — same address)
 
478
  fn OPENGL_INIT = 0x0059_F000;
479
  /// GfxResource__Create_Maybe
480
  fn GFX_RESOURCE_CREATE = 0x004F_6300;
 
 
 
 
481
  /// SpriteGfxTable__Init
482
  fn/Fastcall SPRITE_GFX_TABLE_INIT = 0x0054_1620;
483
  /// RingBuffer__Init
crates/openwa-core/src/display/base.rs CHANGED
@@ -33,13 +33,15 @@ pub struct DisplayBase<V: Vtable = *const DisplayBaseVtable> {
33
  pub vtable: V,
34
  // +0x004: sprite cache wrapper (0x28-byte SpriteCacheWrapper)
35
  pub sprite_cache: Ptr32,
36
- // +0x008..0x1008: sprite pointer array 0 (0x400 entries), zeroed by ctor.
37
- // IsSpriteLoaded checks this at offset 0x1008 (which is array 1 below).
38
- pub sprite_array_0: [u32; 0x400],
39
- // +0x1008..0x2008: sprite pointer array 1 (0x400 entries), zeroed by ctor.
40
- pub sprite_array_1: [u32; 0x400],
41
- // +0x2008..0x3008: sprite pointer array 2 (0x400 entries), zeroed by ctor.
42
- pub sprite_array_2: [u32; 0x400],
 
 
43
  // +0x3008..0x3018: gap (0x10 bytes, 4 u32s: indices 0xC02..0xC05)
44
  pub _gap_3008: [u32; 4],
45
  // +0x3018: field at u32 index 0xC06, zeroed by ctor
@@ -53,12 +55,16 @@ pub struct DisplayBase<V: Vtable = *const DisplayBaseVtable> {
53
  // Indexed by layer (valid range 1-3). Returned by set_active_layer (vtable slot 5).
54
  // Used as palette data input for update_palette (vtable slot 24).
55
  pub layer_contexts: [u32; 4],
56
- // +0x312C: index 0xC4B, zeroed by ctor
57
- pub _field_312c: u32,
58
- // +0x3130..0x352C: slot_table — 0xFF entries, all initialized to 1 by ctor
 
 
 
59
  pub slot_table: [u32; 0xFF],
60
- // +0x352C: index 0xD4B = 0xFFFFFFFF
61
- pub _field_352c: u32,
 
62
  // +0x3530..0x3540: layer visibility flags (4 entries), zeroed by ctor.
63
  // Indexed by layer. Cleared to 0 by set_layer_visibility when visible < 0.
64
  pub layer_visibility: [u32; 4],
@@ -187,8 +193,8 @@ impl DisplayBase {
187
  *slot = 1;
188
  }
189
 
190
- // Sentinel value
191
- (*this)._field_352c = 0xFFFF_FFFF;
192
 
193
  // Create sprite cache: 0x28 wrapper → 0x3C buffer ctrl → 0x80020 buffer.
194
  //
 
33
  pub vtable: V,
34
  // +0x004: sprite cache wrapper (0x28-byte SpriteCacheWrapper)
35
  pub sprite_cache: Ptr32,
36
+ // +0x008..0x1008: per-slot palette/layer ID (0x400 entries), zeroed by ctor.
37
+ // Stores which layer (1-3) each sprite slot belongs to.
38
+ pub sprite_palettes: [u32; 0x400],
39
+ // +0x1008..0x2008: per-slot Sprite* pointers (0x400 entries), zeroed by ctor.
40
+ // Points to Sprite objects (0x70 bytes, vtable 0x66418C). Checked by IsSpriteLoaded/GetSpriteInfo.
41
+ pub sprite_ptrs: [u32; 0x400],
42
+ // +0x2008..0x3008: per-slot SpriteBank* pointers (0x400 entries), zeroed by ctor.
43
+ // Points to SpriteBank objects (0x17C bytes). Fallback path in GetSpriteInfo/LoadSpriteComplex.
44
+ pub sprite_banks: [u32; 0x400],
45
  // +0x3008..0x3018: gap (0x10 bytes, 4 u32s: indices 0xC02..0xC05)
46
  pub _gap_3008: [u32; 4],
47
  // +0x3018: field at u32 index 0xC06, zeroed by ctor
 
55
  // Indexed by layer (valid range 1-3). Returned by set_active_layer (vtable slot 5).
56
  // Used as palette data input for update_palette (vtable slot 24).
57
  pub layer_contexts: [u32; 4],
58
+ // +0x312C: Palette slot table guard. Always 0 — prevents palette_slot_alloc
59
+ // from allocating starting at index 0. Part of the contiguous scan area:
60
+ // [slot_table_guard(0)] [slot_table(1s)...] [slot_table_sentinel(-1)].
61
+ pub slot_table_guard: u32,
62
+ // +0x3130..0x352C: Palette slot table — 0xFF entries, all initialized to 1 by ctor.
63
+ // Value 1 = available, 0 = in-use. Scanned by palette_slot_alloc (set_layer_color).
64
  pub slot_table: [u32; 0xFF],
65
+ // +0x352C: Palette slot table sentinel. Initialized to -1 (0xFFFFFFFF).
66
+ // Terminates the palette_slot_alloc scan with failure if no free slots found.
67
+ pub slot_table_sentinel: u32,
68
  // +0x3530..0x3540: layer visibility flags (4 entries), zeroed by ctor.
69
  // Indexed by layer. Cleared to 0 by set_layer_visibility when visible < 0.
70
  pub layer_visibility: [u32; 4],
 
193
  *slot = 1;
194
  }
195
 
196
+ // Sentinel value — terminates palette_slot_alloc scan
197
+ (*this).slot_table_sentinel = 0xFFFF_FFFF;
198
 
199
  // Create sprite cache: 0x28 wrapper → 0x3C buffer ctrl → 0x80020 buffer.
200
  //
crates/openwa-core/src/display/display_vtable.rs CHANGED
@@ -47,8 +47,19 @@ pub struct DisplayVtable {
47
  #[slot(5)]
48
  pub set_active_layer: fn(this: *mut DisplayGfx, layer: i32) -> *mut u8,
49
  /// get sprite info by layer and id (0x523500, RET 0x10)
 
 
 
 
 
50
  #[slot(6)]
51
- pub get_sprite_info: fn(this: *mut DisplayGfx, layer: i32, p3: u32, p4: u32) -> u32,
 
 
 
 
 
 
52
  /// draw text onto a bitmap surface (0x5236B0, RET 0x1C)
53
  ///
54
  /// font_id low 16 bits = font slot (1-based), high 16 bits = extra flags.
@@ -272,10 +283,12 @@ bind_DisplayVtable!(DisplayGfx, base.vtable);
272
  // Ported DisplayGfx vtable methods
273
  // =========================================================================
274
 
 
275
  use super::gfx::DisplayGfx;
276
  use super::line_draw;
277
  use crate::bitgrid::DisplayBitGrid;
278
  use crate::render::palette::PaletteContext;
 
279
 
280
  /// Port of DisplayGfx::GetDimensions (vtable slot 1, 0x56A460).
281
  ///
@@ -770,7 +783,7 @@ pub unsafe extern "thiscall" fn draw_polyline(
770
  /// Port of DisplayGfx::IsSpriteLoaded (vtable slot 32, 0x56A480).
771
  ///
772
  /// Returns 1 if the sprite ID is loaded in any of the three sprite arrays
773
- /// (DisplayBase sprite_array_1/sprite_array_2, DisplayGfx sprite_table).
774
  /// ID must be in range [1, 0x400).
775
  ///
776
  /// # Safety
@@ -782,8 +795,8 @@ pub unsafe extern "thiscall" fn is_sprite_loaded(this: *mut DisplayGfx, id: i32)
782
  }
783
 
784
  let base = &(*this).base;
785
- if base.sprite_array_1[id as usize] != 0
786
- || base.sprite_array_2[id as usize] != 0
787
  || (*this).sprite_table[id as usize] != 0
788
  {
789
  1
@@ -792,6 +805,132 @@ pub unsafe extern "thiscall" fn is_sprite_loaded(this: *mut DisplayGfx, id: i32)
792
  }
793
  }
794
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
795
  /// Port of DisplayGfx::DrawViaCallback (vtable slot 21, 0x56B7C0).
796
  ///
797
  /// Acquires the render lock, applies camera offset to fixed-point coordinates,
@@ -998,6 +1137,113 @@ pub unsafe extern "thiscall" fn draw_pixel_strip(
998
  }
999
  }
1000
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1001
  /// Port of DisplayGfx::SetActiveLayer (vtable slot 5, 0x523270).
1002
  ///
1003
  /// Returns the layer context pointer for `layer` (valid: 1, 2, 3), or null
 
47
  #[slot(5)]
48
  pub set_active_layer: fn(this: *mut DisplayGfx, layer: i32) -> *mut u8,
49
  /// get sprite info by layer and id (0x523500, RET 0x10)
50
+ ///
51
+ /// Looks up sprite metadata for the given layer ID. Checks sprite_ptrs
52
+ /// (Sprite* pointers) first, then sprite_banks. On success, writes
53
+ /// sprite data, flags, and width to the output pointers.
54
+ /// Returns a pointer to the string "sprite" on success, or 0 on failure.
55
  #[slot(6)]
56
+ pub get_sprite_info: fn(
57
+ this: *mut DisplayGfx,
58
+ layer: i32,
59
+ out_data: *mut u32,
60
+ out_flags: *mut u32,
61
+ out_width: *mut u32,
62
+ ) -> u32,
63
  /// draw text onto a bitmap surface (0x5236B0, RET 0x1C)
64
  ///
65
  /// font_id low 16 bits = font slot (1-based), high 16 bits = extra flags.
 
283
  // Ported DisplayGfx vtable methods
284
  // =========================================================================
285
 
286
+ use super::base::DisplayBase;
287
  use super::gfx::DisplayGfx;
288
  use super::line_draw;
289
  use crate::bitgrid::DisplayBitGrid;
290
  use crate::render::palette::PaletteContext;
291
+ use crate::render::sprite::{Sprite, SpriteBank};
292
 
293
  /// Port of DisplayGfx::GetDimensions (vtable slot 1, 0x56A460).
294
  ///
 
783
  /// Port of DisplayGfx::IsSpriteLoaded (vtable slot 32, 0x56A480).
784
  ///
785
  /// Returns 1 if the sprite ID is loaded in any of the three sprite arrays
786
+ /// (DisplayBase sprite_ptrs/sprite_banks, DisplayGfx sprite_table).
787
  /// ID must be in range [1, 0x400).
788
  ///
789
  /// # Safety
 
795
  }
796
 
797
  let base = &(*this).base;
798
+ if base.sprite_ptrs[id as usize] != 0
799
+ || base.sprite_banks[id as usize] != 0
800
  || (*this).sprite_table[id as usize] != 0
801
  {
802
  1
 
805
  }
806
  }
807
 
808
+ /// Port of DisplayGfx::GetSpriteInfo (vtable slot 6, 0x523500).
809
+ ///
810
+ /// Looks up sprite metadata for `layer` (valid: 1..=0x3FF). Checks
811
+ /// `sprite_ptrs` (Sprite* pointers) first, then `sprite_banks`
812
+ /// (indexed sprite containers). On success, writes data, flags, and
813
+ /// width to the output pointers and returns a pointer to the static
814
+ /// string "sprite" (0x664170). Returns 0 on failure.
815
+ ///
816
+ /// # Safety
817
+ /// `this` must be a valid `*mut DisplayGfx`.
818
+ /// Output pointers must be valid for writing.
819
+ pub unsafe extern "thiscall" fn get_sprite_info(
820
+ this: *mut DisplayGfx,
821
+ layer: i32,
822
+ out_data: *mut u32,
823
+ out_flags: *mut u32,
824
+ out_width: *mut u32,
825
+ ) -> u32 {
826
+ if (layer as u32).wrapping_sub(1) > 0x3FE {
827
+ return 0;
828
+ }
829
+
830
+ let base = &(*this).base;
831
+
832
+ // Path 1: Sprite* in sprite_ptrs
833
+ let sprite_ptr = base.sprite_ptrs[layer as usize];
834
+ if sprite_ptr != 0 {
835
+ return sprite_info_from_sprite(
836
+ sprite_ptr as *const Sprite,
837
+ out_data,
838
+ out_flags,
839
+ out_width,
840
+ );
841
+ }
842
+
843
+ // Path 2: SpriteBank* in sprite_banks
844
+ let bank_ptr = base.sprite_banks[layer as usize];
845
+ if bank_ptr != 0 {
846
+ return sprite_info_from_bank(
847
+ bank_ptr as *const SpriteBank,
848
+ layer,
849
+ out_data,
850
+ out_flags,
851
+ out_width,
852
+ );
853
+ }
854
+
855
+ 0
856
+ }
857
+
858
+ /// Address of the static "sprite" string in WA.exe .rdata.
859
+ /// Used as a type-tag return value by sprite info functions.
860
+ const SPRITE_STRING: u32 = va::STR_SPRITE;
861
+
862
+ /// Extract sprite info from a Sprite object — port of Sprite__GetInfo (0x4FAEC0).
863
+ ///
864
+ /// Usercall: EAX=Sprite*, ESI=out_data, ECX=out_width, stack=out_flags.
865
+ /// Reads frame_meta_ptr (validity check), packed _unknown_08/fps as data,
866
+ /// max_frames as width (doubled-minus-one if flags & 2), and flags & 1.
867
+ unsafe fn sprite_info_from_sprite(
868
+ sprite: *const Sprite,
869
+ out_data: *mut u32,
870
+ out_flags: *mut u32,
871
+ out_width: *mut u32,
872
+ ) -> u32 {
873
+ let s = &*sprite;
874
+
875
+ // Validity check: frame_meta_ptr must be non-null
876
+ if s.frame_meta_ptr.is_null() {
877
+ return 0;
878
+ }
879
+
880
+ // out_data = packed DWORD: _unknown_08 (u16) | fps (u16) << 16
881
+ *out_data = (s._unknown_08 as u32) | ((s.fps as u32) << 16);
882
+
883
+ // Width from max_frames, doubled-minus-one if flags bit 1 set
884
+ let mut width = s.max_frames as u32;
885
+ if s.flags & 2 != 0 {
886
+ width = width * 2 - 1;
887
+ }
888
+ *out_width = width;
889
+
890
+ *out_flags = (s.flags & 1) as u32;
891
+
892
+ crate::rebase::rb(SPRITE_STRING)
893
+ }
894
+
895
+ /// Extract sprite info from a SpriteBank — port of SpriteBank__GetInfo (0x4F98C0).
896
+ ///
897
+ /// Usercall: EAX=layer, ECX=SpriteBank*, ESI=out_width, stack=out_data+out_flags.
898
+ /// Uses the bank's index table to map the layer ID to a frame entry, then
899
+ /// reads width/flags/data from the SpriteBankFrame.
900
+ unsafe fn sprite_info_from_bank(
901
+ bank: *const SpriteBank,
902
+ layer: i32,
903
+ out_data: *mut u32,
904
+ out_flags: *mut u32,
905
+ out_width: *mut u32,
906
+ ) -> u32 {
907
+ let b = &*bank;
908
+
909
+ if b.frame_table.is_null() {
910
+ return 0;
911
+ }
912
+
913
+ let entry_idx = *b.index_table.offset((layer - b.base_id) as isize);
914
+ if entry_idx < 0 || entry_idx >= b.frame_count {
915
+ return 0;
916
+ }
917
+
918
+ let frame = &*b.frame_table.add(entry_idx as usize);
919
+
920
+ *out_data = (frame.data_value as u32) << 8;
921
+ *out_flags = (frame.flags & 1) as u32;
922
+
923
+ if frame.width & 0x8000 != 0 {
924
+ *out_width = 1;
925
+ } else if frame.flags & 2 != 0 {
926
+ *out_width = (frame.width as u32) * 2 - 1;
927
+ } else {
928
+ *out_width = frame.width as u32;
929
+ }
930
+
931
+ crate::rebase::rb(SPRITE_STRING)
932
+ }
933
+
934
  /// Port of DisplayGfx::DrawViaCallback (vtable slot 21, 0x56B7C0).
935
  ///
936
  /// Acquires the render lock, applies camera offset to fixed-point coordinates,
 
1137
  }
1138
  }
1139
 
1140
+ /// Port of DisplayGfx::SetLayerColor (vtable slot 4, 0x5231E0).
1141
+ ///
1142
+ /// Allocates a `PaletteContext` for the given layer (1-3) if one doesn't exist.
1143
+ /// Finds `color` consecutive available entries in the slot table, claims them,
1144
+ /// and initializes a PaletteContext with that palette index range.
1145
+ ///
1146
+ /// # Safety
1147
+ /// `this` must be a valid `*mut DisplayGfx`.
1148
+ pub unsafe extern "thiscall" fn set_layer_color(this: *mut DisplayGfx, layer: i32, color: i32) {
1149
+ // Layer must be 1, 2, or 3
1150
+ if (layer as u32).wrapping_sub(1) >= 3 {
1151
+ return;
1152
+ }
1153
+
1154
+ // Only allocate if no context exists for this layer
1155
+ if (*this).base.layer_contexts[layer as usize] != 0 {
1156
+ return;
1157
+ }
1158
+
1159
+ // Find `color` consecutive available (non-zero) entries in the slot table area.
1160
+ // The scan covers slot_table_guard (guard=0) + slot_table[0..255] + slot_table_sentinel (sentinel=-1).
1161
+ let start = palette_slot_alloc(&mut (*this).base, color);
1162
+
1163
+ // Allocate PaletteContext (0x72C total, zero first 0x70C)
1164
+ let ctx = crate::wa_alloc::wa_malloc(0x72C);
1165
+ core::ptr::write_bytes(ctx, 0, 0x70C);
1166
+
1167
+ let result = if ctx.is_null() {
1168
+ 0u32
1169
+ } else {
1170
+ let ctx = ctx as *mut PaletteContext;
1171
+ palette_context_init(ctx, start as i16, (start as i32 + color - 1) as i16);
1172
+ ctx as u32
1173
+ };
1174
+
1175
+ (*this).base.layer_contexts[layer as usize] = result;
1176
+ (*this).base.layer_visibility[layer as usize] = 0;
1177
+ }
1178
+
1179
+ /// Palette slot allocator — port of FUN_00523190.
1180
+ ///
1181
+ /// Scans the slot table area (this+0x312C) for `count` consecutive entries
1182
+ /// with value > 0 (available). Zeros the found entries to mark them as claimed.
1183
+ /// Returns the start index, or -1 if a negative sentinel is hit.
1184
+ ///
1185
+ /// The scan area is: `slot_table_guard` (guard=0) + `slot_table[0..255]` + `slot_table_sentinel` (sentinel=-1).
1186
+ /// The guard at index 0 is always 0, so allocations start from index 1.
1187
+ /// The sentinel (-1) at index 256 terminates the scan with failure.
1188
+ unsafe fn palette_slot_alloc(base: &mut DisplayBase<*const DisplayVtable>, count: i32) -> i32 {
1189
+ let count = count as usize;
1190
+ let table = &base.slot_table_guard as *const u32;
1191
+ // Total entries: slot_table_guard(1) + slot_table(255) + slot_table_sentinel(1) = 257
1192
+ let total = 1 + 0xFF + 1;
1193
+
1194
+ let mut consecutive = 0usize;
1195
+ let mut scan = 0usize;
1196
+
1197
+ loop {
1198
+ if consecutive == count {
1199
+ let start = scan - count;
1200
+ for i in start..scan {
1201
+ *(table.add(i) as *mut u32) = 0;
1202
+ }
1203
+ return start as i32;
1204
+ }
1205
+ if scan >= total {
1206
+ return -1;
1207
+ }
1208
+ let val = *table.add(scan) as i32;
1209
+ scan += 1;
1210
+ if val == 0 {
1211
+ consecutive = 0;
1212
+ } else if val < 0 {
1213
+ return -1;
1214
+ } else {
1215
+ consecutive += 1;
1216
+ }
1217
+ }
1218
+ }
1219
+
1220
+ /// Initialize a PaletteContext with a palette index range — port of FUN_00541170 + FUN_005411a0.
1221
+ ///
1222
+ /// Sets dirty_range_min/max, fills the free stack with descending indices,
1223
+ /// clears in_use flags and cache, then clears the dirty flag.
1224
+ unsafe fn palette_context_init(ctx: *mut PaletteContext, range_min: i16, range_max: i16) {
1225
+ (*ctx).dirty_range_min = range_min;
1226
+ (*ctx).dirty_range_max = range_max;
1227
+
1228
+ // PaletteContext__Init (0x5411A0)
1229
+ let range_size = range_max - range_min + 1;
1230
+ (*ctx).cache_count = 0;
1231
+ (*ctx).free_count = range_size;
1232
+
1233
+ // Fill free_stack with [range_max, range_max-1, ..., range_min]
1234
+ if range_size > 0 {
1235
+ for i in 0..range_size as usize {
1236
+ (*ctx).free_stack[i] = (range_max as u8).wrapping_sub(i as u8);
1237
+ }
1238
+ }
1239
+
1240
+ (*ctx).cache_iter = 0;
1241
+ core::ptr::write_bytes((*ctx).in_use.as_mut_ptr(), 0, 256);
1242
+
1243
+ // FUN_00541170 epilogue
1244
+ (*ctx).dirty = 0;
1245
+ }
1246
+
1247
  /// Port of DisplayGfx::SetActiveLayer (vtable slot 5, 0x523270).
1248
  ///
1249
  /// Returns the layer context pointer for `layer` (valid: 1, 2, 3), or null
crates/openwa-core/src/render/mod.rs CHANGED
@@ -4,6 +4,7 @@ pub mod palette;
4
  pub mod queue;
5
  pub mod spr;
6
  pub mod sprite;
 
7
  pub mod turn_order;
8
 
9
  pub use landscape::{DirtyRect, PCLandscape};
 
4
  pub mod queue;
5
  pub mod spr;
6
  pub mod sprite;
7
+ pub mod sprite_id;
8
  pub mod turn_order;
9
 
10
  pub use landscape::{DirtyRect, PCLandscape};
crates/openwa-core/src/render/palette.rs CHANGED
@@ -101,3 +101,14 @@ pub unsafe fn palette_map_color(ctx: *mut PaletteContext, rgb: u32) -> u32 {
101
 
102
  slot_idx as u32
103
  }
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
  slot_idx as u32
103
  }
104
+
105
+ crate::define_addresses! {
106
+ class "PaletteContext" {
107
+ /// PaletteContext__Init — usercall EAX=ctx* (no stack params)
108
+ fn/Usercall PALETTE_CONTEXT_INIT = 0x0054_11A0;
109
+ /// PaletteContext__InitRange — usercall ESI=ctx*, 2 stack params (range_min, range_max)
110
+ fn/Usercall PALETTE_CONTEXT_INIT_RANGE = 0x0054_1170;
111
+ /// PaletteContext__MapColor — thiscall(palette_ctx, rgb_u32), returns nearest palette index
112
+ fn/Thiscall PALETTE_CONTEXT_MAP_COLOR = 0x0054_12B0;
113
+ }
114
+ }
crates/openwa-core/src/render/sprite.rs CHANGED
@@ -1,728 +1,7 @@
1
- /// Sprite/animation frame IDs. Range 1-696 with gaps at 620-621.
2
- ///
3
- /// Names preserved from the original sprite file names in the game data.
4
- /// The original names are terse 8-character identifiers from the game's
5
- /// sprite sheets.
6
- ///
7
- /// Source: wkJellyWorm Constants.h
8
- #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9
- #[repr(u32)]
10
- pub enum SpriteId {
11
- Cdrom = 1,
12
- CrshairR = 2,
13
- CrshairB = 3,
14
- CrshairG = 4,
15
- CrshairY = 5,
16
- CrshairP = 6,
17
- CrshairC = 7,
18
- SpangleR = 8,
19
- SpangleB = 9,
20
- SpangleG = 10,
21
- SpangleY = 11,
22
- SpangleP = 12,
23
- SpangleC = 13,
24
- Grave1 = 14,
25
- Grave2 = 15,
26
- Grave3 = 16,
27
- Grave4 = 17,
28
- Grave5 = 18,
29
- Grave6 = 19,
30
- ArrowDnR = 20,
31
- ArrowDnB = 21,
32
- ArrowDnG = 22,
33
- ArrowDnY = 23,
34
- ArrowDnP = 24,
35
- ArrowDnC = 25,
36
- ArrowSdR = 26,
37
- ArrowSdB = 27,
38
- ArrowSdG = 28,
39
- ArrowSdY = 29,
40
- ArrowSdP = 30,
41
- ArrowSdC = 31,
42
- CursorR = 32,
43
- CursorB = 33,
44
- CursorG = 34,
45
- CursorY = 35,
46
- CursorP = 36,
47
- CursorC = 37,
48
- MarkerR = 38,
49
- MarkerB = 39,
50
- MarkerG = 40,
51
- MarkerY = 41,
52
- MarkerP = 42,
53
- MarkerC = 43,
54
- CMarker = 44,
55
- MineOn = 45,
56
- Arrow = 46,
57
- MineOff = 47,
58
- Missile = 48,
59
- Bullet = 49,
60
- Grenade = 50,
61
- Banana = 51,
62
- Mortar = 52,
63
- Cluster = 53,
64
- Clustlet = 54,
65
- PetrolBm = 55,
66
- HGrenade = 56,
67
- Tamborin = 57,
68
- HMissil1 = 58,
69
- HMissil2 = 59,
70
- AirMisl = 60,
71
- SheepFal = 61,
72
- Carpet1 = 62,
73
- Carpet2 = 63,
74
- SheepBrn = 64,
75
- SheepLau = 65,
76
- Meteor = 66,
77
- Drill = 67,
78
- RopeTip = 68,
79
- RopeCuff = 69,
80
- Phone = 70,
81
- MbBomb = 71,
82
- Letter1 = 72,
83
- Letter2 = 73,
84
- Vase = 74,
85
- VaseBit1 = 75,
86
- VaseBit2 = 76,
87
- VaseBit3 = 77,
88
- Dynamite = 78,
89
- FireHit = 79,
90
- KamiSmk = 80,
91
- BaseHit = 81,
92
- Donkey = 82,
93
- Fireball = 83,
94
- Flame1 = 84,
95
- Flame2 = 85,
96
- AirJetB = 86,
97
- AirJetG = 87,
98
- AirJetO = 88,
99
- PostBox = 89,
100
- ShpTruck = 90,
101
- CptTruck = 91,
102
- Targets = 92,
103
- TargetsV = 93,
104
- TargetW = 94,
105
- TargetWV = 95,
106
- WCrate1 = 96,
107
- MCrate1 = 97,
108
- UCrate1 = 98,
109
- WCrate2 = 99,
110
- MCrate2 = 100,
111
- UCrate2 = 101,
112
- Donor = 102,
113
- WCrateV = 103,
114
- MCrateV = 104,
115
- UCrateV = 105,
116
- Fallout = 106,
117
- WCrate0 = 107,
118
- MCrate0 = 108,
119
- UCrate0 = 109,
120
- OilDrum1 = 110,
121
- OilDrum2 = 111,
122
- OilDrum3 = 112,
123
- OilDrum4 = 113,
124
- UziHit = 114,
125
- UziCase = 115,
126
- ShotHit = 116,
127
- ShotCase = 117,
128
- WormHit1 = 118,
129
- WormHit2 = 119,
130
- Petrol1_ = 120,
131
- Petrol2_ = 121,
132
- Petrol3_ = 122,
133
- Petrol4_ = 123,
134
- Petrol1 = 124,
135
- Petrol2 = 125,
136
- Petrol3 = 126,
137
- Petrol4 = 127,
138
- Petrol5 = 128,
139
- Petrol6 = 129,
140
- Exhaust = 130,
141
- HExhaust = 131,
142
- WExhaust = 132,
143
- QExhaust = 133,
144
- Feather = 134,
145
- MExhaust = 135,
146
- TCloud = 136,
147
- WWalk = 137,
148
- WWalkU = 138,
149
- WWalkD = 139,
150
- WBlowW = 140,
151
- WBlowWU = 141,
152
- WBlowWD = 142,
153
- WBlowA = 143,
154
- WBlowAU = 144,
155
- WBlowAD = 145,
156
- WBungee = 146,
157
- WBatRope = 147,
158
- WRoll = 148,
159
- WFly1 = 149,
160
- WFly2 = 150,
161
- WFly3 = 151,
162
- LShpWlk = 152,
163
- SheepWlk = 153,
164
- SpSheep1 = 154,
165
- SpSheep2 = 155,
166
- AquaShp1 = 156,
167
- AquaShp2 = 157,
168
- MoleDive = 158,
169
- MoleDig1 = 159,
170
- MoleDig2 = 160,
171
- MoleDig3 = 161,
172
- Woman = 162,
173
- Sally = 163,
174
- CowWalk = 164,
175
- CowJump = 165,
176
- MoleWalk = 166,
177
- WJetFly1 = 167,
178
- WJetFly2 = 168,
179
- WJetFly3 = 169,
180
- WJetFly4 = 170,
181
- WJetFlmB = 171,
182
- WJetFlmD = 172,
183
- Skunk1 = 173,
184
- Skunk2 = 174,
185
- PigeonR0 = 175,
186
- PigeonR1 = 176,
187
- PigeonR2 = 177,
188
- PigeonR3 = 178,
189
- WpBrth = 179,
190
- WpBrthU = 180,
191
- WpBrthD = 181,
192
- WCough = 182,
193
- WCoughU = 183,
194
- WCoughD = 184,
195
- WBrth1 = 185,
196
- WBrth1U = 186,
197
- WBrth1D = 187,
198
- WBrth2 = 188,
199
- WBrth2U = 189,
200
- WBrth2D = 190,
201
- WBlink1 = 191,
202
- WBlink1U = 192,
203
- WBlink1D = 193,
204
- WBlink2 = 194,
205
- WBlink2U = 195,
206
- WBlink2D = 196,
207
- WGlnce1 = 197,
208
- WGlnce1U = 198,
209
- WGlnce1D = 199,
210
- WGlnce2 = 200,
211
- WGlnce2U = 201,
212
- WGlnce2D = 202,
213
- WBgHead = 203,
214
- WBgHeadU = 204,
215
- WBgHeadD = 205,
216
- WFist = 206,
217
- WFistU = 207,
218
- WFistD = 208,
219
- WPeek = 209,
220
- WPeekU = 210,
221
- WPeekD = 211,
222
- WULook = 212,
223
- WULookU = 213,
224
- WULookD = 214,
225
- WOLook = 215,
226
- WOLookU = 216,
227
- WOLookD = 217,
228
- WScarey = 218,
229
- WScareyU = 219,
230
- WScareyD = 220,
231
- WKami1 = 221,
232
- WKami2 = 222,
233
- WKami3 = 223,
234
- WKami4 = 224,
235
- WKami5 = 225,
236
- WScrtch = 226,
237
- WScrtchU = 227,
238
- WScrtchD = 228,
239
- WAccuse = 229,
240
- WAccuseU = 230,
241
- WAccuseD = 231,
242
- WTache = 232,
243
- WTacheU = 233,
244
- WTacheD = 234,
245
- WMunch = 235,
246
- WMunchU = 236,
247
- WMunchD = 237,
248
- WLazy = 238,
249
- WLazyU = 239,
250
- WLazyD = 240,
251
- WWinner = 241,
252
- WSurndr = 242,
253
- WSurndrU = 243,
254
- WSurndrD = 244,
255
- WSvR = 245,
256
- WSvRU = 246,
257
- WSvRD = 247,
258
- WSvB = 248,
259
- WSvBU = 249,
260
- WSvBD = 250,
261
- WSvG = 251,
262
- WSvGU = 252,
263
- WSvGD = 253,
264
- WSvY = 254,
265
- WSvYU = 255,
266
- WSvYD = 256,
267
- WSvP = 257,
268
- WSvPU = 258,
269
- WSvPD = 259,
270
- WSvC = 260,
271
- WSvCU = 261,
272
- WSvCD = 262,
273
- WSkipGo = 263,
274
- WSkipGoU = 264,
275
- WSkipGoD = 265,
276
- WSelect = 266,
277
- WSelectU = 267,
278
- WSelectD = 268,
279
- WJump = 269,
280
- WJumpU = 270,
281
- WJumpD = 271,
282
- WLand1 = 272,
283
- WLand1U = 273,
284
- WLand1D = 274,
285
- WLand2 = 275,
286
- WLand2U = 276,
287
- WLand2D = 277,
288
- WFlyLnk = 278,
289
- WBackFlp = 279,
290
- WPncLnk = 280,
291
- WPncLnkU = 281,
292
- WPncLnkD = 282,
293
- WPncTop = 283,
294
- WPncBak = 284,
295
- WPncBakU = 285,
296
- WPncBakD = 286,
297
- WParaLnk = 287,
298
- WParaCht = 288,
299
- WParBak = 289,
300
- WParBakU = 290,
301
- WParBakD = 291,
302
- WTwang = 292,
303
- WTwangU = 293,
304
- WTwangD = 294,
305
- WSldLk1 = 295,
306
- WSldLk1U = 296,
307
- WSldLk1D = 297,
308
- WSldLk2 = 298,
309
- WSldLk2U = 299,
310
- WSldLk2D = 300,
311
- WpBrtLk = 301,
312
- WpBrtLkU = 302,
313
- WpBrtLkD = 303,
314
- WBlowLk = 304,
315
- WBlowLkU = 305,
316
- WBlowLkD = 306,
317
- WHatLnk = 307,
318
- WHatLnkU = 308,
319
- WHatLnkD = 309,
320
- WNukeLk = 310,
321
- WNukeLkU = 311,
322
- WNukeLkD = 312,
323
- WGrnLnk = 313,
324
- WGrnLnkU = 314,
325
- WGrnLnkD = 315,
326
- WClsLnk = 316,
327
- WClsLnkU = 317,
328
- WClsLnkD = 318,
329
- WBanLnk = 319,
330
- WBanLnkU = 320,
331
- WBanLnkD = 321,
332
- WPbmLnk = 322,
333
- WPbmLnkU = 323,
334
- WPbmLnkD = 324,
335
- WHgrLnk = 325,
336
- WHgrLnkU = 326,
337
- WHgrLnkD = 327,
338
- WUtlLnk = 328,
339
- WUtlLnkU = 329,
340
- WUtlLnkD = 330,
341
- WJetLnk = 331,
342
- WJetLnkU = 332,
343
- WJetLnkD = 333,
344
- WDynLnk = 334,
345
- WDynLnkU = 335,
346
- WDynLnkD = 336,
347
- WVasLnk = 337,
348
- WVasLnkU = 338,
349
- WVasLnkD = 339,
350
- WCowLnk = 340,
351
- WCowLnkU = 341,
352
- WCowLnkD = 342,
353
- WShpLnk = 343,
354
- WShpLnkU = 344,
355
- WShpLnkD = 345,
356
- WWmnLnk = 346,
357
- WWmnLnkU = 347,
358
- WWmnLnkD = 348,
359
- WSknLnk = 349,
360
- WSknLnkU = 350,
361
- WSknLnkD = 351,
362
- WMolLnk = 352,
363
- WMolLnkU = 353,
364
- WMolLnkD = 354,
365
- WWmn2Lk = 355,
366
- WWmn2LkU = 356,
367
- WWmn2LkD = 357,
368
- WDrlLnk = 358,
369
- WDrlLnkU = 359,
370
- WDrlLnkD = 360,
371
- WDrill = 361,
372
- WPrdLnk = 362,
373
- WPrdLnkU = 363,
374
- WPrdLnkD = 364,
375
- WProd = 365,
376
- WProdU = 366,
377
- WProdD = 367,
378
- WPrdBk2 = 368,
379
- WPrdBk2U = 369,
380
- WPrdBk2D = 370,
381
- WKamLnk = 371,
382
- WKamLnkU = 372,
383
- WKamLnkD = 373,
384
- WKamJmp = 374,
385
- WKamBmr = 375,
386
- WKamBmrU = 376,
387
- WKamBmrD = 377,
388
- WFirBl1 = 378,
389
- WFirBl1U = 379,
390
- WFirBl1D = 380,
391
- WFirBl2 = 381,
392
- WFirBl2U = 382,
393
- WFirBl2D = 383,
394
- WFirAir = 384,
395
- WJapBak = 385,
396
- WJapBakU = 386,
397
- WJapBakD = 387,
398
- WMinLnk = 388,
399
- WMinLnkU = 389,
400
- WMinLnkD = 390,
401
- WSkpLnk = 391,
402
- WSkpLnkU = 392,
403
- WSkpLnkD = 393,
404
- WSelLnk = 394,
405
- WSelLnkU = 395,
406
- WSelLnkD = 396,
407
- WAxeLnk = 397,
408
- WAxeLnkU = 398,
409
- WAxeLnkD = 399,
410
- WAxeLk2 = 400,
411
- WAxeLk2U = 401,
412
- WAxeLk2D = 402,
413
- WAxeSwn = 403,
414
- WAxeSwnU = 404,
415
- WAxeSwnD = 405,
416
- WAxeBak = 406,
417
- WAxeBakU = 407,
418
- WAxeBakD = 408,
419
- WBndLnk = 409,
420
- WBndLnkU = 410,
421
- WBndLnkD = 411,
422
- WAirLnk = 412,
423
- WAirLnkU = 413,
424
- WAirLnkD = 414,
425
- WAirTlk = 415,
426
- WAirTlkU = 416,
427
- WAirTlkD = 417,
428
- WTelLnk = 418,
429
- WTelLnkU = 419,
430
- WTelLnkD = 420,
431
- WTelTlk = 421,
432
- WTelTlkU = 422,
433
- WTelTlkD = 423,
434
- WTelDsv = 424,
435
- WTelDsvU = 425,
436
- WTelDsvD = 426,
437
- WThrBak = 427,
438
- WThrBakU = 428,
439
- WThrBakD = 429,
440
- WBsbLnk = 430,
441
- WBsbLnkU = 431,
442
- WBsbLnkD = 432,
443
- WBsbBk2 = 433,
444
- WBsbBk2U = 434,
445
- WBsbBk2D = 435,
446
- WBazLnk = 436,
447
- WBazLnkU = 437,
448
- WBazLnkD = 438,
449
- WFlmLnk = 439,
450
- WFlmLnkU = 440,
451
- WFlmLnkD = 441,
452
- WBz2Lnk = 442,
453
- WBz2LnkU = 443,
454
- WBz2LnkD = 444,
455
- WBz3Lnk = 445,
456
- WBz3LnkU = 446,
457
- WBz3LnkD = 447,
458
- WSurLnk = 448,
459
- WSurLnkU = 449,
460
- WSurLnkD = 450,
461
- WSvrLnk = 451,
462
- WSvrLnkU = 452,
463
- WSvrLnkD = 453,
464
- WSvbLnk = 454,
465
- WSvbLnkU = 455,
466
- WSvbLnkD = 456,
467
- WSvgLnk = 457,
468
- WSvgLnkU = 458,
469
- WSvgLnkD = 459,
470
- WSvyLnk = 460,
471
- WSvyLnkU = 461,
472
- WSvyLnkD = 462,
473
- WSvpLnk = 463,
474
- WSvpLnkU = 464,
475
- WSvpLnkD = 465,
476
- WSvcLnk = 466,
477
- WSvcLnkU = 467,
478
- WSvcLnkD = 468,
479
- WIceLnk = 469,
480
- WIceLnkU = 470,
481
- WIceLnkD = 471,
482
- WIceLk2 = 472,
483
- WIceLk2U = 473,
484
- WIceLk2D = 474,
485
- WIceLk3 = 475,
486
- WIceLk3U = 476,
487
- WIceLk3D = 477,
488
- WIceLook = 478,
489
- WIceBlnk = 479,
490
- WIceGlnt = 480,
491
- WIceStnd = 481,
492
- WBatLnk = 482,
493
- WBatLnkU = 483,
494
- WBatLnkD = 484,
495
- WBatBk2 = 485,
496
- WBatBk2U = 486,
497
- WBatBk2D = 487,
498
- WUziLnk = 488,
499
- WUziLnkU = 489,
500
- WUziLnkD = 490,
501
- WShgLnk = 491,
502
- WShgLnkU = 492,
503
- WShgLnkD = 493,
504
- WBowLnk = 494,
505
- WBowLnkU = 495,
506
- WBowLnkD = 496,
507
- WBowBk2 = 497,
508
- WBowBk2U = 498,
509
- WBowBk2D = 499,
510
- WHgnLnk = 500,
511
- WHgnLnkU = 501,
512
- WHgnLnkD = 502,
513
- WMgnLnk = 503,
514
- WMgnLnkU = 504,
515
- WMgnLnkD = 505,
516
- WDie = 506,
517
- SkdSmoke = 507,
518
- WFlyUp = 508,
519
- WFlyDn = 509,
520
- WFall = 510,
521
- WFallDn = 511,
522
- WPncUp = 512,
523
- WSlide = 513,
524
- WSlideU = 514,
525
- WSlideD = 515,
526
- WBow = 516,
527
- WBowU = 517,
528
- WBowD = 518,
529
- WBsbAim = 519,
530
- WBsbAimU = 520,
531
- WBsbAimD = 521,
532
- WBsbSwn = 522,
533
- WBsbSwnU = 523,
534
- WBsbSwnD = 524,
535
- WBaz = 525,
536
- WBazU = 526,
537
- WBazD = 527,
538
- WFlm = 528,
539
- WFlmU = 529,
540
- WFlmD = 530,
541
- WBaz2 = 531,
542
- WBaz2U = 532,
543
- WBaz2D = 533,
544
- WBaz3 = 534,
545
- WBaz3U = 535,
546
- WBaz3D = 536,
547
- WBatAim = 537,
548
- WBatAimU = 538,
549
- WBatAimD = 539,
550
- WBatFrd = 540,
551
- WBatFrdU = 541,
552
- WBatFrdD = 542,
553
- WShotG = 543,
554
- WShotGU = 544,
555
- WShotGD = 545,
556
- WShotP = 546,
557
- WShotPU = 547,
558
- WShotPD = 548,
559
- WShotF = 549,
560
- WShotFU = 550,
561
- WShotFD = 551,
562
- WBowP = 552,
563
- WBowPU = 553,
564
- WBowPD = 554,
565
- WBowF = 555,
566
- WBowFU = 556,
567
- WBowFD = 557,
568
- WHandG = 558,
569
- WHandGU = 559,
570
- WHandGD = 560,
571
- WHandF = 561,
572
- WHandFU = 562,
573
- WHandFD = 563,
574
- WUzi = 564,
575
- WUziU = 565,
576
- WUziD = 566,
577
- WUziF = 567,
578
- WUziFU = 568,
579
- WUziFD = 569,
580
- WMini = 570,
581
- WMiniU = 571,
582
- WMiniD = 572,
583
- WMiniF = 573,
584
- WMiniFU = 574,
585
- WMiniFD = 575,
586
- WThrGrn = 576,
587
- WThrGrnU = 577,
588
- WThrGrnD = 578,
589
- WThrCls = 579,
590
- WThrClsU = 580,
591
- WThrClsD = 581,
592
- WThrBan = 582,
593
- WThrBanU = 583,
594
- WThrBanD = 584,
595
- WThrPbm = 585,
596
- WThrPbmU = 586,
597
- WThrPbmD = 587,
598
- WThrHgr = 588,
599
- WThrHgrU = 589,
600
- WThrHgrD = 590,
601
- WThrow = 591,
602
- WThrowU = 592,
603
- WThrowD = 593,
604
- Replay = 594,
605
- NetBreak = 595,
606
- ExFoom = 596,
607
- ExBiff = 597,
608
- ExPoot = 598,
609
- ExPow = 599,
610
- Circle25 = 600,
611
- Circle50 = 601,
612
- Circle75 = 602,
613
- Circl100 = 603,
614
- Elipse25 = 604,
615
- Elipse50 = 605,
616
- Elipse75 = 606,
617
- Elips100 = 607,
618
- GassGrn = 608,
619
- SmkLt25 = 609,
620
- SmkLt50 = 610,
621
- SmkLt75 = 611,
622
- SmkLt100 = 612,
623
- SmkDrk20 = 613,
624
- SmkDrk30 = 614,
625
- SmkDrk40 = 615,
626
- Clouds = 616,
627
- CloudM = 617,
628
- CloudL = 618,
629
- Blob = 619,
630
- // gap: 620-621 unused
631
- Debris = 622,
632
- Bubble1 = 623,
633
- Bubble2 = 624,
634
- Bubble3 = 625,
635
- Bubble4 = 626,
636
- WfDrown = 627,
637
- Grave1V2 = 628,
638
- Grave2V2 = 629,
639
- Grave3V2 = 630,
640
- Grave4V2 = 631,
641
- Grave5V2 = 632,
642
- Grave6V2 = 633,
643
- TargetsV2 = 634,
644
- TargetWV2 = 635,
645
- WCrate1V2 = 636,
646
- MCrate1V2 = 637,
647
- UCrate1V2 = 638,
648
- DonorV2 = 639,
649
- WDrown = 640,
650
- OilDrum = 641,
651
- MeteorV2 = 642,
652
- MineOnV2 = 643,
653
- MineOffV2 = 644,
654
- MissileV2 = 645,
655
- AirMislV2 = 646,
656
- GrenadeV2 = 647,
657
- MortarV2 = 648,
658
- ClusterV2 = 649,
659
- ClustletV2 = 650,
660
- BulletV2 = 651,
661
- ArrowV2 = 652,
662
- Sheep = 653,
663
- Carpet1V2 = 654,
664
- Carpet2V2 = 655,
665
- SheepFalV2 = 656,
666
- SheepBrnV2 = 657,
667
- MbBombV2 = 658,
668
- DrillV2 = 659,
669
- WomanV2 = 660,
670
- SallyV2 = 661,
671
- SkunkV2 = 662,
672
- Mole = 663,
673
- CowWalkV2 = 664,
674
- PigeonR0V2 = 665,
675
- PigeonR1V2 = 666,
676
- PigeonR2V2 = 667,
677
- PigeonR3V2 = 668,
678
- BananaV2 = 669,
679
- PetrolBmV2 = 670,
680
- TamborinV2 = 671,
681
- HGrenadeV2 = 672,
682
- HMissil1V2 = 673,
683
- HMissil2V2 = 674,
684
- AquaShp1V2 = 675,
685
- AquaShp2V2 = 676,
686
- VaseV2 = 677,
687
- VaseBit1V2 = 678,
688
- VaseBit2V2 = 679,
689
- VaseBit3V2 = 680,
690
- DynamiteV2 = 681,
691
- Letter1V2 = 682,
692
- Letter2V2 = 683,
693
- DonkeyV2 = 684,
694
- WBaz4 = 685,
695
- WBaz4U = 686,
696
- WBaz4D = 687,
697
- WBaz5 = 688,
698
- WBaz5U = 689,
699
- WBaz5D = 690,
700
- WBz4Lnk = 691,
701
- WBz4LnkU = 692,
702
- WBz4LnkD = 693,
703
- WBz5Lnk = 694,
704
- WBz5LnkU = 695,
705
- WBz5LnkD = 696,
706
- }
707
-
708
- impl TryFrom<u32> for SpriteId {
709
- type Error = u32;
710
-
711
- fn try_from(value: u32) -> Result<Self, Self::Error> {
712
- match value {
713
- 1..=619 | 622..=696 => {
714
- // SAFETY: all matched values correspond to valid variants
715
- Ok(unsafe { core::mem::transmute(value) })
716
- }
717
- _ => Err(value),
718
- }
719
- }
720
- }
721
-
722
- // ---------------------------------------------------------------------------
723
- // Sprite struct layout (from Ghidra + wkJellyWorm Sprites.h)
724
- // ---------------------------------------------------------------------------
725
 
 
726
  /// Per-frame metadata within a Sprite (0x0C bytes).
727
  ///
728
  /// Describes the bounding box and bitmap data offset for one animation frame.
@@ -752,13 +31,13 @@ const _: () = assert!(core::mem::size_of::<SpriteFrame>() == 0x0C);
752
  /// (0x4FAA30), populated by ProcessSprite (0x4FAB80) from `.spr` file data.
753
  ///
754
  /// Contains frame metadata, palette data, and bitmap pixel data for all frames.
755
- /// Managed by DD_Display's 1024-slot sprite table.
756
  ///
757
  /// Source: wkJellyWorm `Sprites.h`, Ghidra decompilation of ConstructSprite + ProcessSprite.
758
  #[repr(C)]
759
  pub struct Sprite {
760
- /// 0x00: Vtable pointer (0x66418C, 8 entries)
761
- pub vtable: *mut u8,
762
  /// 0x04: Context/parent pointer (ECX from ConstructSprite)
763
  pub context_ptr: *mut u8,
764
  /// 0x08: Unknown
@@ -812,37 +91,110 @@ pub struct Sprite {
812
 
813
  const _: () = assert!(core::mem::size_of::<Sprite>() == 0x70);
814
 
815
- /// DD_Display sprite storage byte offsets (from DD_Display base pointer).
 
 
 
 
 
816
  ///
817
- /// DD_Display manages a 1024-slot sprite table. Each slot stores a palette ID
818
- /// and a Sprite pointer. Sprites are organized into 3 palettes.
819
- pub mod dd_display_sprite_offsets {
820
- /// `sprite_palette[1024]` — u32 per slot, palette ID (1-3)
821
- pub const SPRITE_PALETTE_ARRAY: usize = 0x0008;
822
- /// `sprite_ptrs[1024]` — Sprite* per slot
823
- pub const SPRITE_PTR_ARRAY: usize = 0x1008;
824
- /// `gfx_dirs[3]` — GfxDir* per palette
825
- pub const GFX_DIRS: usize = 0x311C;
826
- /// `sprite_counts[3]` — loaded count per palette
827
- pub const SPRITE_COUNTS: usize = 0x3530;
 
 
 
 
 
 
 
 
 
 
828
  }
829
 
830
- /// Sprite vtable method addresses (vtable at 0x66418C, 8 entries).
831
- pub mod sprite_vtable {
832
- /// Slot 0: Destructor (0x4FAA80) — thiscall
833
- pub const DESTRUCTOR: u32 = 0x004F_AA80;
834
- /// Slot 1: Unknown (0x4FAAD0)
835
- pub const SLOT_1: u32 = 0x004F_AAD0;
836
- /// Slot 2: Unknown (0x4FB5C0)
837
- pub const SLOT_2: u32 = 0x004F_B5C0;
838
- /// Slot 3: Unknown (0x4FE550)
839
- pub const SLOT_3: u32 = 0x004F_E550;
840
- /// Slot 4: Unknown (0x4FE2F0)
841
- pub const SLOT_4: u32 = 0x004F_E2F0;
842
- /// Slot 5: Unknown (0x4FE9C0)
843
- pub const SLOT_5: u32 = 0x004F_E9C0;
844
- /// Slot 6: CTask common stub (0x5613D0)
845
- pub const STUB_6: u32 = 0x0056_13D0;
846
- /// Slot 7: CTask common stub (0x5613D0)
847
- pub const STUB_7: u32 = 0x0056_13D0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
848
  }
 
1
+ // Re-export SpriteId from its own module to keep this file focused on struct layouts.
2
+ pub use super::sprite_id::SpriteId;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
+ // (SpriteId enum moved to sprite_id.rs)
5
  /// Per-frame metadata within a Sprite (0x0C bytes).
6
  ///
7
  /// Describes the bounding box and bitmap data offset for one animation frame.
 
31
  /// (0x4FAA30), populated by ProcessSprite (0x4FAB80) from `.spr` file data.
32
  ///
33
  /// Contains frame metadata, palette data, and bitmap pixel data for all frames.
34
+ /// Managed by DisplayBase's 1024-slot sprite table (`sprite_ptrs`).
35
  ///
36
  /// Source: wkJellyWorm `Sprites.h`, Ghidra decompilation of ConstructSprite + ProcessSprite.
37
  #[repr(C)]
38
  pub struct Sprite {
39
+ /// 0x00: Vtable pointer (0x66418C, 8 slots)
40
+ pub vtable: *const SpriteVtable,
41
  /// 0x04: Context/parent pointer (ECX from ConstructSprite)
42
  pub context_ptr: *mut u8,
43
  /// 0x08: Unknown
 
91
 
92
  const _: () = assert!(core::mem::size_of::<Sprite>() == 0x70);
93
 
94
+ /// SpriteBank — indexed sprite container (0x17C bytes).
95
+ ///
96
+ /// Alternative to Sprite for storing sprite data in DisplayBase's 1024-slot table.
97
+ /// While Sprite (sprite_ptrs, 0x1008) stores individual sprites loaded from `.spr` files,
98
+ /// SpriteBank (sprite_banks, 0x2008) stores collections of sprite frames accessed via
99
+ /// an index table that maps sprite IDs to frame indices.
100
  ///
101
+ /// Created by LoadSpriteEx (0x523310): allocated 0x17C bytes, first 0x15C zeroed,
102
+ /// then constructed and initialized via FUN_004f95a0.
103
+ ///
104
+ /// Used by GetSpriteInfo (0x523500) and LoadSpriteComplex (0x5237C0) as the
105
+ /// fallback path when sprite_ptrs[id] is null.
106
+ #[repr(C)]
107
+ pub struct SpriteBank {
108
+ /// 0x00: Vtable pointer (0x664180, 3 slots)
109
+ pub vtable: *const SpriteBankVtable,
110
+ /// 0x04: Unknown (context pointer?)
111
+ pub _unknown_04: u32,
112
+ /// 0x08: Base sprite ID — subtracted from the lookup ID to compute the index table offset.
113
+ pub base_id: i32,
114
+ /// 0x0C: Index table pointer — maps `(id - base_id)` to frame indices in the frame table.
115
+ pub index_table: *const i32,
116
+ /// 0x10: Frame table pointer — array of SpriteFrame entries (0xC bytes each).
117
+ pub frame_table: *const SpriteBankFrame,
118
+ /// 0x14: Number of valid entries in the frame table (bounds check for lookups).
119
+ pub frame_count: i32,
120
+ /// 0x18 - 0x17B: Remaining fields (unknown)
121
+ pub _unknown_18: [u8; 0x17C - 0x18],
122
  }
123
 
124
+ const _: () = assert!(core::mem::size_of::<SpriteBank>() == 0x17C);
125
+
126
+ /// Frame entry in a SpriteBank's frame table (0xC bytes).
127
+ ///
128
+ /// Structurally identical to SpriteFrame but fields have different semantics
129
+ /// when accessed by SpriteBank__GetInfo (FUN_004f98c0):
130
+ /// - `flags` byte 0: bit 0 = transparency flag, bit 1 = double-width flag
131
+ /// - `width` at +0x08: frame width (bit 15 = single-width override)
132
+ /// - `data_value` at +0x0A: data reference (shifted left 8 for output)
133
+ #[repr(C)]
134
+ #[derive(Clone, Copy, Debug)]
135
+ pub struct SpriteBankFrame {
136
+ /// 0x00: Flags byte. Bit 0 = has transparency, bit 1 = double-width.
137
+ pub flags: u8,
138
+ /// 0x01-0x07: Padding / other fields
139
+ pub _pad_01: [u8; 7],
140
+ /// 0x08: Frame width in pixels. Bit 15 set = force width to 1.
141
+ pub width: u16,
142
+ /// 0x0A: Data reference value (shifted left 8 when returned by GetSpriteInfo).
143
+ pub data_value: u16,
144
+ }
145
+
146
+ const _: () = assert!(core::mem::size_of::<SpriteBankFrame>() == 0x0C);
147
+
148
+ /// Sprite vtable (0x66418C, 8 slots).
149
+ ///
150
+ /// Slots 6-7 are CTask common stubs (0x5613D0).
151
+ #[openwa_core::vtable(size = 8, va = 0x0066_418C, class = "Sprite")]
152
+ pub struct SpriteVtable {
153
+ /// destructor (0x4FAA80)
154
+ pub destructor: fn(this: *mut Sprite, flags: u32),
155
+ /// unknown (0x4FAAD0)
156
+ pub slot_1: fn(this: *mut Sprite),
157
+ /// unknown (0x4FB5C0)
158
+ pub slot_2: fn(this: *mut Sprite),
159
+ /// unknown (0x4FE550)
160
+ pub slot_3: fn(this: *mut Sprite),
161
+ /// unknown (0x4FE2F0)
162
+ pub slot_4: fn(this: *mut Sprite),
163
+ /// unknown (0x4FE9C0)
164
+ pub slot_5: fn(this: *mut Sprite),
165
+ /// CTask common stub (0x5613D0)
166
+ pub slot_6: fn(this: *mut Sprite),
167
+ /// CTask common stub (0x5613D0)
168
+ pub slot_7: fn(this: *mut Sprite),
169
+ }
170
+
171
+ /// SpriteBank vtable (0x664180, 3 slots).
172
+ #[openwa_core::vtable(size = 3, va = 0x0066_4180, class = "SpriteBank")]
173
+ pub struct SpriteBankVtable {
174
+ /// destructor (0x4F94E0)
175
+ pub destructor: fn(this: *mut SpriteBank, flags: u32),
176
+ /// init/load (0x4F9580)
177
+ pub slot_1: fn(this: *mut SpriteBank),
178
+ /// unknown (0x4FA910)
179
+ pub slot_2: fn(this: *mut SpriteBank),
180
+ }
181
+
182
+ crate::define_addresses! {
183
+ class "Sprite" {
184
+ /// ConstructSprite — usercall EAX=sprite_ptr, ECX=context_ptr
185
+ ctor/Usercall CONSTRUCT_SPRITE = 0x004F_AA30;
186
+ /// Sprite destructor — thiscall, vtable slot 0
187
+ fn/Thiscall DESTROY_SPRITE = 0x004F_AA80;
188
+ /// LoadSpriteFromVfs
189
+ fn/Usercall LOAD_SPRITE_FROM_VFS = 0x004F_AAF0;
190
+ /// ProcessSprite — parses .spr binary format
191
+ fn/Usercall PROCESS_SPRITE = 0x004F_AB80;
192
+ /// Sprite__GetInfo — usercall EAX=Sprite*, ESI=out_data, ECX=out_width, stack=out_flags
193
+ fn/Usercall SPRITE_GET_INFO = 0x004F_AEC0;
194
+ }
195
+
196
+ class "SpriteBank" {
197
+ /// SpriteBank__GetInfo — usercall EAX=layer, ECX=bank*, ESI=out_width, 2 stack params
198
+ fn/Usercall SPRITE_BANK_GET_INFO = 0x004F_98C0;
199
+ }
200
  }
crates/openwa-core/src/render/sprite_id.rs ADDED
@@ -0,0 +1,720 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /// Sprite/animation frame IDs. Range 1-696 with gaps at 620-621.
2
+ ///
3
+ /// Names preserved from the original sprite file names in the game data.
4
+ /// The original names are terse 8-character identifiers from the game's
5
+ /// sprite sheets.
6
+ ///
7
+ /// Source: wkJellyWorm Constants.h
8
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9
+ #[repr(u32)]
10
+ pub enum SpriteId {
11
+ Cdrom = 1,
12
+ CrshairR = 2,
13
+ CrshairB = 3,
14
+ CrshairG = 4,
15
+ CrshairY = 5,
16
+ CrshairP = 6,
17
+ CrshairC = 7,
18
+ SpangleR = 8,
19
+ SpangleB = 9,
20
+ SpangleG = 10,
21
+ SpangleY = 11,
22
+ SpangleP = 12,
23
+ SpangleC = 13,
24
+ Grave1 = 14,
25
+ Grave2 = 15,
26
+ Grave3 = 16,
27
+ Grave4 = 17,
28
+ Grave5 = 18,
29
+ Grave6 = 19,
30
+ ArrowDnR = 20,
31
+ ArrowDnB = 21,
32
+ ArrowDnG = 22,
33
+ ArrowDnY = 23,
34
+ ArrowDnP = 24,
35
+ ArrowDnC = 25,
36
+ ArrowSdR = 26,
37
+ ArrowSdB = 27,
38
+ ArrowSdG = 28,
39
+ ArrowSdY = 29,
40
+ ArrowSdP = 30,
41
+ ArrowSdC = 31,
42
+ CursorR = 32,
43
+ CursorB = 33,
44
+ CursorG = 34,
45
+ CursorY = 35,
46
+ CursorP = 36,
47
+ CursorC = 37,
48
+ MarkerR = 38,
49
+ MarkerB = 39,
50
+ MarkerG = 40,
51
+ MarkerY = 41,
52
+ MarkerP = 42,
53
+ MarkerC = 43,
54
+ CMarker = 44,
55
+ MineOn = 45,
56
+ Arrow = 46,
57
+ MineOff = 47,
58
+ Missile = 48,
59
+ Bullet = 49,
60
+ Grenade = 50,
61
+ Banana = 51,
62
+ Mortar = 52,
63
+ Cluster = 53,
64
+ Clustlet = 54,
65
+ PetrolBm = 55,
66
+ HGrenade = 56,
67
+ Tamborin = 57,
68
+ HMissil1 = 58,
69
+ HMissil2 = 59,
70
+ AirMisl = 60,
71
+ SheepFal = 61,
72
+ Carpet1 = 62,
73
+ Carpet2 = 63,
74
+ SheepBrn = 64,
75
+ SheepLau = 65,
76
+ Meteor = 66,
77
+ Drill = 67,
78
+ RopeTip = 68,
79
+ RopeCuff = 69,
80
+ Phone = 70,
81
+ MbBomb = 71,
82
+ Letter1 = 72,
83
+ Letter2 = 73,
84
+ Vase = 74,
85
+ VaseBit1 = 75,
86
+ VaseBit2 = 76,
87
+ VaseBit3 = 77,
88
+ Dynamite = 78,
89
+ FireHit = 79,
90
+ KamiSmk = 80,
91
+ BaseHit = 81,
92
+ Donkey = 82,
93
+ Fireball = 83,
94
+ Flame1 = 84,
95
+ Flame2 = 85,
96
+ AirJetB = 86,
97
+ AirJetG = 87,
98
+ AirJetO = 88,
99
+ PostBox = 89,
100
+ ShpTruck = 90,
101
+ CptTruck = 91,
102
+ Targets = 92,
103
+ TargetsV = 93,
104
+ TargetW = 94,
105
+ TargetWV = 95,
106
+ WCrate1 = 96,
107
+ MCrate1 = 97,
108
+ UCrate1 = 98,
109
+ WCrate2 = 99,
110
+ MCrate2 = 100,
111
+ UCrate2 = 101,
112
+ Donor = 102,
113
+ WCrateV = 103,
114
+ MCrateV = 104,
115
+ UCrateV = 105,
116
+ Fallout = 106,
117
+ WCrate0 = 107,
118
+ MCrate0 = 108,
119
+ UCrate0 = 109,
120
+ OilDrum1 = 110,
121
+ OilDrum2 = 111,
122
+ OilDrum3 = 112,
123
+ OilDrum4 = 113,
124
+ UziHit = 114,
125
+ UziCase = 115,
126
+ ShotHit = 116,
127
+ ShotCase = 117,
128
+ WormHit1 = 118,
129
+ WormHit2 = 119,
130
+ Petrol1_ = 120,
131
+ Petrol2_ = 121,
132
+ Petrol3_ = 122,
133
+ Petrol4_ = 123,
134
+ Petrol1 = 124,
135
+ Petrol2 = 125,
136
+ Petrol3 = 126,
137
+ Petrol4 = 127,
138
+ Petrol5 = 128,
139
+ Petrol6 = 129,
140
+ Exhaust = 130,
141
+ HExhaust = 131,
142
+ WExhaust = 132,
143
+ QExhaust = 133,
144
+ Feather = 134,
145
+ MExhaust = 135,
146
+ TCloud = 136,
147
+ WWalk = 137,
148
+ WWalkU = 138,
149
+ WWalkD = 139,
150
+ WBlowW = 140,
151
+ WBlowWU = 141,
152
+ WBlowWD = 142,
153
+ WBlowA = 143,
154
+ WBlowAU = 144,
155
+ WBlowAD = 145,
156
+ WBungee = 146,
157
+ WBatRope = 147,
158
+ WRoll = 148,
159
+ WFly1 = 149,
160
+ WFly2 = 150,
161
+ WFly3 = 151,
162
+ LShpWlk = 152,
163
+ SheepWlk = 153,
164
+ SpSheep1 = 154,
165
+ SpSheep2 = 155,
166
+ AquaShp1 = 156,
167
+ AquaShp2 = 157,
168
+ MoleDive = 158,
169
+ MoleDig1 = 159,
170
+ MoleDig2 = 160,
171
+ MoleDig3 = 161,
172
+ Woman = 162,
173
+ Sally = 163,
174
+ CowWalk = 164,
175
+ CowJump = 165,
176
+ MoleWalk = 166,
177
+ WJetFly1 = 167,
178
+ WJetFly2 = 168,
179
+ WJetFly3 = 169,
180
+ WJetFly4 = 170,
181
+ WJetFlmB = 171,
182
+ WJetFlmD = 172,
183
+ Skunk1 = 173,
184
+ Skunk2 = 174,
185
+ PigeonR0 = 175,
186
+ PigeonR1 = 176,
187
+ PigeonR2 = 177,
188
+ PigeonR3 = 178,
189
+ WpBrth = 179,
190
+ WpBrthU = 180,
191
+ WpBrthD = 181,
192
+ WCough = 182,
193
+ WCoughU = 183,
194
+ WCoughD = 184,
195
+ WBrth1 = 185,
196
+ WBrth1U = 186,
197
+ WBrth1D = 187,
198
+ WBrth2 = 188,
199
+ WBrth2U = 189,
200
+ WBrth2D = 190,
201
+ WBlink1 = 191,
202
+ WBlink1U = 192,
203
+ WBlink1D = 193,
204
+ WBlink2 = 194,
205
+ WBlink2U = 195,
206
+ WBlink2D = 196,
207
+ WGlnce1 = 197,
208
+ WGlnce1U = 198,
209
+ WGlnce1D = 199,
210
+ WGlnce2 = 200,
211
+ WGlnce2U = 201,
212
+ WGlnce2D = 202,
213
+ WBgHead = 203,
214
+ WBgHeadU = 204,
215
+ WBgHeadD = 205,
216
+ WFist = 206,
217
+ WFistU = 207,
218
+ WFistD = 208,
219
+ WPeek = 209,
220
+ WPeekU = 210,
221
+ WPeekD = 211,
222
+ WULook = 212,
223
+ WULookU = 213,
224
+ WULookD = 214,
225
+ WOLook = 215,
226
+ WOLookU = 216,
227
+ WOLookD = 217,
228
+ WScarey = 218,
229
+ WScareyU = 219,
230
+ WScareyD = 220,
231
+ WKami1 = 221,
232
+ WKami2 = 222,
233
+ WKami3 = 223,
234
+ WKami4 = 224,
235
+ WKami5 = 225,
236
+ WScrtch = 226,
237
+ WScrtchU = 227,
238
+ WScrtchD = 228,
239
+ WAccuse = 229,
240
+ WAccuseU = 230,
241
+ WAccuseD = 231,
242
+ WTache = 232,
243
+ WTacheU = 233,
244
+ WTacheD = 234,
245
+ WMunch = 235,
246
+ WMunchU = 236,
247
+ WMunchD = 237,
248
+ WLazy = 238,
249
+ WLazyU = 239,
250
+ WLazyD = 240,
251
+ WWinner = 241,
252
+ WSurndr = 242,
253
+ WSurndrU = 243,
254
+ WSurndrD = 244,
255
+ WSvR = 245,
256
+ WSvRU = 246,
257
+ WSvRD = 247,
258
+ WSvB = 248,
259
+ WSvBU = 249,
260
+ WSvBD = 250,
261
+ WSvG = 251,
262
+ WSvGU = 252,
263
+ WSvGD = 253,
264
+ WSvY = 254,
265
+ WSvYU = 255,
266
+ WSvYD = 256,
267
+ WSvP = 257,
268
+ WSvPU = 258,
269
+ WSvPD = 259,
270
+ WSvC = 260,
271
+ WSvCU = 261,
272
+ WSvCD = 262,
273
+ WSkipGo = 263,
274
+ WSkipGoU = 264,
275
+ WSkipGoD = 265,
276
+ WSelect = 266,
277
+ WSelectU = 267,
278
+ WSelectD = 268,
279
+ WJump = 269,
280
+ WJumpU = 270,
281
+ WJumpD = 271,
282
+ WLand1 = 272,
283
+ WLand1U = 273,
284
+ WLand1D = 274,
285
+ WLand2 = 275,
286
+ WLand2U = 276,
287
+ WLand2D = 277,
288
+ WFlyLnk = 278,
289
+ WBackFlp = 279,
290
+ WPncLnk = 280,
291
+ WPncLnkU = 281,
292
+ WPncLnkD = 282,
293
+ WPncTop = 283,
294
+ WPncBak = 284,
295
+ WPncBakU = 285,
296
+ WPncBakD = 286,
297
+ WParaLnk = 287,
298
+ WParaCht = 288,
299
+ WParBak = 289,
300
+ WParBakU = 290,
301
+ WParBakD = 291,
302
+ WTwang = 292,
303
+ WTwangU = 293,
304
+ WTwangD = 294,
305
+ WSldLk1 = 295,
306
+ WSldLk1U = 296,
307
+ WSldLk1D = 297,
308
+ WSldLk2 = 298,
309
+ WSldLk2U = 299,
310
+ WSldLk2D = 300,
311
+ WpBrtLk = 301,
312
+ WpBrtLkU = 302,
313
+ WpBrtLkD = 303,
314
+ WBlowLk = 304,
315
+ WBlowLkU = 305,
316
+ WBlowLkD = 306,
317
+ WHatLnk = 307,
318
+ WHatLnkU = 308,
319
+ WHatLnkD = 309,
320
+ WNukeLk = 310,
321
+ WNukeLkU = 311,
322
+ WNukeLkD = 312,
323
+ WGrnLnk = 313,
324
+ WGrnLnkU = 314,
325
+ WGrnLnkD = 315,
326
+ WClsLnk = 316,
327
+ WClsLnkU = 317,
328
+ WClsLnkD = 318,
329
+ WBanLnk = 319,
330
+ WBanLnkU = 320,
331
+ WBanLnkD = 321,
332
+ WPbmLnk = 322,
333
+ WPbmLnkU = 323,
334
+ WPbmLnkD = 324,
335
+ WHgrLnk = 325,
336
+ WHgrLnkU = 326,
337
+ WHgrLnkD = 327,
338
+ WUtlLnk = 328,
339
+ WUtlLnkU = 329,
340
+ WUtlLnkD = 330,
341
+ WJetLnk = 331,
342
+ WJetLnkU = 332,
343
+ WJetLnkD = 333,
344
+ WDynLnk = 334,
345
+ WDynLnkU = 335,
346
+ WDynLnkD = 336,
347
+ WVasLnk = 337,
348
+ WVasLnkU = 338,
349
+ WVasLnkD = 339,
350
+ WCowLnk = 340,
351
+ WCowLnkU = 341,
352
+ WCowLnkD = 342,
353
+ WShpLnk = 343,
354
+ WShpLnkU = 344,
355
+ WShpLnkD = 345,
356
+ WWmnLnk = 346,
357
+ WWmnLnkU = 347,
358
+ WWmnLnkD = 348,
359
+ WSknLnk = 349,
360
+ WSknLnkU = 350,
361
+ WSknLnkD = 351,
362
+ WMolLnk = 352,
363
+ WMolLnkU = 353,
364
+ WMolLnkD = 354,
365
+ WWmn2Lk = 355,
366
+ WWmn2LkU = 356,
367
+ WWmn2LkD = 357,
368
+ WDrlLnk = 358,
369
+ WDrlLnkU = 359,
370
+ WDrlLnkD = 360,
371
+ WDrill = 361,
372
+ WPrdLnk = 362,
373
+ WPrdLnkU = 363,
374
+ WPrdLnkD = 364,
375
+ WProd = 365,
376
+ WProdU = 366,
377
+ WProdD = 367,
378
+ WPrdBk2 = 368,
379
+ WPrdBk2U = 369,
380
+ WPrdBk2D = 370,
381
+ WKamLnk = 371,
382
+ WKamLnkU = 372,
383
+ WKamLnkD = 373,
384
+ WKamJmp = 374,
385
+ WKamBmr = 375,
386
+ WKamBmrU = 376,
387
+ WKamBmrD = 377,
388
+ WFirBl1 = 378,
389
+ WFirBl1U = 379,
390
+ WFirBl1D = 380,
391
+ WFirBl2 = 381,
392
+ WFirBl2U = 382,
393
+ WFirBl2D = 383,
394
+ WFirAir = 384,
395
+ WJapBak = 385,
396
+ WJapBakU = 386,
397
+ WJapBakD = 387,
398
+ WMinLnk = 388,
399
+ WMinLnkU = 389,
400
+ WMinLnkD = 390,
401
+ WSkpLnk = 391,
402
+ WSkpLnkU = 392,
403
+ WSkpLnkD = 393,
404
+ WSelLnk = 394,
405
+ WSelLnkU = 395,
406
+ WSelLnkD = 396,
407
+ WAxeLnk = 397,
408
+ WAxeLnkU = 398,
409
+ WAxeLnkD = 399,
410
+ WAxeLk2 = 400,
411
+ WAxeLk2U = 401,
412
+ WAxeLk2D = 402,
413
+ WAxeSwn = 403,
414
+ WAxeSwnU = 404,
415
+ WAxeSwnD = 405,
416
+ WAxeBak = 406,
417
+ WAxeBakU = 407,
418
+ WAxeBakD = 408,
419
+ WBndLnk = 409,
420
+ WBndLnkU = 410,
421
+ WBndLnkD = 411,
422
+ WAirLnk = 412,
423
+ WAirLnkU = 413,
424
+ WAirLnkD = 414,
425
+ WAirTlk = 415,
426
+ WAirTlkU = 416,
427
+ WAirTlkD = 417,
428
+ WTelLnk = 418,
429
+ WTelLnkU = 419,
430
+ WTelLnkD = 420,
431
+ WTelTlk = 421,
432
+ WTelTlkU = 422,
433
+ WTelTlkD = 423,
434
+ WTelDsv = 424,
435
+ WTelDsvU = 425,
436
+ WTelDsvD = 426,
437
+ WThrBak = 427,
438
+ WThrBakU = 428,
439
+ WThrBakD = 429,
440
+ WBsbLnk = 430,
441
+ WBsbLnkU = 431,
442
+ WBsbLnkD = 432,
443
+ WBsbBk2 = 433,
444
+ WBsbBk2U = 434,
445
+ WBsbBk2D = 435,
446
+ WBazLnk = 436,
447
+ WBazLnkU = 437,
448
+ WBazLnkD = 438,
449
+ WFlmLnk = 439,
450
+ WFlmLnkU = 440,
451
+ WFlmLnkD = 441,
452
+ WBz2Lnk = 442,
453
+ WBz2LnkU = 443,
454
+ WBz2LnkD = 444,
455
+ WBz3Lnk = 445,
456
+ WBz3LnkU = 446,
457
+ WBz3LnkD = 447,
458
+ WSurLnk = 448,
459
+ WSurLnkU = 449,
460
+ WSurLnkD = 450,
461
+ WSvrLnk = 451,
462
+ WSvrLnkU = 452,
463
+ WSvrLnkD = 453,
464
+ WSvbLnk = 454,
465
+ WSvbLnkU = 455,
466
+ WSvbLnkD = 456,
467
+ WSvgLnk = 457,
468
+ WSvgLnkU = 458,
469
+ WSvgLnkD = 459,
470
+ WSvyLnk = 460,
471
+ WSvyLnkU = 461,
472
+ WSvyLnkD = 462,
473
+ WSvpLnk = 463,
474
+ WSvpLnkU = 464,
475
+ WSvpLnkD = 465,
476
+ WSvcLnk = 466,
477
+ WSvcLnkU = 467,
478
+ WSvcLnkD = 468,
479
+ WIceLnk = 469,
480
+ WIceLnkU = 470,
481
+ WIceLnkD = 471,
482
+ WIceLk2 = 472,
483
+ WIceLk2U = 473,
484
+ WIceLk2D = 474,
485
+ WIceLk3 = 475,
486
+ WIceLk3U = 476,
487
+ WIceLk3D = 477,
488
+ WIceLook = 478,
489
+ WIceBlnk = 479,
490
+ WIceGlnt = 480,
491
+ WIceStnd = 481,
492
+ WBatLnk = 482,
493
+ WBatLnkU = 483,
494
+ WBatLnkD = 484,
495
+ WBatBk2 = 485,
496
+ WBatBk2U = 486,
497
+ WBatBk2D = 487,
498
+ WUziLnk = 488,
499
+ WUziLnkU = 489,
500
+ WUziLnkD = 490,
501
+ WShgLnk = 491,
502
+ WShgLnkU = 492,
503
+ WShgLnkD = 493,
504
+ WBowLnk = 494,
505
+ WBowLnkU = 495,
506
+ WBowLnkD = 496,
507
+ WBowBk2 = 497,
508
+ WBowBk2U = 498,
509
+ WBowBk2D = 499,
510
+ WHgnLnk = 500,
511
+ WHgnLnkU = 501,
512
+ WHgnLnkD = 502,
513
+ WMgnLnk = 503,
514
+ WMgnLnkU = 504,
515
+ WMgnLnkD = 505,
516
+ WDie = 506,
517
+ SkdSmoke = 507,
518
+ WFlyUp = 508,
519
+ WFlyDn = 509,
520
+ WFall = 510,
521
+ WFallDn = 511,
522
+ WPncUp = 512,
523
+ WSlide = 513,
524
+ WSlideU = 514,
525
+ WSlideD = 515,
526
+ WBow = 516,
527
+ WBowU = 517,
528
+ WBowD = 518,
529
+ WBsbAim = 519,
530
+ WBsbAimU = 520,
531
+ WBsbAimD = 521,
532
+ WBsbSwn = 522,
533
+ WBsbSwnU = 523,
534
+ WBsbSwnD = 524,
535
+ WBaz = 525,
536
+ WBazU = 526,
537
+ WBazD = 527,
538
+ WFlm = 528,
539
+ WFlmU = 529,
540
+ WFlmD = 530,
541
+ WBaz2 = 531,
542
+ WBaz2U = 532,
543
+ WBaz2D = 533,
544
+ WBaz3 = 534,
545
+ WBaz3U = 535,
546
+ WBaz3D = 536,
547
+ WBatAim = 537,
548
+ WBatAimU = 538,
549
+ WBatAimD = 539,
550
+ WBatFrd = 540,
551
+ WBatFrdU = 541,
552
+ WBatFrdD = 542,
553
+ WShotG = 543,
554
+ WShotGU = 544,
555
+ WShotGD = 545,
556
+ WShotP = 546,
557
+ WShotPU = 547,
558
+ WShotPD = 548,
559
+ WShotF = 549,
560
+ WShotFU = 550,
561
+ WShotFD = 551,
562
+ WBowP = 552,
563
+ WBowPU = 553,
564
+ WBowPD = 554,
565
+ WBowF = 555,
566
+ WBowFU = 556,
567
+ WBowFD = 557,
568
+ WHandG = 558,
569
+ WHandGU = 559,
570
+ WHandGD = 560,
571
+ WHandF = 561,
572
+ WHandFU = 562,
573
+ WHandFD = 563,
574
+ WUzi = 564,
575
+ WUziU = 565,
576
+ WUziD = 566,
577
+ WUziF = 567,
578
+ WUziFU = 568,
579
+ WUziFD = 569,
580
+ WMini = 570,
581
+ WMiniU = 571,
582
+ WMiniD = 572,
583
+ WMiniF = 573,
584
+ WMiniFU = 574,
585
+ WMiniFD = 575,
586
+ WThrGrn = 576,
587
+ WThrGrnU = 577,
588
+ WThrGrnD = 578,
589
+ WThrCls = 579,
590
+ WThrClsU = 580,
591
+ WThrClsD = 581,
592
+ WThrBan = 582,
593
+ WThrBanU = 583,
594
+ WThrBanD = 584,
595
+ WThrPbm = 585,
596
+ WThrPbmU = 586,
597
+ WThrPbmD = 587,
598
+ WThrHgr = 588,
599
+ WThrHgrU = 589,
600
+ WThrHgrD = 590,
601
+ WThrow = 591,
602
+ WThrowU = 592,
603
+ WThrowD = 593,
604
+ Replay = 594,
605
+ NetBreak = 595,
606
+ ExFoom = 596,
607
+ ExBiff = 597,
608
+ ExPoot = 598,
609
+ ExPow = 599,
610
+ Circle25 = 600,
611
+ Circle50 = 601,
612
+ Circle75 = 602,
613
+ Circl100 = 603,
614
+ Elipse25 = 604,
615
+ Elipse50 = 605,
616
+ Elipse75 = 606,
617
+ Elips100 = 607,
618
+ GassGrn = 608,
619
+ SmkLt25 = 609,
620
+ SmkLt50 = 610,
621
+ SmkLt75 = 611,
622
+ SmkLt100 = 612,
623
+ SmkDrk20 = 613,
624
+ SmkDrk30 = 614,
625
+ SmkDrk40 = 615,
626
+ Clouds = 616,
627
+ CloudM = 617,
628
+ CloudL = 618,
629
+ Blob = 619,
630
+ // gap: 620-621 unused
631
+ Debris = 622,
632
+ Bubble1 = 623,
633
+ Bubble2 = 624,
634
+ Bubble3 = 625,
635
+ Bubble4 = 626,
636
+ WfDrown = 627,
637
+ Grave1V2 = 628,
638
+ Grave2V2 = 629,
639
+ Grave3V2 = 630,
640
+ Grave4V2 = 631,
641
+ Grave5V2 = 632,
642
+ Grave6V2 = 633,
643
+ TargetsV2 = 634,
644
+ TargetWV2 = 635,
645
+ WCrate1V2 = 636,
646
+ MCrate1V2 = 637,
647
+ UCrate1V2 = 638,
648
+ DonorV2 = 639,
649
+ WDrown = 640,
650
+ OilDrum = 641,
651
+ MeteorV2 = 642,
652
+ MineOnV2 = 643,
653
+ MineOffV2 = 644,
654
+ MissileV2 = 645,
655
+ AirMislV2 = 646,
656
+ GrenadeV2 = 647,
657
+ MortarV2 = 648,
658
+ ClusterV2 = 649,
659
+ ClustletV2 = 650,
660
+ BulletV2 = 651,
661
+ ArrowV2 = 652,
662
+ Sheep = 653,
663
+ Carpet1V2 = 654,
664
+ Carpet2V2 = 655,
665
+ SheepFalV2 = 656,
666
+ SheepBrnV2 = 657,
667
+ MbBombV2 = 658,
668
+ DrillV2 = 659,
669
+ WomanV2 = 660,
670
+ SallyV2 = 661,
671
+ SkunkV2 = 662,
672
+ Mole = 663,
673
+ CowWalkV2 = 664,
674
+ PigeonR0V2 = 665,
675
+ PigeonR1V2 = 666,
676
+ PigeonR2V2 = 667,
677
+ PigeonR3V2 = 668,
678
+ BananaV2 = 669,
679
+ PetrolBmV2 = 670,
680
+ TamborinV2 = 671,
681
+ HGrenadeV2 = 672,
682
+ HMissil1V2 = 673,
683
+ HMissil2V2 = 674,
684
+ AquaShp1V2 = 675,
685
+ AquaShp2V2 = 676,
686
+ VaseV2 = 677,
687
+ VaseBit1V2 = 678,
688
+ VaseBit2V2 = 679,
689
+ VaseBit3V2 = 680,
690
+ DynamiteV2 = 681,
691
+ Letter1V2 = 682,
692
+ Letter2V2 = 683,
693
+ DonkeyV2 = 684,
694
+ WBaz4 = 685,
695
+ WBaz4U = 686,
696
+ WBaz4D = 687,
697
+ WBaz5 = 688,
698
+ WBaz5U = 689,
699
+ WBaz5D = 690,
700
+ WBz4Lnk = 691,
701
+ WBz4LnkU = 692,
702
+ WBz4LnkD = 693,
703
+ WBz5Lnk = 694,
704
+ WBz5LnkU = 695,
705
+ WBz5LnkD = 696,
706
+ }
707
+
708
+ impl TryFrom<u32> for SpriteId {
709
+ type Error = u32;
710
+
711
+ fn try_from(value: u32) -> Result<Self, Self::Error> {
712
+ match value {
713
+ 1..=619 | 622..=696 => {
714
+ // SAFETY: all matched values correspond to valid variants
715
+ Ok(unsafe { core::mem::transmute(value) })
716
+ }
717
+ _ => Err(value),
718
+ }
719
+ }
720
+ }
crates/openwa-dll/src/replacements/display.rs CHANGED
@@ -673,6 +673,9 @@ pub fn install() -> Result<(), String> {
673
  // Patch DisplayGfx vtable (0x66A218): replace ported methods with Rust.
674
  vtable_replace!(DisplayVtable, va::DISPLAY_GFX_VTABLE, {
675
  get_dimensions => display_vtable::get_dimensions,
 
 
 
676
  draw_polyline => display_vtable::draw_polyline,
677
  draw_line => display_vtable::draw_line,
678
  draw_line_clipped => display_vtable::draw_line_clipped,
@@ -686,12 +689,11 @@ pub fn install() -> Result<(), String> {
686
  set_clip_rect => display_vtable::set_clip_rect,
687
  is_sprite_loaded => display_vtable::is_sprite_loaded,
688
  draw_scaled_sprite => draw_scaled_sprite,
689
- set_active_layer => display_vtable::set_active_layer,
690
  set_layer_visibility => display_vtable::set_layer_visibility,
691
  update_palette => display_vtable::update_palette,
692
  slot 19 => blit_sprite,
693
  })?;
694
- let _ = log_line("[Display] DisplayGfx: patched 18 methods → Rust");
695
  }
696
 
697
  Ok(())
 
673
  // Patch DisplayGfx vtable (0x66A218): replace ported methods with Rust.
674
  vtable_replace!(DisplayVtable, va::DISPLAY_GFX_VTABLE, {
675
  get_dimensions => display_vtable::get_dimensions,
676
+ set_layer_color => display_vtable::set_layer_color,
677
+ set_active_layer => display_vtable::set_active_layer,
678
+ get_sprite_info => display_vtable::get_sprite_info,
679
  draw_polyline => display_vtable::draw_polyline,
680
  draw_line => display_vtable::draw_line,
681
  draw_line_clipped => display_vtable::draw_line_clipped,
 
689
  set_clip_rect => display_vtable::set_clip_rect,
690
  is_sprite_loaded => display_vtable::is_sprite_loaded,
691
  draw_scaled_sprite => draw_scaled_sprite,
 
692
  set_layer_visibility => display_vtable::set_layer_visibility,
693
  update_palette => display_vtable::update_palette,
694
  slot 19 => blit_sprite,
695
  })?;
696
+ let _ = log_line("[Display] DisplayGfx: patched 20 methods → Rust");
697
  }
698
 
699
  Ok(())
crates/openwa-dll/src/replacements/sprite.rs CHANGED
@@ -7,7 +7,7 @@
7
  use openwa_core::address::va;
8
  use openwa_core::rebase::rb;
9
  use openwa_core::render::palette::PaletteContext;
10
- use openwa_core::render::sprite::{Sprite, SpriteFrame};
11
 
12
  use crate::hook::{self, usercall_trampoline};
13
 
@@ -22,7 +22,7 @@ unsafe extern "cdecl" fn construct_sprite_impl(sprite: *mut Sprite, context: *mu
22
  // Zero the entire 0x70-byte struct first
23
  core::ptr::write_bytes(sprite as *mut u8, 0, core::mem::size_of::<Sprite>());
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
 
 
7
  use openwa_core::address::va;
8
  use openwa_core::rebase::rb;
9
  use openwa_core::render::palette::PaletteContext;
10
+ use openwa_core::render::sprite::{Sprite, SpriteFrame, SpriteVtable};
11
 
12
  use crate::hook::{self, usercall_trampoline};
13
 
 
22
  // Zero the entire 0x70-byte struct first
23
  core::ptr::write_bytes(sprite as *mut u8, 0, core::mem::size_of::<Sprite>());
24
 
25
+ (*sprite).vtable = rb(va::SPRITE_VTABLE) as *const SpriteVtable;
26
  (*sprite).context_ptr = context;
27
  (*sprite).bitgrid_vtable = rb(va::BIT_GRID_DISPLAY_VTABLE) as *mut u8;
28
 
crates/openwa-dll/src/startup_checks.rs CHANGED
@@ -13,6 +13,7 @@ use openwa_core::registry::{self, AddrKind};
13
  /// Valid first bytes for x86 function prologues (MSVC).
14
  /// This covers standard prologues plus common non-standard function starts.
15
  const VALID_PROLOGUES: &[u8] = &[
 
16
  0x33, // xor reg, reg
17
  0x51, // push ecx
18
  0x52, // push edx
 
13
  /// Valid first bytes for x86 function prologues (MSVC).
14
  /// This covers standard prologues plus common non-standard function starts.
15
  const VALID_PROLOGUES: &[u8] = &[
16
+ 0x2B, // sub reg, r/m32 (e.g. SpriteBank__GetInfo starts with SUB EAX, [ECX+8])
17
  0x33, // xor reg, reg
18
  0x51, // push ecx
19
  0x52, // push edx