diff options
author | Ben Bridle <ben@derelict.engineering> | 2025-09-19 13:17:14 +1200 |
---|---|---|
committer | Ben Bridle <ben@derelict.engineering> | 2025-09-19 13:32:32 +1200 |
commit | bb1aa5958d1b67707dcf0f6b08bfaf0b408bd46e (patch) | |
tree | b26d07ed58aaf7a5230fc3e28c103d616abfa9b8 /arm9/source/devices/file.c | |
parent | 9612c307f00c4313d73fe0c3a86c05c8d8cd514e (diff) | |
download | bedrock-nds-bb1aa5958d1b67707dcf0f6b08bfaf0b408bd46e.zip |
Massive rewrite
This commit rewrites the emulator halfway from scratch to make it
easier to change and maintain in the future. The emulator core was
rewritten to adhere to the released Bedrock specification (earlier
versions implemented an older prototype specification, which is no
longer relevant).
This commit also adds proper support for running multiple concurrent
Bedrock instances. This was previously supported in a limited manner
for the on-screen keyboard, but now works for any regular program as
well, with switching being performed by pressing the L or R bumper
buttons. This is disabled by default, as programs will still need to
be baked into the emulator and hand-loaded.
Diffstat (limited to 'arm9/source/devices/file.c')
-rw-r--r-- | arm9/source/devices/file.c | 422 |
1 files changed, 233 insertions, 189 deletions
diff --git a/arm9/source/devices/file.c b/arm9/source/devices/file.c index dcc987d..546ca0c 100644 --- a/arm9/source/devices/file.c +++ b/arm9/source/devices/file.c @@ -1,279 +1,323 @@ -#include "nds.h" -#include "fat.h" #include "file.h" -#include <sys/stat.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; -static u8 buffer[255]; + +// Scratch buffer for manipulating paths when ascending and descending. +static u8 scratch[256]; -void init_filesystem() { +// Attempt to initialise the filesystem. +void init_nds_filesystem(void) { FS_ENABLE = fatInitDefault(); } -bool filesystem_enabled() { +// Return whether the filesystem is available. +bool nds_filesystem_enabled() { return FS_ENABLE; } -bool is_valid_path(u8 *path) { - // Prevent non-absolute paths from being opened. - if (path[0] != '/') { - return false; - } - // Remove trailing slash if any, without removing leading slash. - for (int i=2; i<255; i++) { - if (path[i] == 0) { - if (path[i-1] == '/') { - path[i-1] = 0; - } - break; - } - } - // Prevent paths with relative components from being opened. - for (int i=0; i<255; i++) { - if (path[i] == '/') { - if (path[i+1]=='.') { - // Check for '.' components. - if (path[i+2]=='/' || path[i+2]==0) { - return false; - } - // Check for '..' components. - if (path[i+2]=='.' && (path[i+3]=='/' || path[i+3]==0)) { - return false; - } - } - } else if (path[i] == 0) { - break; - } +// Rewind the seek position of the current directory. +void fs_rewind_dir(FileDevice *fs) { + if (fs->dir) { + rewinddir(fs->dir); + fs->dir_i = 0; } - return true; } +// 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) { - fclose(fs->file); - fs->file = NULL; - closedir(fs->dir); - fs->dir = NULL; - fs->open = false; - fs->success = false; - fs->is_dir = false; - fs->child_is_dir = false; - pb_clear(&fs->path); - pb_clear(&fs->child_path); - fs->pointer = 0; + 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->child = NULL; - fs->dir_i = 0; - fs->at_root = false; + fs->length_cached = false; } +// Clear the open and action buffers. void fs_clear(FileDevice *fs) { - pb_clear(&fs->entry); - pb_clear(&fs->action); + pathbuf_clear(&fs->open); + pathbuf_clear(&fs->action); } -void fs_open_entry(FileDevice* fs, u8 *path) { - fs->success = false; - if (!is_valid_path(path)) return; +// 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->open = true; - fs->success = true; - fs->is_dir = true; fs->dir_i = 0; - // Find length of directory - while ((fs->child = readdir(fs->dir))) { - fs->dir_i++; - }; - // If this is not the root directory, reduce len by 2 for . and .. dirs. - if (path[1] == 0) { fs->at_root = true; } else { fs->dir_i -= 2; } - fs->length = fs->dir_i; - fs_select_child(fs, 0); - pb_populate(&fs->path, path); + pathbuf_populate(&fs->path, path); + if (pathbuf_is_root(&fs->path)) fs->is_root = true; return; } - - // MEMORY LEAK HERE ONLY - // for (int i=0; i<500; i++) { - // fs->tmpfile = fopen((char*) path, "r+"); - // fclose(fs->tmpfile); - // } - // END OF MEMORY LEAK CODE - - // FILE CODE MUST COME AFTER DIR CODE TO AVOID MEMORY LEAK + // 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; - fs->open = true; - fs->success = true; - fs->is_dir = false; - // TODO: Only read length when we need to. Keep a bool around. - // fseek(fs->file, 0, SEEK_END); - // fs->length = ftell(fs->file); - // rewind(fs->file); - pb_populate(&fs->path, path); + pathbuf_populate(&fs->path, path); return; } + // Could not open entry. + fs->error = true; } -void fs_select_child(FileDevice *fs, u32 pointer) { - // Increment past the . and .. directories. - if (!fs->at_root) { - pointer += 2; - } - fs->pointer = pointer; - rewinddir(fs->dir); - - for (fs->dir_i=0; fs->dir_i<pointer+1; fs->dir_i++) { +// 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) { - pb_populate(&fs->child_path, (u8*) &fs->child->d_name); - fs->child_is_dir = fs->child->d_type == DT_DIR; + pathbuf_populate(&fs->child_path, (u8*) &fs->child->d_name); } else { - pb_clear(&fs->child_path); - fs->child_is_dir = false; + pathbuf_clear(&fs->child_path); + fs->error = true; } } -void fs_push_entry(FileDevice *fs, u8 byte) { - if (pb_push_byte(&fs->entry, byte)) { +// 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 (fs->entry.mem[0] != 0) { - fs->success = false; - if (pb_is_terminated(&fs->entry)) { - fs_open_entry(fs, fs->entry.mem); + 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 (pb_push_byte(&fs->action, byte)) { - fs->success = false; - bool action = fs->action.mem[0] != 0; - if (fs->open) { - if (action) { - if (!pb_is_terminated(&fs->action)) return; - if (!is_valid_path(fs->action.mem)) return; - fs->success = rename((char*) fs->path.mem, (char*) fs->action.mem); + 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 + // 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); } - } else if (action) { - if (!pb_is_terminated(&fs->action)) return; - if (!is_valid_path(fs->action.mem)) return; - create_parents(fs->action.mem); - FILE *tmpfile = fopen((char*) fs->action.mem, "w"); - fs->success = tmpfile != NULL; - fclose(tmpfile); } fs_close(fs); fs_clear(fs); } } -// Recursively create parent directories. -void create_parents(u8 *path) { - // Iterate over characters left-to-right. - for (u8 *p=path+1; *p; p++) { - if (*p == '/') { - *p = 0; - mkdir((char*) path, 0777); - *p = '/'; - } - } - return; -} - - +// 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) { - fs->success = false; - if (fs->open) { + if (!pathbuf_is_empty(&fs->path)) { // Don't ascend if we're already at the root. - if (fs->path.mem[1] == 0) { + if (fs->is_root) { + fs_reset_entry(fs); + fs->error = true; return; } - // Path is guaranteed to have no trailing slash. - memcpy(&buffer, &fs->path.mem[0], 255); - u8 slash_i = 0; - for (int i=0; i<255; i++) { - if (buffer[i] == '/') { - slash_i = i; - } else if (buffer[i] == 0) { - // Trim to leave the slash - buffer[slash_i+1] = 0; - break; - } + // 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; } - fs_open_entry(fs, &buffer[0]); + 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) { - fs->success = false; - if (!(fs->open && fs->is_dir && fs->child)) { - return; - } - memcpy(&buffer, &fs->path.mem[0], 255); - // Find location of terminating null in buffer. - int start = 0; - for (int i=0; i<255; i++) { - start = i; - if (buffer[i] == 0) break; - } - // Find length of child name. - int len = 0; - for (int i=0; i<255; i++) { - len = i; - if (fs->child->d_name[i] == 0) break; - } - // Check that buffer has capacity for child name. - if (start+len > 255) { - fs->success = false; - return; - } - // Add a slash if not at root. - if (start != 1) { - buffer[start] = '/'; - start++; - } - // Copy child name into buffer. - for (int i=0; i<len; i++) { - buffer[start+i] = fs->child->d_name[i]; + 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; i<name_len; i++) { + scratch[path_len+i] = fs->child->d_name[i]; + } + fs_open_entry(fs, (u8*) &scratch); + fs_reset_entry(fs); + } else { + fs->error = true; } - fs_open_entry(fs, &buffer[0]); } +// Seek within the current entry. void fs_seek(FileDevice *fs) { - if (fs->open) { - if (fs->is_dir) { - fs_select_child(fs, fs->pointer_write); - } else { - fseek(fs->file, fs->pointer_write, SEEK_SET); - fs->pointer = ftell(fs->file); - } + 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->open && !fs->is_dir) { + if (fs->file) { // truncate(fs->file); - // TODO + // TODO: Figure out how to resize a file. } } |