#!/usr/bin/env python3 """Patch a soothe2 PARAM value inside an RPP's base64 state block. Usage: patchparam.py param=value [param=value...] Value formatting: %16 params (depth, band freq/q/sens/balance, offset etc) need %.16f; short params (selectivity, mix, mode, oversample, resolution, attack, band on/balance?) use 'X.0'. Append '!' to force %.16f, e.g. depth=0.0! to guarantee full precision. """ import re, sys, base64 def fmt(val: float, force16: bool): if force16: return '%.16f' % val s = repr(val) if '.' not in s: s += '.0' if '.' in s and float(s) == int(float(s)) and len(s.rstrip('0').rstrip('.')) <= 4: return s.rstrip('0').rstrip('.') if '.' in s else s return '%.16f' % val def main(): src, out = sys.argv[1], sys.argv[2] params = {} for a in sys.argv[3:]: if a in ('-f16',): continue k, v, f16 = a.split('=', 1)[0], a.split('=', 1)[1], '!' in a params[k] = (float(v.rstrip('!')), f16) s = open(src, encoding='utf8', errors='replace').read() i = s.find('PpeX') if i < 0: i = s.find('VkMy') if i < 0: sys.exit('no state block found') j = s.find('>', i) if j < 0: sys.exit('malformed state block') block = s[i:j] raw = bytearray(base64.b64decode(block)) txt = raw.decode('latin-1') n = 0 for pm in re.finditer(r'(', txt): did = pm.group(2) if did in params: val, f16 = params[did] txt = txt[:pm.start()] + pm.group(1) + fmt(val, f16) + pm.group(3) + '/>' + txt[pm.end():] n += 1 if n != len(params): print(f'warning: patched {n}/{len(params)} params') raw = txt.encode('latin-1') enc = base64.b64encode(raw).decode('ascii') line_start = s.rfind('\n', 0, i) + 1 indent = s[line_start:i] wrapped = '\n'.join(indent + enc[i:i+128] for i in range(0, len(enc), 128)) jline = s.rfind('\n', 0, j) + 1 gt_indent = s[jline:j] s = s[:line_start] + wrapped + '\n' + gt_indent + s[j:] open(out, 'w', encoding='utf8').write(s) print(f'{out}: patched {n} params') if __name__ == '__main__': main()