124 lines
4.0 KiB
Python
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()
|