-
Notifications
You must be signed in to change notification settings - Fork 34
1) How UNFLoader works
UNFLoader starts by detecting the connected flashcart and then assigns function pointers to functions related to said cart. These function pointers provide a way to abstract which flashcart the tool is connected to, and makes it easier for developers to extend the tool's functionality without needing to know how each cart works.
UNFLoader provides the following functions (located in device.h) to abstract different processes of flashcart USB communication:
// Finds what cart is connected via USB and initializes the function pointers
void device_find(int automode);
// Opens the USB pipeline
void device_open();
// Sends a ROM via USB given a filepath
void device_sendrom(char* rompath);
// Uploads arbitrary data to the connected flashcart (More on this later)
void device_senddata(int datatype, char* data, u32 size);
// Checks if the USB pipeline is open
bool device_isopen();
// Closes the USB pipeline
void device_close();If the -d argument is provided to UNFLoader during intialization, once a ROM is uploaded to a flashcart, UNFLoader enters debug mode. In this state, the tool enters an loop where it periodically calls the FT_GetQueueStatus function to detect incoming data. This is handled by the debug_main function in debug.cpp.
How does the tool know what to do with the incoming data? How can it differentiate between prints and binary data? How does it know if the incoming data is part of a larger block? The answer is that UNFLoader uses a specific communication protocol to deal with these problems.
When uploading or receiving arbitrary data (Denoted hereafter as "data block"), UNFLoader expects it to be formatted like so:
- 4 Bytes with
'D' 'M' 'A' '@'to signalize data start. - 4 Bytes with the data header (more on this in a bit).
- N bytes with the data, where N is the size value provided in the data header.
- Finally, 4 bytes with
'C' 'M' 'P' 'H'to signalize data end[1].
The data header provides two important chunks of information. The first byte of the data header contains the type of data which is being sent/received, and the next 3 bytes state how large the incoming data is (Which can range between 1 byte to a maximum of 8MB). The data type mentioned in the first byte of the data header is up to the developer to implement, with the following data types already provided by default:
// Incoming data is text for printf
#define DATATYPE_TEXT 0x01
// Incoming data is raw binary
#define DATATYPE_RAWBINARY 0x02
// Incoming data describes contents of next incoming data
#define DATATYPE_HEADER 0x03
// Incoming data is a framebuffer
#define DATATYPE_SCREENSHOT 0x04The DATATYPE_HEADER type exists to allow the N64 to describe the data it's about to send in the next USB data block in more detail. For instance, a framebuffer can be of an arbitrary size (such as 320x240 for NTSC and 320×288 for PAL) and bit depth (16 bits or 32 bits). Because of this, before sending a data block with DATATYPE_SCREENSHOT, a DATATYPE_HEADER data block is sent first, containing 16 bytes of data (4 with DATATYPE_SCREENSHOT so that we know the next incoming block is a framebuffer, 4 for the width in pixels, 4 for the height in pixels, and another 4 with the bit depth). You can use this header data type to assist you when sending complex data through USB.
There is no checksum in place to detect the authenticity of the data. This might be implemented at a later date, but for now it is unused to speed up USB operations. A very simple method for a checksum would be to add every single byte of the received data together, and might be adopted in the future...
[1] The CMP signal is currently not used when uploading arbitrary data to the 64Drive on the N64 side (but it is used on the PC side). The reason for this is explained in the 64Drive chapter.