-
Notifications
You must be signed in to change notification settings - Fork 25
7) Adding support for more flashcarts
This section describes how to add more flashcarts to UNFLoader and the USB library.
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 CartType
enum 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).
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.