wormz-a / docs /re-notes /sprites.md
paavohtl's picture
Claude Opus 4.6
feat: reverse engineer sprite system β€” docs, types, and Ghidra labels
0b09583
|
Raw History Blame Contribute Delete
7.35 kB

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:

  1. palette (1-3) β€” which GfxDir palette to use
  2. sprite_id (1-1023) β€” slot index
  3. size_override β€” packed DWORD: low16 = width override, high16 = height override (0 = no override)
  4. file_archive β€” PC_FileArchive*
  5. filename β€” const char* sprite filename

Returns: 1 on success, 0 on failure.

Flow:

  1. If sprite_id & 0x800000, return 1 (already loaded)
  2. Validate palette in [1,3] and gfx_dirs[palette] is non-null
  3. Validate sprite_id in [1,1023]
  4. Call vtable[0x20] to check if sprite already exists at this slot
  5. Allocate sprite memory via WA_MallocMemset (0x53E910)
  6. Call ConstructSprite (0x4FAA30) to initialize
  7. Call LoadSpriteFromVfs (0x4FAAF0) to load data
  8. Store sprite pointer at this[sprite_id + 0x402] (byte offset +0x1008)
  9. Store palette at this[sprite_id + 2] (byte offset +0x8)
  10. Increment sprite_counts[palette]
  11. Apply size_override to 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 .dir archive. 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