chore: add rt-capture artifacts (rtobj/rtsnap scripts, rwin_*.npy tables, dsp grep dumps, rpp param decoders)

This commit is contained in:
2026-08-19 20:56:58 +03:00
parent 02dd6e8aa7
commit 55623b998f
19 changed files with 558593 additions and 0 deletions
+206
View File
@@ -0,0 +1,206 @@
import ghidra.app.script.GhidraScript;
import ghidra.app.decompiler.DecompInterface;
import ghidra.app.decompiler.DecompileResults;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.FunctionManager;
import ghidra.program.model.listing.Instruction;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.symbol.Reference;
import ghidra.program.model.mem.Memory;
import java.io.PrintWriter;
import java.io.FileWriter;
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.*;
/**
* DumpFuns2 - unfrozen BFS decompiler.
* Differences vs DumpFuns.java:
* 1. NO haveDec gate: always decompiles into decomp_funs2.txt.
* 2. Extended seeds: explicit critical DSP addresses + reverse callers
* (references-to) so indirect/vtable-dispatched functions enter closure.
* 3. Skip threshold lowered from 30 to 8 instructions (keeps dispatchers/stubs).
* Also writes fun_map2.txt (fresh) and re-scans .data float constants.
*/
public class DumpFuns2 extends GhidraScript {
final long LO = 0x180000000L;
final long HI = 0x182a00000L;
long parseAddr(String s) {
try {
return Long.parseLong(s.replaceAll("[^0-9a-fA-F]", ""), 16);
} catch (Exception e) { return -1; }
}
boolean inRange(long a) { return a >= LO && a <= HI; }
@Override
public void run() throws Exception {
FunctionManager fm = currentProgram.getFunctionManager();
AddressSpace as = currentProgram.getAddressFactory().getDefaultAddressSpace();
// --- seeds: all FUN_ from decomp_dsp.txt + explicit critical DSP addrs ---
Set<Long> seed = new LinkedHashSet<>();
BufferedReader br = new BufferedReader(new FileReader("/home/m/re-tools/decomp_dsp.txt"));
String line;
while ((line = br.readLine()) != null) {
int i = line.indexOf("FUN_");
while (i >= 0) {
int e = i + 4;
while (e < line.length() && "0123456789abcdefABCDEF".indexOf(line.charAt(e)) >= 0) e++;
long a = parseAddr(line.substring(i + 4, e));
if (inRange(a)) seed.add(a);
i = line.indexOf("FUN_", e);
}
}
br.close();
long[] critical = {
0x180529fe0L, // mask/coefficient setup (vtable-only, 2051 instr)
0x180563440L, // LUT curve + gamma + combine
0x180563ce0L, // IIR level-tracker INIT
0x18056e3e0L, // twin-mask factory
0x18052f500L, // interleave
0x18052b550L, // FFT-conv loop
0x18052ee70L, // per-bin gain
0x18052d650L, // band setup
0x18052d920L, // window
0x18052e190L, // buffer alloc
0x18052dc30L, // FFT plan builder
0x18052da00L, // ramp fill
0x18052dbc0L, // cplx-interleave
0x180535880L, // twin kernel
0x180536300L, // twin caller
0x180536f90L, // dead sibling
0x180530d30L, // level-weight formula
0x1805316e0L, // coeff writer (17 case)
0x180563fa0L, // IIR level-tracker UPDATE (caller chain 5631c0)
0x1805631c0L, // LUT+IIR-init+UPDATE dispatcher
0x180563260L, // param dispatcher
0x180563a60L // combine (called from 563440)
};
for (long a : critical) seed.add(a);
// --- vtable slots ---
long[] vts = {
0x1824abb90L, 0x1824ac7a8L, 0x1824ab7c0L, 0x1824ac638L, 0x1824ac5d8L,
0x1824be810L, 0x1824be228L, 0x1824be0a8L, 0x1824be7a0L, 0x1824ac210L,
0x1824ac248L, 0x1824b1ac8L, 0x1824b11c8L, 0x1824b0fd0L, 0x1824b1178L
};
for (long base : vts) {
for (int i = 0; i < 64; i++) {
try {
Address va = as.getAddress(base + i * 8L);
long tgt = currentProgram.getMemory().getLong(va);
if (inRange(tgt)) seed.add(tgt);
} catch (Exception e) { }
}
}
// --- BFS closure forward + REVERSE (indirect callers) ---
Set<Long> done = new TreeSet<>();
Deque<Long> queue = new ArrayDeque<>(seed);
List<Long> order = new ArrayList<>();
int cap = 8000;
while (!queue.isEmpty() && order.size() < cap) {
long a = queue.poll();
if (!done.add(a)) continue;
order.add(a);
Function f = fm.getFunctionAt(as.getAddress(a));
if (f == null) continue;
try {
for (Function c : f.getCalledFunctions(monitor)) {
long ca = c.getEntryPoint().getOffset();
if (inRange(ca) && !done.contains(ca)) queue.add(ca);
}
} catch (Exception e) { }
// reverse: any function referencing this entry (indirect dispatch callers)
try {
for (Reference r : currentProgram.getReferenceManager()
.getReferencesTo(as.getAddress(a))) {
Address from = r.getFromAddress();
if (from == null) continue;
Function cf = fm.getFunctionContaining(from);
if (cf != null) {
long ca = cf.getEntryPoint().getOffset();
if (inRange(ca) && !done.contains(ca)) queue.add(ca);
}
}
} catch (Exception e) { }
}
// --- fun_map2.txt fresh ---
PrintWriter pm = new PrintWriter(new java.io.BufferedWriter(
new FileWriter("/home/m/re-tools/fun_map2.txt", false)));
for (long a : order) {
Function f = fm.getFunctionAt(as.getAddress(a));
if (f == null) continue;
pm.println(Long.toHexString(a) + " " + f.getBody().getNumAddresses() + " " + f.getName());
}
pm.close();
// --- decompile ALWAYS into decomp_funs2.txt ---
DecompInterface di = new DecompInterface();
di.openProgram(currentProgram);
PrintWriter pw = new PrintWriter(new java.io.BufferedWriter(
new FileWriter("/home/m/re-tools/decomp_funs2.txt", false)));
int n = 0, skipped = 0;
for (long a : order) {
Function f = fm.getFunctionAt(as.getAddress(a));
if (f == null) continue;
long sz = f.getBody().getNumAddresses();
if (sz < 8) { skipped++; continue; }
DecompileResults res = di.decompileFunction(f, 120, monitor);
if (res != null && res.getDecompiledFunction() != null) {
n++;
pw.println("############ FUN_ " + Long.toHexString(a) + " size=" + sz + " ############");
pw.println(res.getDecompiledFunction().getC());
pw.println();
}
}
pw.close();
di.dispose();
println("DECOMP2_DONE n=" + n + " skipped=" + skipped + " order=" + order.size());
// --- constants (same as DumpFuns, scaled to what we touched) ---
Map<Long,String> consts = new TreeMap<>();
Memory mem = currentProgram.getMemory();
for (Function f : fm.getFunctions(true)) {
long fa = f.getEntryPoint().getOffset();
if (!order.contains(fa)) continue;
Iterator<Instruction> iit = currentProgram.getListing().getInstructions(f.getBody(), true);
while (iit.hasNext()) {
Instruction ins = iit.next();
for (Reference r : currentProgram.getReferenceManager().getReferencesFrom(ins.getAddress())) {
long ta = r.getToAddress().getOffset();
if ((ta >= 0x1824c0000L && ta <= 0x182700000L) ||
(ta >= 0x180000000L && ta <= 0x181200000L)) {
consts.merge(ta, Long.toHexString(fa), (x, y) -> x + ";" + y);
}
}
}
}
PrintWriter pc = new PrintWriter(new java.io.BufferedWriter(
new FileWriter("/home/m/re-tools/consts2.txt", false)));
for (Map.Entry<Long,String> e : consts.entrySet()) {
try {
long ta = e.getKey();
int[] b = new int[8];
boolean ok = true;
for (int k = 0; k < 8; k++) {
Address a = as.getAddress(ta + k);
if (!mem.getLoadedAndInitializedAddressSet().contains(a)) { ok = false; break; }
b[k] = mem.getByte(a) & 0xff;
}
if (!ok) continue;
float f32 = Float.intBitsToFloat(b[0] | (b[1] << 8) | (b[2] << 16) | (b[3] << 24));
float f32b = Float.intBitsToFloat(b[4] | (b[5] << 8) | (b[6] << 16) | (b[7] << 24));
pc.println(Long.toHexString(ta) + " f32=" + f32 + " f32b=" + f32b + " refs=" + e.getValue());
} catch (Exception ex) { }
}
pc.close();
println("CONSTS2_DONE consts=" + consts.size());
}
}
+38
View File
@@ -0,0 +1,38 @@
import ghidra.app.script.GhidraScript;
import ghidra.program.model.symbol.Reference;
import ghidra.program.model.symbol.ReferenceIterator;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.FunctionManager;
import java.io.PrintWriter;
public class XrefDsp extends GhidraScript {
@Override
public void run() throws Exception {
String[] targets = {"1805631c0","180563260","180563a60","180563fa0","180563ce0","180563440",
"18056e3e0","180529fe0","180509900","180564a00","180537410","1805374e0",
"18052e3e0","18052e9b0"};
AddressSpace as = currentProgram.getAddressFactory().getDefaultAddressSpace();
PrintWriter pw = new PrintWriter(new java.io.BufferedWriter(
new java.io.FileWriter("/home/m/re-tools/xrefs2.txt")));
FunctionManager fm = currentProgram.getFunctionManager();
for (String ts : targets) {
long ta = Long.parseLong(ts,16);
Address t = as.getAddress(ta);
pw.println("### TARGET " + ts);
ReferenceIterator it = currentProgram.getReferenceManager().getReferencesTo(t);
int n=0;
while (it.hasNext() && n<200) {
Reference r = it.next();
Function cf = null;
if (r.getFromAddress()!=null) cf = fm.getFunctionContaining(r.getFromAddress());
pw.println(" from " + r.getFromAddress() + " type=" + r.getReferenceType() +
" in=" + (cf!=null?Long.toHexString(cf.getEntryPoint().getOffset()):"?"));
n++;
}
}
pw.close();
println("XREFS2_DONE");
}
}
+41622
View File
File diff suppressed because one or more lines are too long
+508225
View File
File diff suppressed because it is too large Load Diff
+7928
View File
File diff suppressed because it is too large Load Diff
+30
View File
@@ -0,0 +1,30 @@
import base64, re
def decode(path):
txt=open(path).read()
lines=txt.split('\n')
for i,l in enumerate(lines):
if 'soothe2_x64.vst3' in l:
b=[]
j=i+1
while j<len(lines):
t=lines[j].strip()
if t=='}': break
b.append(t); j+=1
raw=re.sub(r'\s','',''.join(b))
raw=raw.rstrip('=')
raw+='='*((-len(raw))%4)
dec=base64.b64decode(raw)
# dec is nested: outer base64 blob contains an inner chunked-xml whose first child is another base64 or actual xml
# find '<PARAM ...' anywhere after utf8 decode of inner
# Look for actual XML PARAM tags embedded (they are inside processorStateData attribute - base64 too)
# Simplest: find 'band1 mode' bytes
for pidname in [b'band1 mode', b'band1 q', b'band1 sens', b'band1 freq', b'band1 on']:
i2=dec.find(pidname)
if i2>=0:
seg=dec[i2-60:i2+80]
m=re.search(rb'<PARAM id="([^"]+)" value="([^"]+)"', seg)
if m:
print(f" {m.group(1).decode():16s} = {m.group(2).decode()}")
return
for f in ['/home/m/soothe-bt/t1kq_b1f_800.rpp','/home/m/soothe-bt/dual_b1q_0.1.rpp','/home/m/soothe-bt/dual_b1q_10.0.rpp','/home/m/soothe-bt/dual_b1q_1.0.rpp','/home/m/soothe-bt/t1k_b1f_1100.rpp']:
print("=== ",f.split('/')[-1]); decode(f)
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env python3
import base64, re, sys
def full_decode(path):
txt = open(path).read()
lines = txt.split('\n')
for i, l in enumerate(lines):
if 'soothe2_x64.vst3' in l:
b = []
j = i + 1
while j < len(lines):
t = lines[j].strip()
if t == '}':
break
b.append(t)
j += 1
raw = re.sub(r'\s', '', ''.join(b))
raw = raw.rstrip('=')
raw += '=' * ((-len(raw)) % 4)
dec = base64.b64decode(raw)
# print first bytes overview
print('decoded blob len', len(dec))
# Look for inner base64 chunked xml (soothe2 vst uses nested b64)
# search for PARAM tags in raw decoded bytes
tags = re.findall(rb'<PARAM id="([^"]+)" value="([^"]+)"', dec)
if tags:
for pid, val in tags:
print(f' {pid.decode():16s} = {val.decode()}')
else:
# try inner b64
inner = re.sub(rb'[^A-Za-z0-9+/=]', b'', dec)
inner = inner.rstrip(b'=')
inner += b'=' * ((-len(inner)) % 4)
try:
dec2 = base64.b64decode(inner)
tags = re.findall(rb'<PARAM id="([^"]+)" value="([^"]+)"', dec2)
for pid, val in tags:
print(f' {pid.decode():16s} = {val.decode()}')
except Exception as e:
print(' inner b64 fail:', e)
return
print('no soothe2_x64.vst3 found')
for f in sys.argv[1:]:
print('===', f.split('/')[-1])
full_decode(f)
+115
View File
@@ -0,0 +1,115 @@
#!/usr/bin/env python3
"""Render-parity harness (Phase 5 step 5, dB stage).
Measures the steady-state per-tone reduction directly on the reference wavs in
/home/m/soothe-bt/ and compares with the B.12 bridge model (g*LUT + w*warp^a).
Model: C(f) = g*LUT(log10(L0/res_band(f,fc,Q))) + w*warp(f)^a -> red = -20*log10(1-C).
LUT is the frozen PCHIP (B.11 knots). g=1.221, w=0.358, a=3.143 (model_fir.py canonical).
Reference renders: 24-bit WAV, input sources 16-bit mono. Tone amplitude estimated by
Goertzel at the exact tone frequency over a late steady window (3.0-3.75 s region).
"""
import wave
import numpy as np
from scipy.interpolate import PchipInterpolator
BT = '/home/m/soothe-bt/'
FS = 44100.0
GAIN = 4.132 # 10^(sens_dB/40) with sens_dB=24.65
QM = [0.1, 0.2, 0.3, 0.5, 0.7, 1.0, 1.5, 2.0, 3.0, 5.0, 10.0]
FCS = [800., 900., 950., 1000., 1050., 1100., 1200.]
L0D = 10 ** (-7.142 / 20) # dual input tone level
L0Q = 10 ** (-18.063 / 20) # t1kq input tone level
L0T = 1.0 # t1k input tone level (0 dBFS)
# frozen LUT knots (B.11)
LX = np.array([-0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.574, 0.61, 0.75, 1.0])
LY = np.array([0.4402, 0.4552, 0.4813, 0.5072, 0.5329, 0.5332, 0.5645, 0.6471, 0.6562, 0.6670])
LUT = PchipInterpolator(LX, LY)
def load(p):
w = wave.open(p, 'rb')
n, ch, sr, sw = w.getnframes(), w.getnchannels(), w.getframerate(), w.getsampwidth()
b = w.readframes(n)
N = n * ch
if sw == 2:
x = np.frombuffer(b, dtype='<i2').astype(np.float64) / 32768.0
else:
raw = np.frombuffer(b, dtype=np.uint8).reshape(N, 3)
x = (raw[:, 0].astype(np.int64) | (raw[:, 1].astype(np.int64) << 8)
| (raw[:, 2].astype(np.int64) << 16))
x = np.where(x >= 0x800000, x - 0x1000000, x).astype(np.float64) / 8388607.0
return x.reshape(-1, ch)
def tone_amp(p, f):
a = load(p)
end = min(len(a), int(3.5 * FS))
seg = a[:end][-int(0.75 * FS):]
x = np.mean(seg, axis=1)
n = len(x)
w = 2 * np.pi * f / FS
cw = 2 * np.cos(w)
s0 = s1 = s2 = 0.0
for v in x:
s2 = s1
s1 = s0
s0 = v + cw * s1 - s2
return np.sqrt(abs(s0 * s0 + s1 * s1 - 2 * cw * s0 * s1)) / n
def red(src, out, f):
return 20 * np.log10(tone_amp(src, f) / tone_amp(out, f))
def res_band(f, fc, Q):
w0 = fc * 2 * np.pi / FS
c, s = np.cos(w0), np.sin(w0)
p = (s * 0.5) / Q
a, a2 = p * GAIN, p / GAIN
A = [a + 1, -2 * c, 1 - a]
B = [a2 + 1, -2 * c, 1 - a2]
ww = 2 * np.pi * f / FS
z = np.exp(-1j * ww)
return abs(2 * (B[0] + B[1] * z + B[2] * z * z) / (A[0] + A[1] * z + A[2] * z * z))
def warp(f):
x = f / 2000.
return 0.87 * 7.942 * x / (7.942 + x)
def pred(f, fc, Q, L0):
xv = np.log10(L0 / res_band(f, fc, Q))
C = 1.221 * float(LUT(float(np.clip(xv, -1, 1.5)))) + 0.358 * warp(f) ** 3.143
return -20 * np.log10(1 - min(C, 0.999))
def main():
meas, pr = [], []
for q in QM:
for f in (500, 2000):
meas.append(red(BT + 'dual.wav', BT + f'dual_b1q_{q}.wav', f))
pr.append(pred(f, 500, q, L0D))
for fc in FCS:
meas.append(red(BT + 'tone1kq.wav', BT + f't1kq_b1f_{int(fc)}.wav', 1000))
pr.append(pred(1000, fc, 0.9999978, L0Q))
for fc in FCS:
meas.append(red(BT + 'tone1k.wav', BT + f't1k_b1f_{int(fc)}.wav', 1000))
pr.append(pred(1000, fc, 0.9999978, L0T))
meas = np.array(meas)
pr = np.array(pr)
print('RENDER-PARITY vs /home/m/soothe-bt (36 pts, real wavs), B.12 (g=1.221,w=0.358,a=3.143):')
print(' TOTAL rmse = %.4f dB' % np.sqrt(np.mean((pr - meas) ** 2)))
for name, sl in [('dual500', slice(0, 22, 2)), ('dual2000', slice(1, 22, 2)),
('t1kq', slice(22, 29)), ('t1k', slice(29, 36))]:
print(' %-9s rmse=%.4f' % (name, np.sqrt(np.mean((pr[sl] - meas[sl]) ** 2))))
print(' dual2000 resid:', ' '.join('%+.2f' % x for x in (pr[1:22:2] - meas[1:22:2])))
print(' t1kq resid: ', ' '.join('%+.2f' % x for x in (pr[22:29] - meas[22:29])))
print(' t1k resid: ', ' '.join('%+.2f' % x for x in (pr[29:] - meas[29:])))
if __name__ == '__main__':
main()
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env python3
import base64, re, sys, html
def rpp_params(path):
txt = open(path, encoding='utf8', errors='ignore').read()
lines = txt.split('\n')
for i, l in enumerate(lines):
if 'soothe2_x64.vst3' in l:
break
b = []
for l in lines[i + 1:]:
if l.strip() == '}':
break
b.append(l.strip())
raw = re.sub(r'[^A-Za-z0-9+/=]', '', ''.join(b))
raw = raw.rstrip('=')
# drop chars until b64 len divisible by 4 (the leading binary header causes odd)
while (len(raw) % 4) != 0:
raw = raw[:-1]
padded = raw + '=' * ((-len(raw)) % 4)
dec = base64.b64decode(padded, validate=False)
p = dec.find(b'<?xml')
xml = dec[p:].decode('utf8', 'ignore')
xml = html.unescape(xml)
params = dict(re.findall(r'<PARAM id="([^"]+)" value="([^"]+)"', xml))
# also decode processorStateData inner
proc = re.search(r'processorStateData="([^"]+)"', xml)
if proc:
inner = proc.group(1)
try:
inner_dec = base64.b64decode(re.sub(r'\s', '', inner))
print(' [processorStateData inner]', inner_dec.decode('utf8', 'ignore')[:400])
except Exception as e:
print(' proc inner err', e)
return params
for f in sys.argv[1:]:
p = rpp_params(f)
print('===', f.split('/')[-1])
for k in sorted(p):
print(f' {k:24s} = {p[k]}')
Binary file not shown.
BIN
View File
Binary file not shown.
+247
View File
@@ -0,0 +1,247 @@
#!/usr/bin/env python3
"""rtobj.py — runtime-capture of soothe2 DSP object heap (v2: periodic heap scan).
Spawns `reaper -nosplash -renderproject <rpp>`, locates the yabridge-host process
with soothe2 mapped, computes modbase, and PERIODICALLY scans ONLY anonymous rw
regions (heaps) for the Soothe2Module vtable pointer, verifying by ctor fields.
On a valid object it dumps twin-state / step-5 window / level weights / mask /
warp / freqaxis / setters into OUTDIR.
"""
import subprocess, os, glob, sys, time, struct, json
VPTR_RVA = 0x24abb80
VPTR_N = 0x60
N_BINS = 342
N_DUMP = 2048
def mem_read(pid, addr, n):
mem = os.open(f'/proc/{pid}/mem', os.O_RDONLY)
try:
return os.pread(mem, n, addr)
except Exception:
return None
finally:
os.close(mem)
def find_host(proc, wait=120):
t0 = time.time()
while time.time() - t0 < wait:
for p in glob.glob('/proc/[0-9]*'):
try:
pid = int(os.path.basename(p))
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 'yabridge-host' in cmd and 'soothe2' in maps:
return pid
time.sleep(0.05)
return None
def soothe2_base(host):
try:
maps = open(f'/proc/{host}/maps').read()
except Exception:
return None
for line in maps.splitlines():
if 'soothe2' not in line:
continue
rlo = int(line.split()[0].split('-')[0], 16)
hdr = mem_read(host, rlo, 0x1000)
if hdr and hdr[:2] == b'MZ':
x = struct.unpack('<I', hdr[0x3c:0x40])[0]
if hdr[x:x + 4] == b'PE\x00\x00':
return rlo
return None
def heap_regions(pid, excl_start=None, excl_end=None):
out = []
try:
for line in open(f'/proc/{pid}/maps').read().splitlines():
p = line.split()
if len(p) < 2:
continue
path = p[5] if len(p) > 5 else ''
if 'r' not in p[1]:
continue
lo, hi = (int(x, 16) for x in p[0].split('-'))
# skip the image file-back regions of the plugin itself
if excl_start is not None and excl_start <= lo < excl_end:
continue
if 'soothe2' in line:
continue
out.append((lo, hi))
except Exception:
pass
return out
def read_vslots(host, base):
"""vtable pointer values = addresses of the .data vtable-slots themselves
(object's first qword points here), range [vstart, vstart+VPTR_N)."""
vstart = base + VPTR_RVA
slots = [vstart + off for off in range(0, VPTR_N, 8)]
return slots
def scan_objects(host, slots, excl_start=None, excl_end=None):
"""Find reg addrs whose qword content == any vtable slot (object vptr)."""
hits = []
mem = os.open(f'/proc/{host}/mem', os.O_RDONLY)
pats = [struct.pack('<Q', s) for s in slots]
try:
for lo, hi in heap_regions(host, excl_start, excl_end):
try:
d = os.pread(mem, hi - lo, lo)
except Exception:
continue
if not d:
continue
for p in pats:
i = 0
while True:
i = d.find(p, i)
if i < 0:
break
hits.append(lo + i)
i += 1
finally:
os.close(mem)
return hits
def f32(d, off):
return struct.unpack_from('<f', d, off)[0] if len(d) >= off + 4 else None
def verify(host, base):
d = mem_read(host, base, 0x541000)
if d is None or len(d) < 0x540900:
return None
f = {
'0x24': f32(d, 0x24),
'0x540874': f32(d, 0x540874),
'0x54087c': f32(d, 0x54087c),
'0x540880': f32(d, 0x540880),
'0x540884': f32(d, 0x540884),
'0x540888': f32(d, 0x540888),
'0x54088c': f32(d, 0x54088c),
'0x540894': f32(d, 0x540894),
}
ok = (f['0x24'] == 44100.0 and f['0x540874'] == 1.0 and f['0x540894'] == 1.0)
return f if ok else None
def dump_floats(host, addr, count, tag, od, fmt=lambda v: f'{v:.17g}'):
d = mem_read(host, addr, count * 4)
if not d:
return
open(f'{tag}.bin', 'wb').write(d)
n = len(d) // 4
vals = struct.unpack('<%df' % n, d[:n * 4])
with open(f'{od}/{tag}.txt', 'w') as f:
for i, v in enumerate(vals):
f.write(f'{i}: {fmt(v)}\n')
def main():
RPP = sys.argv[1] if len(sys.argv) > 1 else '/home/m/soothe-bt/dual300.rpp'
OD = sys.argv[2] if len(sys.argv) > 2 else '/tmp/rtobj_dual300'
os.makedirs(OD, exist_ok=True)
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(f'{OD}/reaper.log', 'w'), stderr=subprocess.STDOUT)
print(f'reaper pid={proc.pid}', flush=True)
host = find_host(proc)
if not host:
proc.kill(); sys.exit('no host')
base = soothe2_base(host)
print(f'host={host} modbase=0x{base:x}', flush=True)
slots = read_vslots(host, base)
print(f'vtable slots: {[hex(s) for s in slots]}', flush=True)
# periodic scan while render alive
got = None
t0 = time.time()
last_host = host
nscan = 0
while time.time() - t0 < 300 and not got:
if proc.poll() is not None:
print('render finished', flush=True)
break
if not os.path.exists(f'/proc/{last_host}'):
print('host died, re-finding...', flush=True)
last_host = find_host(proc, wait=10)
if not last_host:
break
base = soothe2_base(last_host)
if not base:
break
slots = read_vslots(last_host, base)
t1 = time.time()
hits = scan_objects(last_host, slots, excl_start=base, excl_end=base + 0x7000000)
if not hits:
# fallback: no vptr-of-this module found anywhere -> try ctor as marker too
hits = scan_objects(last_host, [base + 0x529610], excl_start=base, excl_end=base + 0x7000000)
if hits:
print(f' (ctor-marker fallback hits={len(hits)})', flush=True)
for a in hits:
f = verify(last_host, a)
if f:
got = (a, f)
break
nscan += 1
if not got:
dt = time.time() - t1
print(f' scan#{nscan} t={dt:.2f}s no object', flush=True)
time.sleep(0.25)
else:
break
host = last_host
if not got:
print('NO VALID DSP OBJECT FOUND', flush=True)
proc.kill(); sys.exit(2)
addr, fields = got
tag = os.path.basename(f'obj_{addr:x}')
meta = {'base': addr, 'modbase': base, 'rpp': RPP, 'host': host, 'fields': fields}
open(f'{OD}/{tag}.json', 'w').write(json.dumps(meta, indent=2))
print(f'OBJECT 0x{addr:x} fields={fields}', flush=True)
dump_floats(host, addr + 0x28, 0x200, f'{tag}.twin_order_f', OD, fmt=lambda v: f'{v:.9g}')
dump_floats(host, addr + 0x40, 0x2ac, f'{tag}.twin_state_A', OD)
dump_floats(host, addr + 0x58, 0x2ac, f'{tag}.twin_state_B', OD)
dump_floats(host, addr + 0x540658, N_DUMP, f'{tag}.window_540658', OD, fmt=lambda v: f'{v:.9g}')
for off, nm in ((0x5406b8, 'w_b8'), (0x5406c8, 'w_c8'),
(0x5406d8, 'w_d8'), (0x5406e8, 'w_e8')):
dump_floats(host, addr + off, N_DUMP, f'{tag}.{nm}', OD, fmt=lambda v: f'{v:.9g}')
dump_floats(host, addr + 0x5406a8, 0x200, f'{tag}.warp_5406a8', OD, fmt=lambda v: f'{v:.9g}')
dump_floats(host, addr + 0x540698, 0x200, f'{tag}.freqaxis_540698', OD, fmt=lambda v: f'{v:.9g}')
dump_floats(host, addr + 0x5407c8, N_DUMP, f'{tag}.maskacc_5407c8', OD, fmt=lambda v: f'{v:.9g}')
# scalar fields
for off, nm in ((0x1a0, 'n1a0'), (0x1ac, 'n1ac'), (0x1a4, 'n1a4'),
(0x540868, 'n540868'), (0x54086c, 'n54086c'),
(0x540870, 's540870'), (0x540878, 's540878'), (0x54087c, 's54087c'),
(0x540880, 's540880'), (0x540884, 's540884'),
(0x540888, 's540888'), (0x54088c, 's54088c'),
(0x540890, 's540890'), (0x540894, 's540894')):
d = mem_read(host, addr + off, 4)
if d:
v = struct.unpack('<f', d)[0] if not nm.startswith('n') else struct.unpack('<I', d)[0]
open(f'{OD}/{tag}.{nm}.txt', 'w').write(f'{v!r}\n')
print('capture done', flush=True)
proc.wait(timeout=30)
if __name__ == '__main__':
main()
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env python3
"""rtsnap.py — differential snapshot of a live process for ctx discovery.
Captures readable regions page-wise (skipping EIO pages), stores: index(file) + regions.raw
Usage: rtsnap.py <pid> <outprefix>
"""
import os, struct, sys
pid = int(sys.argv[1])
pre = sys.argv[2]
mem = os.open(f'/proc/{pid}/mem', os.O_RDONLY)
regs = []
off = 0
for line in open(f'/proc/{pid}/maps'):
p = line.split()[0]
lo, hi = [int(x, 16) for x in p.split('-')]
perm = line.split()[1]
if perm[0] != 'r':
continue
if 'vst3' in line or 'dri' in line or 'shm' in line:
continue
if hi - lo > 0x40000000:
continue
regs.append((lo, hi))
print(f'{len(regs)} regions', flush=True)
idx = []
raw = bytearray()
for lo, hi in regs:
rstart = len(raw)
sz = 0
for pg in range(lo, hi, 0x1000):
try:
b = os.pread(mem, 0x1000, pg)
raw.extend(b)
sz += len(b)
except Exception:
raw.extend(b'\0' * 0x1000)
idx.append((lo, rstart, sz))
with open(pre + '.raw', 'wb') as f:
f.write(bytes(raw))
with open(pre + '.idx', 'wb') as f:
for lo, rstart, sz in idx:
f.write(struct.pack('<QQQ', lo, rstart, sz))
os.close(mem)
print(f'done: raw={len(raw)} bytes', flush=True)
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+51
View File
@@ -0,0 +1,51 @@
### TARGET 1805631c0
from 1826a03cc type=DATA in=?
from 1824b14f0 type=DATA in=?
from 1824b14f8 type=DATA in=?
### TARGET 180563260
from 1826a03e4 type=DATA in=?
from 1824b1510 type=DATA in=?
### TARGET 180563a60
from 1826a0408 type=DATA in=?
from 1805636c2 type=UNCONDITIONAL_CALL in=180563440
### TARGET 180563fa0
from 1826a0420 type=DATA in=?
from 182537570 type=DATA in=?
from 182537580 type=DATA in=?
from 1805631e7 type=UNCONDITIONAL_CALL in=1805631c0
from 1805632b4 type=UNCONDITIONAL_CALL in=180563260
### TARGET 180563ce0
from 1826a0414 type=DATA in=?
from 1805631da type=UNCONDITIONAL_CALL in=1805631c0
from 1805632a7 type=UNCONDITIONAL_CALL in=180563260
### TARGET 180563440
from 1826a03fc type=DATA in=?
from 1805631c9 type=UNCONDITIONAL_CALL in=1805631c0
from 180563296 type=UNCONDITIONAL_CALL in=180563260
### TARGET 18056e3e0
from 1826a0fcc type=DATA in=?
from 1805636a8 type=UNCONDITIONAL_CALL in=180563440
### TARGET 180529fe0
from 18269dc54 type=DATA in=?
from 182531b78 type=DATA in=?
from 182531c1c type=DATA in=?
from 1824ac240 type=DATA in=?
### TARGET 180509900
from 18269c058 type=DATA in=?
from 1824a8b88 type=DATA in=?
### TARGET 180564a00
from 1826a0498 type=DATA in=?
from 1824b1750 type=DATA in=?
### TARGET 180537410
from 1824ac3a0 type=DATA in=?
### TARGET 1805374e0
from 18269e4ac type=DATA in=?
from 1824ab908 type=DATA in=?
### TARGET 18052e3e0
### TARGET 18052e9b0
from 18269dfa8 type=DATA in=?
from 1825320d8 type=DATA in=?
from 182532148 type=DATA in=?
from 18253215c type=DATA in=?
from 18052b942 type=UNCONDITIONAL_CALL in=18052b940
from 18052ba28 type=UNCONDITIONAL_CALL in=18052ba20