#!/usr/bin/env python3 """Extract soothe2 plugin params from an .rpp as a JSON dict. Reaper stores the VST3 state as base64: line1 after is a short binary header, following lines (128-char chunks) are the outer b64 blob which wraps ... at byte offset 16. """ import base64, re, html, sys def b64dec(s): s = re.sub(r'[^A-Za-z0-9+/=]', '', s) best = b'' for pad in range(4): s2 = s + '=' * pad try: d = base64.b64decode(s2, validate=False) if len(d) > len(best): best = d except Exception: continue # if nothing cleanly decodes, brute-force by trimming tail bad chars if not best: for cut in range(1, min(8, len(s))): try: d = base64.b64decode(s[:-cut], validate=False) if len(d) > len(best): best = d except Exception: continue return best def rpp_params(path): lines = open(path, encoding='utf8', errors='ignore').read().split('\n') for i, l in enumerate(lines): if 'soothe2_x64.vst3' in l: break chunks = [] for l in lines[i + 1:]: s = l.strip() if s == '}': break if s: chunks.append(s) # Some RPPs wrap the param blob with the short binary-header line as the # first entry, others start directly with the payload line. Try both joins. for text in (''.join(chunks), ''.join(chunks[1:])): outer = b64dec(text) p = outer.find(b' 2 and sys.argv[1] == '--flat': for f in sys.argv[2:]: p = params_json(f) for k in sorted(p): print(f'{k}={p[k]}') else: for f in sys.argv[1:]: p = params_json(f) print('===', f.split('/')[-1]) for k in sorted(p): print(f' {k:22s} = {p[k]}')