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/types/circbuf.c | 10 ++-- arm9/source/types/circbuf.h | 18 ++++--- arm9/source/types/pathbuf.c | 111 ++++++++++++++++++++++++++++++------------ arm9/source/types/pathbuf.h | 24 +++++---- arm9/source/types/readbuf.c | 14 ++++-- arm9/source/types/readbuf.h | 13 +++-- arm9/source/types/wakequeue.c | 30 ++++++++++++ arm9/source/types/wakequeue.h | 13 +++++ 8 files changed, 172 insertions(+), 61 deletions(-) create mode 100644 arm9/source/types/wakequeue.c create mode 100644 arm9/source/types/wakequeue.h (limited to 'arm9/source/types') diff --git a/arm9/source/types/circbuf.c b/arm9/source/types/circbuf.c index 532a5a0..0398254 100644 --- a/arm9/source/types/circbuf.c +++ b/arm9/source/types/circbuf.c @@ -1,6 +1,8 @@ #include "circbuf.h" -u8 cb_read_byte(CircBuf *buf) { + +// Read a byte from a circular buffer, or zero if the buffer is empty. +u8 circbuf_read(CircBuf *buf) { if (buf->front != buf->back) { return buf->mem[buf->front++]; } else { @@ -8,13 +10,15 @@ u8 cb_read_byte(CircBuf *buf) { } } -void cb_write_byte(CircBuf *buf, u8 byte) { +// Write a byte to a circular buffer if the buffer is not full. +void circbuf_write(CircBuf *buf, u8 byte) { if (((buf->back+1)&0xff) != buf->front) { buf->mem[buf->back++] = byte; } } -void cb_clear(CircBuf *buf) { +// Clear a circular buffer. +void circbuf_clear(CircBuf *buf) { buf->front = 0; buf->back = 0; } diff --git a/arm9/source/types/circbuf.h b/arm9/source/types/circbuf.h index 305f8bf..15b3b63 100644 --- a/arm9/source/types/circbuf.h +++ b/arm9/source/types/circbuf.h @@ -1,16 +1,18 @@ -#include - #ifndef CIRCBUF_H_ #define CIRCBUF_H_ + #include "../bang.h" + + + // A 256-byte circular buffer. typedef struct { u8 mem[256]; - u8 front; // start of buffer, read from here until back - u8 back; // end of buffer, write past here until front + u8 front; // start of buffer, read from here up to back + u8 back; // end of buffer, write past here up to front } CircBuf; - u8 cb_read_byte(CircBuf *buf); - void cb_write_byte(CircBuf *buf, u8 byte); - void cb_clear(CircBuf *buf); - + // Methods. + u8 circbuf_read(CircBuf *buf); + void circbuf_write(CircBuf *buf, u8 byte); + void circbuf_clear(CircBuf *buf); #endif diff --git a/arm9/source/types/pathbuf.c b/arm9/source/types/pathbuf.c index 1a610c8..e94b955 100644 --- a/arm9/source/types/pathbuf.c +++ b/arm9/source/types/pathbuf.c @@ -1,52 +1,99 @@ -#include #include "pathbuf.h" -u8 pb_read(PathBuf *buf) { + +// Read the next byte of the path buffer, or zero if all bytes have been read. +u8 pathbuf_read(PathBuf *buf) { u8 output = buf->mem[buf->p]; if (output) buf->p++; return output; } -void pb_clear(PathBuf *buf) { - memset(&buf->mem, 0, 256); +// Clear a path buffer. +void pathbuf_clear(PathBuf *buf) { + for (int i=0; i<256; i++) buf->mem[i] = 0; + buf->p = 0; } -void pb_reset(PathBuf *buf, bool to_name) { +// Move the pointer to either the start of the path buffer or to the start of +// the final path component. +void pathbuf_set_pointer(PathBuf *buf, bool to_final_component) { buf->p = 0; - if (to_name) for (int i=0; i<255; i++) { - if (buf->mem[i] == '/') { - buf->p = i+1; - } else if (buf->mem[i] == 0) { - break; + if (to_final_component) { + for (int i=0; i<255; i++) { + if (buf->mem[i] == '/') { + buf->p = i+1; + } else if (buf->mem[i] == 0) { + break; + } } } } -// Push a byte to a PathBuf, return true and reset the pointer if byte is null. -bool pb_push_byte(PathBuf *buf, u8 byte) { - // Stop before overwriting the pointer. - if (buf->p != 0xff) { - buf->mem[buf->p++] = byte; - } - if (byte == 0) { - buf->p = 0; - return true; - } else { - return false; - } +// Populate a path buffer with a bytestring. +void pathbuf_populate(PathBuf *buf, u8 *path) { + pathbuf_clear(buf); + u8 c; + for (int i=0; i<256; i++) { + c = path[i]; + buf->mem[i] = c; + if (c == 0) return; + }; } -bool pb_is_terminated(PathBuf *buf) { - for (int i=0; i<255; i++) { - if (buf->mem[i] == 0) { - return true; - } - } - return false; +// Push a byte to a path buffer, return true and reset pointer if byte is null. +bool pathbuf_push(PathBuf *buf, u8 byte) { + buf->mem[buf->p++] = byte; + return byte == 0; } -void pb_populate(PathBuf *buf, u8 *path) { - strncpy((char*) &buf->mem[0], (char*) path, 254); - buf->p = 0; +// Check if a path buffer is empty. +bool pathbuf_is_empty(PathBuf *buf) { + return buf->mem[0] == 0; +} + +// Check if a path buffer contains the path of the root directory. +bool pathbuf_is_root(PathBuf *buf) { + return buf->mem[0] == '/' && buf->mem[1] == 0; +} + +// Check if a path buffer contains a valid path. +bool pathbuf_is_valid(PathBuf *buf) { + // Test that path begins with a forward slash and ends with a null byte. + if (buf->mem[0x00] != '/') return false; + if (buf->mem[0xFF] != 0 ) return false; + // Find the length of the path. + int len = 0; + for (; len<256; len++) if (buf->mem[len] == 0) break; + // End early if path is the root directory. + if (len == 1) return true; + // Test that path contains no control characters. + for (int i=0; imem[i] < 0x20) return false; + // Test that path contains no empty or relative components. This also + // happens to test that it doesn't end with a slash. + bool is_empty = false; + bool all_dots = false; + int dot_count = 0; + u8 c; + for (int i=0; i<=len; i++) { + c = buf->mem[i]; + if (c == '/' || c == 0) { + // Test if component is empty. + if (is_empty) return false; + // Test if component is one or two periods. + if (all_dots && dot_count < 3) return false; + // Reset flags. + is_empty = true; + all_dots = true; + dot_count = 0; + } else { + is_empty = false; + if (c == '.') { + dot_count++; + } else { + all_dots = false; + } + } + } + return true; } diff --git a/arm9/source/types/pathbuf.h b/arm9/source/types/pathbuf.h index 7fd7251..a926262 100644 --- a/arm9/source/types/pathbuf.h +++ b/arm9/source/types/pathbuf.h @@ -1,18 +1,22 @@ -#include - #ifndef PATHBUF_H_ #define PATHBUF_H_ + #include "../bang.h" + + + // A 255-byte path buffer. typedef struct { - u8 mem[255]; + u8 mem[256]; u8 p; } PathBuf; - u8 pb_read(PathBuf *buf); - void pb_clear(PathBuf *buf); - void pb_reset(PathBuf *buf, bool to_name); - bool pb_push_byte(PathBuf *buf, u8 byte); - bool pb_is_terminated(PathBuf *buf); - void pb_populate(PathBuf *buf, u8 *path); - + // Methods. + u8 pathbuf_read(PathBuf *buf); + void pathbuf_clear(PathBuf *buf); + void pathbuf_set_pointer(PathBuf *buf, bool to_final_component); + void pathbuf_populate(PathBuf *buf, u8 *path); + bool pathbuf_push(PathBuf *buf, u8 byte); + bool pathbuf_is_empty(PathBuf *buf); + bool pathbuf_is_root(PathBuf *buf); + bool pathbuf_is_valid(PathBuf *buf); #endif diff --git a/arm9/source/types/readbuf.c b/arm9/source/types/readbuf.c index eaee27a..750ac4d 100644 --- a/arm9/source/types/readbuf.c +++ b/arm9/source/types/readbuf.c @@ -1,12 +1,20 @@ -#include #include "readbuf.h" -u8 rb_read(ReadBuf *buf) { + +// Populate a read buffer with a pointer to a string. +void readbuf_populate(ReadBuf *buf, u8 *string) { + buf->mem = string; + buf->p = 0; +} + +// Read the next byte of a read buffer, or zero if all bytes have been read. +u8 readbuf_read(ReadBuf *buf) { u8 output = buf->mem[buf->p]; if (output) buf->p++; return output; } -void rb_reset(ReadBuf *buf) { +// Move the pointer to the start of the buffer. +void readbuf_set_pointer(ReadBuf *buf) { buf->p = 0; } diff --git a/arm9/source/types/readbuf.h b/arm9/source/types/readbuf.h index 9077a69..a88934b 100644 --- a/arm9/source/types/readbuf.h +++ b/arm9/source/types/readbuf.h @@ -1,14 +1,17 @@ -#include - #ifndef READBUF_H_ #define READBUF_H_ + #include "../bang.h" + + + // A variable-length read buffer. typedef struct { u8 *mem; u8 p; } ReadBuf; - u8 rb_read(ReadBuf *buf); - void rb_reset(ReadBuf *buf); - + // Methods. + void readbuf_populate(ReadBuf *buf, u8 *string); + u8 readbuf_read(ReadBuf *buf); + void readbuf_set_pointer(ReadBuf *buf); #endif diff --git a/arm9/source/types/wakequeue.c b/arm9/source/types/wakequeue.c new file mode 100644 index 0000000..8876123 --- /dev/null +++ b/arm9/source/types/wakequeue.c @@ -0,0 +1,30 @@ +#include "wakequeue.h" + +/* +TODO: This is unused at the moment. It will eventually be used for keeping +track of which device last woke a Bedrock instance, to optimally choose +which device to use when waking from sleep. +*/ + +// Reset a wake queue. +void wakequeue_reset(WakeQueue *queue) { + // Populate the queue with fifteen slot numbers. Slot zero is excluded, + // as it always comes last in the wake order. + for (int i=0; i<15; i++) queue->mem[i] = i+1; +} + +// TODO: Not quite done yet. +bool wakequeue_wake(WakeQueue *queue, u16 device_list) { + // Test each entry in the queue against the device list. + for (int i=0; i<15; i++) { + u8 slot_number = queue->mem[i]; + if (TEST(device_list, 0x8000 >> slot_number)) { + // Move the matched entry to the back of the queue. + for (; i<14; i++) queue->mem[i] = queue->mem[i+1]; + queue->mem[14] = slot_number; + return true; + } + } + // Test system device last. + return TEST(device_list, 0x8000); +} diff --git a/arm9/source/types/wakequeue.h b/arm9/source/types/wakequeue.h new file mode 100644 index 0000000..ed07d6d --- /dev/null +++ b/arm9/source/types/wakequeue.h @@ -0,0 +1,13 @@ +#ifndef WAKEQUEUE_H_ + #define WAKEQUEUE_H_ + + #include "../bang.h" + + + // A variable-length read buffer. + typedef struct { + u8 mem[15]; // List of slot numbers + } WakeQueue; + + // Methods. +#endif -- cgit v1.2.3-70-g09d2