summaryrefslogtreecommitdiff
path: root/arm9/source/devices/file.c
diff options
context:
space:
mode:
authorBen Bridle <ben@derelict.engineering>2024-11-22 16:04:20 +1300
committerBen Bridle <ben@derelict.engineering>2024-11-22 16:15:11 +1300
commit4f8805869c469cb1b3685e03c3ea34d7654b5cb7 (patch)
treeefe555c1064b7a69738e3cf7fd107af7bdc0155f /arm9/source/devices/file.c
parentfcbc3968bd95e4d19b37d9fa4bca51b1db8596ff (diff)
downloadbedrock-nds-4f8805869c469cb1b3685e03c3ea34d7654b5cb7.zip
Implement file device
There is still a small amount of work to be done on the file device: - Read file size only when requested - Hide '.' and '..' directories - Resize files
Diffstat (limited to 'arm9/source/devices/file.c')
-rw-r--r--arm9/source/devices/file.c247
1 files changed, 247 insertions, 0 deletions
diff --git a/arm9/source/devices/file.c b/arm9/source/devices/file.c
new file mode 100644
index 0000000..c9b822c
--- /dev/null
+++ b/arm9/source/devices/file.c
@@ -0,0 +1,247 @@
+#include "nds.h"
+#include "fat.h"
+#include "file.h"
+
+static bool FS_ENABLE = false;
+static u8 buffer[255];
+
+
+void init_filesystem() {
+ FS_ENABLE = fatInitDefault();
+}
+
+bool filesystem_enabled() {
+ return FS_ENABLE;
+}
+
+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;
+ fs->length = 0;
+ fs->child = NULL;
+ fs->dir_i = 0;
+}
+
+void fs_clear(FileDevice *fs) {
+ pb_clear(&fs->entry);
+ pb_clear(&fs->action);
+}
+
+void fs_open_entry(FileDevice* fs, u8 *path) {
+ fs->success = false;
+ // Prevent non-absolute paths from being opened.
+ if (path[0] != '/') {
+ return;
+ }
+ // 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;
+ }
+ // Check for '..' components.
+ if (path[i+2]=='.' && (path[i+3]=='/' || path[i+3]==0)) {
+ return;
+ }
+ }
+ } else if (path[i] == 0) {
+ break;
+ }
+ }
+
+
+ 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++;
+ };
+ fs->length = fs->dir_i;
+ fs_select_child(fs, 0);
+ pb_populate(&fs->path, path);
+ 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
+ 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);
+ return;
+ }
+}
+
+void fs_select_child(FileDevice *fs, u32 pointer) {
+ fs->pointer = pointer;
+ rewinddir(fs->dir);
+ fs->dir_i = 0;
+
+ for (fs->dir_i=0; fs->dir_i<pointer+1; 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;
+ } else {
+ pb_clear(&fs->child_path);
+ fs->child_is_dir = false;
+ }
+}
+
+void fs_push_entry(FileDevice *fs, u8 byte) {
+ if (pb_push_byte(&fs->entry, byte)) {
+ fs_close(fs);
+ if (pb_is_terminated(&fs->entry) && fs->entry.mem[0] != 0) {
+ fs_open_entry(fs, &fs->entry.mem[0]);
+ }
+ fs_clear(fs);
+ }
+}
+
+void fs_push_action(FileDevice *fs, u8 byte) {
+ if (pb_push_byte(&fs->action, byte)) {
+ if (!pb_is_terminated(&fs->action)) return;
+ bool action = fs->action.mem[0] != 0;
+ if (fs->open) {
+ if (action) {
+ fs->success = rename((char*) fs->path.mem, (char*) fs->action.mem);
+ } else {
+ // TODO: DELETE
+ }
+ } else if (action) {
+ FILE *tmpfile = fopen((char*) fs->action.mem, "w");
+ fs->success = tmpfile != NULL;
+ fclose(tmpfile);
+ }
+ fs_close(fs);
+ fs_clear(fs);
+ }
+}
+
+
+u8 fs_read_byte(FileDevice *fs) {
+ return fgetc(fs->file);
+}
+
+void fs_write_byte(FileDevice *fs, u8 byte) {
+ fputc(byte, fs->file);
+}
+
+void fs_ascend(FileDevice *fs) {
+ fs->success = false;
+ if (fs->open) {
+ // Don't ascend if we're already at the root.
+ if (fs->path.mem[1] == 0) {
+ 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;
+ }
+ }
+ fs_open_entry(fs, &buffer[0]);
+ } else {
+ fs_open_entry(fs, (u8*) "/");
+ }
+}
+
+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];
+ }
+ fs_open_entry(fs, &buffer[0]);
+}
+
+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);
+ }
+ }
+}
+
+void fs_resize(FileDevice *fs) {
+ if (fs->open && !fs->is_dir) {
+ // truncate(fs->file);
+ // TODO
+ }
+}