diff options
Diffstat (limited to 'arm9/source/devices')
-rw-r--r-- | arm9/source/devices/clock.c | 101 | ||||
-rw-r--r-- | arm9/source/devices/clock.h | 36 | ||||
-rw-r--r-- | arm9/source/devices/file.c | 422 | ||||
-rw-r--r-- | arm9/source/devices/file.h | 77 | ||||
-rw-r--r-- | arm9/source/devices/input.c | 93 | ||||
-rw-r--r-- | arm9/source/devices/input.h | 33 | ||||
-rw-r--r-- | arm9/source/devices/local.c | 11 | ||||
-rw-r--r-- | arm9/source/devices/local.h | 8 | ||||
-rw-r--r-- | arm9/source/devices/math.c | 143 | ||||
-rw-r--r-- | arm9/source/devices/math.h | 40 | ||||
-rw-r--r-- | arm9/source/devices/memory.c | 231 | ||||
-rw-r--r-- | arm9/source/devices/memory.h | 60 | ||||
-rw-r--r-- | arm9/source/devices/screen.c | 421 | ||||
-rw-r--r-- | arm9/source/devices/screen.h | 100 | ||||
-rw-r--r-- | arm9/source/devices/stream.c | 40 | ||||
-rw-r--r-- | arm9/source/devices/stream.h | 33 | ||||
-rw-r--r-- | arm9/source/devices/system.c | 37 | ||||
-rw-r--r-- | arm9/source/devices/system.h | 15 |
18 files changed, 1096 insertions, 805 deletions
diff --git a/arm9/source/devices/clock.c b/arm9/source/devices/clock.c index 6ecee1d..c05ba2b 100644 --- a/arm9/source/devices/clock.c +++ b/arm9/source/devices/clock.c @@ -1,55 +1,84 @@ -#include <nds.h> -#include <time.h> #include "clock.h" -#include "../bang.h" -// Uptime is the number of 1/256th second ticks since the emulator began. + +/* +TODO: Add functions to set the system time and date. +*/ + + +// ------ UPTIME AND DATETIME -------------------------------------------------- + +// Uptime is the number of 1/256th second ticks elapsed since the emulator began. static u32 uptime; -u32 get_uptime(void) { return uptime; } + +// Increment the local uptime value. void uptime_handler(void) { uptime++; } -// Check if any timer has expired. -bool check_timers(ClockDevice *clk) { - bool output = FALSE; - if (clk->t1.end && clk->t1.end <= uptime) { clk->t1.end = 0; output = TRUE; } - if (clk->t2.end && clk->t2.end <= uptime) { clk->t2.end = 0; output = TRUE; } - if (clk->t3.end && clk->t3.end <= uptime) { clk->t3.end = 0; output = TRUE; } - if (clk->t4.end && clk->t4.end <= uptime) { clk->t4.end = 0; output = TRUE; } - return output; +// Configure timer 0 to increment the local uptime value every tick. +void init_nds_clock(void) { + // Start a 256Hz timer to increment the uptime value. + timerStart(0, ClockDivider_1024, TIMER_FREQ_1024(256), uptime_handler); } -u8 get_timer_high(ClockTimer *t) { - if (t->end > uptime) { - t->read = t->end - uptime; - } else { - t->end = 0; t->read = 0; - } - return HIGH(t->read); +// Return the current time and date. +struct tm* get_datetime(void) { + time_t timestamp = time(NULL); + struct tm* datetime = localtime(×tamp); + return datetime; } -u8 get_timer_low(ClockTimer *t) { - return LOW(t->read); + +// ------ TIMERS --------------------------------------------------------------- + +// Reset a clock timer. +void timer_reset(ClockTimer *timer) { + timer->end = 0; + timer->read = 0; + timer->write = 0; } -void set_timer_high(ClockTimer *t, u8 high) { - SET_HIGH(t->write, high); +// Update the cached read value of a timer. +void timer_read(ClockTimer *timer) { + if (timer->end > uptime) { + timer->read = timer->end - uptime; + } else { + timer->read = 0; + timer->end = 0; + } } -void set_timer_low(ClockTimer *t, u8 low) { - SET_LOW(t->write, low); - if (t->write) { - t->end = uptime + t->write; + +// Update the value of a timer using the cached write value. +void timer_write(ClockTimer *timer) { + if (timer->write) { + timer->end = uptime + timer->write; } else { - t->end = 0; + timer->end = 0; } } -void init_clock(void) { - // Start a 256Hz timer to increment the uptime value. - timerStart(0, ClockDivider_1024, TIMER_FREQ_1024(256), uptime_handler); + +// ------ CLOCK ---------------------------------------------------------------- + +// Reset the clock device. +void clock_reset(ClockDevice *clock) { + clock->start = uptime; + timer_reset(&clock->t1); + timer_reset(&clock->t2); + timer_reset(&clock->t3); + timer_reset(&clock->t4); } -struct tm* get_datetime(void) { - time_t timestamp = time(NULL); - struct tm* datetime = localtime(×tamp); - return datetime; +// Update the cached uptime value. +void clock_uptime_read(ClockDevice *clock) { + clock->uptime = uptime - clock->start; +} + +// Return true if any timer has expired. +bool clock_check_timers(ClockDevice *clock) { + bool output = false; + if (clock->t1.end && clock->t1.end <= uptime) { clock->t1.end = 0; output = true; } + if (clock->t2.end && clock->t2.end <= uptime) { clock->t2.end = 0; output = true; } + if (clock->t3.end && clock->t3.end <= uptime) { clock->t3.end = 0; output = true; } + if (clock->t4.end && clock->t4.end <= uptime) { clock->t4.end = 0; output = true; } + return output; } diff --git a/arm9/source/devices/clock.h b/arm9/source/devices/clock.h index 04c4d5d..4dc4b41 100644 --- a/arm9/source/devices/clock.h +++ b/arm9/source/devices/clock.h @@ -1,18 +1,24 @@ -#include <time.h> - #ifndef CLOCK_H_ #define CLOCK_H_ + #include <time.h> + #include "../bang.h" + + + // A 16-bit countdown timer. typedef struct { - u32 end; // real end time - u16 read, write; // read write caches + u32 end; // End time as an uptime value, zero if inactive + u16 read, write; // Read write caches for remaining duration } ClockTimer; + // Bedrock clock device. typedef struct { - ClockTimer t1, t2, t3, t4; // timers - u32 start; // uptime offset + ClockTimer t1, t2, t3, t4; // Timers + u16 uptime; // Read cache for uptime + u32 start; // Uptime offset } ClockDevice; + // Extract fields from a tm struct. #define YEAR(tm) (tm->tm_year - 100) #define MONTH(tm) (tm->tm_mon) #define DAY(tm) (tm->tm_mday - 1) @@ -20,16 +26,16 @@ #define MINUTE(tm) (tm->tm_min) #define SECOND(tm) (tm->tm_sec) - u32 get_uptime(void); - void uptime_handler(void); - void init_clock(void); + // Functions. + void init_nds_clock(void); struct tm* get_datetime(void); - bool check_timers(ClockDevice *clk); - - u8 get_timer_high(ClockTimer *t); - u8 get_timer_low( ClockTimer *t); - void set_timer_high(ClockTimer *t, u8 high); - void set_timer_low( ClockTimer *t, u8 low); + // Timer methods. + void timer_read(ClockTimer *timer); + void timer_write(ClockTimer *timer); + // Clock methods. + void clock_reset(ClockDevice *clock); + void clock_uptime_read(ClockDevice *clock); + bool clock_check_timers(ClockDevice *clock); #endif 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. } } diff --git a/arm9/source/devices/file.h b/arm9/source/devices/file.h index f295e89..3f27bda 100644 --- a/arm9/source/devices/file.h +++ b/arm9/source/devices/file.h @@ -1,55 +1,54 @@ -#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 + #include <dirent.h> + #include <sys/stat.h> + #include "fat.h" + #include "nds.h" + #include "../bang.h" + #include "../types/pathbuf.h" - 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 - bool at_root; // current entry is the root directory - } FileDevice; + // Bedrock file device. + typedef struct { + FILE *file; // Opaque ID referencing current file (if any) + // See ANSI C89 Standard, page 126 + DIR *dir; // Structure referencing current directory (if any) + // See 'libnds/libs/libnds/include/sys/dirent.h:41' + u32 dir_i; // Index of next child to be read from current directory + bool is_root; // True if the current directory is the root directory. + struct dirent *child; // Information about selected child (file or dir) + // See 'libnds/libs/libnds/include/sys/dirent.h:17' - void init_filesystem(); - bool filesystem_enabled(); + PathBuf open; // Write buffer for open port + PathBuf action; // Write buffer for action port + PathBuf path; // Path of current entry + PathBuf child_path; // Path of selected child + u32 address; // Address into current entry (file or dir) + u32 length; // Length of current entry (file or dir) + bool length_cached; // True if length has been calculated. + u32 address_write; // Write cache for address + u32 length_write; // Write cache for length + bool error; // True if error occurred since last read + } FileDevice; - void create_parents(u8 *path); + // Functions. + void init_nds_filesystem(); + bool nds_filesystem_enabled(); - void fs_push_entry(FileDevice *fs, u8 byte); + // Methods. + void fs_reset(FileDevice *fs); + void fs_calculate_length(FileDevice *fs); + bool fs_get_open(FileDevice *fs); + bool fs_get_error(FileDevice *fs); + bool fs_get_type(FileDevice *fs); + bool fs_get_child_type(FileDevice *fs); + void fs_push_open(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/input.c b/arm9/source/devices/input.c index e5ac5be..401f053 100644 --- a/arm9/source/devices/input.c +++ b/arm9/source/devices/input.c @@ -1,14 +1,40 @@ -#include <nds.h> -#include "../bang.h" #include "input.h" +#include "../config.h" -void inp_receive_byte(InputDevice *inp, u8 byte) { - cb_write_byte(&inp->keybuffer, byte); - inp->wake = true; + +// Reset an input device. +void input_reset(InputDevice *input) { + input->pointer = false; + input->keyboard = false; + input->x = 0; + input->y = 0; + input->x_read = 0; + input->y_read = 0; + circbuf_clear(&input->keybuffer); + input->gamepad = 0; + input->wake = false; +} + +// Update the cached read value for the pointer x coordinate. +void input_read_x(InputDevice *input) { + input->x_read = input->x; +} + +// Update the cached read value for the pointer y coordinate. +void input_read_y(InputDevice *input) { + input->y_read = input->y; } -// Read gamepad state into an input device. -void inp_read_gamepad(InputDevice *inp) { +// Receive a byte from a keyboard. +// TODO: Make this function Unicode-aware by receiving a u32 instead of +// a u8, so that the keybuffer can be checked for capacity before pushing. +void input_receive_key(InputDevice *input, u8 byte) { + circbuf_write(&input->keybuffer, byte); + input->wake = true; +} + +// Read the current gamepad state. +void input_update_gamepad(InputDevice *input) { u32 held = keysHeld(); u8 gamepad = ( TEST(held, KEY_UP) << 7 @@ -20,45 +46,34 @@ void inp_read_gamepad(InputDevice *inp) { | TEST(held, KEY_X) << 1 | TEST(held, KEY_Y) << 0 ); - if (gamepad != inp->gamepad) { - inp->wake = TRUE; - inp->gamepad = gamepad; + // If the L and R bumper keys aren't being used to switch between + // instances, map them to be alternate X and Y keys. + if (!SWITCH_BETWEEN_INSTANCES) { + gamepad |= TEST(held, KEY_L) << 1; + gamepad |= TEST(held, KEY_R) << 0; } -} - -// Read navigation state into an input device. -void inp_read_navigation(InputDevice *inp) { - u32 held = keysHeld(); - u8 navigation = ( - TEST(held, KEY_UP) << 7 - | TEST(held, KEY_DOWN) << 6 - | TEST(held, KEY_LEFT) << 5 - | TEST(held, KEY_RIGHT) << 4 - | TEST(held, KEY_A) << 3 - | TEST(held, KEY_B) << 2 - | TEST(held, KEY_R) << 1 - | TEST(held, KEY_L) << 0 - ); - if (navigation != inp->navigation) { - inp->wake = TRUE; - inp->navigation = navigation; + if (gamepad != input->gamepad) { + input->wake = true; + input->gamepad = gamepad; } } -// Read touchscreen state into an input device. -void inp_read_touch(InputDevice *inp) { +// Read the current touchscreen state. +void input_update_touch(InputDevice *input) { + // Test if pointer is active. bool pointer = TEST(keysHeld(), KEY_TOUCH); - if (pointer != inp->pointer) { - inp->wake = TRUE; - inp->pointer = pointer; + if (pointer != input->pointer) { + input->wake = true; + input->pointer = pointer; } + // Update pointer position. if (pointer) { - touchPosition pos; - touchRead(&pos); - if (pos.px != inp->x || pos.py != inp->y) { - inp->wake = TRUE; - inp->x = pos.px; - inp->y = pos.py; + touchPosition position; + touchRead(&position); + if (position.px != input->x || position.py != input->y) { + input->wake = true; + input->x = position.px; + input->y = position.py; } } } diff --git a/arm9/source/devices/input.h b/arm9/source/devices/input.h index 4f91ed5..6df947b 100644 --- a/arm9/source/devices/input.h +++ b/arm9/source/devices/input.h @@ -1,21 +1,26 @@ -#include <nds.h> -#include "../types/circbuf.h" - #ifndef INPUT_H_ #define INPUT_H_ + #include "../bang.h" + #include "../types/circbuf.h" + + + // Bedrock input device. typedef struct { - bool pointer; // pointer active - bool keyboard; // keyboard active - CircBuf keybuffer; // queued keypresses - u16 x,y; // pointer position - u8 navigation; // navigation state - u8 gamepad; // gamepad state - bool wake; // wake flag + bool pointer; // Pointer active + bool keyboard; // Keyboard active + u16 x, y; // Pointer position + u16 x_read, y_read; // Read caches for pointer position + CircBuf keybuffer; // Queued keypresses + u8 gamepad; // Gamepad state + bool wake; // Wake flag } InputDevice; - void inp_receive_byte(InputDevice *inp, u8 byte); - void inp_read_gamepad(InputDevice *inp); - void inp_read_navigation(InputDevice *inp); - void inp_read_touch(InputDevice *inp); + // Methods. + void input_reset(InputDevice *input); + void input_read_x(InputDevice *input); + void input_read_y(InputDevice *input); + void input_receive_key(InputDevice *input, u8 byte); + void input_update_gamepad(InputDevice *input); + void input_update_touch(InputDevice *input); #endif diff --git a/arm9/source/devices/local.c b/arm9/source/devices/local.c deleted file mode 100644 index e13a1f8..0000000 --- a/arm9/source/devices/local.c +++ /dev/null @@ -1,11 +0,0 @@ -#include "../main.h" -#include "local.h" - -void std_write(u8 byte) { - if (byte) { - receive_keyboard_byte(byte); - } else { - close_keyboard(); - - } -} diff --git a/arm9/source/devices/local.h b/arm9/source/devices/local.h deleted file mode 100644 index 979f5ce..0000000 --- a/arm9/source/devices/local.h +++ /dev/null @@ -1,8 +0,0 @@ -#include <nds.h> - -#ifndef LOCAL_H_ - #define LOCAL_H_ - - void std_write(u8 byte); - -#endif diff --git a/arm9/source/devices/math.c b/arm9/source/devices/math.c index bb592e0..33aeb63 100644 --- a/arm9/source/devices/math.c +++ b/arm9/source/devices/math.c @@ -1,66 +1,119 @@ -#include <nds.h> -#include <math.h> #include "math.h" -#include "../bang.h" -#define CLEAR \ - math->sqrt_rc = FALSE;\ - math->atan_rc = FALSE;\ - math->prod_rc = FALSE;\ - math->quot_rc = FALSE;\ - math->rem_rc = FALSE; -void set_op1_high(MathDevice *math, u8 high) { SET_HIGH(math->op1, high); CLEAR; } -void set_op1_low( MathDevice *math, u8 low) { SET_LOW(math->op1, low); CLEAR; } -void set_op2_high(MathDevice *math, u8 high) { SET_HIGH(math->op2, high); CLEAR; } -void set_op2_low( MathDevice *math, u8 low) { SET_LOW(math->op2, low); CLEAR; } +// Angle conversion constants. +const double fromRadians = 10430.378350470453; // 65536 / 2Ï€ +const double toRadians = 0.00009587379924285257; // 2Ï€ / 65536 -u16 get_sqrt(MathDevice *math) { - if (!math->sqrt_rc) { - u32 input = math->op1 << 16 | math->op2; - math->sqrt = sqrt32(input); - math->sqrt_rc = TRUE; + +// Reset the math device. +void math_reset(MathDevice *math) { + math->x = 0; + math->y = 0; + math->r = 0; + math->t = 0; + math_clear_cartesian(math); + math_clear_polar(math); +} + +// Clear all values calculated from cartesian coordinates. +void math_clear_cartesian(MathDevice *math) { + math->r_cached = false; + math->t_cached = false; + math->prod_cached = false; + math->quot_cached = false; + math->rem_cached = false; +} + +// Clear all values calculated from polar coordinates. +void math_clear_polar(MathDevice *math) { + math->x_cached = false; + math->y_cached = false; +} + +// Calculate the x coordinate of the polar point. +s16 math_get_x(MathDevice *math) { + if (!math->x_cached) { + double x = cos(math->t * toRadians) * math->r; + if (-32768.0 <= x && x <= 32768.0) { + math->x_read = x; + } else { + math->x_read = 0; + } + math->x_cached = true; + } + return math->x_read; +} + +// Calculate the y coordinate of the polar point. +s16 math_get_y(MathDevice *math) { + if (!math->y_cached) { + double y = sin(math->t * toRadians) * math->r; + if (-32768.0 <= y && y <= 32767.0) { + math->y_read = y; + } else { + math->y_read = 0; + } + math->y_cached = true; + } + return math->y_read; +} + +// Calculate the r coordinate of the cartesian point. +u16 math_get_r(MathDevice *math) { + if (!math->r_cached) { + u32 x2 = math->x * math->x; + u32 y2 = math->y * math->y; + math->r_read = sqrt32(x2 + y2); + math->r_cached = true; } - return math->sqrt; + return math->r_read; } -u16 get_atan(MathDevice *math) { - if (!math->atan_rc) { - if (math->op1 || math->op2) { - double op1 = (s16) math->op1; - double op2 = (s16) math->op2; - const double scale = 10430.378350470453; // PI * 32768 - math->atan = (s32) (atan2(op1, op2) * scale); +// Calculate the t coordinate of the cartesian point. +u16 math_get_t(MathDevice *math) { + if (!math->t_cached) { + if (math->x || math->y) { + math->t_read = atan2(math->y, math->x) * fromRadians; + } else { + math->t_read = 0; } - math->atan_rc = TRUE; + math->t_cached = true; } - return math->atan; + return math->t_read; } -u32 get_prod(MathDevice *math) { - if (!math->prod_rc) { - math->prod = math->op1 * math->op2; - math->prod_rc = TRUE; +// Calculate the product of x by y. +u32 math_get_prod(MathDevice *math) { + if (!math->prod_cached) { + math->prod_read = (u16)math->x * (u16)math->y; + math->prod_cached = true; } - return math->prod; + return math->prod_read; } -u16 get_quot(MathDevice *math) { - if (!math->quot_rc) { - if (math->op2) { - math->quot = div32(math->op1, math->op2); +// Calculate the quotient of x by y. +u16 math_get_quot(MathDevice *math) { + if (!math->quot_cached) { + if (math->y) { + math->quot_read = div32((u16)math->x, (u16)math->y); + } else { + math->quot_read = 0; } - math->quot_rc = TRUE; + math->quot_cached = true; } - return math->quot; + return math->quot_read; } -u16 get_rem(MathDevice *math) { - if (!math->rem_rc) { - if (math->op2) { - math->rem = mod32(math->op1, math->op2); +// Calculate the remainder of x by y. +u16 math_get_rem(MathDevice *math) { + if (!math->rem_cached) { + if (math->y) { + math->rem_read = mod32((u16)math->x, (u16)math->y); + } else { + math->rem_read = 0; } - math->rem_rc = TRUE; + math->rem_cached = true; } - return math->rem; + return math->rem_read; } diff --git a/arm9/source/devices/math.h b/arm9/source/devices/math.h index ae52a10..6b173b6 100644 --- a/arm9/source/devices/math.h +++ b/arm9/source/devices/math.h @@ -1,19 +1,33 @@ #ifndef MATH_H_ #define MATH_H_ + #include <math.h> + #include "../bang.h" + + + // Bedrock math device. typedef struct { - u16 op1, op2; - u16 sqrt, atan; u32 prod; u16 quot, rem; // read - bool sqrt_rc, atan_rc, prod_rc, quot_rc, rem_rc; // read cached - } MathDevice ; + s16 x, y; // Cartesian write + u16 r, t; // Polar write + s16 x_read, y_read; // Cartesian read + bool x_cached, y_cached; // Cartesian cached + u16 r_read, t_read; // Polar read + bool r_cached, t_cached; // Polar cached + u32 prod_read; // Multiplication read + bool prod_cached; // Multiplication cached + u16 quot_read, rem_read; // Division read + bool quot_cached, rem_cached; // Division cached + } MathDevice; - void set_op1_high(MathDevice *math, u8 high); - void set_op1_low(MathDevice *math, u8 low); - void set_op2_high(MathDevice *math, u8 high); - void set_op2_low(MathDevice *math, u8 low); - u16 get_sqrt(MathDevice *math); - u16 get_atan(MathDevice *math); - u32 get_prod(MathDevice *math); - u16 get_quot(MathDevice *math); - u16 get_rem(MathDevice *math); + // Methods. + void math_reset(MathDevice *math); + void math_clear_cartesian(MathDevice *math); + void math_clear_polar(MathDevice *math); + s16 math_get_x(MathDevice *math); + s16 math_get_y(MathDevice *math); + u16 math_get_r(MathDevice *math); + u16 math_get_t(MathDevice *math); + u32 math_get_prod(MathDevice *math); + u16 math_get_quot(MathDevice *math); + u16 math_get_rem(MathDevice *math); #endif diff --git a/arm9/source/devices/memory.c b/arm9/source/devices/memory.c index 915aec2..c9e86d7 100644 --- a/arm9/source/devices/memory.c +++ b/arm9/source/devices/memory.c @@ -1,143 +1,158 @@ -#include "nds.h" #include "memory.h" -static u8 heap[HEAP_SIZE][256]; // the page heap. -static u8 unallocated[256]; // page to use for all unallocated reads and writes. -static u8 heap_index[HEAP_SIZE]; -u8 mem_read1(MemoryDevice *mem) { - u8 output = mem->point1[mem->byte1++]; - if (mem->byte1 == 0) { - mem->page1++; - mem_get_page1(mem); - } - return output; +/* +The 'heap' array is the global pool of memory from which pages are allocated +and deallocated, used by every Bedrock instance. The 'heap_index' array tracks +the owner of each page of memory: the value of each entry is the ID of the +instance that owns / has allocated the corresponding page in the heap. An ID +of zero indicates that the page is free / hasn't been allocated yet. + +If multiple instances are repeatedly allocating and deallocating pages, it's +expected that the allocations will become very fragmented, with pages owned +by each instance interleaving with one another. To find the heap address that +corresponds with an allocated page address, the heap will need to be traversed, +which makes random access very expensive. To mitigate this, the heap address +of every page accessible from the current offset value (which determines the +base page to address from) is cached in an array of 256 pointers, and the +pointer corresponding with the current page is further cached as a pointer. + +The 'unallocated' array acts as a sort of null page, with every access of an +unallocated page being routed instead to that page. This allows the read and +write methods to omit a test to check whether a page is allocated or not; +effectively, every page is always allocated. This is allowed because the +Bedrock specification says that an unallocated read or write causes undefined +behaviour, allowing us to optimise away the test. + +TODO: If one instance allocates memory, then a second instance allocates memory, +then the first instance deallocates, then the second instance allocates some +more, the pages allocated to the second instance will be out of order, but the +code so far assumes that all pages are in the correct order (with gaps or not). +There needs to be either a page reordering pass (time expensive) or an array to +track the allocation order of pages for each instance (space expensive). The +former approach would be the best, because it's unlikely that this would be a +frequent occurrence. I'll need to shuffle pages around using memcpy and the null +page. Only touch the pages of the current instance, to avoid having to recalculate +the page caches of every instance. We can determine if pages will need to be +shuffled / defragmented by checking if an unallocated page is traversed before +the final allocated page is found. +*/ + + +// Size of the heap in pages. The value was chosen by steadily increasing it +// until the emulator refused to compile, and then easing back a bit. +#define MAX_PAGES (12000) + +// The heap. +static u8 heap[MAX_PAGES][256]; // the page heap. + +// Null page to use for unallocated reads and writes. +static u8 unallocated[256]; + +// Map heap pages to instances. An ID of zero represents an unallocated page. +static u8 heap_index[MAX_PAGES]; + + +// Reset a read-write head on the memory device. +void memory_head_reset(MemoryHead *head) { + head->offset = 0; + head->byte = 0; + head->point = (u8*) &unallocated; + head->page = 0; + for (int i=0; i<256; i++) head->cache[i] = (u8*) &unallocated; } -u8 mem_read2(MemoryDevice *mem) { - u8 output = mem->point2[mem->byte2++]; - if (mem->byte2 == 0) { - mem->page2++; - mem_get_page2(mem); - } - return output; +// Reset the memory device. +void memory_reset(MemoryDevice *mem) { + mem->count = 0; + mem->copy = 0; + memory_allocate(mem); + memory_head_reset(&mem->head1); + memory_head_reset(&mem->head2); } -void mem_write1(MemoryDevice *mem, u8 value) { - mem->point1[mem->byte1++] = value; - if (mem->byte1 == 0) { - mem->page1++; - mem_get_page1(mem); +// Read a byte from a memory head. +u8 memory_read(MemoryHead *head) { + u8 output = head->point[head->byte++]; + if (head->byte == 0) { + head->page++; + memory_refresh_page(head); } + return output; } -void mem_write2(MemoryDevice *mem, u8 value) { - mem->point2[mem->byte2++] = value; - if (mem->byte2 == 0) { - mem->page2++; - mem_get_page2(mem); +// Write a byte to a memory head. +void memory_write(MemoryHead *head, u8 value) { + head->point[head->byte++] = value; + if (head->byte == 0) { + head->page++; + memory_refresh_page(head); } } - -void mem_load_cache1(MemoryDevice *mem) { - // Set all cached pointers to unallocated. - for (int i=0; i<256; i++) { - mem->cache1[i] = (u8*) &unallocated; - } - // Iterate over all heap pages, gather our ones. - int count = 0; - int cached = 0; - for (int i=0; i<HEAP_SIZE; i++) { - if (heap_index[i] == mem->id) { - if (count >= mem->offset1) { - mem->cache1[cached] = (u8*) &heap[i]; - cached++; - } - count++; - if (cached == 256) break; - } - } +void memory_refresh_page(MemoryHead *head) { + head->point = head->cache[head->page]; } -void mem_load_cache2(MemoryDevice *mem) { - // Set all cached pointers to unallocated. - for (int i=0; i<256; i++) { - mem->cache2[i] = (u8*) &unallocated; - } - // Iterate over all heap pages, gather our ones. - int count = 0; - int cached = 0; - for (int i=0; i<HEAP_SIZE; i++) { - if (heap_index[i] == mem->id) { - if (count >= mem->offset2) { - mem->cache2[cached] = (u8*) &heap[i]; - cached++; +// Recalculate the pointers stored in the page cache. +void memory_refresh_cache(MemoryDevice *mem, MemoryHead *head) { + // Point every page in the cache to the unallocated page. + for (int i=0; i<256; i++) head->cache[i] = (u8*) &unallocated; + // Iterate over every page in the heap, gather all pages allocated to + // this instance that belong to the current offset. + int n = 0; + int cache_i = 0; + for (int heap_i=0; heap_i<MAX_PAGES; heap_i++) { + if (heap_index[heap_i] == mem->id) { + if (n++ >= head->offset) { + head->cache[cache_i++] = (u8*) &heap[heap_i]; + if (cache_i == 256) break; } - count++; - if (cached == 256) break; } } } -// Fetch the page1 pointer from cache1. -void mem_get_page1(MemoryDevice *mem) { - mem->point1 = mem->cache1[mem->page1]; -} - -// Fetch the page2 pointer from cache2. -void mem_get_page2(MemoryDevice *mem) { - mem->point2 = mem->cache2[mem->page2]; -} - -void mem_allocate(MemoryDevice *mem) { - int count = 0; - - // ALLOCATING - if (mem->count_write > mem->count) { - int to_allocate = mem->count_write - mem->count; - for (int i=0; i<HEAP_SIZE; i++) { +// Allocate mem->count pages of memory. +void memory_allocate(MemoryDevice *mem) { + // Track how many pages have been allocated/deallocated so far. + int n = 0; + // Allocate memory. + if (mem->count > mem->allocated) { + int to_allocate = mem->count - mem->allocated; + for (int i=0; i<MAX_PAGES; i++) { if (heap_index[i] == 0) { heap_index[i] = mem->id; - count++; - if (count == to_allocate) { - break; - } + if (++n == to_allocate) break; } } - // DEALLOCATING - } else if (mem->count_write < mem->count) { - for (int i=0; i<HEAP_SIZE; i++) { + // Deallocate memory. + } else if (mem->count < mem->allocated) { + for (int i=0; i<MAX_PAGES; i++) { if (heap_index[i] == mem->id) { - count++; - if (count > mem->count_write) { - heap_index[i] = 0; + if (++n > mem->count) { + // Clear the page when it's deallocated. memset(heap[i], 0, 256); + heap_index[i] = 0; } } } } - - // Count the number of pages allocated to us. - mem->count = 0; - for (int i=0; i<HEAP_SIZE; i++) { - if (heap_index[i]==mem->id) mem->count++; + // Re-count the number of pages allocated to this instance. + mem->allocated = 0; + for (int i=0; i<MAX_PAGES; i++) { + if (heap_index[i]==mem->id) mem->allocated++; } - // Reload the pointer caches for each head. - mem_load_cache1(mem); - mem_load_cache2(mem); + // Refresh the page caches for both heads. + memory_refresh_cache(mem, &mem->head1); + memory_refresh_cache(mem, &mem->head2); } - -void mem_do_copy(MemoryDevice *mem) { - int src = mem->offset2; - int dest = mem->offset1; - if (src == dest) return; - for (int i=0; i<mem->copy_write; i++) { - if (src>=mem->count || dest>=mem->count) { - return; - } - memcpy(&heap[dest++], &heap[src++], 256); +// Copy mem->count pages from head 2 to head 1. +void memory_copy(MemoryDevice *mem) { + if (mem->head1.offset == mem->head2.offset) return; + for (int n=0; n<mem->copy; n++) { + if (mem->head1.cache[n] == (u8*) &unallocated) return; + if (mem->head2.cache[n] == (u8*) &unallocated) return; + memcpy(mem->head1.cache[n], mem->head2.cache[n], 256); } } - diff --git a/arm9/source/devices/memory.h b/arm9/source/devices/memory.h index 0abff16..814f47d 100644 --- a/arm9/source/devices/memory.h +++ b/arm9/source/devices/memory.h @@ -1,42 +1,34 @@ #ifndef MEMORY_H_ #define MEMORY_H_ - #define HEAP_SIZE (4096*3) + #include "../bang.h" - typedef struct { - u8 id; // Unique non-zero identifier for this memory device. - - u16 offset1; // Bedrock offset value for head 1 - u8 page1; // Bedrock address value for head 1 - u8 byte1; // Bedrock address value for head 1 - u16 offset2; // Bedrock offset value for head 2 - u8 page2; // Bedrock address value for head 2 - u8 byte2; // Bedrock address value for head 2 - - u16 count_write; // write cache for page count - u16 count; // number of pages allocated for this bedrock - u16 copy_write; // write cache for page copy length - - u8 *point1; // pointer to current page for head 1 - u8 *point2; // pointer to current page for head 2 - - u8* cache1[256]; - u8* cache2[256]; - } MemoryDevice ; - u8 mem_read1(MemoryDevice *mem); - u8 mem_read2(MemoryDevice *mem); - void mem_write1(MemoryDevice *mem, u8 value); - void mem_write2(MemoryDevice *mem, u8 value); - - void mem_load_cache1(MemoryDevice *mem); - void mem_load_cache2(MemoryDevice *mem); - void mem_get_page1(MemoryDevice *mem); - void mem_get_page2(MemoryDevice *mem); - - - void mem_allocate(MemoryDevice *mem); - void mem_do_copy(MemoryDevice *mem); + // Read-write head for memory device. + typedef struct { + u16 offset; // Index of base page. + u8 page; // Index of current page from offset. + u8 byte; // Index of current byte in page. + u8 *point; // Pointer to current page. + u8 *cache[256]; // Pointers to all pages for the current offset. + } MemoryHead; + // Bedrock memory device. + typedef struct { + u8 id; // Non-zero ID of this Bedrock instance. + u16 allocated; // Number of pages allocated to this instance + u16 count; // Write cache for allocation count + u16 copy; // Write cache for copy length + MemoryHead head1; + MemoryHead head2; + } MemoryDevice; + // Methods. + void memory_reset(MemoryDevice *mem); + u8 memory_read(MemoryHead *head); + void memory_write(MemoryHead *head, u8 value); + void memory_refresh_page(MemoryHead *head); + void memory_refresh_cache(MemoryDevice *mem, MemoryHead *head); + void memory_allocate(MemoryDevice *mem); + void memory_copy(MemoryDevice *mem); #endif diff --git a/arm9/source/devices/screen.c b/arm9/source/devices/screen.c index 698b754..bca436d 100644 --- a/arm9/source/devices/screen.c +++ b/arm9/source/devices/screen.c @@ -1,114 +1,123 @@ +#include "screen.h" + + /* -A screen layer contains 768 tiles (32*24), requiring 24KB. Each tile is 32 bytes, -holding 2 pixels per byte. The upper four bits of a tile byte represent the -colour of the right pixel, and the lower four bits the colour of the left pixel. -The colour is a 4-bit reference into a 16-colour RGB15 palette. +Each screen layer is 24KB and contains 768 tiles (32*24). Each tile is 32 +bytes, and contains 64 pixels (8*8), holding 2 pixels per byte. The upper +four bits of a tile byte represent the colour of the right pixel, and the +lower four bits represent the colour of the left pixel. The colour is a +4-bit index into a 16-colour RGB15 palette. + +Four screen layers are used by each physical screen: a foreground layer, a +background layer, and then back buffers for each. Screen layers are stored +as u16* pointers to raw video memory. + +Each 'tile slot' of video memory is 16KB, so we need two slots per layer. We +can't use memory more efficiently by packing the layers more tightly into the +available slots, because we can only draw the contents of a layer by passing +a tile slot base address to the video system. We can, however, use the gaps to +store our tile map, because each map slot takes only 2KB with a corresponding +increase in address granularity. -Screen memory contains four layers: bg, fg, then the same but for double buffers. -Ignore double buffering for now. We keep these as u32* because a single 32-bit -integer represents a single 8-pixel row. -TODO: This needs to be updated. We have double buffering, and we use u16*. +The tile map is shared by all foreground and background layers on a physical +screen, and is stored just past the contents of the first screen layer (at the +24KB offset in video memory). Each entry in the map is a 16-bit tile index. +The low 10 bits of the value represent the tile index, then h-flip, then +v-flip, then a 4-bit palette identifier. We don't flip any tiles, and we +always use palette zero. The map is initialised with an incrementing sequence, +and then never touched. -The tile map is shared by the foreground and background layers, and is stored -just past the first layer for both screens (at the 24KB offset). Each entry is -a 16-bit tile index (low 10 bits are tile index, then h-flip, then v-flip, then -4-bit palette identifier). +The memory bank system of the NDS is byzantine and most vexing, with banks being +identified by letters A through I, and in sizes of 128/128/128/128/64/16/16/32/16 +KB, respectively. The main 2D engine can use banks A/B/C/D/E for BG data (tiles), +and the sub 2D engine can use bank C for the same. */ -#include <nds.h> -#include "screen.h" -#include "../bang.h" -Screen scr_main = { - .bgv = BG_TILE_RAM(BG_SLOT_VIS), - .fgv = BG_TILE_RAM(FG_SLOT_VIS), - .bg = BG_TILE_RAM(BG_SLOT), - .fg = BG_TILE_RAM(FG_SLOT), +// ------ NDS SCREENS ---------------------------------------------------------- + +// VRAM addresses for the main 2D engine. +NDSScreen main_screen = { + .bgv = BG_TILE_RAM(BG_SLOT_FRONT), + .fgv = BG_TILE_RAM(FG_SLOT_FRONT), + .bg = BG_TILE_RAM(BG_SLOT_BACK), + .fg = BG_TILE_RAM(FG_SLOT_BACK), .map = BG_MAP_RAM(MAP_SLOT), .palv = BG_PALETTE, }; -Screen scr_sub = { - .bgv = BG_TILE_RAM_SUB(BG_SLOT_VIS), - .fgv = BG_TILE_RAM_SUB(FG_SLOT_VIS), - .bg = BG_TILE_RAM_SUB(BG_SLOT), - .fg = BG_TILE_RAM_SUB(FG_SLOT), + +// VRAM addresses for the sub 2D engine. +NDSScreen sub_screen = { + .bgv = BG_TILE_RAM_SUB(BG_SLOT_FRONT), + .fgv = BG_TILE_RAM_SUB(FG_SLOT_FRONT), + .bg = BG_TILE_RAM_SUB(BG_SLOT_BACK), + .fg = BG_TILE_RAM_SUB(FG_SLOT_BACK), .map = BG_MAP_RAM_SUB(MAP_SLOT), .palv = BG_PALETTE_SUB, }; -// TODO: Make an enum thing for main/sub, combine these functions -void scr_make_main(ScreenDevice *scr) { - scr->nds = &scr_main; - for (int i=0; i<16; i++) { - scr->nds->pal[i] = scr->palette[i]; - } - scr->wake = true; -} - -void scr_make_sub(ScreenDevice *scr) { - scr->nds = &scr_sub; - for (int i=0; i<16; i++) { - scr->nds->pal[i] = scr->palette[i]; - } - scr->wake = true; -} - -void scr_unmake(ScreenDevice *scr) { - if (scr->nds) { - black_screen(scr->nds); - scr->nds = NULL; - } -} - -void init_screens(void) { - // Allocate VRAM for screens +// Initialise the VRAM mappings and tile map for both NDS screens. +void init_nds_screens(void) { + // Allocate VRAM for screens. videoSetMode(DISPLAY_BG0_ACTIVE | DISPLAY_BG1_ACTIVE | MODE_0_2D); vramSetBankA(VRAM_A_MAIN_BG); videoSetModeSub(DISPLAY_BG0_ACTIVE | DISPLAY_BG1_ACTIVE | MODE_0_2D); vramSetBankC(VRAM_C_SUB_BG); - - /* Configure screen layers to use tile graphics. */ - REG_BG0CNT = BG_32x32 | BG_COLOR_16 | BG_PRIORITY_3 | BG_TILE_BASE(BG_SLOT_VIS) | BG_MAP_BASE(MAP_SLOT); - REG_BG1CNT = BG_32x32 | BG_COLOR_16 | BG_PRIORITY_2 | BG_TILE_BASE(FG_SLOT_VIS) | BG_MAP_BASE(MAP_SLOT); - REG_BG0CNT_SUB = BG_32x32 | BG_COLOR_16 | BG_PRIORITY_3 | BG_TILE_BASE(BG_SLOT_VIS) | BG_MAP_BASE(MAP_SLOT); - REG_BG1CNT_SUB = BG_32x32 | BG_COLOR_16 | BG_PRIORITY_2 | BG_TILE_BASE(FG_SLOT_VIS) | BG_MAP_BASE(MAP_SLOT); - - /* Populate tile maps with tile indices. */ - int i; + // Configure screen layers to use tile graphics. + REG_BG0CNT = BG_32x32 | BG_COLOR_16 | BG_PRIORITY_3 | BG_TILE_BASE(BG_SLOT_FRONT) | BG_MAP_BASE(MAP_SLOT); + REG_BG1CNT = BG_32x32 | BG_COLOR_16 | BG_PRIORITY_2 | BG_TILE_BASE(FG_SLOT_FRONT) | BG_MAP_BASE(MAP_SLOT); + REG_BG0CNT_SUB = BG_32x32 | BG_COLOR_16 | BG_PRIORITY_3 | BG_TILE_BASE(BG_SLOT_FRONT) | BG_MAP_BASE(MAP_SLOT); + REG_BG1CNT_SUB = BG_32x32 | BG_COLOR_16 | BG_PRIORITY_2 | BG_TILE_BASE(FG_SLOT_FRONT) | BG_MAP_BASE(MAP_SLOT); + // Populate tile maps. u16 *main_map = BG_MAP_RAM(12); u16 *sub_map = BG_MAP_RAM_SUB(12); - for (i = 0; i < TILES_SIZE; i++) { + for (int i=0; i<TILES_COUNT; i++) { *(main_map++) = i; *(sub_map++) = i; } } - -void set_palette_high(ScreenDevice *scr, u8 high) { - SET_HIGH(scr->palette_write, high); +// Copy the contents of the back buffer to the front buffer. +void ndsscreen_flip_buffers(NDSScreen *nds) { + if (nds) { + dmaCopyWords(0, nds->bg, nds->bgv, LAYER_MEM); + dmaCopyWords(0, nds->fg, nds->fgv, LAYER_MEM); + for (int i=0; i<16; i++) nds->palv[i] = nds->pal[i]; + } } -void set_palette_low(ScreenDevice *scr, u8 low) { - SET_LOW(scr->palette_write, low); - u8 i = scr->palette_write >> 12 & 0x0f; - u8 r = scr->palette_write >> 7 & 0x1e; - u8 g = scr->palette_write >> 3 & 0x1e; - u8 b = scr->palette_write << 1 & 0x1e; - scr->palette[i] = RGB15(r,g,b); - if (scr->nds) { - scr->nds->pal[i] = RGB15(r,g,b); + +// Erase both buffers and set every colour to black. +void ndsscreen_clear(NDSScreen *nds) { + if (nds) { + dmaFillWords(0, nds->bgv, LAYER_MEM); + dmaFillWords(0, nds->fgv, LAYER_MEM); + dmaFillWords(0, nds->bg, LAYER_MEM); + dmaFillWords(0, nds->fg, LAYER_MEM); + for (int i=0; i<16; i++) nds->palv[i] = RGB15(0,0,0); } } -void push_sprite(SpriteBuffer *b, u8 row) { - b->mem[b->p] = row; - b->p = (b->p + 1) % 16; + +// ------ SPRITE BUFFER -------------------------------------------------------- + +// Reset a sprite buffer. +void spritebuf_reset(SpriteBuffer *b) { + for (int i=0; i<16; i++) b->mem[i] = 0; + b->cached = false; +} + +// Push a byte to a sprite buffer. +void spritebuf_push(SpriteBuffer *b, u8 row) { + b->mem[b->p++] = row; + b->p %= 16; b->cached = FALSE; } -void prepare_1bit_sprite(SpriteBuffer *b, u8 draw) { +// Parse, transform, and cache a 1-bit sprite. +void spritebuf_prepare_1bit(SpriteBuffer *b, u8 draw) { u8 l,p,x,y; if (b->cached && draw == b->draw) return; - + // Parse and transform the sprite if not already cached. switch (draw & 0x07) { case 0x0: p=b->p+8; for (y=0;y<8;y++) { l=b->mem[p++ % 16]; for (x=0;x<8;x++) { b->sprite[y][x] = l>>(7-x) & 1; } }; break; case 0x1: p=b->p+8; for (y=0;y<8;y++) { l=b->mem[p++ % 16]; for (x=0;x<8;x++) { b->sprite[y][x] = l>>( x) & 1; } }; break; @@ -119,15 +128,15 @@ void prepare_1bit_sprite(SpriteBuffer *b, u8 draw) { case 0x6: p=b->p; for (y=0;y<8;y++) { l=b->mem[--p % 16]; for (x=0;x<8;x++) { b->sprite[x][y] = l>>(7-x) & 1; } }; break; case 0x7: p=b->p; for (y=0;y<8;y++) { l=b->mem[--p % 16]; for (x=0;x<8;x++) { b->sprite[x][y] = l>>( x) & 1; } }; break; } - - b->cached = TRUE; b->draw = draw; + b->cached = true; } -void prepare_2bit_sprite(SpriteBuffer *b, u8 draw) { +// Parse, transform, and cache a 2-bit sprite. +void spritebuf_prepare_2bit(SpriteBuffer *b, u8 draw) { u8 l,h,i,p,s,x,y; if (b->cached && draw == b->draw) return; - + // Parse and transform the sprite if not already cached. switch (draw & 0x07) { case 0x0: p=b->p+8; s=p+8; for (y=0;y<8;y++) { l=b->mem[p++ % 16]; h=b->mem[s++ % 16]; for (x=0;x<8;x++) { i=(7-x); b->sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }; break; case 0x1: p=b->p+8; s=p+8; for (y=0;y<8;y++) { l=b->mem[p++ % 16]; h=b->mem[s++ % 16]; for (x=0;x<8;x++) { i=( x); b->sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }; break; @@ -138,14 +147,77 @@ void prepare_2bit_sprite(SpriteBuffer *b, u8 draw) { case 0x6: p=b->p; s=p+8; for (y=0;y<8;y++) { l=b->mem[--p % 16]; h=b->mem[--s % 16]; for (x=0;x<8;x++) { i=(7-x); b->sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }; break; case 0x7: p=b->p; s=p+8; for (y=0;y<8;y++) { l=b->mem[--p % 16]; h=b->mem[--s % 16]; for (x=0;x<8;x++) { i=( x); b->sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }; break; } - - b->cached = TRUE; b->draw = draw; + b->cached = true; +} + + +// ------ BEDROCK SCREEN ------------------------------------------------------- + +// Reset a screen device. +void screen_reset(ScreenDevice *screen) { + screen->x = 0; + screen->y = 0; + screen->px = 0; + screen->py = 0; + screen->selected = 0; + spritebuf_reset(&screen->sprite); + screen->colour = 0; + for (int i=0; i<16; i++) screen->pal[i] = 0; + ndsscreen_clear(screen->nds); + screen->wake = false; + screen->dirty = false; } +// Unmap a screen device from a screen. +void screen_map_to_none(ScreenDevice *screen) { + if (screen->nds) { + ndsscreen_clear(screen->nds); + screen->nds = NULL; + } +} + +// Map a screen device to the main screen. +void screen_map_to_main(ScreenDevice *screen) { + screen->nds = &main_screen; + memcpy(&screen->nds->pal, &screen->pal, 32); + screen->wake = true; +} + +// Map a screen device to the sub screen. +void screen_map_to_sub(ScreenDevice *screen) { + screen->nds = &sub_screen; + memcpy(&screen->nds->pal, &screen->pal, 32); + screen->wake = true; +} + +// Commit a new colour to the colour palette. +void screen_commit_colour(ScreenDevice *screen) { + u8 i = screen->colour >> 12 & 0x0f; + // Extract 5-bit channel values. + u8 r = screen->colour >> 7 & 0x1e; + u8 g = screen->colour >> 3 & 0x1e; + u8 b = screen->colour << 1 & 0x1e; + screen->pal[i] = RGB15(r,g,b); + if (screen->nds) screen->nds->pal[i] = RGB15(r,g,b); + screen->dirty = true; +} + +// Move the screen cursor. +void screen_move_cursor(ScreenDevice *screen, u8 move) { + switch (move >> 6) { + case 0b00: screen->x += move & 0x3f; return; + case 0b01: screen->y += move & 0x3f; return; + case 0b10: screen->x -= move & 0x3f; return; + case 0b11: screen->y -= move & 0x3f; return; + } +} + + -// --------------------------------------------------------------------------- +// ------ RAW DRAW OPERATIONS -------------------------------------------------- +// Draw a pixel to a screen layer. void draw_pixel(u16 *layer, u16 x, u16 y, u8 colour) { if (x < PIXELS_WIDTH && y < PIXELS_HEIGHT) { u32 addr = \ @@ -156,109 +228,58 @@ void draw_pixel(u16 *layer, u16 x, u16 y, u8 colour) { } } +// Fill a screen layer with a solid colour. void fill_layer(u16 *layer, u8 colour) { u8 byte = colour << 4 | colour; u32 word = byte << 24 | byte << 16 | byte << 8 | byte; - dmaFillWords(word, layer, TILES_MEM); -} - -void erase_screen(Screen *nds) { - if (nds) { - dmaFillWords(0, nds->bg, TILES_MEM); - dmaFillWords(0, nds->fg, TILES_MEM); - } -}; - -void flip_buffer(Screen *nds) { - if (nds) { - dmaCopyWords(0, nds->bg, nds->bgv, TILES_MEM); - dmaCopyWords(0, nds->fg, nds->fgv, TILES_MEM); - for (int i=0; i<16; i++) { - nds->palv[i] = nds->pal[i]; - } - } + dmaFillWords(word, layer, LAYER_MEM); } -void black_screen(Screen *nds) { - if (nds) { - for (int i=0; i<16; i++) { - nds->palv[i] = RGB15(0,0,0); - } - dmaFillWords(0, nds->bgv, TILES_MEM); - dmaFillWords(0, nds->fgv, TILES_MEM); - } -} -// --------------------------------------------------------------------------- -void draw_dispatch(ScreenDevice *scr, u8 draw) { - if (scr->nds) { - switch (draw >> 4) { - case 0x0: scr_draw_pixel(scr, scr->nds->bg, draw); break; - case 0x1: scr_draw_sprite(scr, scr->nds->bg, draw); break; - case 0x2: scr_fill_layer(scr, scr->nds->bg, draw); break; - case 0x3: scr_draw_sprite(scr, scr->nds->bg, draw); break; - case 0x4: scr_draw_line(scr, scr->nds->bg, draw); break; - case 0x5: scr_draw_line(scr, scr->nds->bg, draw); break; - case 0x6: scr_draw_rect(scr, scr->nds->bg, draw); break; - case 0x7: scr_draw_rect(scr, scr->nds->bg, draw); break; - case 0x8: scr_draw_pixel(scr, scr->nds->fg, draw); break; - case 0x9: scr_draw_sprite(scr, scr->nds->fg, draw); break; - case 0xA: scr_fill_layer(scr, scr->nds->fg, draw); break; - case 0xB: scr_draw_sprite(scr, scr->nds->fg, draw); break; - case 0xC: scr_draw_line(scr, scr->nds->fg, draw); break; - case 0xD: scr_draw_line(scr, scr->nds->fg, draw); break; - case 0xE: scr_draw_rect(scr, scr->nds->fg, draw); break; - case 0xF: scr_draw_rect(scr, scr->nds->fg, draw); break; - } - scr->dirty = true; - } - scr->px = scr->x; - scr->py = scr->y; -} +// ------ DRAW OPERATIONS ------------------------------------------------------ -void scr_draw_pixel(ScreenDevice *scr, u16 *layer, u8 draw) { - draw_pixel(layer, scr->x, scr->y, draw&0xf); +void op_draw_pixel(ScreenDevice *screen, u16 *layer, u8 draw) { + draw_pixel(layer, screen->x, screen->y, draw&0xf); } -void scr_fill_layer(ScreenDevice *scr, u16 *layer, u8 draw) { +void op_fill_layer(ScreenDevice *screen, u16 *layer, u8 draw) { fill_layer(layer, draw&0xf); } -void scr_draw_sprite(ScreenDevice *scr, u16 *layer, u8 draw) { - if (draw & 0x20) { prepare_2bit_sprite(&scr->sprite, draw); } - else { prepare_1bit_sprite(&scr->sprite, draw); } - - u8 colours[4] = { - scr->colours >> 12 & 0x000f, - scr->colours >> 8 & 0x000f, - scr->colours >> 4 & 0x000f, - scr->colours & 0x000f, +void op_draw_sprite(ScreenDevice *screen, u16 *layer, u8 draw) { + if (draw & 0x20) { spritebuf_prepare_2bit(&screen->sprite, draw); } + else { spritebuf_prepare_1bit(&screen->sprite, draw); } + // Parse sprite colours. + u8 selected[4] = { + screen->selected >> 12 & 0x000f, + screen->selected >> 8 & 0x000f, + screen->selected >> 4 & 0x000f, + screen->selected & 0x000f, }; - if (draw & 0x08) { - // Draw sprite with transparent background + // Draw sprite with transparent background. for (u8 y=0;y<8;y++) { for (u8 x=0;x<8;x++) { - u8 i = scr->sprite.sprite[y][x]; - if (i) draw_pixel(layer, scr->x+x, scr->y+y, colours[i]); + u8 i = screen->sprite.sprite[y][x]; + if (i) draw_pixel(layer, screen->x+x, screen->y+y, selected[i]); } } } else { - // Draw sprite with opaque background + // Draw sprite with opaque background. for (u8 y=0;y<8;y++) { for (u8 x=0;x<8;x++) { - u8 i = scr->sprite.sprite[y][x]; - draw_pixel(layer, scr->x+x, scr->y+y, colours[i]); + u8 i = screen->sprite.sprite[y][x]; + draw_pixel(layer, screen->x+x, screen->y+y, selected[i]); } } } } -void scr_draw_line(ScreenDevice *scr, u16 *layer, u8 draw) { - s16 x = (s16) scr->x; - s16 y = (s16) scr->y; - s16 x_end = (s16) scr->px; - s16 y_end = (s16) scr->py; +void op_draw_line(ScreenDevice *screen, u16 *layer, u8 draw) { + s16 x = (s16) screen->x; + s16 y = (s16) screen->y; + s16 x_end = (s16) screen->px; + s16 y_end = (s16) screen->py; s32 dx = abs(x_end - x); s32 dy = -abs(y_end - y); @@ -267,24 +288,24 @@ void scr_draw_line(ScreenDevice *scr, u16 *layer, u8 draw) { s32 e1 = dx + dy; if (draw & 0x10) { - // Draw 1-bit textured line. - prepare_1bit_sprite(&scr->sprite, draw); - u8 c1 = scr->colours >> 8 & 0xf; - u8 c0 = scr->colours >> 12 & 0xf; + // Draw 1-bit textured line. + spritebuf_prepare_1bit(&screen->sprite, draw); + u8 c1 = screen->selected >> 8 & 0xf; + u8 c0 = screen->selected >> 12 & 0xf; bool opaque = !(draw & 0x08); while (1) { - if (scr->sprite.sprite[y%8][x%8]) { draw_pixel(layer, x, y, c1); } - else if (opaque) { draw_pixel(layer, x, y, c0); } + if (screen->sprite.sprite[y%8][x%8]) { draw_pixel(layer, x, y, c1); } + else if (opaque) { draw_pixel(layer, x, y, c0); } if (x == x_end && y == y_end) return; s32 e2 = e1 << 1; if (e2 >= dy) { e1 += dy; x += sx; } if (e2 <= dx) { e1 += dx; y += sy; } } } else { - // Draw solid line. - u8 colour = draw & 0xf; + // Draw solid line. + u8 c0 = draw & 0xf; while (1) { - draw_pixel(layer, x, y, colour); + draw_pixel(layer, x, y, c0); if (x == x_end && y == y_end) return; s32 e2 = e1 << 1; if (e2 >= dy) { e1 += dy; x += sx; } @@ -293,46 +314,64 @@ void scr_draw_line(ScreenDevice *scr, u16 *layer, u8 draw) { } } -void scr_draw_rect(ScreenDevice *scr, u16 *layer, u8 draw) { +void op_draw_rect(ScreenDevice *screen, u16 *layer, u8 draw) { #define SWAP(x,y) {u8 temp=x; x=y; y=temp;} #define CLAMP(v,m) {v>0x7fff ? 0 : v>m ? m : v} // Get bounding box. - u16 l = CLAMP(scr->px, PIXELS_WIDTH -1); - u16 r = CLAMP(scr->x , PIXELS_WIDTH -1); - u16 t = CLAMP(scr->py, PIXELS_HEIGHT-1); - u16 b = CLAMP(scr->y , PIXELS_HEIGHT-1); + u16 l = CLAMP(screen->px, PIXELS_WIDTH -1); + u16 r = CLAMP(screen->x , PIXELS_WIDTH -1); + u16 t = CLAMP(screen->py, PIXELS_HEIGHT-1); + u16 b = CLAMP(screen->y , PIXELS_HEIGHT-1); if (l>r) SWAP(l,r); if (t>b) SWAP(t,b); if (draw & 0x10) { - // Draw 1-bit textured rectangle. - prepare_1bit_sprite(&scr->sprite, draw); - u8 c1 = scr->colours >> 8 & 0xf; - u8 c0 = scr->colours >> 12 & 0xf; + // Draw 1-bit textured rectangle. + spritebuf_prepare_1bit(&screen->sprite, draw); + u8 c1 = screen->selected >> 8 & 0xf; + u8 c0 = screen->selected >> 12 & 0xf; bool opaque = !(draw & 0x08); for (u16 x=l; x<r+1; x++) { for (u16 y=t; y<b+1; y++) { - if (scr->sprite.sprite[y%8][x%8]) { draw_pixel(layer, x, y, c1); } - else if (opaque) { draw_pixel(layer, x, y, c0); } + if (screen->sprite.sprite[y%8][x%8]) { draw_pixel(layer, x, y, c1); } + else if (opaque) { draw_pixel(layer, x, y, c0); } } } } else { - // Draw solid rectangle. - u8 colour = draw & 0xf; + // Draw solid rectangle. + u8 c0 = draw & 0xf; for (u16 x=l; x<r+1; x++) { for (u16 y=t; y<b+1; y++) { - draw_pixel(layer, x, y, colour); + draw_pixel(layer, x, y, c0); } } } } -void move_cursor(ScreenDevice *scr, u8 move) { - switch (move >> 6) { - case 0b00: scr->x += move & 0x3f; return; - case 0b01: scr->y += move & 0x3f; return; - case 0b10: scr->x -= move & 0x3f; return; - case 0b11: scr->y -= move & 0x3f; return; +// Dispatch to the correct drawing operation. +void screen_draw(ScreenDevice *screen, u8 draw) { + if (screen->nds) { + switch (draw >> 4) { + case 0x0: op_draw_pixel(screen, screen->nds->bg, draw); break; + case 0x1: op_draw_sprite(screen, screen->nds->bg, draw); break; + case 0x2: op_fill_layer(screen, screen->nds->bg, draw); break; + case 0x3: op_draw_sprite(screen, screen->nds->bg, draw); break; + case 0x4: op_draw_line(screen, screen->nds->bg, draw); break; + case 0x5: op_draw_line(screen, screen->nds->bg, draw); break; + case 0x6: op_draw_rect(screen, screen->nds->bg, draw); break; + case 0x7: op_draw_rect(screen, screen->nds->bg, draw); break; + case 0x8: op_draw_pixel(screen, screen->nds->fg, draw); break; + case 0x9: op_draw_sprite(screen, screen->nds->fg, draw); break; + case 0xA: op_fill_layer(screen, screen->nds->fg, draw); break; + case 0xB: op_draw_sprite(screen, screen->nds->fg, draw); break; + case 0xC: op_draw_line(screen, screen->nds->fg, draw); break; + case 0xD: op_draw_line(screen, screen->nds->fg, draw); break; + case 0xE: op_draw_rect(screen, screen->nds->fg, draw); break; + case 0xF: op_draw_rect(screen, screen->nds->fg, draw); break; + } + screen->dirty = true; } + screen->px = screen->x; + screen->py = screen->y; } diff --git a/arm9/source/devices/screen.h b/arm9/source/devices/screen.h index 1ff58e5..48411b7 100644 --- a/arm9/source/devices/screen.h +++ b/arm9/source/devices/screen.h @@ -1,70 +1,68 @@ #ifndef SCREEN_H_ #define SCREEN_H_ + #include "../bang.h" + + + // Dimension constants. #define PIXELS_WIDTH 256 #define PIXELS_HEIGHT 192 - #define PIXELS_SIZE 49152 + #define PIXELS_COUNT (PIXELS_WIDTH*PIXELS_HEIGHT) #define TILES_WIDTH 32 #define TILES_HEIGHT 24 - #define TILES_SIZE 768 - #define TILES_MEM 24576 // size of a screen layer in bytes + #define TILES_COUNT (TILES_WIDTH*TILES_HEIGHT) + #define LAYER_MEM (TILES_COUNT*32) - #define BG_SLOT_VIS 0 // tile base for visible background tiles - #define FG_SLOT_VIS 2 // tile base for visible foreground tiles - #define BG_SLOT 4 // tile base for background tiles - #define FG_SLOT 6 // tile base for foreground tiles - #define MAP_SLOT 12 // map base for tile map + // VRAM addresses. + #define BG_SLOT_FRONT 0 // Tile base for background tiles in the front buffer + #define FG_SLOT_FRONT 2 // Tile base for foreground tiles in the front buffer + #define BG_SLOT_BACK 4 // Tile base for background tiles in the back buffer + #define FG_SLOT_BACK 6 // Tile base for foreground tiles in the back buffer + #define MAP_SLOT 12 // Map base for tile map + // Sprite buffer for the screen device. typedef struct { - u8 mem[16]; // buffer memory - u8 p; // buffer pointer - u8 sprite[8][8]; // transformed sprite - bool cached; // true if sprite has been transformed - u8 draw; // the cached draw byte + u8 mem[16]; // Buffer memory + u8 p; // Buffer pointer + u8 sprite[8][8]; // Cached transformed sprite + u8 draw; // Cached draw byte + bool cached; // A transformed sprite has been cached } SpriteBuffer; + // Hardware screen on the NDS. typedef struct { - u16 *bgv, *fgv; // visible tile memory - u16 *bg, *fg; // buffered tile memory - u16 *map; // tile map (never changes) - u16 *palv; // visible colour palette - u16 pal[16]; // buffered colour palette - } Screen; + u16 *bgv, *fgv; // Visible tile memory (front buffer) + u16 *bg, *fg; // Buffered tile memory (back buffer) + u16 *map; // Tile map (never changes) + u16 *palv; // Visible colour palette (front buffer) + u16 pal[16]; // Buffered colour palette (back buffer) + } NDSScreen; + // Bedrock screen device. typedef struct { - u16 x,y; // cursor position - u16 px,py; // previous cursor position - u16 colours; // sprite colours - SpriteBuffer sprite; // sprite buffer - u16 palette_write; // palette write cache - u16 palette[16]; // palette as RGB15 colours - Screen *nds; // NDS VRAM pointers - bool wake; // wake flag - bool dirty; // dirty flag + u16 x,y; // Current cursor position + u16 px,py; // Previous cursor position + u16 selected; // Selected sprite colours + SpriteBuffer sprite; // Sprite buffer + u16 colour; // Palette colour write cache + u16 pal[16]; // Palette as RGB15 colours + NDSScreen *nds; // Pointer to hardware screen to draw to. + bool wake; // Wake flag + bool dirty; // Dirty flag } ScreenDevice; - void init_screens(void); - void scr_make_main(ScreenDevice *scr); - void scr_make_sub(ScreenDevice *scr); - void scr_unmake(ScreenDevice *scr); - - void set_palette_high(ScreenDevice *scr, u8 high); - void set_palette_low(ScreenDevice *scr, u8 low); - void draw_pixel(u16 *layer, u16 x, u16 y, u8 colour); - void fill_layer(u16 *layer, u8 colour); - void erase_screen(Screen *nds); - void flip_buffer(Screen *nds); - void black_screen(Screen *nds); - - void scr_draw_pixel( ScreenDevice *scr, u16 *layer, u8 draw); - void scr_draw_sprite(ScreenDevice *scr, u16 *layer, u8 draw); - void scr_fill_layer( ScreenDevice *scr, u16 *layer, u8 draw); - void scr_draw_line( ScreenDevice *scr, u16 *layer, u8 draw); - void scr_draw_rect( ScreenDevice *scr, u16 *layer, u8 draw); - - void draw_dispatch(ScreenDevice *scr, u8 draw); - void move_cursor(ScreenDevice *scr, u8 move); - - void push_sprite(SpriteBuffer *b, u8 row); + // Functions. + void init_nds_screens(void); + // Methods. + void screen_reset(ScreenDevice *screen); + void ndsscreen_flip_buffers(NDSScreen *nds); + void ndsscreen_clear(NDSScreen *nds); + void spritebuf_push(SpriteBuffer *b, u8 row); + void screen_map_to_none(ScreenDevice *screen); + void screen_map_to_main(ScreenDevice *screen); + void screen_map_to_sub(ScreenDevice *screen); + void screen_commit_colour(ScreenDevice *screen); + void screen_move_cursor(ScreenDevice *screen, u8 move); + void screen_draw(ScreenDevice *screen, u8 draw); #endif diff --git a/arm9/source/devices/stream.c b/arm9/source/devices/stream.c new file mode 100644 index 0000000..7329380 --- /dev/null +++ b/arm9/source/devices/stream.c @@ -0,0 +1,40 @@ +#include "stream.h" + +/* +This is a skeleton of a device at the moment. The local.output.connected flag +// of the keyboard program is manually set in main.c so that only the keyboard +can send bytes to the main instance, otherwise user programs that write to the +stream device will send keyboard bytes to themselves. +*/ + + +void channel_reset(Channel *channel) { + channel->connected = false; + channel->transmitting = false; +} + +void bytestream_reset(Bytestream *bstream) { + channel_reset(&bstream->input); + channel_reset(&bstream->output); +} + +// Reset a stream device. +void stream_reset(StreamDevice *stream) { + bytestream_reset(&stream->local); + bytestream_reset(&stream->remote); +} + +// Write a byte to the stream of the main instance. This byte is expected to +// be consumed by a keyboard program running on the sub instance. +void stream_write(StreamDevice *stream, u8 byte) { + if (stream->local.output.connected) { + receive_keyboard_byte(byte); + } +} + +// End the current transmission. Currently this will only hide the keyboard. +void stream_end(StreamDevice *stream) { + if (stream->local.output.connected) { + close_keyboard(); + } +} diff --git a/arm9/source/devices/stream.h b/arm9/source/devices/stream.h new file mode 100644 index 0000000..2ee463c --- /dev/null +++ b/arm9/source/devices/stream.h @@ -0,0 +1,33 @@ +// TODO: Implement this properly. + + +#ifndef STREAM_H_ + #define STREAM_H_ + + #include "../bang.h" + + typedef struct { + bool connected; + bool transmitting; + } Channel; + + typedef struct { + Channel input; + Channel output; + } Bytestream; + + // Bedrock stream device. + typedef struct { + Bytestream local; + Bytestream remote; + } StreamDevice; + + // Methods. + void stream_reset(StreamDevice *stream); + void stream_write(StreamDevice *stream, u8 byte); + void stream_end(StreamDevice *stream); + + // Duplicate declarations from main. + void receive_keyboard_byte(u8 byte); + void close_keyboard(void); +#endif diff --git a/arm9/source/devices/system.c b/arm9/source/devices/system.c index 3406eed..d2aeb7f 100644 --- a/arm9/source/devices/system.c +++ b/arm9/source/devices/system.c @@ -1,16 +1,35 @@ -#include "nds.h" #include "file.h" #include "system.h" -u8 devices_high() { - return 0b11111100; +// Reset a system device. +void system_reset(SystemDevice *system) { + readbuf_set_pointer(&system->name); + readbuf_set_pointer(&system->authors); } -u8 devices_low() { - u8 devices = 0; - if (filesystem_enabled()) { - devices |= 0b00100000; - } - return devices; +// Return a bitmask representing the connected state of each device. +u16 connected_devices(void) { + bool devices[16] = { + /* SYSTEM */ true, + /* MEMORY */ true, + /* MATH */ true, + /* CLOCK */ true, + /* INPUT */ true, + /* SCREEN */ true, + /* TONE */ false, + /* SAMPLER */ false, + /* STREAM */ true, + /* FILE */ nds_filesystem_enabled(), + /* CLIPBOARD */ false, + /* REGISTRY */ false, + /* CUSTOM 1 */ false, + /* CUSTOM 2 */ false, + /* CUSTOM 3 */ false, + /* CUSTOM 4 */ false, + }; + u16 mask = 0; + for (int i=0; i<16; i++) mask |= devices[i] << (15-i); + return mask; } + diff --git a/arm9/source/devices/system.h b/arm9/source/devices/system.h index 7549f0f..029d2e6 100644 --- a/arm9/source/devices/system.h +++ b/arm9/source/devices/system.h @@ -1,16 +1,25 @@ -#include "../types/readbuf.h" +// TODO: Wrap more system device functionality in functions here. #ifndef SYSTEM_H_ #define SYSTEM_H_ + #include "../bang.h" + #include "../types/readbuf.h" + #include "../types/wakequeue.h" + + + // Bedrock system device. typedef struct { ReadBuf name; ReadBuf authors; u16 sleep; // device mask for waking u8 wake; // ID of wake device + WakeQueue queue; } SystemDevice; - u8 devices_high(); - u8 devices_low(); + // Functions. + u16 connected_devices(void); + // Methods. + void system_reset(SystemDevice *system); #endif |