Download docs/re-notes/sprites.md from nmarshall/wormz-a: direct link, hf CLI and curl.
- Browser
- Download file 7.35 kB
-
https://huggingface.co/nmarshall/wormz-a/resolve/main/docs/re-notes/sprites.md
- Command line
-
hf download hf://nmarshall/wormz-a/docs/re-notes/sprites.md
-
curl -L -o sprites.md https://huggingface.co/nmarshall/wormz-a/resolve/main/docs/re-notes/sprites.md
Sprite System
WA.exe uses a custom .spr format for paletted animated sprites. The DD_Display object
manages a 1024-slot sprite table, loading sprites from .dir archive files via the
GfxDir subsystem.
DD_Display Sprite Storage
DD_Display contains four arrays for sprite management:
| Byte Offset | Size | Description |
|---|---|---|
| +0x0008 | 4096 (1024 Γ u32) | sprite_palette[1024] β palette ID (1-3) per slot |
| +0x1008 | 4096 (1024 Γ Ptr32) | sprite_ptrs[1024] β Sprite object pointer per slot |
| +0x311C | 12 (3 Γ Ptr32) | gfx_dirs[3] β GfxDir pointer per palette |
| +0x3530 | 12 (3 Γ u32) | sprite_counts[3] β loaded sprite count per palette |
Sprite IDs range from 1 to 1023. Bit 0x800000 on the ID signals "already loaded" and causes LoadSprite to return immediately with success.
Sprite Object (0x70 bytes)
Vtable: 0x66418C. Created by ConstructSprite (0x4FAA30), populated by ProcessSprite
(0x4FAB80).
| Offset | Size | Type | Name | Source |
|---|---|---|---|---|
| 0x00 | 4 | Ptr32 | vtable | ConstructSprite |
| 0x04 | 4 | Ptr32 | context_ptr | ConstructSprite (ECX param) |
| 0x08 | 2 | u16 | _unknown_08 | wkJellyWorm |
| 0x0A | 2 | u16 | fps | wkJellyWorm |
| 0x0C | 2 | u16 | width | wkJellyWorm, ProcessSprite |
| 0x0E | 2 | u16 | height | wkJellyWorm, ProcessSprite |
| 0x10 | 2 | u16 | flags | wkJellyWorm, ProcessSprite |
| 0x12 | 2 | u16 | frame_count | ProcessSprite (can be overwritten) |
| 0x14 | 2 | u16 | header_flags | ProcessSprite (from .spr raw+8) |
| 0x16 | 2 | u16 | max_frames | wkJellyWorm, ProcessSprite |
| 0x18 | 2 | u16 | _unknown_18 | |
| 0x1A | 2 | u16 | _unknown_1a | |
| 0x1C | 4 | u32 | scale_x | ProcessSprite (negative frame count) |
| 0x20 | 4 | u32 | scale_y | ProcessSprite (negative frame count) |
| 0x24 | 4 | u32 | is_scaled | ProcessSprite (1 if scaled, 0 otherwise) |
| 0x28 | 4 | Ptr32 | frame_meta_ptr | SpriteFrame* array |
| 0x2C | 4 | Ptr32 | secondary_frame_ptr | 0x4000 flag in header_flags |
| 0x30 | 2 | u16 | secondary_frame_count | |
| 0x32 | 2 | u16 | _pad_32 | |
| 0x34 | 4 | Ptr32 | display_gfx | DisplayGfx vtable (0x664144) |
| 0x38 | 40 | [u8; 0x28] | _unknown_38 | |
| 0x60 | 4 | Ptr32 | raw_frame_header_ptr | ProcessSprite |
| 0x64 | 4 | Ptr32 | bitmap_data_ptr | ProcessSprite |
| 0x68 | 4 | Ptr32 | palette_data_ptr | ProcessSprite |
| 0x6C | 4 | u32 | _unknown_6c |
SpriteFrame (0x0C bytes)
Per-frame metadata, pointed to by Sprite::frame_meta_ptr. One entry per animation frame.
| Offset | Size | Type | Name |
|---|---|---|---|
| 0x00 | 4 | u32 | bitmap_offset |
| 0x04 | 2 | u16 | start_x |
| 0x06 | 2 | u16 | start_y |
| 0x08 | 2 | u16 | end_x |
| 0x0A | 2 | u16 | end_y |
Source: wkJellyWorm Sprites.h::SpriteFrame.
Sprite Vtable (0x66418C, 8 entries)
| Slot | Address | Name |
|---|---|---|
| 0 | 0x4FAA80 | Destructor |
| 1 | 0x4FAAD0 | Unknown |
| 2 | 0x4FB5C0 | Unknown |
| 3 | 0x4FE550 | Unknown |
| 4 | 0x4FE2F0 | Unknown |
| 5 | 0x4FE9C0 | Unknown |
| 6 | 0x5613D0 | CTask common stub |
| 7 | 0x5613D0 | CTask common stub |
Key Functions
LoadSprite (0x523400)
__thiscall on DD_Display, RET 0x14 (5 stack params). Loads a sprite into the
DD_Display sprite table.
Parameters:
palette(1-3) β which GfxDir palette to usesprite_id(1-1023) β slot indexsize_overrideβ packed DWORD: low16 = width override, high16 = height override (0 = no override)file_archiveβ PC_FileArchive*filenameβ const char* sprite filename
Returns: 1 on success, 0 on failure.
Flow:
- If
sprite_id & 0x800000, return 1 (already loaded) - Validate
palettein [1,3] andgfx_dirs[palette]is non-null - Validate
sprite_idin [1,1023] - Call vtable[0x20] to check if sprite already exists at this slot
- Allocate sprite memory via
WA_MallocMemset(0x53E910) - Call
ConstructSprite(0x4FAA30) to initialize - Call
LoadSpriteFromVfs(0x4FAAF0) to load data - Store sprite pointer at
this[sprite_id + 0x402](byte offset +0x1008) - Store palette at
this[sprite_id + 2](byte offset +0x8) - Increment
sprite_counts[palette] - Apply
size_overrideto sprite +0x16 (width) and +0x18 (height) if non-zero
ConstructSprite (0x4FAA30)
__usercall β EAX = sprite pointer, ECX = context pointer. Initializes a freshly
allocated 0x70-byte sprite struct: sets vtable to 0x66418C, DisplayGfx vtable at +0x34,
zeroes all data fields.
LoadSpriteFromVfs (0x4FAAF0)
__usercall β EAX = filename string, ECX = PC_FileArchive pointer, 2 stack params
(sprite pointer, GfxDir pointer). Tries GfxDir::FindEntry (0x566520) for a cached
entry first; on miss, calls GfxDir::LoadImage (0x5666D0) to read from the archive.
Allocates a buffer for raw sprite data, then calls ProcessSprite (0x4FAB80).
ProcessSprite (0x4FAB80)
__usercall β EAX = sprite pointer, 1 stack param (raw data pointer from .spr file).
Parses the .spr binary format and populates the Sprite struct fields. See the
.spr File Format section below.
DrawSpriteGlobal (0x541FE0)
__thiscall + EAX = y_pos (usercall). 4 stack params: layer, x_pos, sprite_id, frame.
Enqueues a draw command (type 4) to the rendering queue. World-space coordinates.
DrawSpriteLocal (0x542060)
Identical to DrawSpriteGlobal but enqueues command type 5. Screen-space coordinates.
DestroySprite (0x4FAA80)
__thiscall, sprite vtable slot 0. Frees sprite resources.
.spr File Format
Binary format parsed by ProcessSprite (0x4FAB80). Data layout:
Offset Size Field
+0x00 4 (unused / magic)
+0x04 4 (u32) data_size β added to global counter at 0x7A0864
+0x08 2 (u16) header_flags β sprite+0x14
+0x0A 2 (u16) palette_entry_count
+0x0C 3 Γ N palette entries (3 bytes each, likely RGB)
... varies frame metadata (SpriteFrame, 0x0C bytes each)
... varies bitmap pixel data
Special flags:
- 0x4000 in header_flags: secondary frame table present before the primary frames. Count at first word after palette, followed by 0x0C-byte entries.
- Negative frame count (bit 15 set in the frame count word at the frame header):
high 7 bits β scale_x, low 7 bits β scale_y. Actual frame_count set to 1.
Scale values stored at sprite +0x1C and +0x20 as
(value << 16) >> 5.
Global Counters
Four u32 globals tracking total sprite resource usage, accumulated by ProcessSprite:
| Address | Description |
|---|---|
| 0x7A0864 | Total sprite data bytes loaded |
| 0x7A0868 | Total sprite frame count |
| 0x7A086C | Total sprite pixel area (sum of frame wΓh) |
| 0x7A0870 | Total palette entries Γ 3 |
GfxDir System
Sprites are stored in .dir archive files loaded by the GfxDir subsystem:
- GfxHandler::LoadDir (0x5663E0): Loads a
.dirarchive. Reads"DIR\x1A"header (0x1A524944), parses 0x1000-byte entries, performs pointer relocation. - GfxDir::FindEntry (0x566520): Looks up a cached entry by name.
- GfxDir::LoadImage (0x5666D0): Reads image data from archive, returns through vtable with 0x0C-byte descriptor (vtable 0x66A1C0).
Sources
- Ghidra decompilation of LoadSprite, ConstructSprite, ProcessSprite, DrawSpriteGlobal
- wkJellyWorm
Sprites.h/Sprites.cppβ struct layout, function hooks, calling conventions - Runtime analysis via OpenWA validator DLL