1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
|
#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; 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;
}
}
// 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.
}
}
|