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:
2026-10-14 12:02:22 -07:00
parent 6178f5d0f7
commit e38c751e39
21 changed files with 2109 additions and 3 deletions
+188
View File
@@ -0,0 +1,188 @@
"""Kaleidostrobe Mach-O PPC helper: sections, symbols, stubs, annotated disassembly."""
import struct, sys, re
import capstone
import os
BIN = os.environ.get('KALEIDOSTROBE_BIN', os.path.expanduser(
'~/documents/misc/lil hacky bits/Kaleidostrobe.bundle/Contents/MacOS/Kaleidostrobe'))
D = open(BIN, 'rb').read()
def u32(a): return struct.unpack('>I', D[a:a+4])[0]
def s16(v): return v - 0x10000 if v & 0x8000 else v
sections = {} # name -> (addr,size,off,reserved1,segname)
syms = [] # (addr, sect, name)
allsyms = []
off = 28; ncmds = struct.unpack('>I', D[16:20])[0]
dysym = None; symtab = None
seclist = []
for _ in range(ncmds):
cmd, sz = struct.unpack('>II', D[off:off+8])
if cmd == 1:
ns = struct.unpack('>I', D[off+48:off+52])[0]
so = off + 56
for _ in range(ns):
sn = D[so:so+16].rstrip(b'\0').decode(); sg = D[so+16:so+32].rstrip(b'\0').decode()
a, s, o, al, ro, nr, fl, r1, r2 = struct.unpack('>9I', D[so+32:so+68])
seclist.append((sg, sn, a, s, o, fl, r1, r2))
sections[(sg, sn)] = (a, s, o, r1, r2)
so += 68
elif cmd == 2:
symtab = struct.unpack('>4I', D[off+8:off+24])
elif cmd == 11:
dysym = struct.unpack('>18I', D[off+8:off+80])
off += sz
so, ns, stro, strs = symtab
for i in range(ns):
sx, ty, sect, desc, val = struct.unpack('>IBBhI', D[so+i*12:so+i*12+12])
name = D[stro+sx:D.index(b'\0', stro+sx)].decode()
allsyms.append((name, ty, sect, val))
if not (ty & 0xe0) and (ty & 0x0e) == 0x0e:
syms.append((val, sect, name))
syms.sort()
addr2name = {}
for v, s, n in syms:
addr2name.setdefault(v, n)
funcs = [(v, n) for v, s, n in syms if s == 1]
func_size = {}
for i, (v, n) in enumerate(funcs):
func_size[n] = (v, (funcs[i+1][0] if i+1 < len(funcs) else 0x112e4) - v)
# indirect symbols
indirectsymoff, nindirect = dysym[12], dysym[13]
ind = [u32(indirectsymoff + 4*i) for i in range(nindirect)]
stubname = {}
for (sg, sn, a, s, o, fl, r1, r2) in seclist:
typ = fl & 0xff
if typ in (6, 7): # non-lazy / lazy pointers, 4 bytes each
for k in range(s // 4):
idx = ind[r1 + k]
nm = allsyms[idx][0] if idx < len(allsyms) and idx not in (0x80000000, 0x40000000) else '?local'
stubname[a + 4*k] = ('ptr', nm)
elif typ == 8: # symbol stubs
for k in range(s // r2):
idx = ind[r1 + k]
nm = allsyms[idx][0] if idx < len(allsyms) else '?'
stubname[a + r2*k] = ('stub', nm)
def symfor(a):
if a in addr2name: return addr2name[a]
if 0x112e4 <= a < 0x11f5c and not (0x113c0 <= a < 0x1140c): return f'K_{a:x}'
best = None
for v, s, n in syms:
if v <= a: best = (v, n)
else: break
if best: return f'{best[1]}+{a-best[0]:#x}'
return hex(a)
def section_of(a):
for (sg, sn, sa, s, o, fl, r1, r2) in seclist:
if sa <= a < sa + s: return sn
return None
def fval(a, dbl):
if a + (8 if dbl else 4) > len(D): return None
return struct.unpack('>d' if dbl else '>f', D[a:a+(8 if dbl else 4)])[0]
md = capstone.Cs(capstone.CS_ARCH_PPC, capstone.CS_MODE_32 | capstone.CS_MODE_BIG_ENDIAN)
def disasm(name, seed=None):
start, size = func_size[name]
out = []
base = dict(seed or {})
stack = {}
last_call_ret = None
for ins in md.disasm(D[start:start+size], start):
m, ops = ins.mnemonic, ins.op_str
note = ''
regs = [r.strip() for r in ops.split(',')]
word = u32(ins.address)
opc = word >> 26
dest = regs[0] if regs and re.fullmatch(r'r\d+', regs[0]) else None
handled = False
if opc in (16, 18):
mm = re.search(r'0x[0-9a-f]+$', ops)
if mm:
tgt = int(mm.group(0), 16)
if word & 1:
last_call_ret = ins.address + 4
note = stubname[tgt][1] if tgt in stubname else symfor(tgt)
if tgt != ins.address + 4:
for r in ('r0','r3','r4','r5','r6','r7','r8','r9','r10','r11','r12','ctr'):
base.pop(r, None)
base['lr'] = last_call_ret
elif not (start <= tgt < start + size):
note = symfor(tgt)
handled = True
elif m == 'mflr':
v = base.get('lr', last_call_ret)
if v is not None: base[dest] = v; note = f'= {v:#x}'
handled = True
elif m == 'mtlr':
if regs[0] in base: base['lr'] = base[regs[0]]
else: base.pop('lr', None)
handled = True
elif m == 'mtctr':
if regs[0] in base: base['ctr'] = base[regs[0]]
else: base.pop('ctr', None)
handled = True
elif m == 'mfctr':
if 'ctr' in base: base[dest] = base['ctr']
else: base.pop(dest, None)
handled = True
elif m == 'mr':
if regs[1] in base: base[dest] = base[regs[1]]
else: base.pop(dest, None)
handled = True
elif m == 'addis':
if regs[1] in base:
base[dest] = (base[regs[1]] + (s16(word & 0xffff) << 16)) & 0xffffffff
else: base.pop(dest, None)
handled = True
elif m == 'addi':
if regs[1] in base:
a = (base[regs[1]] + s16(word & 0xffff)) & 0xffffffff
base[dest] = a; note = f'&{symfor(a)}'
else: base.pop(dest, None)
handled = True
else:
mm = re.match(r'(\S+), (-?(?:0x)?[0-9a-f]*)\((r\d+)\)$', ops)
if mm and (m.startswith('l') or m.startswith('st')):
breg = mm.group(3); rt = mm.group(1)
d16 = s16(word & 0xffff)
if breg == 'r1':
if m == 'stw':
if rt in base: stack[d16] = base[rt]
else: stack.pop(d16, None)
elif m == 'lwz':
if d16 in stack: base[rt] = stack[d16]; note = f'(spill {stack[d16]:#x})'
else: base.pop(rt, None)
elif m.startswith('l') and rt.startswith('r'):
base.pop(rt, None)
handled = True
elif breg in base:
a = (base[breg] + d16) & 0xffffffff
note = symfor(a)
loaded = None
if a in stubname and stubname[a][0] == 'ptr':
note = f'[ptr->{stubname[a][1]}]'
if section_of(a) == '__nl_symbol_ptr':
loaded = u32(a); note += f' = {symfor(loaded)}'
if m in ('lfs','lfd','stfs','stfd') and section_of(a) in ('__const','__data'):
v = fval(a, m in ('lfd','stfd'))
note += f' = {v!r}'
if m.startswith('l') and rt.startswith('r'):
if loaded is not None: base[rt] = loaded
else: base.pop(rt, None)
handled = True
else:
if m.startswith('l') and rt.startswith('r'): base.pop(rt, None)
handled = True
if not handled and dest and m not in ('cmpwi','cmplwi','cmpw','cmplw','stmw','fcmpu','stwx','stbx','sthx','stfsx','stfdx','stw','stb','sth','stfs','stfd'):
base.pop(dest, None)
out.append(f'{ins.address:5x} {m:8s} {ops:30s} {("; " + note) if note else ""}')
return '\n'.join(out)
if __name__ == '__main__':
for n in sys.argv[1:]:
print(f'===== {n} ({func_size[n][0]:#x}, {func_size[n][1]} bytes)')
print(disasm(n))