Skip to content

Instantly share code, notes, and snippets.

@shinyquagsire23
Last active January 30, 2024 01:48
Show Gist options
  • Save shinyquagsire23/b6322687a153114e5a0a46e4ff91e8c0 to your computer and use it in GitHub Desktop.
Save shinyquagsire23/b6322687a153114e5a0a46e4ff91e8c0 to your computer and use it in GitHub Desktop.
FPGBC updater script for macOS (and maybe Linux) using Python
# pip3 install pyusb
# Requires root on macOS to kick the kernel driver off the device.
import usb.core
import usb.util
import struct
import time
import base64
import sys
# SCSI stuff
SCSI_CMD_WRITE_10 = 0x2A
SCSI_CMD_READ_10 = 0x28
SCSI_CMD_TEST_UNIT_READY = 0x0
# A FAT table with update.bin populated to trigger the update mechanism
sector_3_update_bin = base64.b64decode("RlVOTllfUExBWV8oAAAAAAAAAAAAAEGOuzIAAAAAAABCIABJAG4AZgBvAA8AcnIAbQBhAHQAaQBvAAAAbgAAAAFTAHkAcwB0AGUADwBybQAgAFYAbwBsAHUAAABtAGUAU1lTVEVNfjEgICAWAEwWiTdYN1gAABeJN1gDAAAAAABVUERBVEUgIEJJTiAYQCKJN1g3WAAAI4k3WAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==")
# Helper
def hex_dump(b, prefix=""):
p = prefix
b = bytes(b)
for i in range(0, len(b)):
if i != 0 and i % 16 == 0:
print (p)
p = prefix
p += ("%02x " % b[i])
print (p)
def main():
global tag
if len(sys.argv) < 2:
print("Usage: sudo python3 FPGBC_update.py <update.bin>")
sys.exit(-1)
dev = usb.core.find(idVendor=0x1a86, idProduct=0xfe10)
if dev is None:
print("\n!!! ERROR !!!\n")
print("Failed to find FPGBC USB device! Exiting...")
print("")
sys.exit(-1)
# get an endpoint instance
cfg = dev.get_active_configuration()
intf = cfg[(0,0)]
# tell macOS kernel drivers to fuck off
if dev.is_kernel_driver_active(intf.bInterfaceNumber):
dev.detach_kernel_driver(intf.bInterfaceNumber)
# set the active configuration. With no arguments, the first
# configuration will be the active one
dev.set_configuration()
def match_ep_out(e):
#print(e)
return usb.util.endpoint_direction(e.bEndpointAddress) == \
usb.util.ENDPOINT_OUT
def match_ep_in(e):
#print(e)
return usb.util.endpoint_direction(e.bEndpointAddress) == \
usb.util.ENDPOINT_IN
ep_out = usb.util.find_descriptor(
intf,
# match the first OUT endpoint
custom_match = match_ep_out)
ep_in = usb.util.find_descriptor(
intf,
# match the first IN endpoint
custom_match = match_ep_in)
if ep_out is None or ep_in is None:
print("\n!!! ERROR !!!\n")
print("Failed to find endpoints! Exiting...")
print("")
sys.exit(-1)
tag = 1
def send_scsi_cmd(cdb, data_expect):
global tag
ums_hdr = struct.pack("<LLLBBB", 0x43425355, tag, data_expect, 0x0 if data_expect == 0 else 0x80, 0x0, len(cdb))
if (len(cdb) < 0x10):
cdb += (struct.pack("<B", 0) * (0x10 - len(cdb)))
try:
ep_out.write(ums_hdr + cdb)
except:
pass
tag += 1
output = []
try:
if (data_expect != 0):
output = ep_in.read(data_expect)
except:
pass
resp = ep_in.read(0xD, 1000)
return bytes(output)
def send_scsi_cmd_wr(cdb, extra, data_expect):
global tag
ums_hdr = struct.pack("<LLLBBB", 0x43425355, tag, data_expect, 0, 0x0, len(cdb))
if (len(cdb) < 0x10):
cdb += (struct.pack("<B", 0) * (0x10 - len(cdb)))
try:
ep_out.write(ums_hdr + cdb)
except:
pass
try:
ep_out.write(extra)
except:
pass
tag += 1
output = None
resp = ep_in.read(0xD, 1000)
return bytes(resp)
test_unit_ready = struct.pack("<BLB", SCSI_CMD_TEST_UNIT_READY, 0, 0)
try:
f = open(sys.argv[1], "rb")
except:
print("\n!!! ERROR !!!\n")
print(f"Failed to open `{sys.argv[1]}`! Exiting...")
print("")
sys.exit(-1)
try:
full_bin = f.read()
except:
print("\n!!! ERROR !!!\n")
print(f"Failed to read `{sys.argv[1]}`! Exiting...")
print("")
sys.exit(-1)
f.close()
# Various safety checks (ie, if someone Ctrl+Ss the github page instead)
if len(full_bin) < 0x40000:
print("\n!!! ERROR !!!\n")
print(f"File `{sys.argv[1]}` is only {hex(len(full_bin))} bytes large!")
print("Doesn't seem right, maybe you Ctrl+S'd the link instead of clicking `Raw`.")
print("")
sys.exit(-1)
if len(full_bin) > 0x100000:
print(f"File `{sys.argv[1]}` is {hex(len(full_bin))} bytes large!")
print("Seems to big, maybe you Ctrl+S'd the link instead of clicking `Raw`.")
print("Or this is something else.")
print("")
sys.exit(-1)
# These unit ready cmds might not be needed.
send_scsi_cmd(test_unit_ready, 0)
print(f"Starting update from bin file: `{sys.argv[1]}`\n------------------------------")
print("")
print(" - Create update.bin on FAT")
try:
to_write = sector_3_update_bin
lba = 0x3
write_block = struct.pack(">BBLBHB", SCSI_CMD_WRITE_10, 0x0, lba, 0, int(len(to_write)//0x200), 0)
send_scsi_cmd_wr(write_block, to_write, len(to_write))
send_scsi_cmd(test_unit_ready, 0)
except Exception as e:
try:
print(" (I still don't know why that first write hangs)")
send_scsi_cmd_wr(write_block, to_write, len(to_write))
except Exception as e2:
print("This shouldn't happen, failed second sector 3 write??")
print(e2)
sys.exit(-1)
print("")
print(" - Set update.bin file size")
try:
to_write = sector_3_update_bin
to_write = to_write[:0x9C] + struct.pack("<L", len(full_bin)) + to_write[0xA0:]
write_block = struct.pack(">BBLBHB", SCSI_CMD_WRITE_10, 0x0, 0x3, 0, int(len(to_write)//0x200), 0)
send_scsi_cmd_wr(write_block, to_write, len(to_write))
send_scsi_cmd(test_unit_ready, 0)
except Exception as e:
print(e)
print("")
print(" - FAT16 FATs are written\n")
print("Biiiig write:\n-------------")
block_size = 0x10000
num_blocks = int(len(full_bin)//block_size)
for j in range(0, num_blocks+1):
lba = 0x25+(j*(block_size//0x200))
to_write = full_bin[block_size*j:(block_size*j)+block_size]
pad_amt = 0x200 - (len(to_write) % 0x200)
if pad_amt != 0 and pad_amt < 0x200:
to_write += bytes([0]*pad_amt)
print(" - Chunk number:", j, "LBA:", hex(lba), "(" + hex(len(to_write)) + " bytes)")
write_block = struct.pack(">BBLBHB", SCSI_CMD_WRITE_10, 0x0, lba, 0, len(to_write)//0x200, 0)
send_scsi_cmd_wr(write_block, to_write, len(to_write))
print("")
print("Done! Update complete!")
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment