v0.53.0: Kaleidostrobe, the 2002 iTunes visualizer, as the fourth style
Reverse-engineered from its PowerPC binary and checked against the original running under emulation: textures byte for byte and every triangle, over 3,600 frames. 25 fps like the plugin; its colour maps and wave images are extracted from its own code; tests/kaleidostrobe_golden.npz was recorded from the original.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
"""Extract Kaleidostrobe's deterministic tables by running the original code.
|
||||
|
||||
- 22 colour maps (makeMap000-021): mapRGB[0], 256 RGBA entries
|
||||
- base wave images (initWaveShape000-022), *before* their random transform()
|
||||
(transform is stubbed out here; the port applies it with its own rand()),
|
||||
plus the gWaveIncScale each one sets
|
||||
Every call is checked to touch nothing but its output.
|
||||
"""
|
||||
import struct, sys
|
||||
import numpy as np
|
||||
sys.path.insert(0, __file__.rsplit('/', 1)[0])
|
||||
import macho
|
||||
from plugin import Plugin
|
||||
from unicorn import UC_HOOK_CODE
|
||||
|
||||
MAPRGB = 0x23fd4
|
||||
TEXIDX = 0x457f8 # textureImageIndexArrays
|
||||
CURTEX = 0x13dbc
|
||||
CURFRAME = 0x13dc0
|
||||
WAVEINC = 0x257f0 # gWaveIncScale (2 floats)
|
||||
|
||||
p = Plugin(seed=1)
|
||||
k = p.k
|
||||
tr = macho.func_size['_transform'][0]
|
||||
k.u.hook_add(UC_HOOK_CODE, lambda u, a, s, d: k._ret(), begin=tr, end=tr + 3)
|
||||
k.u.ctl_remove_cache(0, 0x80000)
|
||||
|
||||
def snapshot():
|
||||
return bytearray(k.mem(0x13000, 0x759f8 - 0x13000))
|
||||
|
||||
def changed(before, after):
|
||||
b = np.frombuffer(bytes(before), np.uint8); a = np.frombuffer(bytes(after), np.uint8)
|
||||
idx = np.nonzero(a != b)[0] + 0x13000
|
||||
return idx
|
||||
|
||||
def regions(idx):
|
||||
out = set()
|
||||
for a in idx[::max(1, len(idx)//200)]:
|
||||
out.add(macho.symfor(int(a)).split('+')[0])
|
||||
return sorted(out)
|
||||
|
||||
k.w32(CURTEX, 0)
|
||||
k.w32(CURFRAME, 0); k.w32(CURFRAME + 4, 0)
|
||||
|
||||
maps = np.zeros((22, 256, 4), np.uint8)
|
||||
for m in range(22):
|
||||
k.u.mem_write(MAPRGB, b'\x55' * 1024)
|
||||
before = snapshot()
|
||||
k.call(f'_makeMap{m:03d}')
|
||||
after = snapshot()
|
||||
idx = changed(before, after)
|
||||
stray = idx[(idx < MAPRGB) | (idx >= MAPRGB + 1024)]
|
||||
data = np.frombuffer(k.mem(MAPRGB, 1024), np.uint8).reshape(256, 4)
|
||||
untouched = int((np.frombuffer(k.mem(MAPRGB, 1024), np.uint8) == 0x55).sum())
|
||||
maps[m] = data
|
||||
print(f'map {m:03d}: stray={regions(stray) if len(stray) else "-"} 0x55-left={untouched}')
|
||||
|
||||
WAVES = list(range(23)) + [31, 32, 33]
|
||||
waves = {}
|
||||
incs = {}
|
||||
for w in WAVES:
|
||||
if w >= 31:
|
||||
continue
|
||||
for fill in (0x00, 0xff):
|
||||
k.u.mem_write(TEXIDX, bytes([fill]) * 0x8000)
|
||||
k.wf(WAVEINC, -999.0)
|
||||
before = snapshot()
|
||||
k.call(f'_initWaveShape{w:03d}')
|
||||
after = snapshot()
|
||||
img = np.frombuffer(k.mem(TEXIDX, 0x4000), np.uint8).reshape(128, 128).copy()
|
||||
if fill == 0:
|
||||
img0 = img
|
||||
else:
|
||||
img1 = img
|
||||
idx = changed(before, after)
|
||||
stray = idx[((idx < TEXIDX) | (idx >= TEXIDX + 0x4000)) & ((idx < WAVEINC) | (idx >= WAVEINC + 4))]
|
||||
partial = int((img0 != img1).sum())
|
||||
waves[w] = img0
|
||||
incs[w] = k.rf(WAVEINC)
|
||||
print(f'wave {w:03d}: inc={incs[w]!r} partial-pixels={partial} stray={regions(stray) if len(stray) else "-"}')
|
||||
|
||||
np.savez_compressed(sys.argv[1] if len(sys.argv) > 1 else 'tables.npz', maps=maps,
|
||||
waves=np.stack([waves[w] for w in range(23)]),
|
||||
wave_inc=np.array([incs[w] for w in range(23)], np.float32))
|
||||
print('saved')
|
||||
Reference in New Issue
Block a user