From c5d2b2ad7f7a82f6053d9e213d8479673853581a Mon Sep 17 00:00:00 2001 From: Matiq Date: Thu, 20 Aug 2026 00:11:25 +0300 Subject: [PATCH] P1.5: rtctx.py live-capture pipeline (registry-scan + ctx discovery); confirms live level-tracker A[]/ctx not reachable (window ptr only in registry, no +0x24==40000 object) => scalars are static-only --- rtctx.py | 219 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 rtctx.py diff --git a/rtctx.py b/rtctx.py new file mode 100644 index 0000000..aa8da95 --- /dev/null +++ b/rtctx.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python3 +"""rtctx.py — P1.5: live-capture of level-tracker coefficients + mask scalars. + +Pipeline: + 1. spawn reaper offline render of render_long.rpp (background), + 2. wait for yabridge-host with soothe2 mapped, + 3. chunked full-heap snapshot to /tmp/snap_all.bin (like rtsnap_fast.py), + 4. locate the registry {u64 count,u64 ptr} run (NOTES_CAPTURE), + 5. derive ctx base = (field addr holding window ptr) - 0x540658, + 6. dump the P1.5 targets: level-tracker A[] (0x4c0528/0x3c0510/0x2c04f8) + and mask scalars (0x540870/874/87c/888/88c) + BandConfig (ctx+0x188). + +Usage: rtctx.py [--keep] +""" +import subprocess, os, glob, time, struct, sys, json + +KEEP = '--keep' in sys.argv[1:] +SNAP = '/tmp/snap_all.bin' +IDX = '/tmp/snap_all.idx' +RPP = '/home/m/soothe-bt/render_long.rpp' + + +def mem_open(pid): + return os.open(f'/proc/{pid}/mem', os.O_RDONLY) + + +def find_host(): + 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 snapshot(host): + fd = mem_open(host) + out = open(SNAP, 'wb') + idx = open(IDX, 'wb') + t0 = time.time(); nreg = 0; nbytes = 0 + for line in open(f'/proc/{host}/maps').read().splitlines(): + p = line.split() + if len(p) < 2: continue + lo, hi = (int(x, 16) for x in p[0].split('-')) + if 'r' not in p[1]: continue + a = lo + while a < hi: + n = min(hi - a, 8 * 1024 * 1024) + try: + d = os.pread(fd, n, a) + except Exception: + a += n; continue + if not d: + a += n; continue + out.write(struct.pack('=4 pairs at stride 0x10, first count==8193 (window registry), + every count in {8193,2049,2048,32768,16384,1024} and each ptr readable.""" + okc = {8193, 2049, 2048, 32768, 16384, 1024, 4096} + min_run = 4 + hits = [] + for lo, body in regs: + if len(body) < 0x2000: + continue + for off in range(0, len(body) - 16 * min_run, 8): + good = True + for k in range(min_run): + if off + 16 + 8 > len(body): + good = False; break + c = struct.unpack_from(' len(body): + good = False; break + p = struct.unpack_from(' 0x40: + dedup.append(h) + return dedup + + +def find_ctx(regs, winptr): + """Find the DSP ctx: field at base+0x24 == 40000.0f (0x472c4400), + and field at base+0x540658 holds the window ptr (winptr).""" + sig = struct.pack('= lo: + # verify window field + b = readabs(regs, base + 0x540658, 8) + if b and struct.unpack('= 8 * n else None + + out['scalars'] = { + '0x24': f32(ctx + 0x24), # 40000.0 (sr?) + '0x540870': f32(ctx + 0x540870), # sens/gain + '0x540874': f32(ctx + 0x540874), # level-dependent weight + '0x54087c': f32(ctx + 0x54087c), # band weight / mix + '0x540888': f32(ctx + 0x540888), # attack coeff = 10^(att/20) + '0x54088c': f32(ctx + 0x54088c), # release coeff + } + # BandConfig at ctx+0x188 (A=+0 B=+4 gamma=+0c flag=+10) + out['bandconfig0'] = { + 'A': f32(ctx + 0x188), + 'B': f32(ctx + 0x188 + 0x04), + 'gamma': f32(ctx + 0x188 + 0x0c), + 'flag': readabs(regs, ctx + 0x188 + 0x10, 1), + } + # level-tracker coefficient arrays (first-order IIR A[]) + for name, off, cnt in [('0x4c0528', 0x4c0528, 32), + ('0x3c0510', 0x3c0510, 32), + ('0x2c04f8', 0x2c04f8, 32)]: + a = f64arr(ctx + off, cnt) + if a is not None: + out[name] = a + return out + + +def main(): + os.system("pkill -9 -x reaper 2>/dev/null; pkill -9 -f '[y]abridge' 2>/dev/null; sleep 1") + proc = subprocess.Popen(['/usr/bin/reaper', '-nosplash', '-ignoreerrors', '-renderproject', RPP], + 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) + time.sleep(6) + nreg, nbytes = snapshot(host) + print('snapshot %d regs %.1f MB' % (nreg, nbytes / 1e6)) + if proc.poll() is None: proc.kill() + + regs = parse_snap() + hits = find_registry(regs) + print('registry candidates:', len(hits)) + # choose the window-registry: first pair count==8193 (identity/window). + rb = None + for h in hits: + b = readabs(regs, h, 8) + if b: + c0 = struct.unpack('