mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
b2929aa504
Reinstates the runtime-loaded overlay DLL cache (Layer A) and the offline overlay compilation pipeline that were built on the pruned codex/cache-discovery branch (tip 2bbafc2) and never merged. Ported onto current master; rebuilt rather than history-restored. Runtime (off by default, opt in via [runtime] overlay_cache = true): - overlay_loader.c/.h, overlay_api.h: CRC32-keyed DLL cache at cache/<game_id>/<region>_<crc>.dll. On a dirty-RAM dispatch miss the loader LoadLibrary's the matching DLL, wires runtime callbacks via overlay_init(), enumerates func_XXXXXXXX exports, and dispatches them BEFORE the interpreter. - overlay_capture.c/.h: write-once capture set; records overlay DMA bytes and writes overlay_captures.json (execution-verified seeds) at clean exit. - dma.c: capture + cd_dma_log hook at CD DMA completion (async model). - cdrom.c: cdrom_get_setloc_lba() for disc-position correlation. - dirty_ram_interp.c: g_dirty_ram_exec_pc_table (exec-verified PCs), a decodability guard on JAL-into-dirty targets, and the overlay dispatch hooks. - debug_server.c: cd_read_log + overlay_loader_status TCP commands. - config_loader: [runtime] overlay_cache flag (default false). Recompiler (compile_overlays.py / extract_overlays.py drive these): - main_psx.cpp --overlay-exact: analyze only explicit entries + direct JAL targets and set split_mid_function_targets=false, so overlay basic-block targets are NOT split into bogus standalone functions (the mid-function-seed failure mode that produced black-on-load / blue-on-new-game). - function_analysis.cpp: analyze_exact_entries(); code_generator.cpp: gate the mid-function split pre-pass behind split_mid_function_targets (default true, so normal main-EXE codegen is unchanged - Tomba still regens to 2854 funcs). Deltas from the original: save states dropped (never worked); overlay enable is runtime-config-gated (off by default) instead of a build-time define; loader stderr logging converted to the overlay_loader_status TCP path (CLAUDE.md Rule 3). Recovered from _backups/2026-06-06-consolidation/psxrecomp-all-PRE.bundle. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
150 lines
5.2 KiB
Python
150 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
dump_overlays.py — Extract executed overlay regions from a live runtime.
|
|
|
|
Queries overlay_dump (dirty_ram bitmap scan) and cd_read_log (CD DMA
|
|
transfer ring) to produce overlay_map.jsonl entries with:
|
|
crc32 — CRC32 of the RAM bytes (for verification)
|
|
load_addr — physical RAM address where the overlay lives
|
|
size — byte count of the region
|
|
start_lba — disc LBA of the first sector of this overlay file
|
|
|
|
The start_lba lets extract_overlays.py seek directly to the correct disc
|
|
position without brute-force scanning the whole disc image.
|
|
|
|
Usage:
|
|
python3 tools/dump_overlays.py [--port PORT] [--lo PHYS] [--dir DIR] [--out JSONL]
|
|
|
|
Defaults:
|
|
port 4470
|
|
lo 0x98000 (above main EXE text)
|
|
dir logs/SCUS-94236/overlays (relative to cwd)
|
|
out logs/SCUS-94236/overlay_map.jsonl
|
|
"""
|
|
|
|
import socket, json, sys, os, argparse
|
|
|
|
def send_cmd(port, cmd, **kwargs):
|
|
payload = {'id': 1, 'cmd': cmd}
|
|
payload.update(kwargs)
|
|
s = socket.create_connection(('127.0.0.1', port), timeout=10)
|
|
s.sendall(json.dumps(payload).encode() + b'\n')
|
|
data = b''
|
|
s.settimeout(30.0)
|
|
while True:
|
|
chunk = s.recv(65536)
|
|
if not chunk: break
|
|
data += chunk
|
|
try:
|
|
json.loads(data.decode())
|
|
break
|
|
except Exception:
|
|
pass
|
|
s.close()
|
|
return json.loads(data.decode())
|
|
|
|
def find_lba_for_region(cd_log, phys_addr, size):
|
|
"""Return the disc start_lba for an overlay region if known, else -1.
|
|
|
|
Groups consecutive CD DMA entries (same setloc_lba, dest advancing by
|
|
sector size) and returns the lba of the group whose dest range covers
|
|
phys_addr for at least `size` bytes.
|
|
"""
|
|
SECTOR = 2048
|
|
entries = cd_log.get('entries', [])
|
|
if not entries:
|
|
return -1
|
|
|
|
# Build file-load groups: consecutive entries where dest advances by 2048
|
|
# and lba is the same (all sectors of one file share the SetLoc LBA).
|
|
groups = []
|
|
cur = None
|
|
for e in entries:
|
|
dest = int(e['dest'], 16)
|
|
lba = e['lba']
|
|
sz = e['size']
|
|
if cur and cur['lba'] == lba and dest == cur['end']:
|
|
cur['end'] += sz
|
|
cur['total'] += sz
|
|
else:
|
|
if cur:
|
|
groups.append(cur)
|
|
cur = {'lba': lba, 'start': dest, 'end': dest + sz, 'total': sz}
|
|
if cur:
|
|
groups.append(cur)
|
|
|
|
# Find a group whose start <= phys_addr and end >= phys_addr + size
|
|
for g in groups:
|
|
if g['start'] <= phys_addr and g['end'] >= phys_addr + size:
|
|
# LBA offset for the exact start of the overlay within this file
|
|
offset_sectors = (phys_addr - g['start']) // SECTOR
|
|
return g['lba'] + offset_sectors
|
|
# Partial overlap — return the group start LBA as best guess
|
|
if g['start'] <= phys_addr < g['end']:
|
|
offset_sectors = (phys_addr - g['start']) // SECTOR
|
|
return g['lba'] + offset_sectors
|
|
return -1
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description=__doc__,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
ap.add_argument('--port', type=int, default=4470)
|
|
ap.add_argument('--lo', default='0x98000')
|
|
ap.add_argument('--dir', default='logs/SCUS-94236/overlays')
|
|
ap.add_argument('--out', default='logs/SCUS-94236/overlay_map.jsonl')
|
|
args = ap.parse_args()
|
|
|
|
os.makedirs(args.dir, exist_ok=True)
|
|
os.makedirs(os.path.dirname(args.out) or '.', exist_ok=True)
|
|
|
|
print('Querying overlay_dump...')
|
|
result = send_cmd(args.port, 'overlay_dump', lo=args.lo, dir=args.dir)
|
|
if not result.get('ok'):
|
|
print(f'ERROR: {result.get("error")}'); sys.exit(1)
|
|
regions = result.get('regions', [])
|
|
|
|
print('Querying cd_read_log...')
|
|
cd_log = send_cmd(args.port, 'cd_read_log', tail=4096)
|
|
|
|
print(f'Found {len(regions)} overlay region(s), {cd_log.get("total", 0)} CD DMA entries')
|
|
|
|
# Load existing manifest to avoid duplicates
|
|
existing = set()
|
|
if os.path.exists(args.out):
|
|
with open(args.out, encoding='utf-8') as f:
|
|
for line in f:
|
|
try:
|
|
e = json.loads(line)
|
|
existing.add((e.get('crc32'), e.get('load_addr')))
|
|
except Exception:
|
|
pass
|
|
|
|
new_count = 0
|
|
with open(args.out, 'a', encoding='utf-8') as f:
|
|
for r in regions:
|
|
key = (r['crc32'], r['addr'])
|
|
phys_addr = int(r['addr'], 16)
|
|
|
|
lba = find_lba_for_region(cd_log, phys_addr, r['size'])
|
|
lba_str = f'0x{lba:X}' if lba >= 0 else 'unknown'
|
|
|
|
if key in existing:
|
|
print(f' skip {r["addr"]} {r["size"]:>8} bytes lba={lba_str} (already logged)')
|
|
continue
|
|
|
|
entry = {
|
|
'crc32': r['crc32'],
|
|
'load_addr': r['addr'],
|
|
'size': r['size'],
|
|
'start_lba': lba_str,
|
|
}
|
|
f.write(json.dumps(entry) + '\n')
|
|
existing.add(key)
|
|
new_count += 1
|
|
print(f' new {r["addr"]} {r["size"]:>8} bytes lba={lba_str} crc32={r["crc32"]}')
|
|
|
|
print(f'\n{new_count} new overlay(s) appended to {args.out}')
|
|
|
|
if __name__ == '__main__':
|
|
main()
|