#!/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()