Action 0x1a carries the textures: header[32] {handle, byte_size, u, v, mode}
+ v rows of 256 bytes, texels in 0x00BBGGRR words. The netdeath session's 20
textures decode to real game art -- 'PLAYER 1/5' HUD labels, a squadron
dragon emblem, metal-panel/grating/terrain surfaces, and the pilot callsign
'Cyd' (texsheet.png in the session scratchpad). The M3 texel FORMAT is
solved; texstore.py builds the handle->RGB store. Remaining: per-quad texid
binding + (texu,texv) sampling to skin the geometry.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
31 lines
1.2 KiB
Python
31 lines
1.2 KiB
Python
"""M3 texel store: parse the game wire's action-0x1a texture uploads into a
|
|
handle -> (u_size, v_size, mode, RGB array) dict. Format: header[32] =
|
|
{handle, byte_size, u, v, mode, ...}, then v rows of 256 bytes, texels in
|
|
0x00BBGGRR little-endian words (bytes R,G,B,0). Verified: decodes to the real
|
|
game textures (HUD 'PLAYER n' labels, squadron emblem, panel/terrain art)."""
|
|
import struct
|
|
import numpy as np
|
|
|
|
|
|
def build_texstore(recs):
|
|
store = {}
|
|
i = 0
|
|
while i < len(recs):
|
|
a, p = recs[i]
|
|
if a == 0x1a and len(p) == 32:
|
|
h = [struct.unpack_from('<I', p, 4 * k)[0] for k in range(8)]
|
|
handle, u, v, mode = h[0], h[2], h[3], h[4]
|
|
rb = b''
|
|
j = i + 1
|
|
while j < len(recs) and recs[j][0] == 0x1a and len(recs[j][1]) == 256:
|
|
rb += bytes(recs[j][1]); j += 1
|
|
n = u * v
|
|
if n and len(rb) >= n * 4:
|
|
arr = np.frombuffer(rb[:n * 4], dtype=np.uint8).reshape(v, u, 4)
|
|
# v-flip (rows arrive bottom-up: 'PLAYER' reads mirrored otherwise)
|
|
store[handle] = (u, v, mode, arr[::-1, :, :3].copy())
|
|
i = j
|
|
else:
|
|
i += 1
|
|
return store
|