Filesystem support
1. Introduction
BlocksDS supports using standard C functions to access different filesystems of the DS:
- SD card of Slot-1 or Slot-2 flashcarts (using DLDI drivers).
- Embedded files in the NDS ROM (NitroFS).
Additionally, in DSi consoles:
- Integrated SD slot.
- Internal NAND memory.
If you want to know how to initialize and access each filesystem, keep reading.
The developer needs to initialize the filesystems before using them, but then they can be accessed using standard C functions.
You may use functions such as fopen(), fread(), fwrite(), fseek(),
fclose(), stat(), rename(), truncate(), mkdir(), unlink(),
access(), chdir(), getcwd(). Function fstat() works, but a limitation:
it doesn’t have access to the modification date of the file. stat() has access
to that date.
You can also use open(), read(), write(), lseek() and close(), but
they aren’t used as frequently as the ones mentioned before.
The dirent.h functions to read the contents of directories are available:
opendir(), closedir(), readdir(), rewinddir(), seekdir(), telldir().
It’s easy to find information online about how to use all of them. There are examples of using filesystem functions in the main repository of BlocksDS, you can also use them as reference.
If any specific function isn’t supported, raise an issue to request it.
2. Slot-1 and Slot-2 flashcarts
Slot-1 and Slot-2 flashcarts usually contain a SD or microSD card where the user stores files. If the flashcart has a DLDI driver it’s possible to access it from homebrew applications. You can use it to read additional data for your program or to write saved data, for example.
Most flashcarts autopatch NDS ROMs with the right DLDI driver, but some (the
very old ones) don’t do it automatically, and they force the user to do it
manually. BlocksDS comes with dldipatch, which you can use to patch your ROMs.
Check this archive and look for your
flashcart, then use dldipatch like this:
$BLOCKSDS/tools/dldipatch/dldipatch patch dldi_driver.dldi rom.ndsNote: dlditool is still included for backwards-compatibility with older
Makefiles and scripts, but its use is discouraged.
Once the ROM is patched, you can initialize the filesystem and use it like this:
#include <fat.h>
int main(int argc, char *argv[])
{
bool init_ok = fatInitDefault();
if (!init_ok)
{
// Handle error
}
// Rest of the code
FILE *f = fopen("fat:/myfile.txt", "rb");
// ...
}3. NitroFS
In most cases it isn’t practical to distribute a NDS ROM with a bunch of additional files that users have to copy to their flashcart. NitroFS is a filesystem that is appended to the end of the NDS ROM file. The files aren’t loaded to RAM when the application is loaded, they are simply appended there so that all the data used by the application is self-contained.
The main problem of NitroFS is finding the NDS ROM so that it can read its contents. This has to be done in very different ways depending on where it runs:
-
Emulators: They support official Nintendo cartridge read commands. NitroFS can use the same commands to easily access the files the same way as any official game.
-
SD cards and NAND: If the application is in a filesystem it can’t be read with official cartridge commands. The application needs to know where the file is located to open it and read from it. Applications written in C can use the value of
argv[0]provided to themain()function, and that’s what NitroFS uses internally. However, many loaders of ROMs don’t provide this value. You will need to use a more modern loader that supports the homebrewargvprotocol, like NDS Homebrew Menu. You can boot this menu from the loader of your flashcard and then load the homebrew application from there. -
Unlaunch: If you load your application with Unlaunch it won’t use the homebrew
argvprotocol, but it uses a different system used by official DSi applications. It’s equivalent, but it limits the length of the path to 64 characters. If your application is inside folders with long names this will be an issue.
Important note: This filesystem is read only!
To use this filesystem you need to add some files to it. Open your Makefile
and look for NITROFSDIR. You can add a list of directories (separated by
spaces) that will be added to the filesystem. The contents of all directories
will be merged into a single filesystem.
Then, in your code, do this:
#include <filesystem.h>
int main(int argc, char *argv[])
{
// Call fatInitDefault() here if you want to also access the SD card. It
// isn't required if the only thing you want to use is NitroFS.
// nitroFSInit() calls fatInitDefault() internally if it's required.
bool init_ok = nitroFSInit(NULL);
if (!init_ok)
{
// Handle error. You probably want to hang here if NitroFS can´t be
// read, because all of your assets will be unavailable. Another
// option is to return from main() or call exit() to return to the
// loader.
}
FILE *f = fopen("nitro:/myfile.txt", "rb");
// Rest of the code
// ...
}Important note: An empty NitroFS filesystem will cause nitroFSInit() to fail.
Check this example
to see how to use NitroFS.
Note for Slot-2 flashcarts
If you’re using NitroFS in a Slot-2 flashcart that doesn’t support DLDI, it will
still work. The flashcart will load the full NDS ROM to its RAM, and libnds will
read data from the Slot-2 memory region. However, this memory area has a size of
32 MB. If your NDS ROM is bigger than 32 MB it won’t be able to access all data.
This can cause unexpected errors if it isn’t detected, so nitroFSInit() will
simply return an error if the ROM is bigger.
4. DSi SD card slot
The driver to use the SD card slot of the DSi is integrated in libnds, so you
don’t need to do any special patching to use it. Simply call fatInitDefault()
as explained before. To open files from this slot, use "sd:/" instead of
"fat:/" when opening files.
5. Internal DSi NAND
The driver to use the internal NAND partitions is also included in libnds.
fatInitDefault() tries to initialize access to NAND, but it isn’t a reliable
way to know if it has been initialized or not (it will return success even if
NAND hasn’t been initialized). Also, it only initializes NAND in read-only mode
(so that applications stored in NAND can use NitroFS).
It’s a good idea to initialize NAND as read-only unless you really really need
to write to it. Users will definitely dislike it if you use NAND to store data
casually. The DSi has a SD slot, use that instead. nandInit(true) initializes
it in read-only mode, nandInit(false) initializes it in read/write mode.
Note that there are two partitions supported by libnds. "nand:/" is the main
NAND partition, and "nand2:/" is the partition used to store photos by the
system camera application.
#include <fat.h>
int main(int argc, char *argv[])
{
bool init_ok = nandInit(true); // true = read-only, false = read/write
if (!init_ok)
{
// Handle error.
}
// Open file from the main NAND partition
FILE *f1 = fopen("nand:/myfile.txt", "rb");
// Open file from the partition used to store photos
FILE *f2 = fopen("nand2:/myfile.txt", "rb");
// Rest of the code
// ...
}6. Using multiple filesystems at the same time
You can use all filesystems at once after initializing it. All you need to do is use the right prefix. For reference:
"fat:/": Slot-1 or Slot-2 flashcart."nitro:/": NitroFS."sd:/": SD slot of the DSi."nand:/": Main NAND partition of the DSi."nand2:/": NAND partition used to store photos.
You can use absolute paths like sd:/folder/file.txt or you can use chdir()
to switch to the main filesystem you want to use and then use relative paths.
The default filesystem depends on the console type and how many filesystems you
have initialized.
fatInitDefault() sets the default filesystem to "fat:/" in DS. In DSi it sets
it to "sd:/" unless the ROM has been loaded from a Slot-1 cartridge and
argv[0] is a path inside "fat:/".
In the unusual case of a program running from NAND, the default filesystem will
be "nand:/".
This is annoying to keep track of, so you can use two helpers of libnds:
-
fatGetDefaultDrive()returns aconst char *that will contain"fat:/in a DS. In DSi it may contain"fat:/","sd:/"or"nand:/"depending on where your ROM is located.Important: This pointer must not be passed to
free(). -
fatGetDefaultCwd()returns achar *that contains the path to the folder that contains the NDS ROM (excluding the name of the ROM). If your ROM is located insd:/folder/file.ndsit will returnsd:/folder/. Ifargv[0]isn’t provided it will return the same value asfatGetDefaultDrive().Important: Remember to call
free()to free the memory used by this string after you have used it.
For example, if most of what you do is use NitroFS, but you sometimes write saved data to the same folder that contains your ROM, you can do:
int main(int argc, char *argv[])
{
bool init_ok = fatInitDefault();
if (!init_ok)
{
// Handle error and hang
}
init_ok = nitroFSInit();
if (!init_ok)
{
// Handle error and hang
}
// Set NitroFS as the default filesystem
if (chdir("nitro:/") != 0)
{
// Handle error and hang
}
FILE *f = fopen("background/data.bin", "rb");
// Load game data
// ...
// When you want to save data
char *cwd = fatGetDefaultCwd();
char path[512];
snprintf(path, sizeof(path), "%s/savedata.bin", cwd);
FILE *fout = fopen(path, "wb");
free(cwd);
// Now you can write data to fout
}7. Running on emulators
Filesystem access works in several emulators. The following ones have been tested:
- melonDS: Slot-1 is emulated (it autopatches DLDI), DSi SD card is emulated.
- no$gba: DSi SD card is emulated.
- DeSmuMe: Slot-1 is emulated (the user must patch it).
melonDS
melonDS supports both DLDI in DS/DSi modes, and the internal SD in DSi mode.
It supports using a folder as a base for the SD of the DSi, or for a DLDI device for DS.
Open “Emu settings”. The “DSi mode” and “DLDI” tabs let you select the folders to use as root of the filesystems (or the filesystem images to be used, if you prefer that).
You don’t need to patch the ROM with any special DLDI to use the Slot-1 emulation, melonDS does it automatically when you load the ROM.
no$gba
no$gba supports DSi mode. You must generate a FAT filesystem image with
tools/imgbuild. The sample Makefile of the provided templates have a
target that lets you do this automatically. Open the Makefile and set the
variables SDROOT and SDIMAGE. To build the image, run:
make sdimageno$gba requires that the image is called DSi-1.sd and is located in the same
directory as no$gba. Set SDIMAGE to <path-to-folder>/DSi-1.sd to avoid
renaming the file all the time.
Then, open no$gba as normal.
DeSmuMe
It supports DS mode only. Run the following command when building the ROM:
make dldipatchThis will patch the ROM with the DLDI driver of the R4, which is required for the emulator to access the filesystem.
You will need to set the “Slot 1” configuration to “R4”, and set the directory to the folder that will act as root of your filesystem. If using DeSmuMe with a graphical interface, the settings can be found in “Config > Slot 1”. If using it through the command line, run it like this:
desmume --slot1=R4 --slot1-fat-dir=<path-to-folder> <path-to-rom>.nds