Here is a video of it in action.
I figured I'd write a brief overview about how I achieved this feat.
usb2snes is an alternative firmware for the sd2snes/FXPAK by RedGuyyyy (now official as of FXPAK Firmware v1.11.0 beta 1). It uses the USB socket on a FXPAK and allows computers to remotely:
- Access the files on the SD-card
- Boot a game from the SD-card
- Reset the console
- Read ROM, Cart-RAM and Work-RAM while a game is running
- Write to ROM and Cart-RAM while a game is running
- Write to Work-RAM via code injection (some libraries/toolkits do not support this)
The first step was to refactor my code so that all resources (rooms, tiles, MetaTiles, MetaSprites, etc) are accessed through a load_resource or load_room function and only one resource is accessed at a time. When I wanted to load multiple resources, important data had to copied to RAM before the next resource is requested.
Since usb2snes does not allow me to directly send data to my PC, I took a page out of SPC communications. When the game wants to load a resource, it writes the following struct to Work-RAM. To signify this is a new request, the request_id is incremented (skipping 0) and written last.
Code: Select all
struct Request {
request_id : u8,
request_type : u8,
resource_id : u8,
};
Code: Select all
enum ResponseStatus : u8 {
NOT_CONNECTED = 0,
OK = 0x20,
OK_RESOURCES_CHANGED = 0x21, // Only `room` requests return this response.
INIT_OK = 0xbb, // Only `init` requests return this response.
NOT_FOUND = 0x40,
ERROR = 0xff,
};
struct Response {
data_size : u16,
status : ResponseStatus,
response_id : u8,
};
After I got the game to successfully retrieve resources over usb2snes, I added a filesystem monitor to detect any changes to the resource files and recompile them in a background thread. If there an error compiling a resource and the game requests that resource, the system will wait until the error has been fixed before transferring it (forcing me to fix the errors immediately).
There were two data structures I could not easily convert to a request-response model, the entity data and MetaSprite table data (as they both depended on the compiled output of all MetaSprite resources). These data structures are accessed via ROM addresses (like normal) and are updated by the python script whenever a room or MetaSprite resource was requested. I dedicated an entire 64KiB bank to the MetaSprite Table data as its size can vary wildly every time a MetaSprite is changed.
I also added a method for the python script to update the game code. When resources_over_usb2snes.py starts, it reads the ROM code running on the device and compares it to the .sfc file on my PC. If they do not match, it sets ROM address 0x00ffb9 (inside the reserved bytes of the ROM Header) to 0x42 and resets the console. When the game resets, it checks 0x00ffb9 and if is non-zero; copies and executes the following code in Work-RAM (with interrupts and HDMA disabled).
Code: Select all
// 8 bit A, 8 bit Index, DP = 0, DB = 0
Loop:
lda a:$ffb9
sta z:0,x
inx
bra Loop
Once the system has confirmed the game is executing the spin-loop (and it is safe to write to ROM), the new build will be transferred to cart ROM (not the SD-card) and a usb2snes Reset command will reset the console.