M3 texel decode: the game's own textures recovered from the wire
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>
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user