#!/usr/bin/env python3 """firtrace.py — live capture of mask->FIR pipeline buffers during realtime playback, with GUI-publish flag forced ON so bands[i] curves are copied into the 0x540728+ slots inside the audio callback (decomp 529fe0:144-183). Dumps full vector-object arrays for every ctx slot in [0x540500,0x540900): reads {data_ptr, end_ptr, cap_ptr} (std::vector layout) when available, falls back to fixed-length float view. Usage: python3 scripts/firtrace.py [rpp] [nsnap] """ import json import os import struct import subprocess import sys import time import numpy as np SNAPDIR = '/tmp/opencode/firtrace' def find_host(): import glob for p in glob.glob('/proc/[0-9]*'): pid = int(os.path.basename(p)) try: cmd = open(f'/proc/{pid}/cmdline', 'rb').read().replace(b'\0', b' ').decode('utf8', 'replace') maps = open(f'/proc/{pid}/maps').read() except Exception: continue if 'soothe2' in maps and 'reaper' not in cmd: return pid return None def mem_read(fd, addr, n): try: return os.pread(fd, n, addr) except OSError: return None def mem_write(fd, addr, data): try: os.pwrite(fd, data, addr) return True except OSError: return False def parse_snap(path): data = open(path, 'rb').read() regs = [] i = 0 while i + 16 <= len(data): lo, sz = struct.unpack_from('= 0: base = lo + j - 0x24 b = readabs(regs, base + 0x540870, 4) if b and struct.unpack(' 100: cands.append(base) j = body.find(sig, j + 1) return sorted(set(cands)) SLOTS = list(range(0x540600, 0x540900, 8)) EXTRA = [0x540548, 0x540550, 0x540558, 0x540560, 0x540568, 0x540570, 0x540578, 0x540580, 0x540588, 0x540590, 0x540598, 0x5405a0, 0x5405a8, 0x5405b0, 0x5405b8, 0x5405c0, 0x5405c8, 0x5405d0, 0x5405d8, 0x5405e0, 0x5405e8, 0x5405f0, 0x5405f8] def dump_slot(regs, ctx, off, maxf=65536): """Read qword at ctx+off; if it looks like a heap array, dump floats.""" b = readabs(regs, ctx + off, 8) if not b: return None ptr = struct.unpack(' 0x7fffffffffff: return None body = readabs(regs, ptr, min(maxf, 262144) * 4) if body is None or len(body) < 64: return None a = np.frombuffer(body, dtype=' 1 else '/home/m/soothe-bt/dual_b1q_0.5.rpp' nsnap = int(sys.argv[2]) if len(sys.argv) > 2 else 4 os.makedirs(SNAPDIR, exist_ok=True) subprocess.run('pkill -9 -x reaser 2>/dev/null; pkill -9 -x reaper 2>/dev/null; ' "pkill -9 -f '[y]abridge' 2>/dev/null; sleep 1", shell=True) proc = subprocess.Popen( ['/usr/bin/reaper', '-nosplash', '-ignoreerrors', rpp, '/home/m/re-tools/play_loop.lua'], stdout=open('/dev/null', 'w'), stderr=subprocess.STDOUT) t0 = time.time() host = None while time.time() - t0 < 60 and not host: host = find_host() time.sleep(0.2) if not host: print('NO HOST') return 1 print('host', host, flush=True) time.sleep(12) fd = os.open(f'/proc/{host}/mem', os.O_RDONLY) # locate ctx via one full snapshot snap0 = f'{SNAPDIR}/probe.bin' snapshot(fd, host, snap0) regs = parse_snap(snap0) cands = find_ctx(regs) print('ctx candidates:', ['0x%x' % c for c in cands][:5], flush=True) if not cands: return 1 ctx = cands[0] # force GUI publish flag (byte at ctx+0x2404bc) ok = mem_write(fd, ctx + 0x2404bc, b'\x01') print('GUI flag write:', ok, flush=True) store = {} meta = {} for k in range(nsnap): time.sleep(1.5) # re-arm flag (callback consumes it) mem_write(fd, ctx + 0x2404bc, b'\x01') time.sleep(0.05) p = f'{SNAPDIR}/s{k}.bin' snapshot(fd, host, p) rg = parse_snap(p) cs = find_ctx(rg) if not cs: continue c = cs[0] tag = f's{k}' scal = {} for a in range(0x540860, 0x5408a8, 4): bb = readabs(rg, c + a, 4) scal[hex(a)] = struct.unpack('