Last active
January 30, 2024 01:48
-
-
Save shinyquagsire23/b6322687a153114e5a0a46e4ff91e8c0 to your computer and use it in GitHub Desktop.
FPGBC updater script for macOS (and maybe Linux) using Python
This file contains 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
# 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