Compare commits
6
Commits
8fdb1dc082
...
55623b998f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55623b998f | ||
|
|
02dd6e8aa7 | ||
|
|
2484829452 | ||
|
|
34fc852b4f | ||
|
|
9c1516171e | ||
|
|
abf09a26cc |
+206
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
File diff suppressed because one or more lines are too long
+508225
File diff suppressed because it is too large
Load Diff
+33
@@ -0,0 +1,33 @@
|
|||||||
|
import struct
|
||||||
|
data=open('/tmp/snap_all.bin','rb').read()
|
||||||
|
i=0; regs=[]
|
||||||
|
while i+16<=len(data):
|
||||||
|
lo,sz,=struct.unpack_from('<QQ',data,i)
|
||||||
|
regs.append((lo,sz,data[i+16:i+16+sz]))
|
||||||
|
i+=16+sz
|
||||||
|
def readabs(addr,n):
|
||||||
|
for lo,sz,body in regs:
|
||||||
|
if lo<=addr<lo+sz and addr-lo+n<=sz:
|
||||||
|
return body[addr-lo:addr-lo+n]
|
||||||
|
return None
|
||||||
|
known={0x2a72600,0x2111140,0x2cd0fc0,0x29f2280,0x29fa300,0x2a02340,0x2a0a3c0,0x2a12400}
|
||||||
|
cands=[]
|
||||||
|
t0=len(regs)
|
||||||
|
for ri,(lo,sz,body) in enumerate(regs):
|
||||||
|
if sz<0x541000: continue
|
||||||
|
# check every 8-aligned offset for the +0x540658 ptr direct
|
||||||
|
for off in range(0, sz-0x540660, 8):
|
||||||
|
b=body[off+0x540658:off+0x540660]
|
||||||
|
if len(b)<8: break
|
||||||
|
p=struct.unpack_from('<Q',b)[0]
|
||||||
|
if p in known:
|
||||||
|
base=lo+off
|
||||||
|
u24=readabs(base+0x24,4); u28=readabs(base+0x28,4)
|
||||||
|
f40=readabs(base+0x40,0x20); f58=readabs(base+0x58,0x20)
|
||||||
|
print('cand base=0x%x reg%d p=0x%x u24=%s u28=%s 40=%s'%(
|
||||||
|
base,ri,p,
|
||||||
|
struct.unpack('<f',u24)[0] if u24 else None,
|
||||||
|
struct.unpack('<f',u28)[0] if u28 else None,
|
||||||
|
[round(x,4) for x in struct.unpack('<4d',f40[:32])] if f40 else None))
|
||||||
|
cands.append(base)
|
||||||
|
print('total cand',len(cands))
|
||||||
+7928
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,78 @@
|
|||||||
|
# RUNTIME CAPTURE — LIVE DSP TABLES (2026-08-19)
|
||||||
|
|
||||||
|
## BREAKTHROUGH: heap "registry" object found & read during offline render
|
||||||
|
- During `reaper -nosplash -renderproject render_long.rpp`, the yabridge-host
|
||||||
|
allocates a DSP arena containing a **registry array**: a run of `{u64 count, u64 ptr}`
|
||||||
|
pairs (stride 0x10) pointing at every DSP buffer. At capture time:
|
||||||
|
registry base = **0x29b06c0** (in anon region 0x2922000, 0x77e000).
|
||||||
|
(GUI-session registry was 0x28b06c0 — same object, shifted by arena layout.)
|
||||||
|
- Finding it live: scan host-readable memory (chunked, 8MB) for `u64==8193` (0x2001)
|
||||||
|
followed by a readable ptr, then require a run of count/ptr pairs at stride 0x10.
|
||||||
|
- Registry owner chain: 0 hits for a u64==registry address, so it is reached
|
||||||
|
structurally (object member at some fixed offset), not via an explicit global.
|
||||||
|
|
||||||
|
## Captured tables (SR=44100 project, but internal freq-axis = 48000!)
|
||||||
|
| reg idx | count | ptr | content |
|
||||||
|
|---------|-------|----------|---------|
|
||||||
|
| [00] | 8193 | 0x2a72600 | identity ~1.0 |
|
||||||
|
| [01] | 8193 | 0x2111140 | **WIN_freq: 0.5 -> 1.0 (saturates; idx1024=0.68, idx2048=0.8)** — the FFT-conv window (0x540658) |
|
||||||
|
| [02] | 8193 | 0x29f2280 | 0 -> ~0.01 (levels/curve) |
|
||||||
|
| [03] | 8193 | 0x29fa300 | **0.596 -> 0.126** = matches known rwin_C0 |
|
||||||
|
| [04] | 8193 | 0x2a02340 | **0.404 -> 0.874** = 1-[03] complement |
|
||||||
|
| [05] | 8193 | 0x2a0a3c0 | 0.0435 -> ~0 (weight, small) |
|
||||||
|
| [06] | 8193 | 0x2a12400 | 0.9565 -> ~1.0 (1-[05]) |
|
||||||
|
| [07] | 8193 | 0x2aca740 | zeros + small negatives |
|
||||||
|
| [0d] | 2049 | 0x2cd0fc0 | **freq-axis 0..23988.3 Hz, spacing 11.713 = 48000/4096** → internal SR=48000 |
|
||||||
|
| [0e] | 8193 | 0x2cd9000 | 2.017 -> 0 (LUT/knee?) |
|
||||||
|
| [0f] | 16384 | 0x2ce9080 | same as [0e] doubled |
|
||||||
|
| [10] | 16384 | 0x2cf90c0 | 1.2914 -> 0 |
|
||||||
|
| [12] | 16384 | 0x2a62540 | 11.29.. (scattering) |
|
||||||
|
| [14]/[15] | 8193 | .. | first-fire IR? 0,0.022,0.104,0.084,0.018,0 |
|
||||||
|
| [17] | 32768 | 0x2d29140 | 0.9999 -> ~1 (ramp) |
|
||||||
|
| [19] | 32768 | 0x2d69200 | 1.2915 -> 1.0 |
|
||||||
|
| others | 32768/65536 | .. | ones / ramps (FFT plans, mirrors) |
|
||||||
|
|
||||||
|
- Key numeric check: registry[03] head 0.5960761, idx1024 0.168, idx2048 0.1257 —
|
||||||
|
byte-identical to earlier GUI rwin_C0 (0.596 -> 0.126). Confirms registry IS the
|
||||||
|
authoritative per-bin weight source; tables are stable across sessions/SR.
|
||||||
|
- **FREQ-AXIS uses internal SR=48000 regardless of project 44100** (spacing 11.713).
|
||||||
|
This reconciles "rwin tables at 48k" even when rendering 44.1k projects.
|
||||||
|
|
||||||
|
## Files saved (handoff/)
|
||||||
|
- `rtwin_freq_44100.npy` — WIN_freq (8193 f32): 0.5 -> 1.0 (this is live 0x540658 window)
|
||||||
|
- `rtfreqaxis_48000_internal.npy` — freq-axis (2048 f32, 0..23988.3, spacing 11.713)
|
||||||
|
- `rtwa_596.npy` — [03] 0.596->0.126
|
||||||
|
- `rtwb_404.npy` — [04] 0.404->0.874
|
||||||
|
- `rtwc_043.npy` — [05]
|
||||||
|
- `rtwd_956.npy` — [06]
|
||||||
|
- Full raw snapshot: /tmp/snap_all.bin (318MB, entries {lo,sz,bytes}), registry.txt list.
|
||||||
|
|
||||||
|
## Method notes (repro)
|
||||||
|
- rtsnap_fast.py: spawn reaper render_long, find host (soothe2 in maps, not reaper),
|
||||||
|
sleep 6s (tables built), pread ALL readable maps chunked 8MB -> snap_all.bin.
|
||||||
|
- pread of large anon regions can EIO -> MUST chunk (8MB); whole-region pread loses data.
|
||||||
|
- Scan ~0.1s for 318MB once chunked; far cheaper than object-base scan.
|
||||||
|
- Earlier vptr-based (rtobj/rtdump2/rtall) and 44100-marker scans all failed because
|
||||||
|
the DSP object has NO static vptr match in a fresh render (host dies / fields live
|
||||||
|
only during audio) and ctor field +0x24 != 44100 live. The registry run is the
|
||||||
|
reliable beacon.
|
||||||
|
|
||||||
|
## Remaining (for twin IIR attack/release per-bin)
|
||||||
|
- twin state A/B (342 double per-bin IIR states) still not uniquely located live;
|
||||||
|
short renders keep them ~0. They are NOT the registry tables.
|
||||||
|
- Next: render_long + capture at t=10-20s into sustain, then locate the per-bin
|
||||||
|
attack/release smoothing coefficients (0x540888/88c set, converted via ln(10)/20)
|
||||||
|
inside the arena near registry.
|
||||||
|
|
||||||
|
## 2026-08-19b (two-point snapshots t1=8s, t2=40s of render_long 180s)
|
||||||
|
- rtsnap2.py: TWO snapshots of the SAME 180s render at t1/t2; each 318MB/547regs ~0.2s.
|
||||||
|
- Registry tables byte-identical between t1/t2 (stable per-band coeffs; confirmed authoritative).
|
||||||
|
- Diff of arbitrary 342-double windows = pure audio-buffer noise (21981 phantom matches; twin
|
||||||
|
per-band state is NOT a 342-dbl array a level away). Real twin kernel state per NOTES_TWIN:64
|
||||||
|
is {double A[3], double B[3]} per band (6 doubles), seeded in build_twin_coeff FUN_180533ec0.
|
||||||
|
- Conclusion: live per-note-band twin state not usefully separable via full-heap diff; the
|
||||||
|
attack/release input coeffs live in the DSP ctx scalars: 0x540888/0x54088c = expf(p*0.11513)
|
||||||
|
(static-derived), registry holds the per-bin WEIGHT tables (already captured). Twin kernels
|
||||||
|
themselves validated statically (twin_check max|err|=1.27e-5). => Step C goal (window + axes +
|
||||||
|
weights + kernel parity) is effectively CLOSED; only scalar A/R params remain, derived from RPP
|
||||||
|
params, no live capture needed.
|
||||||
@@ -556,3 +556,87 @@ Weighted refits move error around but never reduce max < 0.7:
|
|||||||
rp(Q) ~ Q^0.216 means the res-power correction grows with Q: high-Q resonance pit
|
rp(Q) ~ Q^0.216 means the res-power correction grows with Q: high-Q resonance pit
|
||||||
dips harder off-center. Consistent with decomp (res-weighted gain path in twin-mask
|
dips harder off-center. Consistent with decomp (res-weighted gain path in twin-mask
|
||||||
factory). The q1@2000 residual is structural warp/model mismatch, not rp.
|
factory). The q1@2000 residual is structural warp/model mismatch, not rp.
|
||||||
|
|
||||||
|
## ============ 2026-08-19: STATIC DECOMP LOCKED (all DSP bodies recovered) ============
|
||||||
|
New full decomp run (DumpFuns2.java, no haveDec gate, seeds=dsp+vtables+reverse-callers,
|
||||||
|
skip<8): decomp_funs2.txt (7746 bodies), fun_map2.txt (7928 funcs), consts2.txt (41622).
|
||||||
|
Previously-cold roots now present: 180529fe0 (2051in), 180563440, 180563ce0 (IIR INIT),
|
||||||
|
18056e3e0, 18052e9b0 (3167in), 18052e260, 18052e190/52e9b0 allocators.
|
||||||
|
|
||||||
|
### MAIN RENDER LOOP (found): FUN_18052e260
|
||||||
|
- Signature (param_1=DSP obj, param_2=nSamples). iVar5=NFFT/2+1; loop per band
|
||||||
|
(count=0x2404d0): FUN_180536300(param_1+0x3d8, band_in_ptr, 0x540758+bufoff, band, n).
|
||||||
|
Uses full-STFT frame budget 0x2404d8; tail marks bands needing rebuild (0x240467 flags).
|
||||||
|
- => 52e260 = per-frame dual-band SIDE-CHAIN driver; twin = per-band.
|
||||||
|
|
||||||
|
### FUN_180536300 (twin caller, size=475) — audio path per band:
|
||||||
|
- lVar1 normalize = FUN_18052da00(scratch, in, scale_bin, n): pointwise in[i]*scale.
|
||||||
|
- band active (0x814): twin kernel FUN_180535880/180536f90(lVar2, base, band_i, lVar1, n),
|
||||||
|
else fill FUN_18052db50 (1.0 / 0.0).
|
||||||
|
- combine lVar4/lVar3 (52d990, 0x814>1), output copy 52dbc0(base, lVar4, n).
|
||||||
|
- TWIN KERNELS (535880/536f90): Ghidra emits ONLY the tail-call frame —
|
||||||
|
"WARNING: Removing unreachable block ..." => state shown = {pdVar1,*pdVar2} double pairs
|
||||||
|
read from param_3/param_4, then noreturn tail into thunk_FUN_181ba94b0. **The recursive
|
||||||
|
per-bin IIR level-smoothing (tatt/trel) lives INSIDE these two tail-calls, which static
|
||||||
|
decomp cannot recover** — same family as 0x540658 window (step-5 window). Confirmed
|
||||||
|
statically-invisible frontier = {532a715 twin kernels, 0x540658}.
|
||||||
|
|
||||||
|
### Level-param setters (vtable stubs, auto-named .?AV?$Soothe2Module@M$01@@::vtbl_...):
|
||||||
|
- 18052bba0: 0x540870 = expf((p*DAT_1824c4348 + DAT_1824c44a4)*DAT_1824c3cd4) [gain/sens]
|
||||||
|
- 18052bb80/bb60: 0x540878 / 0x54087c = raw int [freq?/bandwidth?]
|
||||||
|
- 18052bb40/bb20: 0x540880 / 0x540884 = raw int [per-band level / dur]
|
||||||
|
- 18052bb00: 0x540874 = p (locked); 18052bad0/baa0: 0x540888 / 0x54088c =
|
||||||
|
expf(p*DAT_1824c3cd4) [attack / release coeff db]
|
||||||
|
- DAT_1824c3cd4 = 0.115129255 = ln(10)/20 => expf(p*0.11513) = 10^(p/20): **all these
|
||||||
|
setters convert dB-speed params to linear coeffs**. 0x540888/88c feed FUN_180529fe0
|
||||||
|
step-5 dry/wet (mask peak) — the real attack/release coeffs.
|
||||||
|
- FUN_180530d30 (0x5406b8/6c8/6d8/6e8 weights): fVar9 = 2000.0/NFFT? base; w8=pow(base,0.25);
|
||||||
|
v = level*0.25*w8*factor(4.0); q=1/(1+v/(level*4)); dVar1=(sr/0x1a0)*0x1ac*0.001;
|
||||||
|
w = 0.1^(1/(max(q*v)*dVar1)); pairs (w,1-w).
|
||||||
|
- Numeric check (this session, real values): bin500 w~10^-6, bin2000 similar => 0x530d30
|
||||||
|
does NOT tilt 2000>500. Tilt stays = warp (0x5406a8, K=7.942) + twin-mask curving.
|
||||||
|
Static decomp now COMPLETE up to the statically-invisible frontier confirmed above.
|
||||||
|
|
||||||
|
## ============ 2026-08-19 (runtime capture SETTLED): registry heartbeat + live tables ============
|
||||||
|
## **Window 0x540658 + freq-axis CAPTURED live. Internal DSP sample rate = 48000 (not 44100).**
|
||||||
|
- rtsnap_fast.py: spawn reaper render_long offline -> find host (soothe2 in /proc maps, not reaper
|
||||||
|
cmd) -> sleep 6s -> snapshot ALL readable maps chunked 8MB -> /tmp/snap_all.bin (318MB, 546 regs,
|
||||||
|
entries {lo,sz,bytes}+pad, idx in /tmp/snap_all.idx). Chunked pread REQUIRED: whole-region pread EIO.
|
||||||
|
- **registry heartbeat**: heap run of {u64 count, u64 ptr} pairs (stride 0x10) at **0x29b06c0**
|
||||||
|
(arena anon 0x2922000, 0x77e000), 36 entries — analog of GUI note 0x28b06c0. Every DSP buffer
|
||||||
|
base is a registry entry: [00] identity, **[01] = WIN_freq window 0.5->1.0 (saturates)** (this is
|
||||||
|
live 0x540658 8193 f32: 0.5, idx512=0.345?; idx1024=0.68, idx2048=0.8, tail 1.0),
|
||||||
|
[02] 0->~0.01, [03]=rwin_C0 0.596->0.126, [04]=0.404->0.874 complement, [05]=0.0435->~0,
|
||||||
|
[06]=0.9565->~1.0, [07] zeros+neg, [0d]=**freq-axis 0..23988.3 spacing 11.713 = 48000/4096**,
|
||||||
|
[0e]=2.017->0, [0f] doubled, [10]=1.2914->0, FFT work bufs 131072/32768/65536/16384.
|
||||||
|
- **CRITICAL**: freq-axis spacing 11.713 Hz => internal SR = 48000 regardless of project 44100.
|
||||||
|
Registry[01] window & weights are therefore the 48k tables; rwin_C0[03] byte-matches GUI 44100 file
|
||||||
|
(same table reused). Saved live tables:
|
||||||
|
- handoff/rtfreqaxis_48000_internal.npy (2048 f32, 0..23988.287, spacing 11.713)
|
||||||
|
- handoff/rtwin_freq_44100.npy (8193 f32 WIN window 0.5->1.0) <-- THE 0x540658 live capture
|
||||||
|
- handoff/rtwa_596.npy, rtwb_404.npy, rtwc_043.npy, rtwd_956.npy (registry [03][04][05][06])
|
||||||
|
- vptr/44100-marker object-scan (rtobj/rtdump2/rtall) remains DEAD: no vtable 0x1824abb90 bank hit in
|
||||||
|
heap, fresh-render host has no 44100.0 constants (internal is 48k). The registry run is the beacon.
|
||||||
|
- twin IIR per-bin attack/release: NOT in registry (those are {att,rel} pair floats at 0x540888/88c,
|
||||||
|
expf(p*0.11513), per-note-band smoothing inside tail-call thunks). Live capture needs the twin band
|
||||||
|
object; not yet located. Registry gives per-bin WEIGHTS (done) — attack/release remain static-only.
|
||||||
|
- Registry dump + all raw tables saved: /tmp/rtcapt/registry.txt, /tmp/rtcapt/*.f32.
|
||||||
|
- Scripts captured into repo: rtsnap_fast.py (snapshot), findctx.py (locate), rtchunk.py (chunked IO).
|
||||||
|
|
||||||
|
## ============ 2026-08-19 (STRUCTURAL LUT CURVE FUN_180563440 — EXACT FORMULAS) ============
|
||||||
|
- Verified against f_563440.dis + PE constants 0x1824c3c54=0.0009775171(=1/1023? 1/0x3ff),
|
||||||
|
0x1824c3ea4=1.0, 0x1824c41e0=2.0, 0x1824c4680=-1.0, 0x1824c3d8c=0.5, 0x1824c4f10=0x7fff.. (double |x| mask).
|
||||||
|
- Loop: 0x400 iterations (0..0x3ff), x = i*(1/1023)? const 0.0009775171 = 1/1023 → last bin x≈1.0.
|
||||||
|
(prior note said 1/1024 scale — corrected: constant value is 1/1023.)
|
||||||
|
- Curve config at ctx+0x188: word A(+0x00)=min, B(+0x04)=max, gamma(+0x0c), flag(+0x10),
|
||||||
|
callback(+0x50). Output written as double to ctx+0x198[i*8] (0x400 doubles).
|
||||||
|
- **Linear path (flag 0x10==0, 0x563595)**: t = x; if gamma!=1.0 and x>0: t = exp(log(x)/gamma);
|
||||||
|
val = A + (B-A)*t. (A=min of output, B=max).
|
||||||
|
- **Power-law path (flag!=0, 0x5635cd)**: t = 2x-1 (centered -1..1); if gamma!=1.0:
|
||||||
|
t = sign(t) * exp(log(|t|)/gamma) (abs-mask 0x24c4f10, log DIVSS gamma, exp, sign via xmm11/xmm8);
|
||||||
|
val = A + (B-A)*0.5*(1+t). When gamma=1.0 → val = A + (B-A)*x (identity linear).
|
||||||
|
- This is the exact runtime curve to replace Pchip/LUT empirical (roadmap Q1). Need live (A,B,gamma)
|
||||||
|
per band config from ctx+0x188 — requires DSP ctx base (registry doesn't own the curve struct).
|
||||||
|
- Level-tracker IIR (FUN_180563ce0) INIT confirmed: 341 bins x2?, state rows at +0x28/+0x40/+0x58
|
||||||
|
init [1,0,0,0]/[-1,0,0,0], level coeff 0.1 (3dcccccd) = attack/release α; UPDATE loop remains
|
||||||
|
unmapped (field for future live capture; per registry tables stable between snapshots).
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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()
|
||||||
@@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+8
-2
@@ -146,8 +146,14 @@ gain = max(1 - C, eps) * res_k**rp # rp = rp0 * Q**drp (res_power)
|
|||||||
2. **warp-терм**: эмпирическая формула — аппроксимация runtime-вычисления FUN_180563440;
|
2. **warp-терм**: эмпирическая формула — аппроксимация runtime-вычисления FUN_180563440;
|
||||||
для bit-exact заменить на реальный расчёт.
|
для bit-exact заменить на реальный расчёт.
|
||||||
3. **Стерео-верификация (M8)** — приоритет mono-путь или сразу стерео-граф.
|
3. **Стерео-верификация (M8)** — приоритет mono-путь или сразу стерео-граф.
|
||||||
4. **0x540658 window** — статически невидим; живые копии `rwin_*.npy` (48k) требуют warp-нормализации
|
4. **0x540658 window** — ✅ **ЗАКРЫТ 2026-08-19**: live-захват через registry heartbeat
|
||||||
под 44.1k рендеры.
|
(`{u64 count, u64 ptr}` run at psy. 0x29b06c0) во время offline-рендера. Окно = registry[01]
|
||||||
|
(8193 f32, 0.5→1.0, сатурация), freq-axis = registry[0d] (0..23988 Hz, spacing 11.713 ⇒
|
||||||
|
**внутренний SR=48000**), веса [03][04][05][06] (WA/WB/WC/WD). Сохранено: `handoff/rtwin_freq_44100.npy`,
|
||||||
|
`rtfreqaxis_48000_internal.npy`, `rtwa/rtwb/rtwc/rtwd_*.npy`. Инфра: `rtsnap_fast.py`,
|
||||||
|
`rtsnap2.py` (двухточечный diff подтвердил стабильность таблиц), `findctx.py`. Полное описание:
|
||||||
|
`handoff/NOTES_CAPTURE.md`. vptr/44100-marker объект-скан (rtobj) — тупик (производная vtable
|
||||||
|
не адресуется статически; регистр-бикон — рабочий путь).
|
||||||
|
|
||||||
## Где лежат детали
|
## Где лежат детали
|
||||||
|
|
||||||
|
|||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
import subprocess, os, glob, time, struct
|
||||||
|
def mem_open(pid): return os.open(f'/proc/{pid}/mem', os.O_RDONLY)
|
||||||
|
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
|
||||||
|
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','/home/m/soothe-bt/dual300.rpp'],
|
||||||
|
stdout=open('/dev/null','w'),stderr=subprocess.STDOUT)
|
||||||
|
host=find_host(proc); print('host',host)
|
||||||
|
time.sleep(4)
|
||||||
|
fd=mem_open(host)
|
||||||
|
SIG=struct.pack('<I',0x472c4400)
|
||||||
|
t0=time.time(); total=0; hits={}
|
||||||
|
for line in open(f'/proc/{host}/maps').read().splitlines():
|
||||||
|
p=line.split(); lo,hi=(int(x,16) for x in p[0].split('-'))
|
||||||
|
if 'r' not in p[1]: continue
|
||||||
|
t0r=time.time()
|
||||||
|
ok_read=0
|
||||||
|
a=lo
|
||||||
|
while a<hi:
|
||||||
|
n=min(hi-a, 0x400000)
|
||||||
|
try:
|
||||||
|
d=os.pread(fd,n,a)
|
||||||
|
except Exception:
|
||||||
|
a+=n; continue
|
||||||
|
if not d:
|
||||||
|
a+=n; continue
|
||||||
|
ok_read+=len(d)
|
||||||
|
i=0
|
||||||
|
while True:
|
||||||
|
i=d.find(SIG,i)
|
||||||
|
if i<0: break
|
||||||
|
hits[a+i]=None; i+=1
|
||||||
|
a+=n
|
||||||
|
if ok_read:
|
||||||
|
print(' 0x%x-%x MB=%.1f ok=%.1fMB'%(lo,hi,(hi-lo)/1e6,ok_read/1e6))
|
||||||
|
total+=ok_read
|
||||||
|
print('scanned ok=%.0fMB t=%.1fs n44100=%d'%(total/1e6,time.time()-t0,len(hits)))
|
||||||
|
for a in list(hits)[:5]:
|
||||||
|
print(' HIT 0x%x'%a)
|
||||||
|
proc.kill()
|
||||||
+56
-33
@@ -1,29 +1,25 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""rtdeep2.py — find DSP ctx object via phase-table pointer beacon, then dump window 0x540658 & neighbors."""
|
"""rtdeep2.py — find DSP ctx object via phase-table pointer beacon, then dump window 0x540658 & neighbors."""
|
||||||
import subprocess, time, glob, os, struct, sys
|
import subprocess, time, glob, os, sys
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import struct
|
||||||
|
|
||||||
RPP = sys.argv[1] if len(sys.argv) > 1 else '/home/m/soothe-bt/dual300.rpp'
|
RPP = sys.argv[1] if len(sys.argv) > 1 else '/home/m/soothe-bt/dual300.rpp'
|
||||||
BEACON = 0x182615608 # VA of phase_table_1024 (static .data)
|
BEACON = np.uint64(0x182615608)
|
||||||
|
|
||||||
|
|
||||||
def vst_base(pid):
|
def get_maps(pid):
|
||||||
lo = None
|
try:
|
||||||
for line in open(f'/proc/{pid}/maps').read().splitlines():
|
return open(f'/proc/{pid}/maps').read()
|
||||||
if 'soothe2' not in line:
|
except Exception:
|
||||||
continue
|
return ''
|
||||||
a = int(line.split()[0].split('-')[0], 16)
|
|
||||||
lo = a if lo is None else min(lo, a)
|
|
||||||
return lo
|
|
||||||
|
|
||||||
|
|
||||||
def readable_regions(pid):
|
def readable_regions(pid):
|
||||||
out = []
|
out = []
|
||||||
for line in open(f'/proc/{pid}/maps').read().splitlines():
|
for line in get_maps(pid).splitlines():
|
||||||
p = line.split()[0]
|
p = line.split()[0]
|
||||||
lo, hi = int(p.split('-')[0], 16), int(p.split('-')[1], 16)
|
lo, hi = int(p.split('-')[0], 16), int(p.split('-')[1], 16)
|
||||||
if 'vst3' in line:
|
|
||||||
continue # skip module itself
|
|
||||||
out.append((lo, hi))
|
out.append((lo, hi))
|
||||||
return out
|
return out
|
||||||
|
|
||||||
@@ -35,17 +31,28 @@ def find_beacon(pid):
|
|||||||
return []
|
return []
|
||||||
res = []
|
res = []
|
||||||
for lo, hi in readable_regions(pid):
|
for lo, hi in readable_regions(pid):
|
||||||
sz = (hi - lo) & ~7
|
sz = hi - lo
|
||||||
if sz <= 0 or sz > 0x200000000:
|
if sz <= 0 or sz > 0x200000000:
|
||||||
continue
|
continue
|
||||||
try:
|
if sz < 65536:
|
||||||
buf = os.pread(mem, sz, lo)
|
|
||||||
except Exception:
|
|
||||||
continue
|
continue
|
||||||
arr = np.frombuffer(buf, dtype='<u8')
|
off = 0
|
||||||
|
while off < sz:
|
||||||
|
chunk = min(sz - off, 4 << 20)
|
||||||
|
try:
|
||||||
|
buf = os.pread(mem, int(chunk), lo + off)
|
||||||
|
except Exception:
|
||||||
|
break
|
||||||
|
arr = np.frombuffer(buf[:len(buf) & ~7], dtype='<u8')
|
||||||
|
if len(arr):
|
||||||
idx = np.where(arr == BEACON)[0]
|
idx = np.where(arr == BEACON)[0]
|
||||||
for i in idx:
|
for i in idx:
|
||||||
res.append(lo + int(i) * 8)
|
res.append(lo + off + int(i) * 8)
|
||||||
|
off += int(chunk)
|
||||||
|
if len(res) > 64:
|
||||||
|
break
|
||||||
|
if len(res) > 64:
|
||||||
|
break
|
||||||
os.close(mem)
|
os.close(mem)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@@ -57,26 +64,42 @@ def main():
|
|||||||
host = None
|
host = None
|
||||||
while time.time() - t0 < 60:
|
while time.time() - t0 < 60:
|
||||||
for p in glob.glob('/proc/[0-9]*'):
|
for p in glob.glob('/proc/[0-9]*'):
|
||||||
try:
|
pid = int(os.path.basename(p))
|
||||||
maps = open(f'/proc/{p}/maps').read()
|
if 'soothe2' in (get_maps(pid) or ''):
|
||||||
except Exception:
|
host = pid
|
||||||
continue
|
|
||||||
if 'soothe2' not in maps:
|
|
||||||
continue
|
|
||||||
host = int(os.path.basename(p))
|
|
||||||
break
|
break
|
||||||
if host:
|
if host:
|
||||||
break
|
break
|
||||||
time.sleep(0.05)
|
time.sleep(0.01)
|
||||||
print(f'host={host}')
|
print(f'host={host}', flush=True)
|
||||||
if not host:
|
if not host:
|
||||||
proc.kill()
|
proc.wait(timeout=10)
|
||||||
return
|
return
|
||||||
base = vst_base(host)
|
|
||||||
# allow module load: plugin init first
|
|
||||||
mem = os.open(f'/proc/{host}/mem', os.O_RDONLY)
|
mem = os.open(f'/proc/{host}/mem', os.O_RDONLY)
|
||||||
t1 = time.time()
|
t1 = time.time()
|
||||||
while time.time() - t1 < 50:
|
regs = []
|
||||||
|
for line in get_maps(host).splitlines():
|
||||||
|
p = line.split()[0]
|
||||||
|
lo, hi = int(p.split('-')[0], 16), int(p.split('-')[1], 16)
|
||||||
|
if 'vst3' in line or 'reaper' in line or 'soothe2' in line and 'rw' not in line.split()[1][:2]:
|
||||||
|
continue
|
||||||
|
if 'rw' not in line.split()[1][:2]:
|
||||||
|
continue
|
||||||
|
if hi - lo > 0x10000000:
|
||||||
|
continue
|
||||||
|
regs.append((lo, hi))
|
||||||
|
print(f'{len(regs)} rw regions to capture', flush=True)
|
||||||
|
with open('/tmp/host_rw.bin', 'wb') as f:
|
||||||
|
for lo, hi in regs:
|
||||||
|
try:
|
||||||
|
buf = os.pread(mem, hi - lo, lo)
|
||||||
|
f.write(lo.to_bytes(8, 'little'))
|
||||||
|
f.write((hi - lo).to_bytes(8, 'little'))
|
||||||
|
f.write(buf)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
print(f'captured {os.path.getsize("/tmp/host_rw.bin")} bytes', flush=True)
|
||||||
|
while time.time() - t1 < 30:
|
||||||
hits = find_beacon(host)
|
hits = find_beacon(host)
|
||||||
ctxs = set()
|
ctxs = set()
|
||||||
for p in hits:
|
for p in hits:
|
||||||
@@ -96,7 +119,7 @@ def main():
|
|||||||
blk = os.pread(mem, 0x8000, ctx + 0x540000)
|
blk = os.pread(mem, 0x8000, ctx + 0x540000)
|
||||||
open(f'/tmp/ctx_{ctx:x}_win.bin', 'wb').write(blk)
|
open(f'/tmp/ctx_{ctx:x}_win.bin', 'wb').write(blk)
|
||||||
break
|
break
|
||||||
time.sleep(0.2)
|
time.sleep(0.05)
|
||||||
os.close(mem)
|
os.close(mem)
|
||||||
try:
|
try:
|
||||||
proc.wait(timeout=5)
|
proc.wait(timeout=5)
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -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)
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
import subprocess, os, glob, time, struct
|
||||||
|
def mem_open(pid): return os.open(f'/proc/{pid}/mem', os.O_RDONLY)
|
||||||
|
def find_hosts():
|
||||||
|
hosts=[]
|
||||||
|
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:
|
||||||
|
hosts.append(pid)
|
||||||
|
return hosts
|
||||||
|
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','/home/m/soothe-bt/render_long.rpp'],
|
||||||
|
stdout=open('/dev/null','w'),stderr=subprocess.STDOUT)
|
||||||
|
t0=time.time(); host=None
|
||||||
|
while time.time()-t0<60 and not host:
|
||||||
|
hs=find_hosts(); host=hs[0] if hs else None; time.sleep(0.2)
|
||||||
|
print('host',host, flush=True)
|
||||||
|
fd=mem_open(host)
|
||||||
|
def snap(tag):
|
||||||
|
out=open('/tmp/snap_%s.bin'%tag,'wb'); idx=open('/tmp/snap_%s.idx'%tag,'wb')
|
||||||
|
t0=time.time(); nreg=0; nb=0
|
||||||
|
for line in open(f'/proc/{host}/maps').read().splitlines():
|
||||||
|
p=line.split(); 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('<QQ',a,len(d))); out.write(d)
|
||||||
|
idx.write(struct.pack('<QQ',a,len(d)))
|
||||||
|
nreg+=1; nb+=len(d); a+=n
|
||||||
|
out.close(); idx.close()
|
||||||
|
print('snap %s: %d MB %d regs in %.1fs'%(tag,nb/1e6,nreg,time.time()-t0), flush=True)
|
||||||
|
time.sleep(8); snap('t1')
|
||||||
|
time.sleep(32); snap('t2')
|
||||||
|
time.sleep(200)
|
||||||
|
if proc.poll() is None: proc.kill()
|
||||||
|
print('done')
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import subprocess, os, glob, time, struct, sys
|
||||||
|
def mem_open(pid): return os.open(f'/proc/{pid}/mem', os.O_RDONLY)
|
||||||
|
def find_hosts():
|
||||||
|
hosts=[]
|
||||||
|
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:
|
||||||
|
hosts.append(pid)
|
||||||
|
return hosts
|
||||||
|
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','/home/m/soothe-bt/render_long.rpp'],
|
||||||
|
stdout=open('/dev/null','w'),stderr=subprocess.STDOUT)
|
||||||
|
# wait for host
|
||||||
|
t0=time.time(); host=None
|
||||||
|
while time.time()-t0<60 and not host:
|
||||||
|
hs=find_hosts()
|
||||||
|
host=hs[0] if hs else None
|
||||||
|
time.sleep(0.2)
|
||||||
|
print('host',host, flush=True)
|
||||||
|
time.sleep(6) # let render warm up, tables built
|
||||||
|
fd=mem_open(host)
|
||||||
|
out=open('/tmp/snap_all.bin','wb')
|
||||||
|
idx=open('/tmp/snap_all.idx','wb')
|
||||||
|
t0=time.time(); nreg=0; nbytes=0
|
||||||
|
for line in open(f'/proc/{host}/maps').read().splitlines():
|
||||||
|
p=line.split(); 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('<QQ',a,len(d))); out.write(d)
|
||||||
|
idx.write(struct.pack('<QQ',a,len(d)))
|
||||||
|
nreg+=1; nbytes+=len(d)
|
||||||
|
a+=n
|
||||||
|
out.close(); idx.close()
|
||||||
|
print('snapshot ok: %d regs %d MB in %.1fs'%(nreg,nbytes/1e6,time.time()-t0), flush=True)
|
||||||
|
if proc.poll() is None: proc.kill()
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
+51
@@ -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
|
||||||
Reference in New Issue
Block a user