Skip to content

7) Adding support for more flashcarts

Buu342 edited this page Apr 12, 2023 · 7 revisions

This section describes how to add more flashcarts to UNFLoader and the USB library.

PC Side

First, it is recommended that a new cpp source and header file be created which will house the functions relevant to your flashcart. To keep the code consistent, it is recommended to call it device_FLASHCARTNAME.cpp and device_FLASHCARTNAME.h (obviously replacing FLASHCARTNAME with an easily identifiable flashcart name). Then, it is recommended to have the following public functions:

DeviceError device_test_FLASHCARTNAME(CartDevice* cart); // Checks whether the connected flashcart is FLASHCARTNAME
DeviceError device_open_FLASHCARTNAME(CartDevice* cart); // Opens the USB pipeline
DeviceError device_sendrom_FLASHCARTNAME(CartDevice* cart, byte* rom, uint32_t size); // Sends the ROM to the flashcart
uint32_t    device_maxromsize_FLASHCARTNAME(); // Gets the max ROM size supported by the flashcart
bool        device_shouldpadrom_FLASHCARTNAME(); // Checks whether the ROM should be padded to the nearest power of 2 MB.
bool        device_explicitcic_FLASHCARTNAME(byte* bootcode); // Checks whether this cart requires the CIC to be explicitly set
DeviceError device_testdebug_FLASHCARTNAME(CartDevice* cart); // Checks whether debug mode can be used with this flashcart
DeviceError device_senddata_FLASHCARTNAME(CartDevice* cart, USBDataType datatype, byte* data, uint32_t size); // Sends arbitrary data to the flashcart via USB
DeviceError device_receivedata_FLASHCARTNAME(CartDevice* cart, uint32_t* dataheader, byte** buff); // Receives arbitrary data to the flashcart via USB
DeviceError device_close_FLASHCARTNAME(CartDevice* cart); // Closes the USB pipeline

A shorthand name (CART_FLASHCARTNAME) should also be created and added to the CartTypeenum in device.h, with a number which is not in use by another cart, and your relevant header file should be added to the top of device.cpp.

When UNFLoader starts, it calls the function device_find (located in device.cpp) to check which flashcart is connected, by calling each cart's device_test_FLASHCARTNAME function. Your test function should be called like the rest. The test function should populate the structure value of the CartDevice struct passed into the function with a helper structure for your cart. Despite the 3 carts currently implemented using D2XX, you are not required to do so!

It is also recommended that your testing function not stall the program, because if your cart is very early on the list of carts that need to be tested, it will slow down the program for users who's carts are on the bottom. If the testing function succeeds, it should call a function that you create (preferably named device_set_FLASHCARTNAME) which will store the saved flashcart (cart->carttype = CART_FLASHCARTNAME;) and assign function pointers. The function pointers are as follows:

DeviceError (*funcPointer_open)(CartDevice*); // Should be device_open_FLASHCARTNAME
DeviceError (*funcPointer_sendrom)(CartDevice*, byte* rom, uint32_t size); // Should be device_sendrom_FLASHCARTNAME
DeviceError (*funcPointer_testdebug)(CartDevice*); // Should be device_testdebug_FLASHCARTNAME
bool        (*funcPointer_shouldpadrom)(); // Should be device_shouldpadrom_FLASHCARTNAME
bool        (*funcPointer_explicitcic)(byte* bootcode); // Should be device_explicitcic_FLASHCARTNAME
uint32_t    (*funcPointer_maxromsize)();  // Should be device_maxromsize_FLASHCARTNAME
DeviceError (*funcPointer_senddata)(CartDevice*, USBDataType datatype, byte* data, uint32_t size); // Should be device_senddata_FLASHCARTNAME
DeviceError (*funcPointer_receivedata)(CartDevice*, uint32_t* dataheader, byte** buff); // Should be device_receivedata_FLASHCARTNAME
DeviceError (*funcPointer_close)(CartDevice*); // Should be device_close_FLASHCARTNAME

For a practical example, look at the device_set_everdrive function and how it is called in device_find.

This is all that needs to be done, as now UNFLoader has enough information on how to call and use your functions. It is now up to you to actually implement the open, sendrom, senddata, and close functions. These functions headers should not be modified in any way, as they exist to abstract the connected flashcart to the end user.

If you choose to implement FLASHCARTNAME_receivedata support for your device, it is recommended that you have read the both Section 1 and Section 2 of this wiki to fully understand the communication protocol (more importantly, the data header format).

N64 Side

First, open usb.h and add a relevant CART_FLASHCARTNAME macro using #define that references your cart. Much like on the PC side, the N64 USB library has a function called usb_findcart where each known cart type is tested individually, and if it passes, a global variable (usb_cart) gets assigned the CART_FLASHCARTNAME value. But due to how the library is written, the function pointers aren't assigned here and are instead in a switch statement in usb_initialize which use the value of the usb_cart variable to assign the pointers. The functions which you should create, relevant to your flashcart are:

void usb_FLASHCARTNAME_write(int datatype, const void* data, int size); // Sends data to USB with the specific datatype and size
u32  usb_FLASHCARTNAME_poll(); // Checks if there is incoming USB data, and places it in ROM space 
void usb_FLASHCARTNAME_read(); // Reads 512 bytes of the USB data in ROM (offset by the value of the global variable usb_readblock) and places it in the global variable usb_buffer.

These functions should be assigned to the following function pointers:

void (*funcPointer_write)(int datatype, const void* data, int size);
u32  (*funcPointer_poll)();
void (*funcPointer_read)();

As usual, it is up to you to implement the functions as you see fit, and you are free to make as many helper functions as you want, so long as the original function arguments remain unchanged. It is recommended that you have read the both Section 1 and Section 2 of this wiki to fully understand the communication protocol. You can use the USBHEADER_CREATE macro to assist you in creating the data header, and the USBHEADER_GETTYPE and USBHEADER_GETSIZE to deconstruct it. You should ensure that, when reading from USB, that the usb_datatype and usb_datasize global variables are properly set, that usb_dataleft is given the same value as usb_datasize, and that usb_readblock is reset to -1. For a practical example, check out how the EverDrive is implemented in the USB library.

It is also up to you to ensure that the library is compatible with both libdragon and libultra. Please use the provided usb_io_X and usb_dma_X functions as opposed to the built-in PI/DMA functions provided by either SDK.