Created
May 27, 2026 05:44
-
-
Save TakuikaNinja/64ba846b38d211edc6c785e5b64406a6 to your computer and use it in GitHub Desktop.
Python script to list files in FC Turbo File & SFC Turbo File Twin (TFII) save data
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| File lister for FC Turbo File & SFC Turbo File Twin (TFII) | |
| """ | |
| def reverse_bits(x): | |
| y = 0 | |
| for i in range(8): | |
| y = (y << 1) | (x & 1) | |
| x >>= 1 | |
| return y | |
| def bit_reverse_bytes(data): | |
| for i in range(len(data)): | |
| data[i] = reverse_bits(data[i]) | |
| return data | |
| if __name__ == '__main__': | |
| import sys | |
| import argparse | |
| from pathlib import Path | |
| import binascii | |
| parser = argparse.ArgumentParser(description='File lister for FC Turbo File & SFC Turbo File Twin (TFII)') | |
| parser.add_argument('sav', metavar='save_file', help='Turbo File save data (e.g. *.turbofile.sav)') | |
| parser.add_argument('-v', '--verbose', action='store_true', help='verbose file display') | |
| args = parser.parse_args() | |
| path = Path(args.sav) | |
| try: | |
| size = path.stat().st_size | |
| except FileNotFoundError as err: | |
| print("File Not Found: {0}".format(err)) | |
| sys.exit(1) | |
| if not path.is_file(): | |
| print("Error: Not a file: '{0}'".format(args.sav)) | |
| sys.exit(1) | |
| data = bytearray(path.read_bytes()) | |
| assert len(data) == 8192, "Save file should be 8192 bytes" | |
| MINIMUM = 20 # 2+2+16+0+2, minimum file size with 0 bytes of data | |
| start_pos = 1 # standard TF filesystem does not use offset 0 | |
| files = [] | |
| # check for bit-reversed Castle Excellent data (??, AA, 55) | |
| if data[1] == 0x55 and data[2] == 0xAA: | |
| file_size = 512 | |
| title_string = "Castle Excellent" | |
| files.append(dict(title = title_string, size = file_size, checksum = None)) | |
| # display raw file data in verbose mode | |
| if args.verbose: | |
| print(title_string) | |
| print(bit_reverse_bytes(data[:file_size]).hex() + "\n") | |
| start_pos = file_size | |
| data = data[start_pos:] | |
| # seek through standard TF files until the file ID is something other than "AB" | |
| while len(data) >= MINIMUM and data[0] == 0x41 and data[1] == 0x42: | |
| # fetch the file ID for raw display | |
| file_id = data[:2] | |
| data = data[2:] | |
| # fetch the data size (includes 16-byte title and 16-bit checksum) | |
| file_size_bytes = data[:2] | |
| file_size = file_size_bytes[0] + (file_size_bytes[1] << 8) | |
| data = data[2:] | |
| # fetch the title | |
| title_data = data[:16] | |
| title_string = binascii.b2a_qp(title_data) # quoted-printable encoding | |
| # display raw file data in verbose mode | |
| file_data = data[16:file_size] | |
| data = data[file_size:] | |
| if args.verbose: | |
| print(title_string) | |
| raw_data = file_id + file_size_bytes + title_data + file_data | |
| print(raw_data.hex()) | |
| # fetch & verify 16-bit file checksum | |
| file_checksum = file_data[-2] + (file_data[-1] << 8) | |
| file_data = file_data[:-2] | |
| new_checksum = 0 | |
| for byte in file_data: | |
| new_checksum += byte | |
| new_checksum &= 0xFFFF | |
| if file_checksum == new_checksum: | |
| if args.verbose: | |
| print(f"File checksum matches {hex(file_checksum)}\n") | |
| files.append(dict(title = title_string, size = file_size, checksum = file_checksum)) | |
| else: | |
| if args.verbose: | |
| print(f"{hex(new_checksum)} didn't match saved checksum of {hex(file_checksum)}\n") | |
| break | |
| # display the detected valid files | |
| if len(files) > 0: | |
| print("Found files:") | |
| free_space = 8191 | |
| for file in files: | |
| free_space -= file["size"] | |
| if file['checksum'] == None: | |
| print(f"{file['title']}\t{file['size']} bytes") | |
| free_space += 1 # account for Castle Excellent data containing offset 0 | |
| else: | |
| print(f"{file['title']}\t{file['size']} bytes\tchecksum = {hex(file['checksum'])}") | |
| print(f"{free_space} bytes free") | |
| else: | |
| print("No valid files found.") | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment