#include "file.h" /* TODO: I believe that multiple open files are supported, but I'm not so sure about multiple open directories. I'm not sure what the limit is for maximum open files or directories, or whether it's possible to accidentally exceed this limit with multiple Bedrock instances. TODO: I need to thoroughly test every function of the device on hardware. There's a lot that I'm not sure about. */ // True if the filesystem is available. static bool FS_ENABLE = false; // Scratch buffer for manipulating paths when ascending and descending. static u8 scratch[256]; // Attempt to initialise the filesystem. void init_nds_filesystem(void) { FS_ENABLE = fatInitDefault(); } // Return whether the filesystem is available. bool nds_filesystem_enabled() { return FS_ENABLE; } // Rewind the seek position of the current directory. void fs_rewind_dir(FileDevice *fs) { if (fs->dir) { rewinddir(fs->dir); fs->dir_i = 0; } } // Reset the current entry to its original state. void fs_reset_entry(FileDevice *fs) { if (fs->file) fseek(fs->file, 0, SEEK_SET); fs_rewind_dir(fs); fs->child = NULL; pathbuf_clear(&fs->child_path); fs->address = 0; fs->length = 0; fs->length_cached = false; } // Close the current entry, if any. Don't clear the open and action buffers, // they'll be needed for opening a file or performing an action after this // method returns. They'll need to be cleared manually. void fs_close(FileDevice *fs) { if (fs->file) { fclose(fs->file); fs->file = NULL; } if (fs->dir) { closedir(fs->dir); fs->dir = NULL; fs->dir_i = 0; fs->is_root = false; fs->child = NULL; } // Clear variables and buffers. pathbuf_clear(&fs->path); pathbuf_clear(&fs->child_path); fs->address = 0; fs->length = 0; fs->length_cached = false; } // Clear the open and action buffers. void fs_clear(FileDevice *fs) { pathbuf_clear(&fs->open); pathbuf_clear(&fs->action); } // Reset a file device. void fs_reset(FileDevice *fs) { fs_close(fs); fs_clear(fs); fs->address_write = 0; fs->length_write = 0; fs->error = false; } // Calculate the length of the current entry. void fs_calculate_length(FileDevice *fs) { if (fs->length_cached) return; if (fs->dir) { // Find directory length by reading directory entries until null is // returned. If the current directory is not the root directory, // decrease the length by two to ignore the . and .. dirs. fs_rewind_dir(fs); for (fs->length=0; readdir(fs->dir); fs->length++); if (!fs->is_root) fs->length -= 2; fs_rewind_dir(fs); } else if (fs->file) { // Find file length by seeking to the end of the file. fseek(fs->file, 0, SEEK_END); fs->length = ftell(fs->file); // Restore previous seek position. fseek(fs->file, fs->address, SEEK_SET); } fs->length_cached = true; } // Returns true if an entry is open. bool fs_get_open(FileDevice *fs) { return fs->file || fs->dir; } // Returns true if the error flag is set, then clears the error flag. bool fs_get_error(FileDevice *fs) { if (fs->error) { fs->error = false; return true; } else { return false; } } // Returns true if a directory is open. bool fs_get_type(FileDevice *fs) { return fs->dir; } // Returns true if a child is selected and that child is a directory. bool fs_get_child_type(FileDevice *fs) { return fs->child && fs->child->d_type == DT_DIR; } // Attempt to open the given path as a filesystem entry, keeping the original // entry open on failure. Path must be valid. void fs_open_entry(FileDevice* fs, u8 *path) { // Attempt to open the path as a directory. DIR *tmpdir = opendir((char*) path); if (tmpdir) { fs_close(fs); fs->dir = tmpdir; fs->dir_i = 0; pathbuf_populate(&fs->path, path); if (pathbuf_is_root(&fs->path)) fs->is_root = true; return; } // Attempt to open the path as a file. Moving this to before the // directory check will cause a memory leak. Specifically, the memory // leak is caused when opening and closing a file many thousands of times. FILE *tmpfile = fopen((char*) path, "r+"); if (tmpfile) { fs_close(fs); fs->file = tmpfile; pathbuf_populate(&fs->path, path); return; } // Could not open entry. fs->error = true; } // Select the nth child of the current directory. Directory must be open. void fs_select_child(FileDevice *fs, u32 n) { fs->address = n; // Ignore the . and .. entries. if (!fs->is_root) n += 2; // Iterate to the nth child. for (fs_rewind_dir(fs); fs->dir_i<=n; fs->dir_i++) { fs->child = readdir(fs->dir); }; if (fs->child) { pathbuf_populate(&fs->child_path, (u8*) &fs->child->d_name); } else { pathbuf_clear(&fs->child_path); fs->error = true; } } // Push a byte to the open port. void fs_push_open(FileDevice *fs, u8 byte) { if (pathbuf_push(&fs->open, byte)) { fs_close(fs); if (!pathbuf_is_empty(&fs->open)) { if (pathbuf_is_valid(&fs->open)) { fs_open_entry(fs, (u8*) &fs->open.mem); } else { fs->error = true; } } fs_clear(fs); } } // Push a byte to the action port. void fs_push_action(FileDevice *fs, u8 byte) { if (pathbuf_push(&fs->action, byte)) { bool is_entry = !pathbuf_is_empty(&fs->path); bool is_action = !pathbuf_is_empty(&fs->action); if (is_entry) { if (is_action) { if (!pathbuf_is_valid(&fs->action)) return; // Move open entry to action path. if (rename((char*) &fs->path.mem, (char*) &fs->action.mem)) { fs->error = true; }; } else { // TODO: Delete the open entry. The remove() function will // delete a file or an empty directory, so we will need to // create a function that can recursively delete all entries // within a directory. } } else { if (is_action) { if (!pathbuf_is_valid(&fs->action)) return; // Create all parent directories. for (u8 *p=&fs->action.mem[1]; *p; p++) { // Temporarily null out each path separator to get the // path of each parent directory. if (*p == '/') { *p = 0; mkdir((char*) &fs->action.mem, 0777); *p = '/'; } } // Create a new file at action path. FILE *tmpfile = fopen((char*) &fs->action.mem, "w"); if (!tmpfile) fs->error = true; fclose(tmpfile); } } fs_close(fs); fs_clear(fs); } } // Read a byte from the file head. u8 fs_read_byte(FileDevice *fs) { // TODO: Check that this returns 0 if fs->file is NULL. Look into // setting fs->error on error. fs->address++; return fgetc(fs->file); } // Write a byte to the file head. void fs_write_byte(FileDevice *fs, u8 byte) { // TODO: Look into setting fs->error on error. fs->address++; fputc(byte, fs->file); } // Attempt to ascend to the parent directory. void fs_ascend(FileDevice *fs) { if (!pathbuf_is_empty(&fs->path)) { // Don't ascend if we're already at the root. if (fs->is_root) { fs_reset_entry(fs); fs->error = true; return; } // Find and null the final slash in the path to get the path of the // parent directory. Path is guaranteed not to have a trailing slash. // If the final slash is at index 0, we need to null past the slash // instead, getting the root directory. memcpy(&scratch, &fs->path.mem, 256); u8 slash_i = 1; for (int i=1; i<256; i++) { if (scratch[i] == '/') slash_i = i; if (scratch[i] == 0 ) break; } scratch[slash_i] = 0; fs_open_entry(fs, (u8*) &scratch); fs_reset_entry(fs); } else { // Open the default directory if no entry is open. fs_open_entry(fs, (u8*) "/"); fs_reset_entry(fs); } } // Attempt to descend to the selected child entry. void fs_descend(FileDevice *fs) { if (fs->child) { memcpy(&scratch, &fs->path.mem, 256); // Find length of current path. int path_len = 0; while (scratch[path_len]) path_len++; // Find length of child name. int name_len = 0; while (fs->child->d_name[name_len]) name_len++; // Test that buffer has capacity for child name. if (path_len+1+name_len > 255) { fs_reset_entry(fs); fs->error = true; return; } // Append a slash to the path if not at root. if (path_len != 1) scratch[path_len++] = '/'; // Copy child name into buffer. for (int i=0; ichild->d_name[i]; } fs_open_entry(fs, (u8*) &scratch); fs_reset_entry(fs); } else { fs->error = true; } } // Seek within the current entry. void fs_seek(FileDevice *fs) { if (fs->dir) { fs_select_child(fs, fs->address_write); } else if (fs->file) { fseek(fs->file, fs->address_write, SEEK_SET); fs->address = ftell(fs->file); } } // Resize the current entry. void fs_resize(FileDevice *fs) { if (fs->file) { // truncate(fs->file); // TODO: Figure out how to resize a file. } }