Skip to content

Instantly share code, notes, and snippets.

@TakuikaNinja
Created May 27, 2026 05:44
Show Gist options
  • Select an option

  • Save TakuikaNinja/64ba846b38d211edc6c785e5b64406a6 to your computer and use it in GitHub Desktop.

Select an option

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