File size: 7,350 Bytes
0b09583 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | # 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
|