Package Apple Silicon VM exports and cached runtime switching with local cabinet controls and conagent provisioning. Preserve experimental SPIKE 2 emulation. Improve preparation concurrency, Ghidra checkpoints, signature importing, sound indexing and client-side spectrograms. Include regression tests and validation notes.
74 lines
4.5 KiB
Python
74 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Run sound-catalog recovery against retained extracted ROMs via the local API.
|
|
|
|
Stores scripts and hash-bound evidence, without restarting running analysis or
|
|
rewriting archived audio. Scratch inputs are removed after each release.
|
|
"""
|
|
import argparse
|
|
import json
|
|
import pathlib
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
import urllib.parse
|
|
import urllib.request
|
|
sys.path.insert(0,str(pathlib.Path(__file__).resolve().parents[1]/'plugins'))
|
|
import godot_scripts
|
|
from ram_workspace import ram_workspace
|
|
|
|
|
|
def main():
|
|
parser=argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--config',type=pathlib.Path,default=pathlib.Path('config.json'))
|
|
parser.add_argument('--api',default='http://127.0.0.1:8080')
|
|
parser.add_argument('--output',type=pathlib.Path,default=pathlib.Path('data/validation/sound-catalog'))
|
|
args=parser.parse_args();config=json.loads(args.config.read_text());scratch,budget=ram_workspace(config)
|
|
args.output.mkdir(parents=True,exist_ok=True)
|
|
summaries=[]
|
|
def get(path):return json.load(urllib.request.urlopen(args.api+path,timeout=120))
|
|
snapshots=get('/api/snapshots')
|
|
for snapshot in snapshots:
|
|
if snapshot['layer']!='extracted':continue
|
|
manifest=get('/api/snapshots/'+snapshot['id'])
|
|
entries={e['path']:e for e in manifest['entries'] if e['kind']=='file'}
|
|
scripts=[p for p in entries if p.endswith(('.pck','.gd','.gdc'))]
|
|
row={'snapshot':snapshot['id'],'release':snapshot['release'],'script_inputs':len(scripts)}
|
|
print('BEGIN',snapshot['release'],flush=True)
|
|
if not scripts:
|
|
row.update(status='no_script_catalog',cues=0,named_sounds=0);summaries.append(row);continue
|
|
selected=set(scripts)
|
|
for script in scripts:
|
|
for parent in pathlib.PurePosixPath(script).parents:
|
|
image,game=str(parent/'image.bin'),str(parent/'game')
|
|
if image in entries and game in entries:
|
|
selected.update((image,game));break
|
|
if sum(entries[p]['size'] for p in selected)>budget//2:raise ValueError('Sound validation inputs exceed scratch budget')
|
|
with tempfile.TemporaryDirectory(prefix='sound-catalog-',dir=scratch) as temp:
|
|
root=pathlib.Path(temp);source=root/'input';source.mkdir();output=root/'output';output.mkdir()
|
|
for path in sorted(selected):
|
|
dest=source/path;dest.parent.mkdir(parents=True,exist_ok=True)
|
|
url=args.api+'/api/file/'+snapshot['id']+'?'+urllib.parse.urlencode({'path':path})
|
|
with urllib.request.urlopen(url,timeout=180) as response,dest.open('wb') as stream:shutil.copyfileobj(response,stream)
|
|
if dest.stat().st_size!=entries[path]['size']:raise ValueError('Incomplete archived input read')
|
|
result=godot_scripts.run({'protocol':1,'input_dir':str(source),'output_dir':str(output),'workspace_bytes':budget,'settings':config['plugins']['godot-scripts']['settings']})
|
|
evidence=json.loads((output/'godot-scripts-evidence.json').read_text())
|
|
catalogs=[r['sound_catalog'] for r in evidence['scripts'] if 'sound_catalog' in r]
|
|
row.update(status=result['coverage'],cues=sum(len(c['cues']) for c in catalogs),
|
|
linked_cues=sum(len({r['name'] for r in c['links']}) for c in catalogs),
|
|
named_sounds=sum(len({r['index'] for r in c['links']}) for c in catalogs),
|
|
recovered_scripts=evidence['summary']['recovered'],failed_scripts=evidence['summary']['failed'])
|
|
destination=args.output/snapshot['id'];destination.mkdir(exist_ok=True)
|
|
shutil.copyfile(output/'godot-scripts-evidence.json',destination/'godot-scripts-evidence.json')
|
|
# The cue source and its evidence are sufficient to reproduce naming;
|
|
# omit unrelated recovered scripts from the persisted validation set.
|
|
for record in evidence['scripts']:
|
|
if 'sound_catalog' in record:
|
|
target=destination/record['output'];target.parent.mkdir(parents=True,exist_ok=True);shutil.copyfile(output/record['output'],target)
|
|
row['evidence']=str(destination/'godot-scripts-evidence.json')
|
|
summaries.append(row)
|
|
(args.output/'summary.json').write_text(json.dumps(summaries,indent=2)+'\n')
|
|
print('DONE',json.dumps(row),flush=True)
|
|
(args.output/'summary.json').write_text(json.dumps(summaries,indent=2)+'\n')
|
|
|
|
if __name__=='__main__':main()
|