mirror of
https://github.com/Polprzewodnikowy/SummerCart64.git
synced 2025-02-16 12:19:11 +01:00
dum.py rewrite
This commit is contained in:
parent
19c34a861b
commit
da9c3f6aed
@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
docker run \
|
||||
--rm \
|
||||
--user $(id -u):$(id -g) \
|
||||
--mount type=bind,src="$(pwd)",target="/workdir" \
|
||||
ghcr.io/polprzewodnikowy/sc64env:v1.2 \
|
||||
|
294
sw/pc/dum.py
294
sw/pc/dum.py
@ -1,34 +1,149 @@
|
||||
from serial import Serial, SerialException
|
||||
from serial.tools import list_ports
|
||||
import argparse
|
||||
import os
|
||||
import serial
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
|
||||
class SC64Exception(Exception):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class SC64:
|
||||
__SDRAM_SIZE = 64 * 1024 * 1024
|
||||
__CFG_ID_SCR = 0
|
||||
__CFG_ID_SDRAM_SWITCH = 1
|
||||
__CFG_ID_SDRAM_WRITABLE = 2
|
||||
__CFG_ID_DD_ENABLE = 3
|
||||
__CFG_ID_SAVE_TYPE = 4
|
||||
__CFG_ID_CIC_SEED = 5
|
||||
__CFG_ID_TV_TYPE = 6
|
||||
__CFG_ID_SAVE_OFFEST = 7
|
||||
__CFG_ID_DD_OFFEST = 8
|
||||
__CFG_ID_SKIP_BOOTLOADER = 9
|
||||
__CFG_ID_FLASH_OPERATION = 10
|
||||
__CFG_ID_RECONFIGURE = 11
|
||||
|
||||
__CONFIG_QUERY_SAVE_TYPE = 4
|
||||
__CONFIG_QUERY_SAVE_OFFSET = 7
|
||||
__SC64_VERSION_V2 = 0x53437632
|
||||
|
||||
def __init__(self, port):
|
||||
self.__serial = serial.Serial(port)
|
||||
self.__save_type = self.__query_config(self.__CONFIG_QUERY_SAVE_TYPE)
|
||||
self.__save_offset = self.__query_config(self.__CONFIG_QUERY_SAVE_OFFSET)
|
||||
print('{:08X}'.format(self.__save_type))
|
||||
print('{:08X}'.format(self.__save_offset))
|
||||
__UPDATE_OFFSET = 0x03B60000
|
||||
|
||||
__MIN_ROM_LENGTH = 0x101000
|
||||
__DDIPL_ROM_LENGTH = 0x400000
|
||||
|
||||
__serial = None
|
||||
|
||||
|
||||
def __query_config(self, query):
|
||||
self.__serial.write(b'CMDQ')
|
||||
self.__serial.write(query.to_bytes(4, byteorder='big'))
|
||||
self.__serial.write(bytes(4))
|
||||
value = self.__serial.read(4)
|
||||
if (self.__serial.read(4).decode() != 'CMPQ'):
|
||||
raise Exception('Bad query response')
|
||||
return int.from_bytes(value, byteorder='big')
|
||||
def __init__(self) -> None:
|
||||
self.__find_sc64()
|
||||
|
||||
|
||||
def __save_length(self):
|
||||
def __del__(self) -> None:
|
||||
if (self.__serial):
|
||||
self.__serial.close()
|
||||
|
||||
|
||||
def __align(self, value: int, align: int) -> int:
|
||||
return (value + (align - 1)) & ~(align - 1)
|
||||
|
||||
|
||||
def __read(self, bytes: int) -> bytes:
|
||||
return self.__serial.read(bytes)
|
||||
|
||||
|
||||
def __write(self, data: bytes) -> None:
|
||||
self.__serial.write(data)
|
||||
|
||||
|
||||
def __write_dummy(self, length: int) -> None:
|
||||
self.__write(bytes(length))
|
||||
|
||||
|
||||
def __read_int(self) -> int:
|
||||
return int.from_bytes(self.__read(4), byteorder="big")
|
||||
|
||||
|
||||
def __write_int(self, value: int) -> None:
|
||||
self.__write(value.to_bytes(4, byteorder="big"))
|
||||
|
||||
|
||||
def __read_cmd_status(self, cmd: str) -> None:
|
||||
if (self.__read(4).decode() != f"CMP{cmd[0]}"):
|
||||
raise SC64Exception("Wrong command response")
|
||||
|
||||
|
||||
def __write_cmd(self, cmd: str, arg1: int, arg2: int) -> None:
|
||||
self.__write(f"CMD{cmd[0]}".encode())
|
||||
self.__write_int(arg1)
|
||||
self.__write_int(arg2)
|
||||
|
||||
|
||||
def __find_sc64(self) -> None:
|
||||
ports = list_ports.comports()
|
||||
device_found = False
|
||||
|
||||
for p in ports:
|
||||
if (p.vid == 0x0403 and p.pid == 0x6014 and p.serial_number.startswith("SC64")):
|
||||
try:
|
||||
self.__serial = Serial(p.device, timeout=10.0, write_timeout=10.0)
|
||||
self.__probe_device()
|
||||
except SerialException or SC64Exception:
|
||||
if (self.__serial):
|
||||
self.__serial.close()
|
||||
continue
|
||||
device_found = True
|
||||
break
|
||||
|
||||
if (not device_found):
|
||||
raise SC64Exception("No SummerCart64 device was found")
|
||||
|
||||
|
||||
def __probe_device(self) -> None:
|
||||
self.__write_cmd("V", 0, 0)
|
||||
version = self.__read_int()
|
||||
self.__read_cmd_status("V")
|
||||
if (version != self.__SC64_VERSION_V2):
|
||||
raise SC64Exception(f"Unknown hardware version: {hex(version)}")
|
||||
|
||||
|
||||
def __query_config(self, id: int, arg: int = 0) -> int:
|
||||
self.__write_cmd("Q", id, arg)
|
||||
value = self.__read_int()
|
||||
self.__read_cmd_status("Q")
|
||||
return value
|
||||
|
||||
|
||||
def __change_config(self, id: int, arg: int = 0, ignore_response: bool = False) -> None:
|
||||
self.__write_cmd("C", id, arg)
|
||||
if (not ignore_response):
|
||||
self.__read_cmd_status("C")
|
||||
|
||||
|
||||
def __read_file_from_sdram(self, file: str, offset: int, length: int, align: int = 2) -> None:
|
||||
with open(file, "wb") as f:
|
||||
transfer_size = self.__align(length, align)
|
||||
self.__write_cmd("R", offset, transfer_size)
|
||||
f.write(self.__read(length))
|
||||
if (transfer_size != length):
|
||||
self.__read(transfer_size - length)
|
||||
self.__read_cmd_status("R")
|
||||
|
||||
|
||||
def __write_file_to_sdram(self, file: str, offset: int, align: int = 2, min_length: int = 0) -> None:
|
||||
with open(file, "rb") as f:
|
||||
length = os.fstat(f.fileno()).st_size
|
||||
transfer_size = self.__align(max(length, min_length), align)
|
||||
self.__write_cmd("W", offset, transfer_size)
|
||||
self.__write(f.read())
|
||||
if (transfer_size != length):
|
||||
self.__write_dummy(transfer_size - length)
|
||||
self.__read_cmd_status("W")
|
||||
|
||||
|
||||
def __get_save_length(self) -> None:
|
||||
save_type = self.__query_config(self.__CFG_ID_SAVE_TYPE)
|
||||
return {
|
||||
0: 0,
|
||||
1: 512,
|
||||
@ -38,71 +153,94 @@ class SC64:
|
||||
5: (3 * 32 * 1024),
|
||||
6: (128 * 1024),
|
||||
7: 0
|
||||
}[self.__save_type]
|
||||
}[save_type]
|
||||
|
||||
|
||||
def dump_save(self, file):
|
||||
length = self.__save_length()
|
||||
if (length == 0):
|
||||
raise Exception('No save type is selected')
|
||||
self.__serial.write(b'CMDR')
|
||||
self.__serial.write(self.__save_offset.to_bytes(4, byteorder='big'))
|
||||
self.__serial.write(length.to_bytes(4, byteorder='big'))
|
||||
save = self.__serial.read(length)
|
||||
response = self.__serial.read(4)
|
||||
if (response.decode() == 'CMPR'):
|
||||
with open(file, 'wb') as f:
|
||||
f.write(save)
|
||||
def __reconfigure(self) -> None:
|
||||
magic = self.__query_config(self.__CFG_ID_RECONFIGURE)
|
||||
self.__change_config(self.__CFG_ID_RECONFIGURE, magic, ignore_response=True)
|
||||
time.sleep(0.2)
|
||||
|
||||
|
||||
def update_firmware(self, file: str, backup_file: str = None) -> None:
|
||||
if (backup_file != None):
|
||||
backup_length = self.__query_config(self.__CFG_ID_FLASH_OPERATION, self.__UPDATE_OFFSET)
|
||||
self.__read_file_from_sdram(backup_file, self.__UPDATE_OFFSET, backup_length)
|
||||
self.__write_file_to_sdram(file, self.__UPDATE_OFFSET)
|
||||
self.__change_config(self.__CFG_ID_FLASH_OPERATION, self.__UPDATE_OFFSET)
|
||||
self.__reconfigure()
|
||||
self.__probe_device()
|
||||
|
||||
|
||||
def set_skip_bootloader(self, enable: bool) -> None:
|
||||
self.__change_config(self.__CFG_ID_SKIP_BOOTLOADER, 1 if enable else 0)
|
||||
|
||||
|
||||
def download_rom(self, file: str, length: int, offset: int = 0) -> None:
|
||||
self.__read_file_from_sdram(file, offset, length)
|
||||
|
||||
|
||||
def upload_rom(self, file: str, offset: int = 0) -> None:
|
||||
self.__write_file_to_sdram(file, offset, min_length=self.__MIN_ROM_LENGTH)
|
||||
|
||||
|
||||
def set_save_type(self, type: int) -> None:
|
||||
if (type >= 0 and type <= 6):
|
||||
self.__change_config(self.__CFG_ID_SAVE_TYPE, type)
|
||||
else:
|
||||
raise Exception('There was a problem while dumping save data')
|
||||
raise SC64Exception("Save type outside of supported values")
|
||||
|
||||
|
||||
def redump_save(self, file):
|
||||
length = self.__save_length()
|
||||
if (not length):
|
||||
raise Exception('No save type is selected')
|
||||
if (os.path.getsize(file) != length):
|
||||
raise Exception('Wrong save file size')
|
||||
with open(file, 'rb') as f:
|
||||
self.__serial.write(b'CMDW')
|
||||
self.__serial.write(self.__save_offset.to_bytes(4, byteorder='big'))
|
||||
self.__serial.write(length.to_bytes(4, byteorder='big'))
|
||||
self.__serial.write(f.read())
|
||||
response = self.__serial.read(4)
|
||||
if (response.decode() != 'CMPW'):
|
||||
raise Exception('There was a problem while redumping save data')
|
||||
|
||||
|
||||
def dump_all(self, file):
|
||||
self.__serial.write(b'CMDR')
|
||||
self.__serial.write((0).to_bytes(4, byteorder='big'))
|
||||
self.__serial.write((self.__SDRAM_SIZE).to_bytes(4, byteorder='big'))
|
||||
save = self.__serial.read(self.__SDRAM_SIZE)
|
||||
response = self.__serial.read(4)
|
||||
if (response.decode() == 'CMPR'):
|
||||
with open(file, 'wb') as f:
|
||||
f.write(save)
|
||||
def download_save(self, file: str) -> None:
|
||||
length = self.__get_save_length()
|
||||
if (length > 0):
|
||||
offset = self.__query_config(self.__CFG_ID_SAVE_OFFEST)
|
||||
self.__read_file_from_sdram(file, offset, length)
|
||||
else:
|
||||
raise Exception('There was a problem while dumping all data')
|
||||
raise SC64Exception("Can't read save data - no save type is set")
|
||||
|
||||
|
||||
def upload_save(self, file: str) -> None:
|
||||
length = self.__get_save_length()
|
||||
save_length = os.path.getsize(file)
|
||||
if (length <= 0):
|
||||
raise SC64Exception("Can't write save data - no save type is set")
|
||||
elif (length != save_length):
|
||||
raise SC64Exception("Can't write save data - save file size is different than expected")
|
||||
else:
|
||||
offset = self.__query_config(self.__CFG_ID_SAVE_OFFEST)
|
||||
self.__write_file_to_sdram(file, offset)
|
||||
|
||||
|
||||
def set_dd_enable(self, enable: bool) -> None:
|
||||
self.__change_config(self.__CFG_ID_DD_ENABLE, 1 if enable else 0)
|
||||
|
||||
|
||||
def download_dd_ipl(self, file: str) -> None:
|
||||
dd_ipl_offset = self.__query_config(self.__CFG_ID_DD_OFFEST)
|
||||
self.__read_file_from_sdram(file, dd_ipl_offset, length=self.__DDIPL_ROM_LENGTH)
|
||||
|
||||
|
||||
def upload_dd_ipl(self, file: str, offset: int = None) -> None:
|
||||
if (offset != None):
|
||||
self.__change_config(self.__CFG_ID_DD_OFFEST, offset)
|
||||
dd_ipl_offset = self.__query_config(self.__CFG_ID_DD_OFFEST)
|
||||
self.__write_file_to_sdram(file, dd_ipl_offset, min_length=self.__DDIPL_ROM_LENGTH)
|
||||
|
||||
|
||||
|
||||
mode = 'r'
|
||||
file = 'save.dat'
|
||||
port = 'COM7'
|
||||
|
||||
if (len(sys.argv) >= 2):
|
||||
mode = sys.argv[1]
|
||||
if (len(sys.argv) >= 3):
|
||||
file = sys.argv[2]
|
||||
if (len(sys.argv) >= 4):
|
||||
port = sys.argv[3]
|
||||
|
||||
sc64 = SC64(port)
|
||||
|
||||
if (mode == 'r'):
|
||||
sc64.dump_save(file)
|
||||
elif (mode == 'w'):
|
||||
sc64.redump_save(file)
|
||||
elif (mode == 'a'):
|
||||
sc64.dump_all(file)
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
sc64 = SC64()
|
||||
sc64.update_firmware("../../fw/output_files/SC64_update.bin", "SC64_backup.bin")
|
||||
sc64.set_skip_bootloader(False)
|
||||
sc64.set_save_type(1)
|
||||
sc64.set_dd_enable(True)
|
||||
sc64.upload_rom("rom.z64")
|
||||
sc64.upload_dd_ipl("ddipl.z64")
|
||||
sc64.upload_save("saves/save.eep")
|
||||
sc64.download_rom("rom.bin", 0x1000)
|
||||
sc64.download_save("save.bin")
|
||||
sc64.download_dd_ipl("dd_ipl.bin")
|
||||
except SC64Exception as e:
|
||||
print(e)
|
||||
|
@ -1,97 +0,0 @@
|
||||
import os
|
||||
import serial
|
||||
import time
|
||||
import filecmp
|
||||
|
||||
|
||||
|
||||
class SC64:
|
||||
__CFG_ID_FLASH_OPERATION = 10
|
||||
__CFG_ID_RECONFIGURE = 11
|
||||
|
||||
|
||||
def __init__(self, port):
|
||||
self.__serial = serial.Serial(port, timeout=10.0, write_timeout=10.0)
|
||||
|
||||
|
||||
def __query_config(self, query, arg=0):
|
||||
self.__serial.write(b'CMDQ')
|
||||
self.__serial.write(query.to_bytes(4, byteorder='big'))
|
||||
self.__serial.write(arg.to_bytes(4, byteorder='big'))
|
||||
value = self.__serial.read(4)
|
||||
if (self.__serial.read(4).decode() != 'CMPQ'):
|
||||
raise Exception('Bad query response')
|
||||
return int.from_bytes(value, byteorder='big')
|
||||
|
||||
|
||||
def __change_config(self, change, arg=0, ignore_response=False):
|
||||
self.__serial.write(b'CMDC')
|
||||
self.__serial.write(change.to_bytes(4, byteorder='big'))
|
||||
self.__serial.write(arg.to_bytes(4, byteorder='big'))
|
||||
if (not ignore_response and self.__serial.read(4).decode() != 'CMPC'):
|
||||
raise Exception('Bad change response')
|
||||
|
||||
|
||||
def reconfigure(self):
|
||||
magic = self.__query_config(self.__CFG_ID_RECONFIGURE)
|
||||
self.__change_config(self.__CFG_ID_RECONFIGURE, magic, ignore_response=True)
|
||||
time.sleep(0.2)
|
||||
|
||||
|
||||
def read_flash(self, file):
|
||||
size = self.__query_config(self.__CFG_ID_FLASH_OPERATION)
|
||||
print(f'Flash size: {(size / 1024.0):1.1f} kB')
|
||||
self.__serial.write(b'CMDR')
|
||||
self.__serial.write((0).to_bytes(4, byteorder='big'))
|
||||
self.__serial.write((size).to_bytes(4, byteorder='big'))
|
||||
flash = self.__serial.read(size)
|
||||
response = self.__serial.read(4)
|
||||
if (response.decode() == 'CMPR'):
|
||||
with open(file, 'wb') as f:
|
||||
f.write(flash)
|
||||
else:
|
||||
raise Exception('There was a problem while reading flash data')
|
||||
|
||||
|
||||
def program_flash(self, file):
|
||||
length = os.path.getsize(file)
|
||||
offset = 0
|
||||
with open(file, 'rb') as f:
|
||||
self.__serial.write(b'CMDW')
|
||||
self.__serial.write(offset.to_bytes(4, byteorder='big'))
|
||||
self.__serial.write(length.to_bytes(4, byteorder='big'))
|
||||
self.__serial.write(f.read())
|
||||
response = self.__serial.read(4)
|
||||
if (response.decode() != 'CMPW'):
|
||||
raise Exception('There was a problem while sending flash data')
|
||||
self.__change_config(self.__CFG_ID_FLASH_OPERATION)
|
||||
|
||||
|
||||
|
||||
file = '../../fw/output_files/SC64_update.bin'
|
||||
backup_file = 'SC64_backup.bin'
|
||||
verify_file = 'SC64_update_verify.bin'
|
||||
port = 'COM7'
|
||||
|
||||
sc64 = SC64(port)
|
||||
|
||||
print('Making backup...')
|
||||
sc64.read_flash(backup_file)
|
||||
print('done\n')
|
||||
|
||||
print('Flashing... ')
|
||||
sc64.program_flash(file)
|
||||
print('done\n')
|
||||
|
||||
print('Reconfiguring... ')
|
||||
sc64.reconfigure()
|
||||
print('done\n')
|
||||
|
||||
print('Verifying... ')
|
||||
sc64.read_flash(verify_file)
|
||||
if (filecmp.cmp(file, verify_file)):
|
||||
print('success!\n')
|
||||
else:
|
||||
print('failure.\n')
|
||||
|
||||
print('Update done!')
|
@ -103,6 +103,10 @@ static void set_save_type (enum save_type save_type) {
|
||||
}
|
||||
|
||||
|
||||
uint32_t cfg_get_version (void) {
|
||||
return CFG->VERSION;
|
||||
}
|
||||
|
||||
void cfg_update (uint32_t *args) {
|
||||
switch (args[0]) {
|
||||
case CFG_ID_SCR:
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "sys.h"
|
||||
|
||||
|
||||
uint32_t cfg_get_version (void);
|
||||
void cfg_update (uint32_t *args);
|
||||
void cfg_query (uint32_t *args);
|
||||
void cfg_init (void);
|
||||
|
@ -222,6 +222,12 @@ void process_usb (void) {
|
||||
|
||||
case STATE_DATA:
|
||||
switch (p.cmd) {
|
||||
case 'V':
|
||||
if (tx_word(cfg_get_version())) {
|
||||
p.state = STATE_RESPONSE;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'C':
|
||||
cfg_update(p.args);
|
||||
p.state = STATE_RESPONSE;
|
||||
|
Loading…
x
Reference in New Issue
Block a user