summaryrefslogtreecommitdiff
path: root/src/boot/O2
diff options
context:
space:
mode:
Diffstat (limited to 'src/boot/O2')
-rw-r--r--src/boot/O2/__osMalloc.c458
-rw-r--r--src/boot/O2/__osMemcpy.c23
-rw-r--r--src/boot/O2/__osMemset.c11
-rw-r--r--src/boot/O2/__osStrcmp.c16
-rw-r--r--src/boot/O2/__osStrcpy.c12
-rw-r--r--src/boot/O2/debug.c11
-rw-r--r--src/boot/O2/fmodf.c12
-rw-r--r--src/boot/O2/gfxprint.c236
-rw-r--r--src/boot/O2/loadfragment.c231
-rw-r--r--src/boot/O2/loadfragment2.c177
-rw-r--r--src/boot/O2/math64.c191
-rw-r--r--src/boot/O2/mtxuty-cvt.c19
-rw-r--r--src/boot/O2/padsetup.c34
-rw-r--r--src/boot/O2/padutils.c92
-rw-r--r--src/boot/O2/printutils.c17
-rw-r--r--src/boot/O2/rand.c96
-rw-r--r--src/boot/O2/rcp_utils.c23
-rw-r--r--src/boot/O2/sleep.c27
-rw-r--r--src/boot/O2/stackcheck.c122
-rw-r--r--src/boot/O2/system_heap.c123
-rw-r--r--src/boot/O2/system_malloc.c51
21 files changed, 1982 insertions, 0 deletions
diff --git a/src/boot/O2/__osMalloc.c b/src/boot/O2/__osMalloc.c
new file mode 100644
index 000000000..71a5a5ba3
--- /dev/null
+++ b/src/boot/O2/__osMalloc.c
@@ -0,0 +1,458 @@
+#include "os_malloc.h"
+#include "libc/stdbool.h"
+#include "libc/stdint.h"
+#include "macros.h"
+#include "functions.h"
+
+#define FILL_ALLOCBLOCK (1 << 0)
+#define FILL_FREEBLOCK (1 << 1)
+#define CHECK_FREE_BLOCK (1 << 2)
+
+#define NODE_MAGIC (0x7373)
+
+#define BLOCK_UNINIT_MAGIC (0xAB)
+#define BLOCK_UNINIT_MAGIC_32 (0xABABABAB)
+#define BLOCK_ALLOC_MAGIC (0xCD)
+#define BLOCK_ALLOC_MAGIC_32 (0xCDCDCDCD)
+#define BLOCK_FREE_MAGIC (0xEF)
+#define BLOCK_FREE_MAGIC_32 (0xEFEFEFEF)
+
+OSMesg sArenaLockMsg[1];
+
+void __osMallocAddHeap(Arena* arena, void* heap, size_t size);
+
+void ArenaImpl_LockInit(Arena* arena) {
+ osCreateMesgQueue(&arena->lock, sArenaLockMsg, ARRAY_COUNT(sArenaLockMsg));
+}
+
+void ArenaImpl_Lock(Arena* arena) {
+ osSendMesg(&arena->lock, NULL, OS_MESG_BLOCK);
+}
+
+void ArenaImpl_Unlock(Arena* arena) {
+ osRecvMesg(&arena->lock, NULL, OS_MESG_BLOCK);
+}
+
+ArenaNode* ArenaImpl_GetLastBlock(Arena* arena) {
+ ArenaNode* last;
+ ArenaNode* iter;
+
+ last = arena->head;
+
+ if (last != NULL) {
+ iter = last->next;
+ while (iter != NULL) {
+ last = iter;
+ iter = iter->next;
+ }
+ }
+ return last;
+}
+
+/**
+ * Initializes \p arena to manage the memory region \p heap.
+ *
+ * @param arena The Arena to initialize.
+ * @param heap The memory region to use as heap space.
+ * @param size The size of the heap.
+ */
+void __osMallocInit(Arena* arena, void* heap, size_t size) {
+ bzero(arena, sizeof(Arena));
+
+ ArenaImpl_LockInit(arena);
+
+ __osMallocAddHeap(arena, heap, size);
+ arena->isInit = true;
+}
+
+// Original name: __osMallocAddBlock
+void __osMallocAddHeap(Arena* arena, void* heap, size_t size) {
+ ptrdiff_t diff;
+ s32 alignedSize;
+ ArenaNode* firstNode;
+ ArenaNode* lastNode;
+
+ if (heap == NULL) {
+ return;
+ }
+
+ firstNode = (ArenaNode*)ALIGN16((uintptr_t)heap);
+ diff = (uintptr_t)firstNode - (uintptr_t)heap;
+ alignedSize = ((s32)size - diff) & ~0xF;
+
+ // If the size of the heap is smaller than sizeof(ArenaNode), then the initialization will silently fail
+ if (alignedSize > (s32)sizeof(ArenaNode)) {
+ firstNode->next = NULL;
+ firstNode->prev = NULL;
+ firstNode->size = alignedSize - sizeof(ArenaNode);
+ firstNode->isFree = true;
+ firstNode->magic = NODE_MAGIC;
+
+ ArenaImpl_Lock(arena);
+
+ lastNode = ArenaImpl_GetLastBlock(arena);
+
+ // Checks if there's already a block
+ if (lastNode == NULL) {
+ arena->head = firstNode;
+ arena->start = heap;
+ } else {
+ // Chain the existing block with the new one
+ firstNode->prev = lastNode;
+ lastNode->next = firstNode;
+ }
+
+ ArenaImpl_Unlock(arena);
+ }
+}
+
+/**
+ * Clears the whole \p arena, invalidating every allocated pointer to it.
+ *
+ * @param arena The Arena to clear.
+ */
+void __osMallocCleanup(Arena* arena) {
+ bzero(arena, sizeof(Arena));
+}
+
+/**
+ * Returns whether or not the \p arena has been initialized.
+ *
+ * @param arena The Arena to check.
+ * @return u8 `true` if the \p arena has been initialized. `false` otherwise.
+ */
+u8 __osMallocIsInitalized(Arena* arena) {
+ return arena->isInit;
+}
+
+/**
+ * Allocates at least \p size bytes of memory using the given \p arena.
+ * The block of memory will be allocated at the start of the first sufficiently large free block.
+ *
+ * - If there's not enough space in the given \p arena, this function will fail, returning `NULL`.
+ * - If \p size is zero, then an empty region of memory is returned.
+ *
+ * To avoid memory leaks, the returned pointer should be eventually deallocated using either `__osFree` or
+ * `__osRealloc`.
+ *
+ * @param[in, out] arena The specific Arena to be used for the allocation.
+ * @param[in] size The size in bytes that will be allocated.
+ * @return void* On success, the allocated area of the \p arena memory. Otherwise, `NULL`.
+ */
+void* __osMalloc(Arena* arena, size_t size) {
+ ArenaNode* iter;
+ ArenaNode* newNode;
+ void* alloc = NULL;
+
+ size = ALIGN16(size);
+
+ ArenaImpl_Lock(arena);
+
+ // Start iterating from the head of the arena.
+ iter = arena->head;
+
+ // Iterate over the arena looking for a big enough space of memory.
+ while (iter != NULL) {
+ if (iter->isFree && iter->size >= size) {
+ size_t blockSize = ALIGN16(size) + sizeof(ArenaNode);
+
+ // If the block is larger than the requested size, then split it and just use the required size of the
+ // current block.
+ if (blockSize < iter->size) {
+ ArenaNode* next;
+
+ newNode = (ArenaNode*)((uintptr_t)iter + blockSize);
+ newNode->next = iter->next;
+ newNode->prev = iter;
+ newNode->size = iter->size - blockSize;
+ newNode->isFree = true;
+ newNode->magic = NODE_MAGIC;
+
+ iter->next = newNode;
+ iter->size = size;
+
+ next = newNode->next;
+ if (next != NULL) {
+ next->prev = newNode;
+ }
+ }
+
+ iter->isFree = false;
+ alloc = (void*)((uintptr_t)iter + sizeof(ArenaNode));
+ break;
+ }
+
+ iter = iter->next;
+ }
+
+ ArenaImpl_Unlock(arena);
+
+ return alloc;
+}
+
+/**
+ * Allocates at least \p size bytes of memory using the given \p arena.
+ * Unlike __osMalloc, the block of memory will be allocated from the end of the \p arena.
+ *
+ * - If there's not enough space in the given \p arena, this function will fail, returning `NULL`.
+ * - If \p size is zero, then an empty region of memory is returned.
+ *
+ * To avoid memory leaks, the returned pointer should be eventually deallocated using `__osFree` or `__osRealloc`.
+ *
+ * @param[in, out] arena The specific Arena to be used for the allocation.
+ * @param[in] size The size in bytes that will be allocated.
+ * @return void* On success, the allocated area of the \p arena memory. Otherwise, `NULL`.
+ */
+void* __osMallocR(Arena* arena, size_t size) {
+ ArenaNode* iter;
+ ArenaNode* newNode;
+ size_t blockSize;
+ void* alloc = NULL;
+
+ size = ALIGN16(size);
+
+ ArenaImpl_Lock(arena);
+
+ // Start iterating from the last block of the arena.
+ iter = ArenaImpl_GetLastBlock(arena);
+
+ // Iterate in reverse the arena looking for a big enough space of memory.
+ while (iter != NULL) {
+ if (iter->isFree && iter->size >= size) {
+ blockSize = ALIGN16(size) + sizeof(ArenaNode);
+
+ // If the block is larger than the requested size, then split it and just use the required size of the
+ // current block.
+ if (blockSize < iter->size) {
+ ArenaNode* next;
+
+ newNode = (ArenaNode*)((uintptr_t)iter + (iter->size - size));
+ newNode->next = iter->next;
+ newNode->prev = iter;
+ newNode->size = size;
+ newNode->magic = NODE_MAGIC;
+
+ iter->next = newNode;
+ iter->size -= blockSize;
+
+ next = newNode->next;
+ if (next != NULL) {
+ next->prev = newNode;
+ }
+ iter = newNode;
+ }
+
+ iter->isFree = false;
+ alloc = (void*)((uintptr_t)iter + sizeof(ArenaNode));
+ break;
+ }
+ iter = iter->prev;
+ }
+
+ ArenaImpl_Unlock(arena);
+
+ return alloc;
+}
+
+/**
+ * Deallocates the pointer \p ptr previously allocated by `__osMalloc`, `__osMallocR` or `__osRealloc`.
+ * If \p ptr is `NULL` or it has been already been freed, then this function does nothing.
+ *
+ * - The behaviour is undefined if \p ptr is not a memory region returned by one of the cited allocating
+ * functions.
+ * - The behaviour is undefined if \p ptr doesn't correspond to the given \p arena.
+ * - Any access to the freed pointer is undefined behaviour.
+ *
+ * @param[in, out] arena The specific Arena to be used for the allocation.
+ * @param[in, out] ptr The allocated memory block to deallocate.
+ */
+void __osFree(Arena* arena, void* ptr) {
+ ArenaNode* node;
+ ArenaNode* next;
+ ArenaNode* prev;
+
+ ArenaImpl_Lock(arena);
+
+ node = (ArenaNode*)((uintptr_t)ptr - sizeof(ArenaNode));
+
+ if ((ptr != NULL) && (node->magic == NODE_MAGIC) && !node->isFree) {
+ next = node->next;
+ prev = node->prev;
+ node->isFree = true;
+
+ // Checks if the next node is contiguous to the current node and if it isn't currently allocated. Then merge the
+ // two nodes into one.
+ if ((uintptr_t)next == (uintptr_t)node + sizeof(ArenaNode) + node->size && next->isFree) {
+ ArenaNode* newNext = next->next;
+
+ if (newNext != NULL) {
+ newNext->prev = node;
+ }
+
+ node->size += next->size + sizeof(ArenaNode);
+
+ node->next = newNext;
+ next = newNext;
+ }
+
+ // Checks if the previous node is contiguous to the current node and if it isn't currently allocated. Then merge
+ // the two nodes into one.
+ if ((prev != NULL) && prev->isFree && ((uintptr_t)node == (uintptr_t)prev + sizeof(ArenaNode) + prev->size)) {
+ if (next != NULL) {
+ next->prev = prev;
+ }
+
+ prev->next = next;
+ prev->size += node->size + sizeof(ArenaNode);
+ }
+ }
+
+ ArenaImpl_Unlock(arena);
+}
+
+/**
+ * Reallocates the pointer \p ptr.
+ * \p ptr must be either a pointer previously allocated by `__osMalloc`, `__osMallocR` or `__osRealloc` and
+ * not freed yet, or a `NULL` pointer.
+ *
+ * - If \p ptr is `NULL` a new pointer is allocated. See `__osMalloc` for more details.
+ * - If \p newSize is 0, then the given pointer is freed and `NULL` is returned. See `__osFree` for more details.
+ * - If \p newSize is bigger than the currently allocated allocated pointer, then the area of memory is expanded to a
+ * size big enough to fit the requested size.
+ *
+ * - The behaviour is undefined if \p ptr is not a memory region returned by one of the cited allocating
+ * functions.
+ * - The behaviour is undefined if \p ptr doesn't correspond to the given \p arena.
+ * - If the pointer is freed, then any access to the original freed pointer is undefined behaviour.
+ *
+ * @param[in, out] arena The specific Arena to be used for the allocation.
+ * @param[in, out] ptr The allocated memory block to deallocate.
+ * @param[in] newSize The new requested size.
+ * @return void* On success, the pointer to the reallocated area of memory. On failure, `NULL` is returned,
+ * and the original parameter \p ptr remains valid.
+ */
+void* __osRealloc(Arena* arena, void* ptr, size_t newSize) {
+ ArenaImpl_Lock(arena);
+
+ (void)"__osRealloc(%08x, %d)\n";
+
+ if (ptr == NULL) {
+ // if the `ptr` is NULL, then allocate a new pointer with the specified size
+ // if newSize is 0, then __osMalloc would return a NULL pointer
+ ptr = __osMalloc(arena, newSize);
+ } else if (newSize == 0) {
+ // if the requested size is zero, then free the pointer
+ __osFree(arena, ptr);
+ ptr = NULL;
+ } else {
+ size_t diff;
+ void* newPtr;
+ // Gets the start of the ArenaNode pointer embedded
+ ArenaNode* node = (void*)((uintptr_t)ptr - sizeof(ArenaNode));
+
+ newSize = ALIGN16(newSize);
+
+ // Only reallocate the memory if the new size isn't smaller than the actual node size
+ if ((newSize != node->size) && (node->size < newSize)) {
+ ArenaNode* next = node->next;
+
+ diff = newSize - node->size;
+ // Checks if the next node is contiguous to the current allocated node and it has enough space to fit the
+ // new requested size
+ if (((uintptr_t)next == (uintptr_t)node + node->size + sizeof(ArenaNode)) && (next->isFree) &&
+ (next->size >= diff)) {
+ ArenaNode* next2 = next->next;
+
+ next->size = (next->size - diff);
+ if (next2 != NULL) {
+ // Update the previous element of the linked list
+ next2->prev = (void*)((uintptr_t)next + diff);
+ }
+
+ next2 = (void*)((uintptr_t)next + diff);
+ node->next = next2;
+ node->size = newSize;
+ __osMemcpy(next2, next, sizeof(ArenaNode));
+ } else {
+ // Create a new pointer and manually copy the data from the old pointer to the new one
+ newPtr = __osMalloc(arena, newSize);
+ if (newPtr != NULL) {
+ bcopy(newPtr, ptr, node->size);
+ __osFree(arena, ptr);
+ }
+ ptr = newPtr;
+ }
+ }
+ }
+
+ ArenaImpl_Unlock(arena);
+
+ return ptr;
+}
+
+/**
+ * Gets the size of the largest free block, the total free space and the total allocated space.
+ *
+ * @param[in, out] arena The Arena which will be used to get the values from.
+ * @param[out] outMaxFree The size of the largest free block.
+ * @param[out] outFree The total free space.
+ * @param[out] outAlloc The total allocated space.
+ */
+void __osGetSizes(Arena* arena, size_t* outMaxFree, size_t* outFree, size_t* outAlloc) {
+ ArenaNode* iter;
+
+ ArenaImpl_Lock(arena);
+
+ *outMaxFree = 0;
+ *outFree = 0;
+ *outAlloc = 0;
+
+ iter = arena->head;
+ while (iter != NULL) {
+ if (iter->isFree) {
+ *outFree += iter->size;
+ if (*outMaxFree < iter->size) {
+ *outMaxFree = iter->size;
+ }
+ } else {
+ *outAlloc += iter->size;
+ }
+
+ iter = iter->next;
+ }
+
+ ArenaImpl_Unlock(arena);
+}
+
+/**
+ * Checks the validity of every node of the \p arena.
+ *
+ * @param arena The Arena to check.
+ * @return s32 0 if every pointer is valid. 1 otherwise.
+ */
+s32 __osCheckArena(Arena* arena) {
+ ArenaNode* iter;
+ s32 err = 0;
+
+ ArenaImpl_Lock(arena);
+
+ // "Checking the contents of the arena..."
+ (void)"アリーナの内容をチェックしています... (%08x)\n";
+
+ for (iter = arena->head; iter != NULL; iter = iter->next) {
+ if (iter->magic != NODE_MAGIC) {
+ // "Oops!!"
+ (void)"おおっと!! (%08x %08x)\n";
+
+ err = 1;
+ break;
+ }
+ }
+
+ // "The arena still looks good"
+ (void)"アリーナはまだ、いけそうです\n";
+
+ ArenaImpl_Unlock(arena);
+
+ return err;
+}
diff --git a/src/boot/O2/__osMemcpy.c b/src/boot/O2/__osMemcpy.c
new file mode 100644
index 000000000..2e25b23f0
--- /dev/null
+++ b/src/boot/O2/__osMemcpy.c
@@ -0,0 +1,23 @@
+#include "global.h"
+
+void* __osMemcpy(void* dst, void* src, size_t size) {
+ u8* _dst = dst;
+ u8* _src = src;
+ register s32 rem;
+
+ if (_dst == _src) {
+ return dst;
+ }
+ if (_dst < _src) {
+ for (rem = size--; rem != 0; rem = size--) {
+ *_dst++ = *_src++;
+ }
+ } else {
+ _dst += size - 1;
+ _src += size - 1;
+ for (rem = size--; rem != 0; rem = size--) {
+ *_dst-- = *_src--;
+ }
+ }
+ return dst;
+}
diff --git a/src/boot/O2/__osMemset.c b/src/boot/O2/__osMemset.c
new file mode 100644
index 000000000..0c4172e9f
--- /dev/null
+++ b/src/boot/O2/__osMemset.c
@@ -0,0 +1,11 @@
+#include "global.h"
+
+void* __osMemset(void* ptr, s32 val, size_t size) {
+ u8* dst = ptr;
+ register s32 rem;
+
+ for (rem = size--; rem != 0; rem = size--) {
+ *dst++ = val;
+ }
+ return ptr;
+}
diff --git a/src/boot/O2/__osStrcmp.c b/src/boot/O2/__osStrcmp.c
new file mode 100644
index 000000000..10bd2fa92
--- /dev/null
+++ b/src/boot/O2/__osStrcmp.c
@@ -0,0 +1,16 @@
+#include "global.h"
+
+s32 __osStrcmp(const char* str1, const char* str2) {
+ char c1;
+ char c2;
+
+ do {
+ c1 = *str1++;
+ c2 = *str2++;
+ if (c1 != c2) {
+ return c1 - c2;
+ }
+ } while (c1);
+
+ return 0;
+}
diff --git a/src/boot/O2/__osStrcpy.c b/src/boot/O2/__osStrcpy.c
new file mode 100644
index 000000000..86a98a804
--- /dev/null
+++ b/src/boot/O2/__osStrcpy.c
@@ -0,0 +1,12 @@
+#include "global.h"
+
+char* __osStrcpy(char* dst, const char* src) {
+ char* _dst = dst;
+
+ while (*src != '\0') {
+ *_dst++ = *src++;
+ }
+ *_dst = '\0';
+
+ return dst;
+}
diff --git a/src/boot/O2/debug.c b/src/boot/O2/debug.c
new file mode 100644
index 000000000..3933b585e
--- /dev/null
+++ b/src/boot/O2/debug.c
@@ -0,0 +1,11 @@
+#include "global.h"
+#include "fault.h"
+
+void _dbg_hungup(const char* file, int lineNum) {
+ osGetThreadId(NULL);
+ Fault_AddHungupAndCrash(file, lineNum);
+}
+
+void Reset(void) {
+ Fault_AddHungupAndCrash("Reset", 0);
+}
diff --git a/src/boot/O2/fmodf.c b/src/boot/O2/fmodf.c
new file mode 100644
index 000000000..2f8aa74bf
--- /dev/null
+++ b/src/boot/O2/fmodf.c
@@ -0,0 +1,12 @@
+#include "global.h"
+
+f32 fmodf(f32 dividend, f32 divisor) {
+ s32 quotient;
+
+ if (divisor == 0.0f) {
+ return 0.0f;
+ }
+ quotient = dividend / divisor;
+
+ return dividend - quotient * divisor;
+}
diff --git a/src/boot/O2/gfxprint.c b/src/boot/O2/gfxprint.c
new file mode 100644
index 000000000..b52675829
--- /dev/null
+++ b/src/boot/O2/gfxprint.c
@@ -0,0 +1,236 @@
+#include "global.h"
+
+#define GFXP_FLAG_HIRAGANA (1 << 0)
+#define GFXP_FLAG_RAINBOW (1 << 1)
+#define GFXP_FLAG_SHADOW (1 << 2)
+#define GFXP_FLAG_UPDATE (1 << 3)
+#define GFXP_FLAG_ENLARGE (1 << 6)
+#define GFXP_FLAG_OPEN (1 << 7)
+
+//! TODO: Need to extract
+extern u16 sGfxPrintFontTLUT[64];
+extern u16 sGfxPrintRainbowTLUT[16];
+extern u8 sGfxPrintRainbowData[8];
+extern u8 sGfxPrintFontData[2048];
+
+void GfxPrint_Setup(GfxPrint* this) {
+ s32 width = 16;
+ s32 height = 256;
+ s32 i;
+
+ gDPPipeSync(this->dList++);
+ gDPSetOtherMode(this->dList++,
+ G_AD_DISABLE | G_CD_DISABLE | G_CK_NONE | G_TC_FILT | G_TF_BILERP | G_TT_IA16 | G_TL_TILE |
+ G_TD_CLAMP | G_TP_NONE | G_CYC_1CYCLE | G_PM_NPRIMITIVE,
+ G_AC_NONE | G_ZS_PRIM | G_RM_XLU_SURF | G_RM_XLU_SURF2);
+ gDPSetCombineMode(this->dList++, G_CC_DECALRGBA, G_CC_DECALRGBA);
+ gDPLoadTextureBlock_4b(this->dList++, sGfxPrintFontData, G_IM_FMT_CI, width, height, 0, G_TX_NOMIRROR | G_TX_WRAP,
+ G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD);
+ gDPLoadTLUT(this->dList++, 64, 256, sGfxPrintFontTLUT);
+
+ for (i = 1; i < 4; i++) {
+ gDPSetTile(this->dList++, G_IM_FMT_CI, G_IM_SIZ_4b, 1, 0, i * 2, i, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK,
+ G_TX_NOLOD, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOLOD);
+ gDPSetTileSize(this->dList++, i * 2, 0, 0, 60, 1020);
+ }
+
+ gDPSetColor(this->dList++, G_SETPRIMCOLOR, this->color.rgba);
+
+ gDPLoadMultiTile_4b(this->dList++, sGfxPrintRainbowData, 0, 1, G_IM_FMT_CI, 2, 8, 0, 0, 1, 7, 4,
+ G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, 1, 3, G_TX_NOLOD, G_TX_NOLOD);
+
+ gDPLoadTLUT(this->dList++, 16, 320, sGfxPrintRainbowTLUT);
+
+ for (i = 1; i < 4; i++) {
+ gDPSetTile(this->dList++, G_IM_FMT_CI, G_IM_SIZ_4b, 1, 0, i * 2 + 1, 4, G_TX_NOMIRROR | G_TX_WRAP, 3,
+ G_TX_NOLOD, G_TX_NOMIRROR | G_TX_WRAP, 1, G_TX_NOLOD);
+ gDPSetTileSize(this->dList++, i * 2 + 1, 0, 0, 4, 28);
+ }
+}
+
+void GfxPrint_SetColor(GfxPrint* this, u32 r, u32 g, u32 b, u32 a) {
+ this->color.r = r;
+ this->color.g = g;
+ this->color.b = b;
+ this->color.a = a;
+ gDPPipeSync(this->dList++);
+ gDPSetColor(this->dList++, G_SETPRIMCOLOR, this->color.rgba);
+}
+
+void GfxPrint_SetPosPx(GfxPrint* this, s32 x, s32 y) {
+ this->posX = this->baseX + (x << 2);
+ this->posY = this->baseY + (y << 2);
+}
+
+void GfxPrint_SetPos(GfxPrint* this, s32 x, s32 y) {
+ GfxPrint_SetPosPx(this, x << 3, y << 3);
+}
+
+void GfxPrint_SetBasePosPx(GfxPrint* this, s32 x, s32 y) {
+ this->baseX = x << 2;
+ this->baseY = y << 2;
+}
+
+void GfxPrint_PrintCharImpl(GfxPrint* this, u8 c) {
+ u32 tile = (c & 0xFF) * 2;
+ u16 s = c & 4;
+ u16 t = c >> 3;
+
+ if (this->flags & GFXP_FLAG_UPDATE) {
+ this->flags &= ~GFXP_FLAG_UPDATE;
+
+ gDPPipeSync(this->dList++);
+ if (this->flags & GFXP_FLAG_RAINBOW) {
+ gDPSetTextureLUT(this->dList++, G_TT_RGBA16);
+ gDPSetCycleType(this->dList++, G_CYC_2CYCLE);
+ gDPSetRenderMode(this->dList++, G_RM_PASS, G_RM_XLU_SURF2);
+ gDPSetCombineMode(this->dList++, G_CC_INTERFERENCE, G_CC_PASS2);
+ } else {
+ gDPSetTextureLUT(this->dList++, G_TT_IA16);
+ gDPSetCycleType(this->dList++, G_CYC_1CYCLE);
+ gDPSetRenderMode(this->dList++, G_RM_XLU_SURF, G_RM_XLU_SURF2);
+ gDPSetCombineMode(this->dList++, G_CC_MODULATEIDECALA_PRIM, G_CC_MODULATEIDECALA_PRIM);
+ }
+ }
+
+ if (this->flags & GFXP_FLAG_SHADOW) {
+ gDPSetColor(this->dList++, G_SETPRIMCOLOR, 0);
+
+ gSPTextureRectangle(this->dList++, this->posX + 4, this->posY + 4, this->posX + 4 + 32, this->posY + 4 + 32,
+ tile, s << 6, t << 8, 1 << 10, 1 << 10);
+
+ gDPSetColor(this->dList++, G_SETPRIMCOLOR, this->color.rgba);
+ }
+
+ gSPTextureRectangle(this->dList++, this->posX, this->posY, this->posX + 32, this->posY + 32, tile, s << 6, t << 8,
+ 1 << 10, 1 << 10);
+
+ this->posX += 32;
+}
+
+void GfxPrint_PrintChar(GfxPrint* this, u8 c) {
+ if (c == ' ') {
+ this->posX += 32;
+ } else if (c > ' ' && c < 0x7F) {
+ GfxPrint_PrintCharImpl(this, c);
+ } else if (c >= 0xA0 && c < 0xE0) {
+ if (this->flags & GFXP_FLAG_HIRAGANA) {
+ if (c < 0xC0) {
+ c -= 0x20;
+ } else {
+ c += 0x20;
+ }
+ }
+ GfxPrint_PrintCharImpl(this, c);
+ } else {
+ switch (c) {
+ case '\0':
+ break;
+ case '\n':
+ this->posY += 32;
+ case '\r':
+ this->posX = this->baseX;
+ break;
+ case '\t':
+ do {
+ GfxPrint_PrintCharImpl(this, ' ');
+ } while ((this->posX - this->baseX) % 256);
+ break;
+ case GFXP_HIRAGANA_CHAR:
+ this->flags |= GFXP_FLAG_HIRAGANA;
+ break;
+ case GFXP_KATAKANA_CHAR:
+ this->flags &= ~GFXP_FLAG_HIRAGANA;
+ break;
+ case GFXP_RAINBOW_ON_CHAR:
+ this->flags |= GFXP_FLAG_RAINBOW;
+ this->flags |= GFXP_FLAG_UPDATE;
+ break;
+ case GFXP_RAINBOW_OFF_CHAR:
+ this->flags &= ~GFXP_FLAG_RAINBOW;
+ this->flags |= GFXP_FLAG_UPDATE;
+ break;
+ case GFXP_UNUSED_CHAR:
+ default:
+ break;
+ }
+ }
+}
+
+void GfxPrint_PrintStringWithSize(GfxPrint* this, const void* buffer, size_t charSize, size_t charCount) {
+ const char* str = (const char*)buffer;
+ size_t count = charSize * charCount;
+
+ while (count != 0) {
+ GfxPrint_PrintChar(this, *str++);
+ count--;
+ }
+}
+
+void GfxPrint_PrintString(GfxPrint* this, const char* str) {
+ while (*str != '\0') {
+ GfxPrint_PrintChar(this, *str++);
+ }
+}
+
+void* GfxPrint_Callback(void* arg, const char* str, size_t size) {
+ GfxPrint* this = arg;
+
+ GfxPrint_PrintStringWithSize(this, str, sizeof(char), size);
+
+ return this;
+}
+
+void GfxPrint_Init(GfxPrint* this) {
+ this->flags &= ~GFXP_FLAG_OPEN;
+
+ this->callback = GfxPrint_Callback;
+ this->dList = NULL;
+ this->posX = 0;
+ this->posY = 0;
+ this->baseX = 0;
+ this->baseY = 0;
+ this->color.rgba = 0;
+
+ this->flags &= ~GFXP_FLAG_HIRAGANA;
+ this->flags &= ~GFXP_FLAG_RAINBOW;
+ this->flags |= GFXP_FLAG_SHADOW;
+ this->flags |= GFXP_FLAG_UPDATE;
+}
+
+void GfxPrint_Destroy(GfxPrint* this) {
+}
+
+void GfxPrint_Open(GfxPrint* this, Gfx* dList) {
+ if (!(this->flags & GFXP_FLAG_OPEN)) {
+ this->flags |= GFXP_FLAG_OPEN;
+ this->dList = dList;
+ GfxPrint_Setup(this);
+ }
+}
+
+Gfx* GfxPrint_Close(GfxPrint* this) {
+ Gfx* ret;
+
+ this->flags &= ~GFXP_FLAG_OPEN;
+ ret = this->dList;
+ this->dList = NULL;
+
+ return ret;
+}
+
+s32 GfxPrint_VPrintf(GfxPrint* this, const char* fmt, va_list args) {
+ return PrintUtils_VPrintf(&this->callback, fmt, args);
+}
+
+s32 GfxPrint_Printf(GfxPrint* this, const char* fmt, ...) {
+ s32 ret;
+ va_list args;
+ va_start(args, fmt);
+
+ ret = GfxPrint_VPrintf(this, fmt, args);
+
+ va_end(args);
+
+ return ret;
+}
diff --git a/src/boot/O2/loadfragment.c b/src/boot/O2/loadfragment.c
new file mode 100644
index 000000000..b22b1a692
--- /dev/null
+++ b/src/boot/O2/loadfragment.c
@@ -0,0 +1,231 @@
+/**
+ * @file loadfragment.c
+ *
+ * Functions used to process and relocate dynamically loadable code segments (overlays).
+ *
+ * @note:
+ * These are completly unused in favor of the fragment overlay functions in `loadfragment2.c`.
+ *
+ * The main difference between them seems to be the lack of vramEnd arguments here.
+ * Instead they are calculated on the fly.
+ */
+
+#include "global.h"
+#include "system_malloc.h"
+#include "loadfragment.h"
+
+s32 gLoadLogSeverity = 2;
+
+// Extract MIPS register rs from an instruction word
+#define MIPS_REG_RS(insn) (((insn) >> 0x15) & 0x1F)
+
+// Extract MIPS register rt from an instruction word
+#define MIPS_REG_RT(insn) (((insn) >> 0x10) & 0x1F)
+
+// Extract MIPS jump target from an instruction word
+#define MIPS_JUMP_TARGET(insn) (((insn)&0x03FFFFFF) << 2)
+
+/**
+ * Performs runtime relocation of overlay files, loadable code segments.
+ *
+ * Overlays are expected to be loadable anywhere in direct-mapped cached (KSEG0) memory, with some appropriate
+ * alignment requirements; memory addresses in such code must be updated once loaded to execute properly.
+ * When compiled, overlays are given 'fake' KSEG0 RAM addresses larger than the total possible available main memory
+ * (>= 0x80800000), such addresses are referred to as Virtual RAM (VRAM) to distinguish them. When loading the overlay,
+ * the relocation table produced at compile time is consulted to determine where and how to update these VRAM addresses
+ * to correct RAM addresses based on the location the overlay was loaded at, enabling the code to execute at this
+ * address as if it were compiled to run at this address.
+ *
+ * Each relocation is represented by a packed 32-bit value, formatted in the following way:
+ * - [31:30] 2-bit section id, taking values from the `RelocSectionId` enum.
+ * - [29:24] 6-bit relocation type describing which relocation operation should be performed. Same as ELF32 MIPS.
+ * - [23: 0] 24-bit section-relative offset indicating where in the section to apply this relocation.
+ *
+ * @param allocatedRamAddr Memory address the binary was loaded at.
+ * @param ovlRelocs Overlay relocation section containing overlay section layout and runtime relocations.
+ * @param vramStart Virtual RAM address that the overlay was compiled at.
+ */
+void Fragment_Relocate(void* allocatedRamAddr, OverlayRelocationSection* ovlRelocs, uintptr_t vramStart) {
+ u32 sections[RELOC_SECTION_MAX];
+ u32* relocDataP;
+ u32 reloc;
+ uintptr_t relocatedAddress;
+ u32 i;
+ u32* luiInstRef;
+ uintptr_t allocu32 = (uintptr_t)allocatedRamAddr;
+ u32* regValP;
+ //! MIPS ELF relocation does not generally require tracking register values, so at first glance it appears this
+ //! register tracking was an unnecessary complication. However there is a bug in the IDO compiler that can cause
+ //! relocations to be emitted in the wrong order under rare circumstances when the compiler attempts to reuse a
+ //! previous HI16 relocation for a different LO16 relocation as an optimization. This register tracking is likely
+ //! a workaround to prevent improper matching of unrelated HI16 and LO16 relocations that would otherwise arise
+ //! due to the incorrect ordering.
+ u32* luiRefs[32];
+ u32 luiVals[32];
+ u32 isLoNeg;
+
+ if (gLoadLogSeverity >= 3) {}
+
+ sections[RELOC_SECTION_NULL] = 0;
+ sections[RELOC_SECTION_TEXT] = allocu32;
+ sections[RELOC_SECTION_DATA] = allocu32 + ovlRelocs->textSize;
+ sections[RELOC_SECTION_RODATA] = sections[RELOC_SECTION_DATA] + ovlRelocs->dataSize;
+
+ for (i = 0; i < ovlRelocs->numRelocations; i++) {
+ // This will always resolve to a 32-bit aligned address as each section
+ // containing code or pointers must be aligned to at least 4 bytes and the
+ // MIPS ABI defines the offset of both 16-bit and 32-bit relocations to be
+ // the start of the 32-bit word containing the target.
+ reloc = ovlRelocs->relocations[i];
+ relocDataP = (u32*)(sections[RELOC_SECTION(reloc)] + RELOC_OFFSET(reloc));
+
+ switch (RELOC_TYPE_MASK(reloc)) {
+ case R_MIPS_32 << RELOC_TYPE_SHIFT:
+ // Handles 32-bit address relocation, used for things such as jump tables and pointers in data.
+ // Just relocate the full address
+
+ // Check address is valid for relocation
+ if ((*relocDataP & 0x0F000000) == 0) {
+ *relocDataP = *relocDataP - vramStart + allocu32;
+ } else if (gLoadLogSeverity >= 3) {
+ }
+ break;
+
+ case R_MIPS_26 << RELOC_TYPE_SHIFT:
+ // Handles 26-bit address relocation, used for jumps and jals.
+ // Extract the address from the target field of the J-type MIPS instruction.
+ // Relocate the address and update the instruction.
+
+ if (1) {
+ *relocDataP =
+ (*relocDataP & 0xFC000000) |
+ (((PHYS_TO_K0(MIPS_JUMP_TARGET(*relocDataP)) - vramStart + allocu32) & 0x0FFFFFFF) >> 2);
+ }
+ break;
+
+ case R_MIPS_HI16 << RELOC_TYPE_SHIFT:
+ // Handles relocation for a hi/lo pair, part 1.
+ // Store the reference to the LUI instruction (hi) using the `rt` register of the instruction.
+ // This will be updated later in the `R_MIPS_LO16` section.
+
+ luiRefs[(*relocDataP >> 0x10) & 0x1F] = relocDataP;
+ luiVals[(*relocDataP >> 0x10) & 0x1F] = *relocDataP;
+ break;
+
+ case R_MIPS_LO16 << RELOC_TYPE_SHIFT:
+ // Handles relocation for a hi/lo pair, part 2.
+ // Grab the stored LUI (hi) from the `R_MIPS_HI16` section using the `rs` register of the instruction.
+ // The full address is calculated, relocated, and then used to update both the LUI and lo instructions.
+ // If the lo part is negative, add 1 to the LUI value.
+ // Note: The lo instruction is assumed to have a signed immediate.
+
+ luiInstRef = luiRefs[(*relocDataP >> 0x15) & 0x1F];
+ regValP = &luiVals[(*relocDataP >> 0x15) & 0x1F];
+
+ // Check address is valid for relocation
+ if ((((*luiInstRef << 0x10) + (s16)*relocDataP) & 0x0F000000) == 0) {
+ relocatedAddress = ((*regValP << 0x10) + (s16)*relocDataP) - vramStart + allocu32;
+ isLoNeg = (relocatedAddress & 0x8000) ? 1 : 0;
+ *luiInstRef = (*luiInstRef & 0xFFFF0000) | (((relocatedAddress >> 0x10) & 0xFFFF) + isLoNeg);
+ *relocDataP = (*relocDataP & 0xFFFF0000) | (relocatedAddress & 0xFFFF);
+ } else if (gLoadLogSeverity >= 3) {
+ }
+ break;
+ }
+ }
+}
+
+size_t Fragment_Load(uintptr_t vromStart, uintptr_t vromEnd, uintptr_t vramStart, void* allocatedRamAddr,
+ size_t allocatedBytes) {
+ size_t size = vromEnd - vromStart;
+ void* end;
+ s32 pad;
+ OverlayRelocationSection* ovlRelocs;
+
+ if (gLoadLogSeverity >= 3) {}
+ if (gLoadLogSeverity >= 3) {}
+
+ end = (uintptr_t)allocatedRamAddr + size;
+ DmaMgr_SendRequest0(allocatedRamAddr, vromStart, size);
+
+ ovlRelocs = (OverlayRelocationSection*)((uintptr_t)end - ((s32*)end)[-1]);
+
+ if (gLoadLogSeverity >= 3) {}
+
+ if (allocatedBytes < ovlRelocs->bssSize + size) {
+ if (gLoadLogSeverity >= 3) {}
+ return 0;
+ }
+
+ allocatedBytes = ovlRelocs->bssSize + size;
+
+ if (gLoadLogSeverity >= 3) {}
+
+ Fragment_Relocate(allocatedRamAddr, ovlRelocs, vramStart);
+
+ if (ovlRelocs->bssSize != 0) {
+ if (gLoadLogSeverity >= 3) {}
+ bzero(end, ovlRelocs->bssSize);
+ }
+
+ osWritebackDCache(allocatedRamAddr, allocatedBytes);
+ osInvalICache(allocatedRamAddr, allocatedBytes);
+
+ if (gLoadLogSeverity >= 3) {}
+
+ return allocatedBytes;
+}
+
+void* Fragment_AllocateAndLoad(uintptr_t vromStart, uintptr_t vromEnd, uintptr_t vramStart) {
+ size_t size = vromEnd - vromStart;
+ void* end;
+ void* allocatedRamAddr;
+ uintptr_t ovlOffset;
+ OverlayRelocationSection* ovlRelocs;
+ size_t allocatedBytes;
+
+ if (gLoadLogSeverity >= 3) {}
+
+ allocatedRamAddr = SystemArena_MallocR(size);
+ end = (uintptr_t)allocatedRamAddr + size;
+
+ if (gLoadLogSeverity >= 3) {}
+
+ DmaMgr_SendRequest0(allocatedRamAddr, vromStart, size);
+
+ if (gLoadLogSeverity >= 3) {}
+
+ ovlOffset = (uintptr_t)end - 4;
+ ovlRelocs = (OverlayRelocationSection*)((uintptr_t)end - ((s32*)end)[-1]);
+
+ if (1) {}
+
+ allocatedBytes = ovlRelocs->bssSize + size;
+
+ allocatedRamAddr = SystemArena_Realloc(allocatedRamAddr, allocatedBytes);
+
+ if (gLoadLogSeverity >= 3) {}
+
+ if (allocatedRamAddr == NULL) {
+ if (gLoadLogSeverity >= 3) {}
+ return allocatedRamAddr;
+ }
+
+ end = (uintptr_t)allocatedRamAddr + size;
+ ovlRelocs = (OverlayRelocationSection*)((uintptr_t)end - *(uintptr_t*)ovlOffset);
+
+ if (gLoadLogSeverity >= 3) {}
+
+ Fragment_Relocate(allocatedRamAddr, ovlRelocs, vramStart);
+
+ if (ovlRelocs->bssSize != 0) {
+ if (gLoadLogSeverity >= 3) {}
+ bzero(end, ovlRelocs->bssSize);
+ }
+
+ osInvalICache(allocatedRamAddr, allocatedBytes);
+
+ if (gLoadLogSeverity >= 3) {}
+
+ return allocatedRamAddr;
+}
diff --git a/src/boot/O2/loadfragment2.c b/src/boot/O2/loadfragment2.c
new file mode 100644
index 000000000..6925aaaac
--- /dev/null
+++ b/src/boot/O2/loadfragment2.c
@@ -0,0 +1,177 @@
+/**
+ * @file loadfragment2.c
+ *
+ * Functions used to process and relocate dynamically loadable code segments (overlays).
+ *
+ * @note:
+ * These are for specific fragment overlays with the .ovl file extension
+ */
+#include "global.h"
+#include "system_malloc.h"
+#include "loadfragment.h"
+
+s32 gOverlayLogSeverity = 2;
+
+// Extract MIPS register rs from an instruction word
+#define MIPS_REG_RS(insn) (((insn) >> 0x15) & 0x1F)
+
+// Extract MIPS register rt from an instruction word
+#define MIPS_REG_RT(insn) (((insn) >> 0x10) & 0x1F)
+
+// Extract MIPS jump target from an instruction word
+#define MIPS_JUMP_TARGET(insn) (((insn)&0x03FFFFFF) << 2)
+
+/**
+ * Performs runtime relocation of overlay files, loadable code segments.
+ *
+ * Overlays are expected to be loadable anywhere in direct-mapped cached (KSEG0) memory, with some appropriate
+ * alignment requirements; memory addresses in such code must be updated once loaded to execute properly.
+ * When compiled, overlays are given 'fake' KSEG0 RAM addresses larger than the total possible available main memory
+ * (>= 0x80800000), such addresses are referred to as Virtual RAM (VRAM) to distinguish them. When loading the overlay,
+ * the relocation table produced at compile time is consulted to determine where and how to update these VRAM addresses
+ * to correct RAM addresses based on the location the overlay was loaded at, enabling the code to execute at this
+ * address as if it were compiled to run at this address.
+ *
+ * Each relocation is represented by a packed 32-bit value, formatted in the following way:
+ * - [31:30] 2-bit section id, taking values from the `RelocSectionId` enum.
+ * - [29:24] 6-bit relocation type describing which relocation operation should be performed. Same as ELF32 MIPS.
+ * - [23: 0] 24-bit section-relative offset indicating where in the section to apply this relocation.
+ *
+ * @param allocatedRamAddress Memory address the binary was loaded at.
+ * @param ovlRelocs Overlay relocation section containing overlay section layout and runtime relocations.
+ * @param vramStart Virtual RAM address that the overlay was compiled at.
+ */
+void Overlay_Relocate(void* allocatedRamAddr, OverlayRelocationSection* ovlRelocs, uintptr_t vramStart) {
+ u32 sections[RELOC_SECTION_MAX];
+ u32* relocDataP;
+ u32 reloc;
+ uintptr_t relocatedAddress;
+ u32 i;
+ u32* luiInstRef;
+ uintptr_t allocu32 = (uintptr_t)allocatedRamAddr;
+ u32* regValP;
+ //! MIPS ELF relocation does not generally require tracking register values, so at first glance it appears this
+ //! register tracking was an unnecessary complication. However there is a bug in the IDO compiler that can cause
+ //! relocations to be emitted in the wrong order under rare circumstances when the compiler attempts to reuse a
+ //! previous HI16 relocation for a different LO16 relocation as an optimization. This register tracking is likely
+ //! a workaround to prevent improper matching of unrelated HI16 and LO16 relocations that would otherwise arise
+ //! due to the incorrect ordering.
+ u32* luiRefs[32];
+ u32 luiVals[32];
+ u32 isLoNeg;
+
+ if (gOverlayLogSeverity >= 3) {}
+
+ sections[RELOC_SECTION_NULL] = 0;
+ sections[RELOC_SECTION_TEXT] = allocu32;
+ sections[RELOC_SECTION_DATA] = allocu32 + ovlRelocs->textSize;
+ sections[RELOC_SECTION_RODATA] = sections[RELOC_SECTION_DATA] + ovlRelocs->dataSize;
+
+ for (i = 0; i < ovlRelocs->numRelocations; i++) {
+ // This will always resolve to a 32-bit aligned address as each section
+ // containing code or pointers must be aligned to at least 4 bytes and the
+ // MIPS ABI defines the offset of both 16-bit and 32-bit relocations to be
+ // the start of the 32-bit word containing the target.
+ reloc = ovlRelocs->relocations[i];
+ relocDataP = (u32*)(sections[RELOC_SECTION(reloc)] + RELOC_OFFSET(reloc));
+
+ switch (RELOC_TYPE_MASK(reloc)) {
+ case R_MIPS_32 << RELOC_TYPE_SHIFT:
+ // Handles 32-bit address relocation, used for things such as jump tables and pointers in data.
+ // Just relocate the full address
+
+ // Check address is valid for relocation
+ if ((*relocDataP & 0x0F000000) == 0) {
+ *relocDataP = *relocDataP - vramStart + allocu32;
+ } else if (gOverlayLogSeverity >= 3) {
+ }
+ break;
+
+ case R_MIPS_26 << RELOC_TYPE_SHIFT:
+ // Handles 26-bit address relocation, used for jumps and jals.
+ // Extract the address from the target field of the J-type MIPS instruction.
+ // Relocate the address and update the instruction.
+
+ if (1) {
+ *relocDataP =
+ (*relocDataP & 0xFC000000) |
+ (((PHYS_TO_K0(MIPS_JUMP_TARGET(*relocDataP)) - vramStart + allocu32) & 0x0FFFFFFF) >> 2);
+ }
+ break;
+
+ case R_MIPS_HI16 << RELOC_TYPE_SHIFT:
+ // Handles relocation for a hi/lo pair, part 1.
+ // Store the reference to the LUI instruction (hi) using the `rt` register of the instruction.
+ // This will be updated later in the `R_MIPS_LO16` section.
+
+ luiRefs[(*relocDataP >> 0x10) & 0x1F] = relocDataP;
+ luiVals[(*relocDataP >> 0x10) & 0x1F] = *relocDataP;
+ break;
+
+ case R_MIPS_LO16 << RELOC_TYPE_SHIFT:
+ // Handles relocation for a hi/lo pair, part 2.
+ // Grab the stored LUI (hi) from the `R_MIPS_HI16` section using the `rs` register of the instruction.
+ // The full address is calculated, relocated, and then used to update both the LUI and lo instructions.
+ // If the lo part is negative, add 1 to the LUI value.
+ // Note: The lo instruction is assumed to have a signed immediate.
+
+ luiInstRef = luiRefs[(*relocDataP >> 0x15) & 0x1F];
+ regValP = &luiVals[(*relocDataP >> 0x15) & 0x1F];
+
+ // Check address is valid for relocation
+ if ((((*luiInstRef << 0x10) + (s16)*relocDataP) & 0x0F000000) == 0) {
+ relocatedAddress = ((*regValP << 0x10) + (s16)*relocDataP) - vramStart + allocu32;
+ isLoNeg = (relocatedAddress & 0x8000) ? 1 : 0;
+ *luiInstRef = (*luiInstRef & 0xFFFF0000) | (((relocatedAddress >> 0x10) & 0xFFFF) + isLoNeg);
+ *relocDataP = (*relocDataP & 0xFFFF0000) | (relocatedAddress & 0xFFFF);
+ } else if (gOverlayLogSeverity >= 3) {
+ }
+ break;
+ }
+ }
+}
+
+size_t Overlay_Load(uintptr_t vromStart, uintptr_t vromEnd, uintptr_t vramStart, uintptr_t vramEnd,
+ void* allocatedRamAddr) {
+ s32 pad[2];
+ s32 size = vromEnd - vromStart;
+ void* end;
+ OverlayRelocationSection* ovlRelocs;
+
+ if (gOverlayLogSeverity >= 3) {}
+ if (gOverlayLogSeverity >= 3) {}
+
+ end = (uintptr_t)allocatedRamAddr + size;
+ DmaMgr_SendRequest0(allocatedRamAddr, vromStart, size);
+
+ ovlRelocs = (OverlayRelocationSection*)((uintptr_t)end - ((s32*)end)[-1]);
+
+ if (gOverlayLogSeverity >= 3) {}
+ if (gOverlayLogSeverity >= 3) {}
+
+ Overlay_Relocate(allocatedRamAddr, ovlRelocs, vramStart);
+
+ if (ovlRelocs->bssSize != 0) {
+ if (gOverlayLogSeverity >= 3) {}
+ bzero(end, ovlRelocs->bssSize);
+ }
+
+ size = vramEnd - vramStart;
+
+ osWritebackDCache(allocatedRamAddr, size);
+ osInvalICache(allocatedRamAddr, size);
+
+ if (gOverlayLogSeverity >= 3) {}
+
+ return size;
+}
+
+void* Overlay_AllocateAndLoad(uintptr_t vromStart, uintptr_t vromEnd, uintptr_t vramStart, uintptr_t vramEnd) {
+ void* allocatedRamAddr = SystemArena_MallocR(vramEnd - vramStart);
+
+ if (allocatedRamAddr != NULL) {
+ Overlay_Load(vromStart, vromEnd, vramStart, vramEnd, allocatedRamAddr);
+ }
+
+ return allocatedRamAddr;
+}
diff --git a/src/boot/O2/math64.c b/src/boot/O2/math64.c
new file mode 100644
index 000000000..cb77bed50
--- /dev/null
+++ b/src/boot/O2/math64.c
@@ -0,0 +1,191 @@
+/**
+ * MathF library
+ * Contains tangent function, wrappers for a number of the handwritten functions in fp, and a suite of arctangents
+ */
+#include "global.h"
+#include "fixed_point.h"
+
+s32 gUseAtanContFrac;
+
+/**
+ * Tangent function computed using libultra sinf and cosf
+ */
+f32 Math_FTanF(f32 x) {
+ return sinf(x) / cosf(x);
+}
+
+// Unused
+f32 Math_FFloorF(f32 x) {
+ return floorf(x);
+}
+
+// Unused
+f32 Math_FCeilF(f32 x) {
+ return ceilf(x);
+}
+
+// Unused
+f32 Math_FRoundF(f32 x) {
+ return roundf(x);
+}
+
+// Unused
+f32 Math_FTruncF(f32 x) {
+ return truncf(x);
+}
+
+f32 Math_FNearbyIntF(f32 x) {
+ return nearbyintf(x);
+}
+
+/**
+ * Arctangent approximation using a Maclaurin series [https://mathworld.wolfram.com/MaclaurinSeries.html]
+ * (one quadrant, i.e. |x| < 1)
+ */
+f32 Math_FAtanTaylorQF(f32 x) {
+ // Coefficients of Maclaurin series of arctangent
+ static const f32 coeffs[] = {
+ -1.0f / 3, +1.0f / 5, -1.0f / 7, +1.0f / 9, -1.0f / 11, +1.0f / 13, -1.0f / 15, +1.0f / 17, 0.0f,
+ };
+
+ f32 poly = x;
+ f32 sq = SQ(x);
+ f32 exp = x * sq;
+ const f32* c = coeffs;
+ f32 term;
+
+ // Calculate the series until adding more terms does not change the float
+ while (true) {
+ term = *c++ * exp;
+ if (poly + term == poly) {
+ break;
+ }
+ poly += term;
+ exp *= sq;
+ }
+
+ return poly;
+}
+
+/**
+ * Extends previous arctangent function to the rest of the real numbers.
+ * Uses the formulae arctan(x) = pi/2 - arctan(1/x)
+ * and arctan(x) = pi/4 - arctan( (1-x)/(1+x) )
+ * to extend the range in which the series computed by Math_FAtanTaylorQF is a good approximation
+ */
+f32 Math_FAtanTaylorF(f32 x) {
+ f32 t;
+ f32 q;
+
+ if (x > 0.0f) {
+ t = x;
+ } else if (x < 0.0f) {
+ t = -x;
+ } else if (x == 0.0f) {
+ return 0.0f;
+ } else {
+ return qNaN0x10000;
+ }
+
+ if (t <= M_SQRT2 - 1.0f) {
+ return Math_FAtanTaylorQF(x);
+ }
+
+ if (t >= M_SQRT2 + 1.0f) {
+ q = M_PI / 2 - Math_FAtanTaylorQF(1.0f / t);
+ } else { // in the interval (\sqrt{2} - 1, \sqrt{2} + 1)
+ q = M_PI / 4 - Math_FAtanTaylorQF((1.0f - t) / (1.0f + t));
+ }
+
+ if (x > 0.0f) {
+ return q;
+ } else {
+ return -q;
+ }
+}
+
+/**
+ * Arctangent approximation using a continued fraction
+ * Cf. https://en.wikipedia.org/wiki/Gauss%27s_continued_fraction#The_series_2F1_2 ,
+ * https://dlmf.nist.gov/4.25#E4
+ */
+f32 Math_FAtanContFracF(f32 x) {
+ s32 sector;
+ f32 z;
+ f32 conv;
+ f32 sq;
+ s32 i;
+
+ if (x >= -1.0f && x <= 1.0f) {
+ sector = 0;
+ } else if (x > 1.0f) {
+ sector = 1;
+ x = 1.0f / x;
+ } else if (x < -1.0f) {
+ sector = -1;
+ x = 1.0f / x;
+ } else {
+ return qNaN0x10000;
+ }
+
+ // Builds the continued fraction from the innermost fraction out
+ sq = SQ(x);
+ conv = 0.0f;
+ z = 8.0f;
+ for (i = 8; i != 0; i--) {
+ conv = SQ(z) * sq / (2.0f * z + 1.0f + conv);
+ z -= 1.0f;
+ }
+ conv = x / (1.0f + conv);
+
+ if (sector == 0) {
+ return conv;
+ } else if (sector > 0) {
+ return M_PI / 2 - conv;
+ } else {
+ return -M_PI / 2 - conv;
+ }
+}
+
+/**
+ * Single-argument arctangent, only used by the two-argument function.
+ * Nothing else sets the bss variable gUseAtanContFrac, so the Maclaurin series is always used
+ */
+f32 Math_FAtanF(f32 x) {
+ if (!gUseAtanContFrac) {
+ return Math_FAtanTaylorF(x);
+ } else {
+ return Math_FAtanContFracF(x);
+ }
+}
+
+/**
+ * Main two-argument arctangent function
+ */
+f32 Math_FAtan2F(f32 y, f32 x) {
+ if (x == 0.0f) {
+ if (y == 0.0f) {
+ return 0.0f;
+ } else if (y > 0.0f) {
+ return M_PI / 2;
+ } else if (y < 0.0f) {
+ return -M_PI / 2;
+ } else {
+ return qNaN0x10000;
+ }
+ } else if (x >= 0.0f) {
+ return Math_FAtanF(y / x);
+ } else if (y < 0.0f) {
+ return Math_FAtanF(y / x) - M_PI;
+ } else {
+ return M_PI - Math_FAtanF(-(y / x));
+ }
+}
+
+f32 Math_FAsinF(f32 x) {
+ return Math_FAtan2F(x, sqrtf(1.0f - SQ(x)));
+}
+
+f32 Math_FAcosF(f32 x) {
+ return M_PI / 2 - Math_FAsinF(x);
+}
diff --git a/src/boot/O2/mtxuty-cvt.c b/src/boot/O2/mtxuty-cvt.c
new file mode 100644
index 000000000..a5da24522
--- /dev/null
+++ b/src/boot/O2/mtxuty-cvt.c
@@ -0,0 +1,19 @@
+#include "global.h"
+
+void MtxConv_F2L(Mtx* mtx, MtxF* mf) {
+ s32 i;
+ s32 j;
+
+ for (i = 0; i < 4; i++) {
+ for (j = 0; j < 4; j++) {
+ s32 value = (mf->mf[i][j] * 0x10000);
+
+ mtx->intPart[i][j] = value >> 16;
+ mtx->fracPart[i][j] = value;
+ }
+ }
+}
+
+void MtxConv_L2F(MtxF* mtx, Mtx* mf) {
+ guMtxL2F(mtx->mf, mf);
+}
diff --git a/src/boot/O2/padsetup.c b/src/boot/O2/padsetup.c
new file mode 100644
index 000000000..9cef6f196
--- /dev/null
+++ b/src/boot/O2/padsetup.c
@@ -0,0 +1,34 @@
+#include "global.h"
+
+s32 PadSetup_Init(OSMesgQueue* mq, u8* outMask, OSContStatus* status) {
+ s32 ret;
+ s32 i;
+
+ *outMask = 0xFF;
+ ret = osContInit(mq, outMask, status);
+ if (ret != 0) {
+ return ret;
+ }
+ if (*outMask == 0xFF) {
+ if (osContStartQuery(mq) != 0) {
+ return 1;
+ }
+ osRecvMesg(mq, NULL, OS_MESG_BLOCK);
+ osContGetQuery(status);
+
+ *outMask = 0;
+
+ for (i = 0; i < MAXCONTROLLERS; i++) {
+ switch (status[i].errno) {
+ case 0:
+ if (status[i].type == CONT_TYPE_NORMAL) {
+ *outMask |= 1 << i;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ return 0;
+}
diff --git a/src/boot/O2/padutils.c b/src/boot/O2/padutils.c
new file mode 100644
index 000000000..1c6bbe868
--- /dev/null
+++ b/src/boot/O2/padutils.c
@@ -0,0 +1,92 @@
+#include "padutils.h"
+
+void PadUtils_Init(Input* input) {
+ bzero(input, sizeof(Input));
+}
+
+void func_80085150(void) {
+}
+
+void PadUtils_ResetPressRel(Input* input) {
+ input->press.button = 0;
+ input->rel.button = 0;
+}
+
+u32 PadUtils_CheckCurExact(Input* input, u16 value) {
+ return value == input->cur.button;
+}
+
+u32 PadUtils_CheckCur(Input* input, u16 key) {
+ return key == (input->cur.button & key);
+}
+
+u32 PadUtils_CheckPressed(Input* input, u16 key) {
+ return key == (input->press.button & key);
+}
+
+u32 PadUtils_CheckReleased(Input* input, u16 key) {
+ return key == (input->rel.button & key);
+}
+
+u16 PadUtils_GetCurButton(Input* input) {
+ return input->cur.button;
+}
+
+u16 PadUtils_GetPressButton(Input* input) {
+ return input->press.button;
+}
+
+s8 PadUtils_GetCurX(Input* input) {
+ return input->cur.stick_x;
+}
+
+s8 PadUtils_GetCurY(Input* input) {
+ return input->cur.stick_y;
+}
+
+void PadUtils_SetRelXY(Input* input, s32 x, s32 y) {
+ input->rel.stick_x = x;
+ input->rel.stick_y = y;
+}
+
+s8 PadUtils_GetRelXImpl(Input* input) {
+ return input->rel.stick_x;
+}
+
+s8 PadUtils_GetRelYImpl(Input* input) {
+ return input->rel.stick_y;
+}
+
+s8 PadUtils_GetRelX(Input* input) {
+ return PadUtils_GetRelXImpl(input);
+}
+
+s8 PadUtils_GetRelY(Input* input) {
+ return PadUtils_GetRelYImpl(input);
+}
+
+void PadUtils_UpdateRelXY(Input* input) {
+ s32 curX = PadUtils_GetCurX(input);
+ s32 curY = PadUtils_GetCurY(input);
+ s32 relX;
+ s32 relY;
+
+ if (curX > 7) {
+ relX = (curX < 0x43) ? curX - 7 : 0x43 - 7;
+ } else if (curX < -7) {
+ relX = (curX > -0x43) ? curX + 7 : -0x43 + 7;
+ } else {
+ relX = 0;
+ }
+
+ if (curY > 7) {
+ relY = (curY < 0x43) ? curY - 7 : 0x43 - 7;
+
+ } else if (curY < -7) {
+ relY = (curY > -0x43) ? curY + 7 : -0x43 + 7;
+ } else {
+ relY = 0;
+ }
+
+ PadUtils_SetRelXY(input, relX, relY);
+}
diff --git a/src/boot/O2/printutils.c b/src/boot/O2/printutils.c
new file mode 100644
index 000000000..3fb8cf367
--- /dev/null
+++ b/src/boot/O2/printutils.c
@@ -0,0 +1,17 @@
+#include "global.h"
+
+s32 PrintUtils_VPrintf(PrintCallback* pfn, const char* fmt, va_list args) {
+ return _Printf(*pfn, pfn, fmt, args);
+}
+
+s32 PrintUtils_Printf(PrintCallback* pfn, const char* fmt, ...) {
+ s32 ret;
+ va_list args;
+ va_start(args, fmt);
+
+ ret = PrintUtils_VPrintf(pfn, fmt, args);
+
+ va_end(args);
+
+ return ret;
+}
diff --git a/src/boot/O2/rand.c b/src/boot/O2/rand.c
new file mode 100644
index 000000000..2db64e9d5
--- /dev/null
+++ b/src/boot/O2/rand.c
@@ -0,0 +1,96 @@
+#include "global.h"
+
+//! The latest generated random number, used to generate the next number in the sequence.
+static u32 sRandInt = 1;
+
+//! Space to store a value to be re-interpreted as a float.
+//! This can't be static because it is used in z_kankyo.
+u32 sRandFloat;
+
+//! These values are recommended by the algorithms book *Numerical Recipes in C. The Art of Scientific Computing*, 2nd
+//! Edition, 1992, ISBN 0-521-43108-5. (p. 284):
+//! > This is about as good as any 32-bit linear congruential generator, entirely adequate for many uses.
+#define RAND_MULTIPLIER 1664525
+#define RAND_INCREMENT 1013904223
+
+/**
+ * Generates the next pseudo-random integer.
+ */
+u32 Rand_Next(void) {
+ return sRandInt = (sRandInt * RAND_MULTIPLIER) + RAND_INCREMENT;
+}
+
+/**
+ * Seeds the internal pseudo-random number generator with a provided starting value.
+ */
+void Rand_Seed(u32 seed) {
+ sRandInt = seed;
+}
+
+/**
+ * Returns a pseudo-random float between 0.0f and 1.0f from the internal PRNG.
+ *
+ * @note Works by generating the next integer, masking it to an IEEE-754 compliant float between 1.0f and 2.0f, and
+ * subtracting 1.0f.
+ *
+ * @remark This is also recommended by Numerical Recipes, pp. 284-5.
+ */
+f32 Rand_ZeroOne(void) {
+ sRandInt = (sRandInt * RAND_MULTIPLIER) + RAND_INCREMENT;
+ sRandFloat = ((sRandInt >> 9) | 0x3F800000);
+ return *((f32*)&sRandFloat) - 1.0f;
+}
+
+/**
+ * Returns a pseudo-random float between -0.5f and 0.5f in the same way as Rand_ZeroOne().
+ */
+f32 Rand_Centered(void) {
+ sRandInt = (sRandInt * RAND_MULTIPLIER) + RAND_INCREMENT;
+ sRandFloat = ((sRandInt >> 9) | 0x3F800000);
+ return *((f32*)&sRandFloat) - 1.5f;
+}
+
+//! All functions below are unused variants of the above four, that use a provided random number variable instead of the
+//! internal `sRandInt`
+
+/**
+ * Seeds a provided pseudo-random number with a provided starting value.
+ *
+ * @see Rand_Seed
+ */
+void Rand_Seed_Variable(u32* rndNum, u32 seed) {
+ *rndNum = seed;
+}
+
+/**
+ * Generates the next pseudo-random number from the provided rndNum.
+ *
+ * @see Rand_Next
+ */
+u32 Rand_Next_Variable(u32* rndNum) {
+ return *rndNum = (*rndNum * RAND_MULTIPLIER) + RAND_INCREMENT;
+}
+
+/**
+ * Generates the next pseudo-random float between 0.0f and 1.0f from the provided rndNum.
+ *
+ * @see Rand_ZeroOne
+ */
+f32 Rand_ZeroOne_Variable(u32* rndNum) {
+ u32 next = (*rndNum * RAND_MULTIPLIER) + RAND_INCREMENT;
+
+ sRandFloat = ((*rndNum = next) >> 9) | 0x3F800000;
+ return *((f32*)&sRandFloat) - 1.0f;
+}
+
+/**
+ * Generates the next pseudo-random float between -0.5f and 0.5f from the provided rndNum.
+ *
+ * @see Rand_ZeroOne, Rand_Centered
+ */
+f32 Rand_Centered_Variable(u32* rndNum) {
+ u32 next = (*rndNum * RAND_MULTIPLIER) + RAND_INCREMENT;
+
+ sRandFloat = ((*rndNum = next) >> 9) | 0x3F800000;
+ return *((f32*)&sRandFloat) - 1.5f;
+}
diff --git a/src/boot/O2/rcp_utils.c b/src/boot/O2/rcp_utils.c
new file mode 100644
index 000000000..f587fad21
--- /dev/null
+++ b/src/boot/O2/rcp_utils.c
@@ -0,0 +1,23 @@
+#include "ultra64.h"
+
+void RcpUtils_PrintRegisterStatus(void) {
+ u32 spStatus = __osSpGetStatus();
+ u32 dpStatus = osDpGetStatus();
+
+ if (spStatus) {
+ // stubbed debug prints
+ }
+
+ if (dpStatus) {
+ // stubbed debug prints
+ }
+}
+
+void RcpUtils_Reset(void) {
+ RcpUtils_PrintRegisterStatus();
+ // Flush the RDP pipeline and freeze clock counter
+ osDpSetStatus(DPC_SET_FREEZE | DPC_SET_FLUSH);
+ // Halt the RSP, disable interrupt on break and set "task done" signal
+ __osSpSetStatus(SP_SET_HALT | SP_SET_TASKDONE | SP_CLR_INTR_BREAK);
+ RcpUtils_PrintRegisterStatus();
+}
diff --git a/src/boot/O2/sleep.c b/src/boot/O2/sleep.c
new file mode 100644
index 000000000..a35abc5b2
--- /dev/null
+++ b/src/boot/O2/sleep.c
@@ -0,0 +1,27 @@
+#include "global.h"
+
+void Sleep_Cycles(u64 time) {
+ OSMesgQueue mq;
+ OSMesg msg[1];
+ OSTimer timer;
+
+ osCreateMesgQueue(&mq, msg, ARRAY_COUNT(msg));
+ osSetTimer(&timer, time, 0, &mq, NULL);
+ osRecvMesg(&mq, NULL, OS_MESG_BLOCK);
+}
+
+void Sleep_Nsec(u32 nsec) {
+ Sleep_Cycles(OS_NSEC_TO_CYCLES(nsec));
+}
+
+void Sleep_Usec(u32 usec) {
+ Sleep_Cycles(OS_USEC_TO_CYCLES(usec));
+}
+
+void Sleep_Msec(u32 ms) {
+ Sleep_Cycles((ms * OS_CPU_COUNTER) / 1000ULL);
+}
+
+void Sleep_Sec(u32 sec) {
+ Sleep_Cycles(sec * OS_CPU_COUNTER);
+}
diff --git a/src/boot/O2/stackcheck.c b/src/boot/O2/stackcheck.c
new file mode 100644
index 000000000..edd1e3a30
--- /dev/null
+++ b/src/boot/O2/stackcheck.c
@@ -0,0 +1,122 @@
+#include "stackcheck.h"
+#include "libc/stdbool.h"
+#include "libc/stdint.h"
+
+StackEntry* sStackInfoListStart = NULL;
+StackEntry* sStackInfoListEnd = NULL;
+
+void StackCheck_Init(StackEntry* entry, void* stackBottom, void* stackTop, u32 initValue, s32 minSpace,
+ const char* name) {
+ if (entry == NULL) {
+ sStackInfoListStart = NULL;
+ } else {
+ StackEntry* iter;
+
+ entry->head = stackBottom;
+ entry->tail = stackTop;
+ entry->initValue = initValue;
+ entry->minSpace = minSpace;
+ entry->name = name;
+ iter = sStackInfoListStart;
+ while (iter) {
+ if (iter == entry) {
+ return;
+ }
+ iter = iter->next;
+ }
+
+ entry->prev = sStackInfoListEnd;
+ entry->next = NULL;
+
+ if (sStackInfoListEnd) {
+ sStackInfoListEnd->next = entry;
+ }
+
+ sStackInfoListEnd = entry;
+ if (sStackInfoListStart == NULL) {
+ sStackInfoListStart = entry;
+ }
+
+ if (entry->minSpace != -1) {
+ u32* addr = entry->head;
+
+ while (addr < (u32*)entry->tail) {
+ *addr++ = entry->initValue;
+ }
+ }
+ }
+}
+
+void StackCheck_Cleanup(StackEntry* entry) {
+ u32 inconsistency = false;
+
+ if (entry->prev == NULL) {
+ if (entry == sStackInfoListStart) {
+ sStackInfoListStart = entry->next;
+ } else {
+ inconsistency = true;
+ }
+ } else {
+ entry->prev->next = entry->next;
+ }
+
+ if (!entry->next) {
+ if (entry == sStackInfoListEnd) {
+ sStackInfoListEnd = entry->prev;
+ } else {
+ inconsistency = true;
+ }
+ }
+
+ if (inconsistency) {}
+}
+
+StackStatus StackCheck_GetState(StackEntry* entry) {
+ u32* last;
+ size_t used;
+ size_t free;
+ StackStatus status;
+
+ for (last = entry->head; last < (u32*)entry->tail; last++) {
+ if (entry->initValue != *last) {
+ break;
+ }
+ }
+
+ used = (uintptr_t)entry->tail - (uintptr_t)last;
+ free = (uintptr_t)last - (uintptr_t)entry->head;
+
+ if (free == 0) {
+ status = STACK_STATUS_OVERFLOW;
+ } else if ((free < (size_t)entry->minSpace) && (entry->minSpace != -1)) {
+ status = STACK_STATUS_WARNING;
+ } else {
+ status = STACK_STATUS_OK;
+ }
+
+ return status;
+}
+
+u32 StackCheck_CheckAll(void) {
+ u32 ret = 0;
+ StackEntry* iter = sStackInfoListStart;
+
+ while (iter != NULL) {
+ StackStatus state = StackCheck_GetState(iter);
+
+ if (state != STACK_STATUS_OK) {
+ ret = 1;
+ }
+ iter = iter->next;
+ }
+
+ return ret;
+}
+
+u32 StackCheck_Check(StackEntry* entry) {
+ if (entry == NULL) {
+ return StackCheck_CheckAll();
+ } else {
+ return StackCheck_GetState(entry);
+ }
+}
diff --git a/src/boot/O2/system_heap.c b/src/boot/O2/system_heap.c
new file mode 100644
index 000000000..365d94596
--- /dev/null
+++ b/src/boot/O2/system_heap.c
@@ -0,0 +1,123 @@
+/**
+ * @file system_heap.c
+ *
+ * @note:
+ * Only SystemHeap_Init() is used, and is essentially just a wrapper for SystemArena_Init().
+ *
+ */
+#include "global.h"
+#include "system_malloc.h"
+
+typedef void (*BlockFunc)(void*);
+typedef void (*BlockFunc1)(void*, u32);
+typedef void (*BlockFunc8)(void*, u32, u32, u32, u32, u32, u32, u32, u32);
+
+typedef struct InitFunc {
+ /* 0x0 */ uintptr_t nextOffset;
+ /* 0x4 */ void (*func)(void);
+} InitFunc; // size = 0x8
+
+void* sInitFuncs = NULL;
+
+char sNew[] = "";
+
+UNK_TYPE1 D_80097508[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x80, 0x00, 0x00,
+ 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+};
+
+void* SystemHeap_Malloc(size_t size) {
+ if (size == 0) {
+ size = 1;
+ }
+
+ return __osMalloc(&gSystemArena, size);
+}
+
+void SystemHeap_Free(void* ptr) {
+ if (ptr != NULL) {
+ __osFree(&gSystemArena, ptr);
+ }
+}
+
+void SystemHeap_RunBlockFunc(void* blk, size_t nBlk, size_t blkSize, BlockFunc blockFunc) {
+ uintptr_t pos = blk;
+
+ for (; pos < (uintptr_t)blk + (nBlk * blkSize); pos += (blkSize & ~0)) {
+ blockFunc(pos);
+ }
+}
+
+void SystemHeap_RunBlockFunc1(void* blk, size_t nBlk, size_t blkSize, BlockFunc1 blockFunc) {
+ uintptr_t pos = blk;
+
+ for (; pos < (uintptr_t)blk + (nBlk * blkSize); pos += (blkSize & ~0)) {
+ blockFunc(pos, 2);
+ }
+}
+
+void* SystemHeap_RunBlockFunc8(void* blk, size_t nBlk, size_t blkSize, BlockFunc8 blockFunc) {
+ if (blk == NULL) {
+ blk = SystemHeap_Malloc(nBlk * blkSize);
+ }
+
+ if ((blk != NULL) && (blockFunc != NULL)) {
+ uintptr_t pos = blk;
+
+ for (; pos < (uintptr_t)blk + (nBlk * blkSize); pos += (blkSize & ~0)) {
+ blockFunc(pos, 0, 0, 0, 0, 0, 0, 0, 0);
+ }
+ }
+
+ return blk;
+}
+
+void SystemHeap_RunBlockFunc1Reverse(void* blk, size_t nBlk, size_t blkSize, BlockFunc1 blockFunc, s32 shouldFree) {
+ uintptr_t pos;
+ uintptr_t start;
+ size_t maskedBlkSize;
+
+ if (blk == NULL) {
+ return;
+ }
+
+ if (blockFunc != NULL) {
+ start = blk;
+ maskedBlkSize = (blkSize & ~0);
+ pos = (uintptr_t)start + (nBlk * blkSize);
+
+ while (pos > start) {
+ pos -= maskedBlkSize;
+ blockFunc(pos, 2);
+ }
+ }
+
+ if (shouldFree) {
+ SystemHeap_Free(blk);
+ }
+}
+
+void SystemHeap_RunInits(void) {
+ InitFunc* initFunc = (InitFunc*)&sInitFuncs;
+ u32 nextOffset = initFunc->nextOffset;
+ InitFunc* prev = NULL;
+
+ while (nextOffset != 0) {
+ initFunc = (InitFunc*)((uintptr_t)initFunc + nextOffset);
+
+ if (initFunc->func != NULL) {
+ (*initFunc->func)();
+ }
+
+ nextOffset = initFunc->nextOffset;
+ initFunc->nextOffset = (uintptr_t)prev;
+ prev = initFunc;
+ }
+
+ sInitFuncs = prev;
+}
+
+void SystemHeap_Init(void* start, size_t size) {
+ SystemArena_Init(start, size);
+ SystemHeap_RunInits();
+}
diff --git a/src/boot/O2/system_malloc.c b/src/boot/O2/system_malloc.c
new file mode 100644
index 000000000..cea017b38
--- /dev/null
+++ b/src/boot/O2/system_malloc.c
@@ -0,0 +1,51 @@
+#include "global.h"
+#include "os_malloc.h"
+
+Arena gSystemArena;
+
+void* SystemArena_Malloc(size_t size) {
+ return __osMalloc(&gSystemArena, size);
+}
+
+void* SystemArena_MallocR(size_t size) {
+ return __osMallocR(&gSystemArena, size);
+}
+
+void* SystemArena_Realloc(void* oldPtr, size_t newSize) {
+ return __osRealloc(&gSystemArena, oldPtr, newSize);
+}
+
+void SystemArena_Free(void* ptr) {
+ __osFree(&gSystemArena, ptr);
+}
+
+void* SystemArena_Calloc(u32 elements, size_t size) {
+ void* ptr;
+ size_t totalSize = elements * size;
+
+ ptr = __osMalloc(&gSystemArena, totalSize);
+ if (ptr != NULL) {
+ bzero(ptr, totalSize);
+ }
+ return ptr;
+}
+
+void SystemArena_GetSizes(size_t* maxFreeBlock, size_t* bytesFree, size_t* bytesAllocated) {
+ __osGetSizes(&gSystemArena, maxFreeBlock, bytesFree, bytesAllocated);
+}
+
+u32 SystemArena_CheckArena(void) {
+ return __osCheckArena(&gSystemArena);
+}
+
+void SystemArena_Init(void* start, size_t size) {
+ __osMallocInit(&gSystemArena, start, size);
+}
+
+void SystemArena_Cleanup(void) {
+ __osMallocCleanup(&gSystemArena);
+}
+
+u8 SystemArena_IsInitialized(void) {
+ return __osMallocIsInitalized(&gSystemArena);
+}