diff options
author | Ben Bridle <ben@derelict.engineering> | 2024-11-22 16:04:20 +1300 |
---|---|---|
committer | Ben Bridle <ben@derelict.engineering> | 2024-11-22 16:15:11 +1300 |
commit | 4f8805869c469cb1b3685e03c3ea34d7654b5cb7 (patch) | |
tree | efe555c1064b7a69738e3cf7fd107af7bdc0155f | |
parent | fcbc3968bd95e4d19b37d9fa4bca51b1db8596ff (diff) | |
download | bedrock-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
-rw-r--r-- | arm9/source/bang.h | 20 | ||||
-rw-r--r-- | arm9/source/core.c | 41 | ||||
-rw-r--r-- | arm9/source/core.h | 18 | ||||
-rw-r--r-- | arm9/source/devices/file.c | 247 | ||||
-rw-r--r-- | arm9/source/devices/file.h | 52 | ||||
-rw-r--r-- | arm9/source/devices/system.c | 16 | ||||
-rw-r--r-- | arm9/source/devices/system.h | 3 | ||||
-rw-r--r-- | arm9/source/main.c | 1 | ||||
-rw-r--r-- | arm9/source/types/pathbuf.c | 52 | ||||
-rw-r--r-- | arm9/source/types/pathbuf.h | 18 |
10 files changed, 447 insertions, 21 deletions
diff --git a/arm9/source/bang.h b/arm9/source/bang.h index 1e13f9f..110e243 100644 --- a/arm9/source/bang.h +++ b/arm9/source/bang.h @@ -1,15 +1,19 @@ #ifndef BANG_H_ #define BANG_H_ - #define SET_HIGH(v,high) v = high << 8 | (v & 0x00ff) - #define SET_LOW(v,low) v = (v & 0xff00) | low - #define HIGH(v) (u8)((v) >> 8) - #define LOW(v) (u8)((v) ) + #define SET_HIGH(v,high) v = high << 8 | (v & 0x00ff) + #define SET_LOW(v,low) v = (v & 0xff00) | low + #define HIGH(v) (u8)((v) >> 8) + #define LOW(v) (u8)((v) ) - #define H_HIGH(v) (u8)((v) >> 24) - #define H_LOW(v) (u8)((v) >> 16) - #define L_HIGH(v) (u8)((v) >> 8) - #define L_LOW(v) (u8)((v) ) + #define SET_H_HIGH(v,high) v = high << 24 | (v & 0x00ffffff) + #define SET_H_LOW(v,low) v = low << 16 | (v & 0xff00ffff) + #define SET_L_HIGH(v,high) v = high << 8 | (v & 0xffff00ff) + #define SET_L_LOW(v,low) v = low | (v & 0xffffff00) + #define H_HIGH(v) (u8)((v) >> 24) + #define H_LOW(v) (u8)((v) >> 16) + #define L_HIGH(v) (u8)((v) >> 8) + #define L_LOW(v) (u8)((v) ) #define LEFT(x) ((x) >> 4) #define RIGHT(x) ((x) & 0xf) diff --git a/arm9/source/core.c b/arm9/source/core.c index 7136777..d792360 100644 --- a/arm9/source/core.c +++ b/arm9/source/core.c @@ -62,12 +62,9 @@ u8 dev_read(Bedrock *br, u8 port) { case 0x03: return 0x00; // program memory size case 0x04: return 0x00; // working stack size case 0x05: return 0x00; // return stack size - case 0x06: return 0xbc; - case 0x07: return 0x00; - - // TODO: available devices + case 0x06: return devices_high(); + case 0x07: return devices_low(); case 0x0A: return br->sys.wake; - // MEMORY DEVICE case 0x10: return mem_read1(&br->mem); case 0x11: return mem_read1(&br->mem); @@ -137,6 +134,23 @@ u8 dev_read(Bedrock *br, u8 port) { case 0x55: return LOW(br->scr.x); case 0x56: return HIGH(br->scr.y); case 0x57: return LOW(br->scr.y); +// FILE DEVICE + case 0xA0: return BOOL(br->fs.open); + case 0xA1: return BOOL(br->fs.success); + case 0xA2: return pb_read(&br->fs.path); + case 0xA3: return BOOL(br->fs.is_dir); + case 0xA4: return fs_read_byte(&br->fs); + case 0xA5: return fs_read_byte(&br->fs); + case 0xA6: return pb_read(&br->fs.child_path); + case 0xA7: return BOOL(br->fs.child_is_dir); + case 0xA8: return H_HIGH(br->fs.pointer); + case 0xA9: return H_LOW(br->fs.pointer); + case 0xAA: return L_HIGH(br->fs.pointer); + case 0xAB: return L_LOW(br->fs.pointer); + case 0xAC: return H_HIGH(br->fs.length); + case 0xAD: return H_LOW(br->fs.length); + case 0xAE: return L_HIGH(br->fs.length); + case 0xAF: return L_LOW(br->fs.length); default: return 0; } @@ -197,6 +211,23 @@ Signal dev_write(Bedrock *br, u8 port, u8 v) { // LOCAL DEVICE case 0x86: printf("%c", v); return 0; case 0x87: printf("%c", v); return 0; +// FILE DEVICE + case 0xA0: fs_push_entry(&br->fs,v); return 0; + case 0xA1: fs_push_action(&br->fs,v); return 0; + case 0xA2: pb_reset(&br->fs.path,v); return 0; + case 0xA3: fs_ascend(&br->fs); return 0; + case 0xA4: fs_write_byte(&br->fs,v); return 0; + case 0xA5: fs_write_byte(&br->fs,v); return 0; + case 0xA6: pb_reset(&br->fs.child_path,v); return 0; + case 0xA7: fs_descend(&br->fs); return 0; + case 0xA8: SET_H_HIGH(br->fs.pointer_write,v); return 0; + case 0xA9: SET_H_LOW(br->fs.pointer_write,v); return 0; + case 0xAA: SET_L_HIGH(br->fs.pointer_write,v); return 0; + case 0xAB: SET_L_LOW(br->fs.pointer_write,v); fs_seek(&br->fs); return 0; + case 0xAC: SET_H_HIGH(br->fs.length_write,v); return 0; + case 0xAD: SET_H_LOW(br->fs.length_write,v); return 0; + case 0xAE: SET_L_HIGH(br->fs.length_write,v); return 0; + case 0xAF: SET_L_LOW(br->fs.length_write,v); fs_resize(&br->fs); return 0; default: return SIG_NONE; } diff --git a/arm9/source/core.h b/arm9/source/core.h index fde13a7..ec3f2fc 100644 --- a/arm9/source/core.h +++ b/arm9/source/core.h @@ -2,11 +2,12 @@ #define CORE_H_ #include "devices/system.h" + #include "devices/memory.h" + #include "devices/math.h" #include "devices/clock.h" #include "devices/input.h" - #include "devices/math.h" #include "devices/screen.h" - #include "devices/memory.h" + #include "devices/file.h" #define WST br->wst #define RST br->rst @@ -74,12 +75,13 @@ ProgramMemory prg; // program memory StackMemory wst, rst; // working and return stacks - SystemDevice sys; - MemoryDevice mem; - MathDevice math; - ClockDevice clk; - InputDevice inp; - ScreenDevice scr; + SystemDevice sys; + MemoryDevice mem; + MathDevice math; + ClockDevice clk; + InputDevice inp; + ScreenDevice scr; + FileDevice fs; } Bedrock; void reset_br(Bedrock *br); 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 diff --git a/arm9/source/main.c b/arm9/source/main.c index ae983b3..8be7837 100644 --- a/arm9/source/main.c +++ b/arm9/source/main.c @@ -68,6 +68,7 @@ int main(void) { init_screens(); init_clock(); + init_filesystem(); lcdMainOnBottom(); // TODO: Remove diff --git a/arm9/source/types/pathbuf.c b/arm9/source/types/pathbuf.c new file mode 100644 index 0000000..1a610c8 --- /dev/null +++ b/arm9/source/types/pathbuf.c @@ -0,0 +1,52 @@ +#include <nds.h> +#include "pathbuf.h" + +u8 pb_read(PathBuf *buf) { + u8 output = buf->mem[buf->p]; + if (output) buf->p++; + return output; +} + +void pb_clear(PathBuf *buf) { + memset(&buf->mem, 0, 256); +} + +void pb_reset(PathBuf *buf, bool to_name) { + buf->p = 0; + if (to_name) for (int i=0; i<255; i++) { + if (buf->mem[i] == '/') { + buf->p = i+1; + } else if (buf->mem[i] == 0) { + break; + } + } +} + +// Push a byte to a PathBuf, return true and reset the pointer if byte is null. +bool pb_push_byte(PathBuf *buf, u8 byte) { + // Stop before overwriting the pointer. + if (buf->p != 0xff) { + buf->mem[buf->p++] = byte; + } + if (byte == 0) { + buf->p = 0; + return true; + } else { + return false; + } +} + +bool pb_is_terminated(PathBuf *buf) { + for (int i=0; i<255; i++) { + if (buf->mem[i] == 0) { + return true; + } + } + return false; +} + +void pb_populate(PathBuf *buf, u8 *path) { + strncpy((char*) &buf->mem[0], (char*) path, 254); + buf->p = 0; +} + diff --git a/arm9/source/types/pathbuf.h b/arm9/source/types/pathbuf.h new file mode 100644 index 0000000..7fd7251 --- /dev/null +++ b/arm9/source/types/pathbuf.h @@ -0,0 +1,18 @@ +#include <nds.h> + +#ifndef PATHBUF_H_ + #define PATHBUF_H_ + + typedef struct { + u8 mem[255]; + u8 p; + } PathBuf; + + u8 pb_read(PathBuf *buf); + void pb_clear(PathBuf *buf); + void pb_reset(PathBuf *buf, bool to_name); + bool pb_push_byte(PathBuf *buf, u8 byte); + bool pb_is_terminated(PathBuf *buf); + void pb_populate(PathBuf *buf, u8 *path); + +#endif |