[SC64][SW] Simplify sc64.py arguments / add save autodetection (#33)

This commit is contained in:
Mateusz Faderewski 2023-02-24 23:47:40 +01:00 committed by GitHub
parent 5b2ee0b6a1
commit f8cb1b20bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 136 additions and 74 deletions

View File

@ -14,10 +14,10 @@
**Windows platform: replace `./sc64` in examples below with `sc64.exe`** **Windows platform: replace `./sc64` in examples below with `sc64.exe`**
1. Download the latest `sc64-{os}-{version}` (choose OS matching your system) and `sc64-firmware-{version}.bin` from GitHub releases page 1. Download the latest `sc64-{os}-{version}.{ext}` (choose OS matching your system) and `sc64-firmware-{version}.bin` from GitHub releases page
2. Extract `sc64-{os}-{version}` package contents to a folder and place `sc64-firmware-{version}.bin` inside it 2. Extract `sc64-{os}-{version}.{ext}` package contents to a folder and place `sc64-firmware-{version}.bin` inside it
3. Update SC64 firmware to the latest version with `./sc64 --update-firmware sc64-firmware-{version}.bin` 3. Update SC64 firmware to the latest version with `./sc64 --update-firmware sc64-firmware-{version}.bin`
4. Run `./sc64 --print-state` to check if SC64 is detected correctly 4. Run `./sc64 --print-state` to check if update process finished successfully and SC64 is detected correctly
--- ---
@ -41,8 +41,8 @@ SC64 holds some internal configuration options after `sc64` executable finished
`./sc64 --boot rom --rom path_to_rom.n64 --save-type eeprom-4k --save path_to_save.sav` `./sc64 --boot rom --rom path_to_rom.n64 --save-type eeprom-4k --save path_to_save.sav`
Replace `path_to_rom.n64` / `eeprom-4k` / `path_to_save.sav` with appropriate values for desired game. Check included help in program to check available save types. Replace `path_to_rom.n64` / `eeprom-4k` / `path_to_save.sav` with appropriate values for desired game. Script will try to autodetect used save type so explicitly setting save type usually isn't needed. Check included help in program to list available save types.
Arguments `--save-type` and/or `--save` can be omitted if game doesn't require any save. Arguments `--save-type` and/or `--save` can be omitted if game doesn't require any save or you want to start fresh.
--- ---
@ -58,14 +58,14 @@ Replace `path_to_save.sav` with appropriate value. Specifying save type isn't re
64DD games require DDIPL ROM and disk images. To run disk game type `./sc64 --boot ddipl --ddipl path_to_ddipl.n64 --disk path_to_disk_1.ndd --disk path_to_disk_2.ndd`. 64DD games require DDIPL ROM and disk images. To run disk game type `./sc64 --boot ddipl --ddipl path_to_ddipl.n64 --disk path_to_disk_1.ndd --disk path_to_disk_2.ndd`.
Replace `path_to_ddipl.n64` / `path_to_disk_x.ndd` with appropriate values. Argument `--disk` can be specified multiple times. Only `.ndd` disk format is supported currently. To change inserted disk press button on the back of SC64 flashcart. Replace `path_to_ddipl.n64` / `path_to_disk_x.ndd` with appropriate values. Argument `--disk` can be specified multiple times. Only `.ndd` disk format is supported currently. To change inserted disk press button on the back of SC64 flashcart. Make sure retail and development disks aren't mixed together. 64DD IPL can handle only one drive type at a time.
--- ---
## Direct boot option ## Direct boot option
If booting game through included bootloader isn't a desired option then flashcart can be put in special mode that omits this step. If booting game through included bootloader isn't a desired option then flashcart can be put in special mode that omits this step.
Run `./sc64 --boot direct-rom --rom path_to_rom.n64` to disable bootloader during boot and console reset. By default `sc64` executable will try to guess CIC seed and calculate checksum. To change seed or disable CIC use `--cic-params 0x3F,0` argument with appropriate values. Refer to included help in program for values meaning. This option is useful only for very specific cases (e.g. testing custom IPL3 or running SC64 on top of GameShark). Run `./sc64 --boot direct-rom --rom path_to_rom.n64` to disable bootloader during boot and console reset. This option is useful only for very specific cases (e.g. testing custom IPL3 or running SC64 on top of GameShark).
--- ---

View File

@ -624,24 +624,85 @@ class SC64:
if (self.__read_memory(address, len(data)) != data): if (self.__read_memory(address, len(data)) != data):
raise ConnectionException('Flash memory program failure') raise ConnectionException('Flash memory program failure')
def autodetect_save_type(self, data: bytes) -> Optional[SaveType]: def autodetect_save_type(self, data: bytes) -> SaveType:
if (len(data) < 0x40): if (len(data) < 0x40):
return None return self.SaveType.NONE
if (data[0x3C:0x3E] != b'ED'):
return None if (data[0x3C:0x3E] == b'ED'):
save = (data[0x3F] >> 4) & 0x0F save = (data[0x3F] >> 4) & 0x0F
if (save < 0 or save > 6): if (save < 0 or save > 6):
return None return self.SaveType.NONE
save_type_mapping = [
self.SaveType.NONE,
self.SaveType.EEPROM_4K,
self.SaveType.EEPROM_16K,
self.SaveType.SRAM,
self.SaveType.SRAM_BANKED,
self.SaveType.FLASHRAM,
self.SaveType.SRAM,
]
return save_type_mapping[save]
# Original ROM database sourced from ares emulator: https://github.com/ares-emulator/ares/blob/master/mia/medium/nintendo-64.cpp
rom_id = data[0x3B:0x3E]
region = data[0x3E]
revision = data[0x3F]
save_type_mapping = [ save_type_mapping = [
self.SaveType.NONE, ([
self.SaveType.EEPROM_4K, b'NTW', b'NHF', b'NOS', b'NTC', b'NER', b'NAG', b'NAB', b'NS3', b'NTN', b'NBN', b'NBK', b'NFH',
self.SaveType.EEPROM_16K, b'NMU', b'NBC', b'NBH', b'NHA', b'NBM', b'NBV', b'NBD', b'NCT', b'NCH', b'NCG', b'NP2', b'NXO',
self.SaveType.SRAM, b'NCU', b'NCX', b'NDY', b'NDQ', b'NDR', b'NN6', b'NDU', b'NJM', b'NFW', b'NF2', b'NKA', b'NFG',
self.SaveType.SRAM_BANKED, b'NGL', b'NGV', b'NGE', b'NHP', b'NPG', b'NIJ', b'NIC', b'NFY', b'NKI', b'NLL', b'NLR', b'NKT',
self.SaveType.FLASHRAM, b'CLB', b'NLB', b'NMW', b'NML', b'NTM', b'NMI', b'NMG', b'NMO', b'NMS', b'NMR', b'NCR', b'NEA',
self.SaveType.SRAM, b'NPW', b'NPY', b'NPT', b'NRA', b'NWQ', b'NSU', b'NSN', b'NK2', b'NSV', b'NFX', b'NFP', b'NS6',
b'NNA', b'NRS', b'NSW', b'NSC', b'NSA', b'NB6', b'NSS', b'NTX', b'NT6', b'NTP', b'NTJ', b'NRC',
b'NTR', b'NTB', b'NGU', b'NIR', b'NVL', b'NVY', b'NWC', b'NAD', b'NWU', b'NYK', b'NMZ', b'NSM',
b'NWR',
], self.SaveType.EEPROM_4K),
([
b'NB7', b'NGT', b'NFU', b'NCW', b'NCZ', b'ND6', b'NDO', b'ND2', b'N3D', b'NMX', b'NGC', b'NIM',
b'NNB', b'NMV', b'NM8', b'NEV', b'NPP', b'NUB', b'NPD', b'NRZ', b'NR7', b'NEP', b'NYS',
], self.SaveType.EEPROM_16K),
([
b'NTE', b'NVB', b'NB5', b'CFZ', b'NFZ', b'NSI', b'NG6', b'NGP', b'NYW', b'NHY', b'NIB', b'NPS',
b'NPA', b'NP4', b'NJ5', b'NP6', b'NPE', b'NJG', b'CZL', b'NZL', b'NKG', b'NMF', b'NRI', b'NUT',
b'NUM', b'NOB', b'CPS', b'NPM', b'NRE', b'NAL', b'NT3', b'NS4', b'NA2', b'NVP', b'NWL', b'NW2',
b'NWX',
], self.SaveType.SRAM),
([
b'CDZ',
], self.SaveType.SRAM_BANKED),
([
b'NCC', b'NDA', b'NAF', b'NJF', b'NKJ', b'NZS', b'NM6', b'NCK', b'NMQ', b'NPN', b'NPF', b'NPO',
b'CP2', b'NP3', b'NRH', b'NSQ', b'NT9', b'NW4', b'NDP',
], self.SaveType.FLASHRAM),
] ]
return save_type_mapping[save]
for (rom_id_table, save) in save_type_mapping:
if (rom_id in rom_id_table):
return save
special_mapping = [
(b'NKD', b'J', None, self.SaveType.EEPROM_4K),
(b'NWT', b'J', None, self.SaveType.EEPROM_4K),
(b'ND3', b'J', None, self.SaveType.EEPROM_16K),
(b'ND4', b'J', None, self.SaveType.EEPROM_16K),
(b'N3H', b'J', None, self.SaveType.SRAM),
(b'NK4', b'J', 2, self.SaveType.SRAM),
]
for (special_rom_id, special_region, special_revision, save) in special_mapping:
if (rom_id != special_rom_id):
continue
if (region != special_region):
continue
if (special_revision != None and revision >= special_revision):
continue
return save
return self.SaveType.NONE
def reset_state(self) -> None: def reset_state(self) -> None:
self.__link.execute_cmd(cmd=b'R') self.__link.execute_cmd(cmd=b'R')
@ -754,8 +815,11 @@ class SC64:
raise ValueError('CIC seed outside of allowed values') raise ValueError('CIC seed outside of allowed values')
self.__set_config(self.__CfgId.CIC_SEED, seed) self.__set_config(self.__CfgId.CIC_SEED, seed)
def set_tv_type(self, type: TVType) -> None: def set_tv_type(self, type: TVType) -> bool:
self.__set_config(self.__CfgId.TV_TYPE, type) self.__set_config(self.__CfgId.TV_TYPE, type)
boot_mode = self.__get_config(self.__CfgId.BOOT_MODE)
direct = (boot_mode == self.BootMode.DIRECT_ROM) or (boot_mode == self.BootMode.DIRECT_DDIPL)
return direct
def set_save_type(self, type: SaveType) -> None: def set_save_type(self, type: SaveType) -> None:
self.__set_config(self.__CfgId.SAVE_TYPE, type) self.__set_config(self.__CfgId.SAVE_TYPE, type)
@ -794,7 +858,7 @@ class SC64:
raise ConnectionException('Error while getting firmware backup') raise ConnectionException('Error while getting firmware backup')
return self.__read_memory(address, length) return self.__read_memory(address, length)
def update_cic_parameters(self, seed: Optional[int]=None, disabled=False) -> tuple[int, int, bool]: def update_cic_parameters(self, seed: Optional[int]=None, disabled: Optional[bool]=False) -> tuple[int, int, bool, bool]:
if ((seed != None) and (seed < 0 or seed > 0xFF)): if ((seed != None) and (seed < 0 or seed > 0xFF)):
raise ValueError('CIC seed outside of allowed values') raise ValueError('CIC seed outside of allowed values')
boot_mode = self.__get_config(self.__CfgId.BOOT_MODE) boot_mode = self.__get_config(self.__CfgId.BOOT_MODE)
@ -808,7 +872,8 @@ class SC64:
checksum = self.__calculate_ipl3_checksum(ipl3, seed) checksum = self.__calculate_ipl3_checksum(ipl3, seed)
data = [(1 << 0) if disabled else 0, seed, *checksum.to_bytes(6, byteorder='big')] data = [(1 << 0) if disabled else 0, seed, *checksum.to_bytes(6, byteorder='big')]
self.__link.execute_cmd(cmd=b'B', args=[self.__get_int(data[0:4]), self.__get_int(data[4:8])]) self.__link.execute_cmd(cmd=b'B', args=[self.__get_int(data[0:4]), self.__get_int(data[4:8])])
return (seed, checksum, boot_mode == self.BootMode.DIRECT_DDIPL) direct = (boot_mode == self.BootMode.DIRECT_ROM) or (boot_mode == self.BootMode.DIRECT_DDIPL)
return (seed, checksum, boot_mode == self.BootMode.DIRECT_DDIPL, direct)
def __guess_ipl3_seed(self, ipl3: bytes) -> int: def __guess_ipl3_seed(self, ipl3: bytes) -> int:
checksum = crc32(ipl3) checksum = crc32(ipl3)
@ -1196,14 +1261,6 @@ class EnumAction(argparse.Action):
if __name__ == '__main__': if __name__ == '__main__':
def cic_params_type(argument: str):
params = argument.split(',')
if (len(params) > 2):
raise argparse.ArgumentError()
seed = int(params[0], 0) if len(params) >= 1 else None
disabled = bool(int(params[1])) if len(params) >= 2 else None
return (seed, disabled)
def download_memory_type(argument: str): def download_memory_type(argument: str):
params = argument.split(',') params = argument.split(',')
if (len(params) < 2 or len(params) > 3): if (len(params) < 2 or len(params) > 3):
@ -1214,25 +1271,23 @@ if __name__ == '__main__':
return (address, length, file) return (address, length, file)
parser = argparse.ArgumentParser(description='SC64 control software') parser = argparse.ArgumentParser(description='SC64 control software')
parser.add_argument('rom', nargs='?', help='upload ROM from specified file')
parser.add_argument('--backup-firmware', metavar='file', help='backup SC64 firmware and write it to specified file') parser.add_argument('--backup-firmware', metavar='file', help='backup SC64 firmware and write it to specified file')
parser.add_argument('--update-firmware', metavar='file', help='update SC64 firmware from specified file') parser.add_argument('--update-firmware', metavar='file', help='update SC64 firmware from specified file')
parser.add_argument('--reset-state', action='store_true', help='reset SC64 internal state') parser.add_argument('--reset-state', action='store_true', help='reset SC64 internal state')
parser.add_argument('--print-state', action='store_true', help='print SC64 internal state') parser.add_argument('--print-state', action='store_true', help='print SC64 internal state')
parser.add_argument('--led-blink', metavar='{yes,no}', help='disable or enable LED I/O activity blinking') parser.add_argument('--led-blink', metavar='{yes,no}', help='enable or disable LED I/O activity blinking')
parser.add_argument('--boot', type=SC64.BootMode, action=EnumAction, help='set boot mode')
parser.add_argument('--tv', type=SC64.TVType, action=EnumAction, help='force TV type to set value, not used when direct boot mode is enabled')
parser.add_argument('--cic', type=SC64.CICSeed, action=EnumAction, help='force CIC seed to set value, not used when direct boot mode is enabled')
parser.add_argument('--cic-params', metavar='seed,[disabled]', type=cic_params_type, help='set CIC emulation parameters')
parser.add_argument('--rtc', action='store_true', help='update clock in SC64 to system time') parser.add_argument('--rtc', action='store_true', help='update clock in SC64 to system time')
parser.add_argument('--boot', type=SC64.BootMode, action=EnumAction, help='set boot mode')
parser.add_argument('--tv', type=SC64.TVType, action=EnumAction, help='force TV type to set value, ignored when one of direct boot modes are selected')
parser.add_argument('--no-shadow', action='store_false', help='do not put last 128 kB of ROM inside flash memory (can corrupt non EEPROM saves)') parser.add_argument('--no-shadow', action='store_false', help='do not put last 128 kB of ROM inside flash memory (can corrupt non EEPROM saves)')
parser.add_argument('--rom', metavar='file', help='upload ROM from specified file')
parser.add_argument('--save-type', type=SC64.SaveType, action=EnumAction, help='set save type') parser.add_argument('--save-type', type=SC64.SaveType, action=EnumAction, help='set save type')
parser.add_argument('--save', metavar='file', help='upload save from specified file') parser.add_argument('--save', metavar='file', help='upload save from specified file')
parser.add_argument('--backup-save', metavar='file', help='download save and write it to specified file') parser.add_argument('--backup-save', metavar='file', help='download save and write it to specified file')
parser.add_argument('--ddipl', metavar='file', help='upload 64DD IPL from specified file') parser.add_argument('--ddipl', metavar='file', help='upload 64DD IPL from specified file')
parser.add_argument('--disk', metavar='file', action='append', help='path to 64DD disk (.ndd format), can be specified multiple times') parser.add_argument('--disk', metavar='file', action='append', help='path to 64DD disk (.ndd format), can be specified multiple times')
parser.add_argument('--isv', type=lambda x: int(x, 0), default=0, help='enable IS-Viewer64 support at provided ROM offset') parser.add_argument('--isv', metavar='offset', type=lambda x: int(x, 0), default=0, help='enable IS-Viewer64 support at provided ROM offset')
parser.add_argument('--gdb', metavar='port', type=int, help='expose socket port for GDB debugging') parser.add_argument('--gdb', metavar='port', type=int, help='expose TCP socket port for GDB debugging')
parser.add_argument('--debug', action='store_true', help='run debug loop') parser.add_argument('--debug', action='store_true', help='run debug loop')
parser.add_argument('--download-memory', metavar='address,length,[file]', type=download_memory_type, help='download specified memory region and write it to file') parser.add_argument('--download-memory', metavar='address,length,[file]', type=download_memory_type, help='download specified memory region and write it to file')
@ -1242,6 +1297,15 @@ if __name__ == '__main__':
args = parser.parse_args() args = parser.parse_args()
def fix_rom_endianness(rom: bytes) -> bytes:
data = bytearray(rom)
pi_config = int.from_bytes(rom[0:4], byteorder='big')
if (pi_config == 0x37804012):
data[0::2], data[1::2] = data[1::2], data[0::2]
elif (pi_config == 0x40123780):
data[0::4], data[1::4], data[2::4], data[3::4] = data[3::4], data[2::4], data[1::4], data[0::4]
return bytes(data)
try: try:
sc64 = SC64() sc64 = SC64()
autodetected_save_type = None autodetected_save_type = None
@ -1261,7 +1325,7 @@ if __name__ == '__main__':
(version, script_outdated) = sc64.check_firmware_version() (version, script_outdated) = sc64.check_firmware_version()
print(f'SC64 firmware version: [{version}]') print(f'\x1b[32mSC64 firmware version: [{version}]\x1b[0m')
if (script_outdated): if (script_outdated):
print('\x1b[33m') print('\x1b[33m')
print('[ SC64 firmware is newer than last known version ]') print('[ SC64 firmware is newer than last known version ]')
@ -1273,27 +1337,11 @@ if __name__ == '__main__':
sc64.reset_state() sc64.reset_state()
print('SC64 internal state reset') print('SC64 internal state reset')
if (args.print_state):
state = sc64.get_state()
print('Current SC64 internal state:')
for key, value in state.items():
if (hasattr(value, 'name')):
value = getattr(value, 'name')
print(f' {key}: {value}')
if (args.led_blink): if (args.led_blink):
blink = (args.led_blink == 'yes') blink = (args.led_blink == 'yes')
sc64.set_led_enable(blink) sc64.set_led_enable(blink)
print(f'LED blinking set to [{"ENABLED" if blink else "DISABLED"}]') print(f'LED blinking set to [{"ENABLED" if blink else "DISABLED"}]')
if (args.tv != None):
sc64.set_tv_type(args.tv)
print(f'TV type set to [{args.tv.name}]')
if (args.cic != None):
sc64.set_cic_seed(args.cic)
print(f'CIC seed set to [0x{args.cic:X}]')
if (args.rtc): if (args.rtc):
value = datetime.now() value = datetime.now()
sc64.set_rtc(value) sc64.set_rtc(value)
@ -1301,8 +1349,8 @@ if __name__ == '__main__':
if (args.rom): if (args.rom):
with open(args.rom, 'rb') as f: with open(args.rom, 'rb') as f:
print('Uploading ROM... ', end='', flush=True) rom_data = fix_rom_endianness(f.read())
rom_data = f.read() print(f'Uploading ROM ({len(rom_data) / (1 * 1024 * 1024):.2f} MiB)... ', end='', flush=True)
sc64.upload_rom(rom_data, use_shadow=args.no_shadow) sc64.upload_rom(rom_data, use_shadow=args.no_shadow)
autodetected_save_type = sc64.autodetect_save_type(rom_data) autodetected_save_type = sc64.autodetect_save_type(rom_data)
print('done') print('done')
@ -1313,6 +1361,25 @@ if __name__ == '__main__':
sc64.upload_ddipl(f.read()) sc64.upload_ddipl(f.read())
print('done') print('done')
if (args.rom or args.ddipl or args.boot != None):
mode = args.boot
if (mode == None):
mode = SC64.BootMode.ROM if args.rom else SC64.BootMode.DDIPL
sc64.set_boot_mode(mode)
print(f'Boot mode set to [{mode.name}]')
(seed, checksum, dd_mode, direct) = sc64.update_cic_parameters()
if (direct):
print('CIC parameters set to [', end='')
print(f'{"DDIPL" if dd_mode else "ROM"}, ', end='')
print(f'seed: 0x{seed:02X}, checksum: 0x{checksum:012X}', end='')
print(']')
if (args.rom or args.ddipl or args.tv != None):
tv = args.tv if args.tv else SC64.TVType.AUTO
direct = sc64.set_tv_type(tv)
if (args.tv != None):
print(f'TV type set to [{args.tv.name}]{" (ignored)" if direct else ""}')
if (args.save_type != None or autodetected_save_type != None): if (args.save_type != None or autodetected_save_type != None):
save_type = args.save_type if args.save_type != None else autodetected_save_type save_type = args.save_type if args.save_type != None else autodetected_save_type
sc64.set_save_type(save_type) sc64.set_save_type(save_type)
@ -1324,20 +1391,13 @@ if __name__ == '__main__':
sc64.upload_save(f.read()) sc64.upload_save(f.read())
print('done') print('done')
if (args.boot != None): if (args.print_state):
sc64.set_boot_mode(args.boot) state = sc64.get_state()
print(f'Boot mode set to [{args.boot.name}]') print('Current SC64 internal state:')
for key, value in state.items():
if (args.cic_params != None): if (hasattr(value, 'name')):
(seed, disabled) = args.cic_params value = getattr(value, 'name')
(seed, checksum, dd_mode) = sc64.update_cic_parameters(seed, disabled) print(f' {key}: {value}')
print('CIC parameters set to [', end='')
print(f'{"DISABLED" if disabled else "ENABLED"}, ', end='')
print(f'{"DDIPL" if dd_mode else "ROM"}, ', end='')
print(f'seed: 0x{seed:02X}, checksum: 0x{checksum:012X}', end='')
print(']')
else:
sc64.update_cic_parameters()
if (args.debug or args.isv or args.disk or args.gdb): if (args.debug or args.isv or args.disk or args.gdb):
sc64.debug_loop(isv=args.isv, disks=args.disk, gdb_port=args.gdb) sc64.debug_loop(isv=args.isv, disks=args.disk, gdb_port=args.gdb)
@ -1358,3 +1418,5 @@ if __name__ == '__main__':
print(f'\n\x1b[31mValue error: {e}\x1b[0m\n') print(f'\n\x1b[31mValue error: {e}\x1b[0m\n')
except ConnectionException as e: except ConnectionException as e:
print(f'\n\x1b[31mSC64 error: {e}\x1b[0m\n') print(f'\n\x1b[31mSC64 error: {e}\x1b[0m\n')
except Exception as e:
print(f'\n\x1b[31mUnhandled error "{e.__class__.__name__}": {e}\x1b[0m\n')