Dragon File System
Once you’ve generated your sprite using mksprite, it’s time to add it into the Dragon File System (DFS) so that it can be loaded into your ROM. You can also use this to load text or binary files.
Dragon File System (DFS)
The Dragon File System (DFS) is the method most generally used to load sprite images into memory. In order to use it, we need to go through a few steps.
Manually generating a .dfs file with mkdfs
Similar to mksprite, we need to use an executable called mkdfs. This one is a lot easier to make since it doesn’t require any external libraries. Simply use the terminal to navigate to the /libdragon/tools/mkdfs/
directory and run the make
command. This will give you a file called mkdfs.exe
.
Chuck that file into your libdragon executables folder (for me it’s C:\libdragon
) and it should be ready to use. Here is the general usage of mkdfs:
mkdfs.exe <Output .dfs file> <Input directory>
Using a Makefile to generate it automatically
The much easier way to generate a .dfs file is to use the Makefile. The Libdragon Docker container already has mkdfs installed on it and can be summoned like this:
# make the ROM build require a DFS
hello.z64: $(BUILD_DIR)/hello.dfs
# Tell the DFS which folders/files should be included
$(BUILD_DIR)/hello.dfs: $(wildcard $(SOURCE_DIR)/filesystem/*)
This will tell the compiler to require a .dfs file to build the ROM, and then tell it how to build that .dfs file. The compiler has some logic to detect when a .dfs is being built and then run mkdfs on its own.
Initialising the DFS
Like with most Libdragon modules, you need to initialise the DFS before doing anything.
// Prototype
int dfs_init(uint32_t base_fs_loc);
// Example usage
dfs_init(DFS_DEFAULT_LOCATION);
This function allows us to access data from within the cartridge. It’s basically just a stepping stone when it comes to working with the DFS. The function will search for a .dfs file and store a reference to that.
We use DFS_DEFAULT_LOCATION
by default since that will search for it in the defult location, but another location might be needed if we have multiple .dfs files or if there is an error when searching for one.
Reading files from the DFS
The DFS serves as a file system. This means that it can serve as a way to read sprite data, but it can also read any other kind of data as well. For example, it can also be used to read binaries or text files that contain data that you don’t want to hard code into your .c
files.
This can be done via either of these methods after DFS has been initialised. Use which ever one makes more sense for you.
Reading using stdio.h
This method is probably more familiar to you if you’ve used C programming in other contexts. Basically, you want to do something like this:
// Get the file pointer
FILE* f = fopen("rom://squid.sprite", "r")
// Allocate a buffer for our file
sprite_t* sprite = malloc(getSize(f));
// Load the sprite into the buffer
fread(sprite, 1, getSize(f), f);
// Close the file
fclose(f);
This is assuming that you have a function getSize()
which returns the size of the file in bytes. But otherwise, this is the standard file read that you’ve probably seen a million times so I won’t get into it in too much detail.
Just remember that DFS is all read-only, so functions like fprintf()
will not work.
Reading using DFS functions
DFS has its own library of functions that perform a similar task. Let’s try to do the same thing (fill a buffer with sprite data):
// Get the file handle for our sprite
int fp = dfs_open("/squid.sprite");
// Allocate a buffer for our file
sprite_t *squid = malloc(dfs_size(fp));
// Load the sprite into the buffer
dfs_read(squid, 1, dfs_size(fp), fp);
// Reading is complete, close the file
dfs_close(fp);
Example
Have a look at the highlighted lines to see how the DFS is implemented.
#include <libdragon.h>
#include <stdlib.h>
int main(void) {
static display_context_t disp;
display_init(RESOLUTION_320x240, DEPTH_16_BPP, 2, GAMMA_NONE, FILTERS_RESAMPLE);
// Initialise the DFS to its default location
dfs_init(DFS_DEFAULT_LOCATION);
// Main loop
while (1) {
// Get the file handle for our sprite
int fp = dfs_open("/squid.sprite");
// Allocate a buffer for our file
sprite_t *squid = malloc(dfs_size(fp));
// Load the sprite into the buffer
dfs_read(squid, 1, dfs_size(fp), fp);
// Reading is complete, close the file
dfs_close(fp);
while(!(disp = display_lock()));
graphics_draw_box(disp, 20, 20, 280, 200, graphics_make_color(255, 255, 255, 255));
// Draw the sprite onto the frame buffer
graphics_draw_sprite_trans(disp, 100, 100, squid);
display_show(disp);
}
}
Which would output this:
DFS directories
For small projects, it’s fine if you place all your files in the same folder. But if you’re building something larger, you are probably going to want to use some kind of directory structure to sort your files. For example, you might have a sprite set for each level, or you might want to separate 16-bit sprites from 32-bit sprites.
Changing directories using dfs_chdir
dfs_init()
takes you to the root directory of your dfs, but you might want to change into a subdirectory. To do this, use this function:
int dfs_chdir(const char * const path);
It takes a relative or aboslute path as a parameter, and returns DFS_ESUCCESS
(aka zero) on success or a negative number on error.
Finding files
This is useful if you want to get all the files in a directory. We’ll need to use these two functions:
// Find the first file in the folder
int dfs_dir_findfirst(const char * const path, char *buf);
// Find the next file in the folder
int dfs_dir_findnext(char *buf);
The main thing to consider here is the char* buf
string. The first function will fill buf
with the first file in a directory path determined in const char * const path
. The second function will continue returning files in the same directory, but you will need to put it in a loop with an exit condition.
Here is an example that searches for all files in the root directory:
// Initialise a string buffer that starts with 'rom:/'
char sbuf[1024];
strcpy(sbuf, "rom:/");
// Find the first file in the directory
if (dfs_dir_findfirst(".", sbuf+5) == FLAGS_FILE) {
// Loop as long as we are finding new files
do {
// Do stuff with each file here
} while (dfs_dir_findnext(sbuf+5) == FLAGS_FILE);
}
Note that these two functions both return an int which can equal to these constants:
- FLAGS_FILE – Item found is a file
- FLAGS_DIR – Item found is a directory
- FLAGS_EOF – We’ve reached the end of the directory
Advanced file reading
In most cases, you’ll want to read a whole file into a buffer at once using dfs_read()
. However, there are some cases where you are going to want to read a file in chunks or just extract a small portion of it at a time. For this we can use these DFS functions, which are pretty much just equivalents of stdio.h
functions.
// Equivalent to stdlib fread()
int dfs_read(void * const buf, int size, int count, uint32_t handle);
// Equivalent to stdlib fseek()
int dfs_seek(uint32_t handle, int offset, int origin);
// Equivalent to stdlib ftell()
int dfs_tell(uint32_t handle);
// Equivalent to stdlib feof()
int dfs_eof(uint32_t handle);
// No stdlib equivalent, but returns the size of a file
int dfs_size(uint32_t handle);
Example
Here is an example that uses these functions to print individual characters from a text file:
int main(void) {
dfs_init(DFS_DEFAULT_LOCATION);
console_init();
// Text buffer
char sbuf[1024];
// Load the txt file
int fp = dfs_open("/data.txt");
// Main loop
while (1) {
// Loop until we reach the end of the file
while (!dfs_eof(fp)) {
// Read one character and move the offset up by one
dfs_read(sbuf, 1, 1, fp);
// Print the current offset and the character at that offset
printf("%i\t%s\n", dfs_tell(fp), sbuf);
}
// Move the offset back to the start of the file
dfs_seek(fp, 0, SEEK_SET);
printf("\n");
// Same as above, but use dfs_seek to skip one character each time
while (!dfs_eof(fp)) {
dfs_read(sbuf, 1, 1, fp);
printf("%i\t%s\n", dfs_tell(fp), sbuf);
dfs_seek(fp, 1, SEEK_CUR);
}
break;
}
}
And here is the output: