From 4f8805869c469cb1b3685e03c3ea34d7654b5cb7 Mon Sep 17 00:00:00 2001
From: Ben Bridle <ben@derelict.engineering>
Date: Fri, 22 Nov 2024 16:04:20 +1300
Subject: 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
---
 arm9/source/devices/file.c   | 247 +++++++++++++++++++++++++++++++++++++++++++
 arm9/source/devices/file.h   |  52 +++++++++
 arm9/source/devices/system.c |  16 +++
 arm9/source/devices/system.h |   3 +
 4 files changed, 318 insertions(+)
 create mode 100644 arm9/source/devices/file.c
 create mode 100644 arm9/source/devices/file.h

(limited to 'arm9/source/devices')

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
+    }
+}
diff --git a/arm9/source/devices/file.h b/arm9/source/devices/file.h
new file mode 100644
index 0000000..86ca039
--- /dev/null
+++ b/arm9/source/devices/file.h
@@ -0,0 +1,52 @@
+#include <dirent.h>
+#include "../types/pathbuf.h"
+
+// #include <stdio.h>
+// #include <dirent.h>
+// #include <string.h>
+// #include <sys/stat.h>
+
+#ifndef FILE_H_
+    #define FILE_H_
+
+    typedef struct {
+        bool open;            // true if an entry is open
+        bool success;         // true if the previous operation was successful
+        bool is_dir;          // true if the open entry is a directory
+        bool child_is_dir;    // true if the selected child is a directory
+        PathBuf entry;        // write buffer for entry port
+        PathBuf action;       // write buffer for action port
+        PathBuf path;         // path of the open entry
+        PathBuf child_path;   // path of the selected child
+        u32 pointer;          // pointer in the open entry, whether dir or file
+        u32 length;           // length of the open entry, whether dir or file
+        u32 pointer_write;    // write cache for pointer
+        u32 length_write;     // write cache for length
+
+        DIR  *dir;            // structure pointing to current directory
+        FILE *file;           // opaque ID referencing the open file
+
+        FILE *tmpfile;        // to open file and keep existing file open
+        struct dirent *child; // currently-selected directory child information
+        u32 dir_i;            // index of next child to read from dir
+    } FileDevice;
+
+    void init_filesystem();
+    bool filesystem_enabled();
+
+    void fs_push_entry(FileDevice *fs, u8 byte);
+    void fs_push_action(FileDevice *fs, u8 byte);
+    bool fs_push_byte(PathBuf *buf, u8 byte);
+
+    u8 fs_read_byte(FileDevice *fs);
+    void fs_write_byte(FileDevice *fs, u8 byte);
+
+    void fs_ascend(FileDevice *fs);
+    void fs_descend(FileDevice *fs);
+
+    void fs_seek(FileDevice *fs);
+    void fs_resize(FileDevice *fs);
+
+    void fs_select_child(FileDevice *fs, u32 pointer);
+
+#endif
diff --git a/arm9/source/devices/system.c b/arm9/source/devices/system.c
index e69de29..3406eed 100644
--- a/arm9/source/devices/system.c
+++ b/arm9/source/devices/system.c
@@ -0,0 +1,16 @@
+#include "nds.h"
+#include "file.h"
+#include "system.h"
+
+
+u8 devices_high() {
+    return 0b11111100;
+}
+
+u8 devices_low() {
+    u8 devices = 0;
+    if (filesystem_enabled()) {
+        devices |= 0b00100000;
+    }
+    return devices;
+}
diff --git a/arm9/source/devices/system.h b/arm9/source/devices/system.h
index 27619e3..7549f0f 100644
--- a/arm9/source/devices/system.h
+++ b/arm9/source/devices/system.h
@@ -10,4 +10,7 @@
         u8 wake;          // ID of wake device
     } SystemDevice;
 
+    u8 devices_high();
+    u8 devices_low();
+
 #endif
-- 
cgit v1.2.3-70-g09d2