Files
soothe2-re/rtcapture.py
T

146 lines
5.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""rtcapture.py — runtime-захват soothe2 из yabridge-host во время offline-рендера.
Запускает reaper -renderproject <rpp>, находит yabridge-host.exe.so с soothe2_x64.vst3,
вычисляет runtime-базу модуля (по MZ/PE) и непрерывно сэмплирует целевую RAM плагина
(RVA = VA - 0x180000000). Печатает последние стабильные значения по окончании рендера.
Цели (RVA от базы модуля):
0x540658 окно/FIR (размер окна из plan) [искомое 0.540658]
0x540678 per-bin уровень-трекеры
0x5407c8 аккумулятор маски (C)
0x540698 freq-axis значение
0x540534 N/2 (FFT size)
0x5408b0 узлы кривой 0x188 (20 doubles)
0x540888 0x540880 ctor-константы
0x540530..0x5405a0 план-таблицы FFT
"""
import os, sys, glob, time, struct, subprocess
RPP = sys.argv[1] if len(sys.argv) > 1 else '/home/m/soothe-bt/render_long.rpp'
BASE = 0x180000000
TARGETS = {
'window_0x540658': (0x540658, 0x1000),
'levels_0x540678': (0x540678, 0x400),
'acc_0x5407c8': (0x5407c8, 0x40),
'freqaxis_0x540698': (0x540698, 0x20),
'n2_0x540534': (0x540534, 0x20),
'curve_0x5408b0': (0x5408b0, 0x200),
'ctor_0x540870': (0x540870, 0x60),
'plan_0x540530': (0x540530, 0x80),
}
def find_base(pid):
maps = open(f'/proc/{pid}/maps').read()
mem = os.open(f'/proc/{pid}/mem', os.O_RDONLY)
seen = set()
for line in maps.splitlines():
p = line.split()
lo = int(p[0].split('-')[0], 16)
if lo in seen:
continue
seen.add(lo)
try:
hdr = os.pread(mem, 0x1000, lo)
except Exception:
continue
if hdr[:2] != b'MZ':
continue
try:
x = struct.unpack('<I', hdr[0x3c:0x40])[0]
if hdr[x:x + 4] != b'PE\x00\x00':
continue
magic = struct.unpack('<H', hdr[x + 24:x + 26])[0]
if magic == 0x20b:
ib = struct.unpack('<Q', hdr[x + 24 + 24:x + 24 + 32])[0] # PE32+ ImageBase
elif magic == 0x10b:
ib = struct.unpack('<I', hdr[x + 24 + 28:x + 24 + 32])[0]
else:
continue
return lo, ib
except Exception:
continue
os.close(mem)
return None, 0
def 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 main():
proc = subprocess.Popen(['reaper', '-nosplash', '-renderproject', RPP],
stdout=open('/tmp/rtcap.log', 'w'), stderr=subprocess.STDOUT)
host = None
t0 = time.time()
while time.time() - t0 < 120:
for pid in glob.glob('/proc/[0-9]*'):
pid = int(os.path.basename(pid))
try:
cmd = open(f'/proc/{pid}/cmdline', 'rb').read().decode('utf8', 'replace')
maps = open(f'/proc/{pid}/maps').read()
except Exception:
continue
if 'yabridge-host' in cmd and ('soothe2' in maps or 'vst3' in maps):
host = pid
break
if host:
break
time.sleep(0.2)
if not host:
print('HOST NOT FOUND')
proc.kill()
return
base, ib = find_base(host)
print(f'host pid={host} module_base={hex(base)} PE_ImageBase={hex(ib)} delta={hex(base - ib)}', flush=True)
if not base:
proc.kill()
return
# sample until reaper exits
last = {}
ts = time.time()
while proc.poll() is None and time.time() - ts < 150:
for name, (rva, n) in TARGETS.items():
d = read(host, base + rva, n)
if d is not None:
last[name] = d
time.sleep(0.05)
proc.wait(timeout=10)
print('--- captured (render done) ---')
for name, (rva, n) in TARGETS.items():
d = last.get(name)
if not d:
print(f'{name:20s} MISSING')
continue
vals = struct.unpack('<%dI' % (min(n, len(d)) // 4), d[:min(n, len(d))])
if name == 'window_0x540658':
fl = struct.unpack('<%df' % (len(d) // 4), d[:len(d) // 4 * 4])
nz = [(i, round(v, 6)) for i, v in enumerate(fl) if abs(v) > 1e-9]
print(f'{name:20s} nonzero={len(nz)} first20={nz[:20]}')
elif name == 'acc_0x5407c8':
print(f'{name:20s} ints={[hex(v) for v in vals[:8]]}')
elif name == 'curve_0x5408b0':
dl = struct.unpack('<%dd' % (len(d) // 8), d[:len(d) // 8 * 8])
print(f'{name:20s} doubles={[round(v, 6) for v in dl[:20]]}')
elif name == 'levels_0x540678':
fl = struct.unpack('<%df' % (len(d) // 4), d[:len(d) // 4 * 4])
print(f'{name:20s} floats={[round(v, 5) for v in fl[:40]]}')
elif name in ('ctor_0x540870',):
fl = struct.unpack('<%df' % (len(d) // 4), d[:len(d) // 4 * 4])
print(f'{name:20s} floats={[round(v, 5) for v in fl[:16]]}')
elif name in ('plan_0x540530',):
print(f'{name:20s} ints={[hex(v) for v in vals[:24]]}')
else:
print(f'{name:20s} ints={[hex(v) for v in vals[:8]]}')
print('done')
if __name__ == '__main__':
main()