Files
ps3-undervolt/syscon_dump.py
2026-08-03 13:02:16 +02:00

124 lines
4.0 KiB
Python

#!/usr/bin/python3
"""
SYSCON EEPROM dumper for Sherwood/SW-type consoles.
Reuses PS3UART from ps3_syscon_uart_script.py (must be in the same
directory, or edit the import path below).
Reads in 0x40-byte chunks (the max chunk size this SC firmware accepts,
confirmed by "r 0 41" -> Invalid arg vs "r 0 40" -> OK) starting at
--start and continuing until --end is reached OR the console reports
an error/invalid address, whichever comes first. Saves progress
incrementally so a Ctrl+C or a mid-dump failure doesn't lose what's
already been read.
Usage:
python3 syscon_dump.py /dev/ttyUSB0 SW --start 0 --end 8000 -o backup
Output:
backup.bin - raw binary, FF-padded for any addresses never reached
backup.txt - human-readable hex dump (address + 16 bytes + ascii)
"""
import argparse
import re
import sys
import time
from ps3_syscon_uart_script import PS3UART
CHUNK = 0x40
HEXROW_RE = re.compile(r'^[0-9A-Fa-f]{2}$')
def parse_chunk(lines):
"""Pull the actual data bytes out of the '# xx xx xx ...' rows,
skipping the '+0 +1 ...' header and '----' separator lines."""
out = []
for line in lines:
line = line.strip()
if line.startswith('#'):
line = line[1:].strip()
tokens = line.split()
if tokens and all(HEXROW_RE.match(t) for t in tokens):
out.extend(int(t, 16) for t in tokens)
return out
def dump(port, sctype, start, end, out_prefix, wait):
ps3 = PS3UART(port, sctype)
print(ps3.auth())
data = bytearray()
addr = start
last_good = start - 1
try:
while addr < end:
ret, lines = ps3.command('r {:x} {:x}'.format(addr, CHUNK), wait=wait)
if ret != 0:
print('\nStopped at 0x{:04X}: ret=0x{:08X} ({})'.format(
addr, ret, ' '.join(lines) if lines else 'no message'))
break
chunk_bytes = parse_chunk(lines)
if not chunk_bytes:
print('\nStopped at 0x{:04X}: no parseable data in response'.format(addr))
break
# pad/truncate defensively so addressing stays aligned even
# if a row comes back short
chunk_bytes = (chunk_bytes + [0xFF] * CHUNK)[:CHUNK]
data.extend(chunk_bytes)
last_good = addr + CHUNK - 1
print('\r0x{:04X} / 0x{:04X}'.format(addr, end), end='', flush=True)
addr += CHUNK
except KeyboardInterrupt:
print('\nInterrupted by user, saving what we have so far...')
print()
if not data:
print('Nothing read, nothing saved.')
return
bin_path = out_prefix + '.bin'
txt_path = out_prefix + '.txt'
with open(bin_path, 'wb') as f:
f.write(bytes(data))
with open(txt_path, 'w') as f:
for i in range(0, len(data), 16):
row = data[i:i + 16]
hex_part = ' '.join('{:02X}'.format(b) for b in row)
ascii_part = ''.join(chr(b) if 32 <= b < 127 else '.' for b in row)
f.write('{:08X}: {:<47} {}\n'.format(start + i, hex_part, ascii_part))
print('Saved {} bytes (0x{:04X}-0x{:04X})'.format(len(data), start, last_good))
print(' raw: {}'.format(bin_path))
print(' text: {}'.format(txt_path))
def main():
p = argparse.ArgumentParser()
p.add_argument('port', help='serial port, e.g. /dev/ttyUSB0 or COM3')
p.add_argument('sctype', choices=['CXR', 'CXRF', 'SW'], help='use SW for Sherwood')
p.add_argument('--start', default='0', help='start address in hex, default 0')
p.add_argument('--end', default='8000', help='end address (exclusive) in hex, default 8000')
p.add_argument('-o', '--out', default='syscon_backup', help='output filename prefix')
p.add_argument('--wait', type=float, default=0.3, help='seconds to wait per read, default 0.3')
args = p.parse_args()
start = int(args.start, 16)
end = int(args.end, 16)
dump(args.port, args.sctype, start, end, args.out, args.wait)
if __name__ == '__main__':
main()