From bb1aa5958d1b67707dcf0f6b08bfaf0b408bd46e Mon Sep 17 00:00:00 2001 From: Ben Bridle Date: Fri, 19 Sep 2025 13:17:14 +1200 Subject: Massive rewrite This commit rewrites the emulator halfway from scratch to make it easier to change and maintain in the future. The emulator core was rewritten to adhere to the released Bedrock specification (earlier versions implemented an older prototype specification, which is no longer relevant). This commit also adds proper support for running multiple concurrent Bedrock instances. This was previously supported in a limited manner for the on-screen keyboard, but now works for any regular program as well, with switching being performed by pressing the L or R bumper buttons. This is disabled by default, as programs will still need to be baked into the emulator and hand-loaded. --- arm9/source/devices/clock.c | 101 ++++++---- arm9/source/devices/clock.h | 36 ++-- arm9/source/devices/file.c | 422 +++++++++++++++++++++++------------------- arm9/source/devices/file.h | 79 ++++---- arm9/source/devices/input.c | 93 ++++++---- arm9/source/devices/input.h | 33 ++-- arm9/source/devices/local.c | 11 -- arm9/source/devices/local.h | 8 - arm9/source/devices/math.c | 143 ++++++++++----- arm9/source/devices/math.h | 40 ++-- arm9/source/devices/memory.c | 231 ++++++++++++----------- arm9/source/devices/memory.h | 64 +++---- arm9/source/devices/screen.c | 425 +++++++++++++++++++++++-------------------- arm9/source/devices/screen.h | 100 +++++----- arm9/source/devices/stream.c | 40 ++++ arm9/source/devices/stream.h | 33 ++++ arm9/source/devices/system.c | 37 +++- arm9/source/devices/system.h | 15 +- 18 files changed, 1101 insertions(+), 810 deletions(-) delete mode 100644 arm9/source/devices/local.c delete mode 100644 arm9/source/devices/local.h create mode 100644 arm9/source/devices/stream.c create mode 100644 arm9/source/devices/stream.h (limited to 'arm9/source/devices') 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 -#include #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 - #ifndef CLOCK_H_ #define CLOCK_H_ + #include + #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 + +/* +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_idir_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; ichild->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; ichild->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 -#include "../types/pathbuf.h" - -// #include -// #include -// #include -// #include - #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 + #include + #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 + // 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' + + 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 init_filesystem(); - bool filesystem_enabled(); - - void create_parents(u8 *path); - - void fs_push_entry(FileDevice *fs, u8 byte); + // Functions. + void init_nds_filesystem(); + bool nds_filesystem_enabled(); + + // 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 -#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 -#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 - -#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 -#include #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 + #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; iid) { - 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; iid) { - 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_iid) { + 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; icount 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; iid; - count++; - if (count == to_allocate) { - break; - } + if (++n == to_allocate) break; } } - // DEALLOCATING - } else if (mem->count_write < mem->count) { - for (int i=0; icount < mem->allocated) { + for (int i=0; iid) { - 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; iid) mem->count++; + // Re-count the number of pages allocated to this instance. + mem->allocated = 0; + for (int i=0; iid) 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; icopy_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; ncopy; 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) - - 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); + #include "../bang.h" + // 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. - -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 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). +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. + +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 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 -#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; ipalette_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; xsprite.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> 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 -- cgit v1.2.3-70-g09d2