summaryrefslogtreecommitdiff
path: root/src/boot
diff options
context:
space:
mode:
authorDerek Hensley <hensley.derek58@gmail.com>2023-09-11 17:38:31 -0700
committerGitHub <noreply@github.com>2023-09-12 10:38:31 +1000
commit190b78549e0fa1a801786e73d914185e1fbca2a6 (patch)
treebc62310b90bbd59005ef01873ca4a007d8c8ef31 /src/boot
parent39523baf8c52d59b8ca52832c22e70eadf518f8a (diff)
Non libultra Boot Cleanup (#1370)
* reorganize * math64 * rcp_utils * osSyncPrintfUnused * comment spacing
Diffstat (limited to 'src/boot')
-rw-r--r--src/boot/CIC6105.c32
-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
-rw-r--r--src/boot/boot_main.c24
-rw-r--r--src/boot/build.c3
-rw-r--r--src/boot/fault.c1128
-rw-r--r--src/boot/fault_drawer.c310
-rw-r--r--src/boot/idle.c127
-rw-r--r--src/boot/irqmgr.c170
-rw-r--r--src/boot/syncprintf.c10
-rw-r--r--src/boot/viconfig.c57
-rw-r--r--src/boot/yaz0.c150
-rw-r--r--src/boot/z_std_dma.c242
32 files changed, 4235 insertions, 0 deletions
diff --git a/src/boot/CIC6105.c b/src/boot/CIC6105.c
new file mode 100644
index 000000000..e18223b03
--- /dev/null
+++ b/src/boot/CIC6105.c
@@ -0,0 +1,32 @@
+#include "prevent_bss_reordering.h"
+#include "global.h"
+#include "fault.h"
+
+UNK_TYPE4 D_8009BE30;
+UNK_TYPE4 D_8009BE34;
+FaultClient romInfoFaultClient;
+
+void CIC6105_Nop80081820(void) {
+}
+
+void CIC6105_Nop80081828(void) {
+}
+
+void CIC6105_PrintRomInfo(void) {
+ FaultDrawer_DrawText(80, 200, "SP_STATUS %08x", HW_REG(SP_STATUS_REG, u32));
+ FaultDrawer_DrawText(40, 184, "ROM_F [Creator:%s]", gBuildTeam);
+ FaultDrawer_DrawText(56, 192, "[Date:%s]", gBuildDate);
+}
+
+void CIC6105_AddRomInfoFaultPage(void) {
+ Fault_AddClient(&romInfoFaultClient, (void*)CIC6105_PrintRomInfo, NULL, NULL);
+}
+
+void CIC6105_RemoveRomInfoFaultPage(void) {
+ Fault_RemoveClient(&romInfoFaultClient);
+}
+
+void func_800818F4(void) {
+ D_8009BE30 = *(u32*)0xA02FB1F4;
+ D_8009BE34 = *(u32*)0xA02FE1C0;
+}
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);
+}
diff --git a/src/boot/boot_main.c b/src/boot/boot_main.c
new file mode 100644
index 000000000..dcb66fefd
--- /dev/null
+++ b/src/boot/boot_main.c
@@ -0,0 +1,24 @@
+#include "prevent_bss_reordering.h"
+#include "global.h"
+#include "idle.h"
+#include "stack.h"
+#include "stackcheck.h"
+#include "z64thread.h"
+
+StackEntry sBootStackInfo;
+OSThread sIdleThread;
+STACK(sIdleStack, 0x400);
+StackEntry sIdleStackInfo;
+STACK(sBootStack, 0x400);
+
+void bootproc(void) {
+ StackCheck_Init(&sBootStackInfo, sBootStack, STACK_TOP(sBootStack), 0, -1, "boot");
+ osMemSize = osGetMemSize();
+ func_800818F4();
+ osInitialize();
+ osUnmapTLBAll();
+ gCartHandle = osCartRomInit();
+ StackCheck_Init(&sIdleStackInfo, sIdleStack, STACK_TOP(sIdleStack), 0, 0x100, "idle");
+ osCreateThread(&sIdleThread, Z_THREAD_ID_IDLE, Idle_ThreadEntry, NULL, STACK_TOP(sIdleStack), Z_PRIORITY_IDLE);
+ osStartThread(&sIdleThread);
+}
diff --git a/src/boot/build.c b/src/boot/build.c
new file mode 100644
index 000000000..23bd1cd59
--- /dev/null
+++ b/src/boot/build.c
@@ -0,0 +1,3 @@
+const char gBuildTeam[] = "zelda@srd44";
+const char gBuildDate[] = "00-07-31 17:04:16";
+const char gBuildMakeOption[] = "";
diff --git a/src/boot/fault.c b/src/boot/fault.c
new file mode 100644
index 000000000..f62dfb8b7
--- /dev/null
+++ b/src/boot/fault.c
@@ -0,0 +1,1128 @@
+/**
+ * @file fault.c
+ *
+ * This file implements the screen that may be viewed when the game crashes.
+ * This is the second known version of the crash screen, an evolved version from OoT's.
+ *
+ * When the game crashes, a red bar will be drawn to the top-left of the screen, indicating that the
+ * crash screen is available for use. Once this bar appears, it is possible to open the crash screen
+ * with the following button combination:
+ *
+ * (DPad-Left & L & R & C-Right) & Start
+ *
+ * When entering this button combination, buttons that are &'d together must all be pressed together.
+ *
+ * "Clients" may be registered with the crash screen to extend its functionality. There are
+ * two kinds of client, "Client" and "AddressConverterClient". Clients contribute one or
+ * more pages to the crash debugger, while Address Converter Clients allow the crash screen to look up
+ * the virtual addresses of dynamically allocated overlays.
+ *
+ * The crash screen has multiple pages:
+ * - Thread Context
+ * This page shows information about the thread on which the program crashed. It displays
+ * the cause of the crash, state of general-purpose registers, state of floating-point registers
+ * and the floating-point status register. If a floating-point exception caused the crash, it will
+ * be displayed next to the floating-point status register.
+ * - Stack Trace
+ * This page displays a full backtrace from the crashing function back to the start of the thread. It
+ * displays the Program Counter for each function and, if applicable, the Virtual Program Counter
+ * for relocated functions in overlays.
+ * - Client Pages
+ * After the stack trace page, currently registered clients are processed and their pages are displayed.
+ * - Memory Dump
+ * This page implements a scrollable memory dump.
+ * - End Screen
+ * This page informs you that there are no more pages to display.
+ *
+ * To navigate the pages, START and A may be used to advance to the next page, and L toggles whether to
+ * automatically scroll to the next page after some time has passed.
+ * DPad-Up may be pressed to enable sending fault pages over osSyncPrintf as well as displaying them on-screen.
+ * DPad-Down disables sending fault pages over osSyncPrintf.
+ */
+
+#include "fault_internal.h"
+#include "fault.h"
+#include "global.h"
+#include "vt.h"
+#include "PR/osint.h"
+#include "stackcheck.h"
+#include "z64thread.h"
+
+FaultMgr* sFaultInstance;
+f32 sFaultTimeTotal; // read but not set anywhere
+
+// data
+const char* sCpuExceptions[] = {
+ "Interrupt",
+ "TLB modification",
+ "TLB exception on load",
+ "TLB exception on store",
+ "Address error on load",
+ "Address error on store",
+ "Bus error on inst.",
+ "Bus error on data",
+ "System call exception",
+ "Breakpoint exception",
+ "Reserved instruction",
+ "Coprocessor unusable",
+ "Arithmetic overflow",
+ "Trap exception",
+ "Virtual coherency on inst.",
+ "Floating point exception",
+ "Watchpoint exception",
+ "Virtual coherency on data",
+};
+
+const char* sFpuExceptions[] = {
+ "Unimplemented operation", "Invalid operation", "Division by zero", "Overflow", "Underflow", "Inexact operation",
+};
+
+void Fault_SleepImpl(u32 duration) {
+ OSTime value = (duration * OS_CPU_COUNTER) / 1000ULL;
+
+ Sleep_Cycles(value);
+}
+
+/**
+ * Registers a fault client.
+ *
+ * Clients contribute at least one page to the crash screen, drawn by `callback`.
+ * Arguments are passed on to the callback through `arg0` and `arg1`.
+ */
+void Fault_AddClient(FaultClient* client, FaultClientCallback callback, void* arg0, void* arg1) {
+ OSIntMask mask;
+ u32 alreadyExists = false;
+
+ mask = osSetIntMask(1);
+
+ // Ensure the client is not already registered
+ {
+ FaultClient* iter = sFaultInstance->clients;
+
+ while (iter != NULL) {
+ if (iter == client) {
+ alreadyExists = true;
+ goto end;
+ }
+ iter = iter->next;
+ }
+ }
+
+ client->callback = callback;
+ client->arg0 = arg0;
+ client->arg1 = arg1;
+ client->next = sFaultInstance->clients;
+ sFaultInstance->clients = client;
+
+end:
+ osSetIntMask(mask);
+
+ if (alreadyExists) {
+ osSyncPrintf(VT_COL(RED, WHITE) "fault_AddClient: %08x は既にリスト中にある\n" VT_RST, client);
+ }
+}
+
+/**
+ * Removes a fault client so that the page is no longer displayed if a crash occurs.
+ */
+void Fault_RemoveClient(FaultClient* client) {
+ FaultClient* iter = sFaultInstance->clients;
+ FaultClient* lastIter = NULL;
+ OSIntMask mask;
+ u32 listIsEmpty = false;
+
+ mask = osSetIntMask(1);
+
+ while (iter) {
+ if (iter == client) {
+ if (lastIter != NULL) {
+ lastIter->next = client->next;
+ } else {
+ sFaultInstance->clients = client;
+ if (sFaultInstance->clients) {
+ sFaultInstance->clients = client->next;
+ } else {
+ listIsEmpty = 1;
+ }
+ }
+ break;
+ }
+
+ lastIter = iter;
+ iter = iter->next;
+ }
+
+ osSetIntMask(mask);
+
+ if (listIsEmpty) {
+ osSyncPrintf(VT_COL(RED, WHITE) "fault_RemoveClient: %08x リスト不整合です\n" VT_RST, client);
+ }
+}
+
+/**
+ * Registers an address converter client. This enables the crash screen to look up virtual
+ * addresses of overlays relocated during runtime. Address conversion is carried out by
+ * `callback`, which either returns a virtual address or NULL if the address could not
+ * be converted.
+ *
+ * The callback is intended to be
+ * `uintptr_t (*callback)(uintptr_t addr, void* arg)`
+ * The callback may return 0 if it could not convert the address
+ */
+void Fault_AddAddrConvClient(FaultAddrConvClient* client, FaultAddrConvClientCallback callback, void* arg) {
+ OSIntMask mask;
+ s32 alreadyExists = false;
+
+ mask = osSetIntMask(1);
+
+ {
+ FaultAddrConvClient* iter = sFaultInstance->addrConvClients;
+
+ while (iter != NULL) {
+ if (iter == client) {
+ alreadyExists = true;
+ goto end;
+ }
+ iter = iter->next;
+ }
+ }
+
+ client->callback = callback;
+ client->arg = arg;
+ client->next = sFaultInstance->addrConvClients;
+ sFaultInstance->addrConvClients = client;
+
+end:
+ osSetIntMask(mask);
+
+ if (alreadyExists) {
+ osSyncPrintf(VT_COL(RED, WHITE) "fault_AddressConverterAddClient: %08x は既にリスト中にある\n" VT_RST, client);
+ }
+}
+
+void Fault_RemoveAddrConvClient(FaultAddrConvClient* client) {
+ FaultAddrConvClient* iter = sFaultInstance->addrConvClients;
+ FaultAddrConvClient* lastIter = NULL;
+ OSIntMask mask;
+ bool listIsEmpty = false;
+
+ mask = osSetIntMask(1);
+
+ while (iter) {
+ if (iter == client) {
+ if (lastIter != NULL) {
+ lastIter->next = client->next;
+ } else {
+ sFaultInstance->addrConvClients = client;
+ if (sFaultInstance->addrConvClients) {
+ sFaultInstance->addrConvClients = client->next;
+ } else {
+ listIsEmpty = true;
+ }
+ }
+ break;
+ }
+
+ lastIter = iter;
+ iter = iter->next;
+ }
+
+ osSetIntMask(mask);
+
+ if (listIsEmpty) {
+ osSyncPrintf(VT_COL(RED, WHITE) "fault_AddressConverterRemoveClient: %08x は既にリスト中にある\n" VT_RST,
+ client);
+ }
+}
+
+/**
+ * Converts `addr` to a virtual address via the registered
+ * address converter clients
+ */
+uintptr_t Fault_ConvertAddress(uintptr_t addr) {
+ uintptr_t ret;
+ FaultAddrConvClient* iter = sFaultInstance->addrConvClients;
+
+ while (iter != NULL) {
+ if (iter->callback != NULL) {
+ ret = iter->callback(addr, iter->arg);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+ iter = iter->next;
+ }
+
+ return 0;
+}
+
+void Fault_Sleep(u32 msec) {
+ Fault_SleepImpl(msec);
+}
+
+void Fault_PadCallback(Input* input) {
+ PadMgr_GetInput2(input, false);
+}
+
+void Fault_UpdatePadImpl(void) {
+ sFaultInstance->padCallback(sFaultInstance->inputs);
+}
+
+/**
+ * Awaits user input
+ *
+ * L toggles auto-scroll
+ * DPad-Up enables osSyncPrintf output
+ * DPad-Down disables osSyncPrintf output
+ * A and DPad-Right continues and returns true
+ * DPad-Left continues and returns false
+ */
+u32 Fault_WaitForInputImpl(void) {
+ Input* input = &sFaultInstance->inputs[0];
+ s32 count = 600;
+ u32 pressedBtn;
+
+ while (true) {
+ Fault_Sleep(1000 / 60);
+ Fault_UpdatePadImpl();
+
+ pressedBtn = input->press.button;
+
+ if (pressedBtn == BTN_L) {
+ sFaultInstance->autoScroll = !sFaultInstance->autoScroll;
+ }
+
+ if (sFaultInstance->autoScroll) {
+ if (count-- < 1) {
+ return false;
+ }
+ } else {
+ if ((pressedBtn == BTN_A) || (pressedBtn == BTN_DRIGHT)) {
+ return false;
+ }
+
+ if (pressedBtn == BTN_DLEFT) {
+ return true;
+ }
+
+ if (pressedBtn == BTN_DUP) {
+ FaultDrawer_SetOsSyncPrintfEnabled(true);
+ }
+
+ if (pressedBtn == BTN_DDOWN) {
+ FaultDrawer_SetOsSyncPrintfEnabled(false);
+ }
+ }
+ }
+}
+
+void Fault_WaitForInput(void) {
+ Fault_WaitForInputImpl();
+}
+
+void Fault_DrawRec(s32 x, s32 y, s32 w, s32 h, u16 color) {
+ FaultDrawer_DrawRecImpl(x, y, x + w - 1, y + h - 1, color);
+}
+
+void Fault_FillScreenBlack(void) {
+ FaultDrawer_SetForeColor(GPACK_RGBA5551(255, 255, 255, 1));
+ FaultDrawer_SetBackColor(GPACK_RGBA5551(0, 0, 0, 1));
+ FaultDrawer_FillScreen();
+ FaultDrawer_SetBackColor(GPACK_RGBA5551(0, 0, 0, 0));
+}
+
+void Fault_FillScreenRed(void) {
+ FaultDrawer_SetForeColor(GPACK_RGBA5551(255, 255, 255, 1));
+ FaultDrawer_SetBackColor(GPACK_RGBA5551(240, 0, 0, 1));
+ FaultDrawer_FillScreen();
+ FaultDrawer_SetBackColor(GPACK_RGBA5551(0, 0, 0, 0));
+}
+
+void Fault_DrawCornerRec(u16 color) {
+ Fault_DrawRec(22, 16, 8, 1, color);
+}
+
+void Fault_PrintFReg(s32 index, f32* value) {
+ u32 raw = *(u32*)value;
+ s32 v0 = ((raw & 0x7F800000) >> 0x17) - 0x7F;
+
+ if ((v0 >= -0x7E && v0 < 0x80) || raw == 0) {
+ FaultDrawer_Printf("F%02d:%14.7e ", index, *value);
+ } else {
+ // Print subnormal floats as their IEEE-754 hex representation
+ FaultDrawer_Printf("F%02d: %08x(16) ", index, raw);
+ }
+}
+
+void Fault_LogFReg(s32 idx, f32* value) {
+ u32 raw = *(u32*)value;
+ s32 v0 = ((raw & 0x7F800000) >> 0x17) - 0x7F;
+
+ if ((v0 >= -0x7E && v0 < 0x80) || raw == 0) {
+ osSyncPrintf("F%02d:%14.7e ", idx, *value);
+ } else {
+ osSyncPrintf("F%02d: %08x(16) ", idx, *(u32*)value);
+ }
+}
+
+void Fault_PrintFPCR(u32 value) {
+ s32 i;
+ u32 flag = 0x20000;
+
+ FaultDrawer_Printf("FPCSR:%08xH ", value);
+
+ // Go through each of the six causes and print the name of
+ // the first cause that is set
+ for (i = 0; i < ARRAY_COUNT(sFpuExceptions); i++) {
+ if (value & flag) {
+ FaultDrawer_Printf("(%s)", sFpuExceptions[i]);
+ break;
+ }
+ flag >>= 1;
+ }
+ FaultDrawer_Printf("\n");
+}
+
+void Fault_LogFPCSR(u32 value) {
+ s32 i;
+ u32 flag = 0x20000;
+
+ osSyncPrintf("FPCSR:%08xH ", value);
+ for (i = 0; i < ARRAY_COUNT(sFpuExceptions); i++) {
+ if (value & flag) {
+ osSyncPrintf("(%s)\n", sFpuExceptions[i]);
+ break;
+ }
+ flag >>= 1;
+ }
+}
+
+void Fault_PrintThreadContext(OSThread* thread) {
+ __OSThreadContext* threadCtx;
+ s16 causeStrIdx = _SHIFTR((u32)thread->context.cause, 2, 5);
+
+ if (causeStrIdx == 23) { // Watchpoint
+ causeStrIdx = 16;
+ }
+ if (causeStrIdx == 31) { // Virtual coherency on data
+ causeStrIdx = 17;
+ }
+
+ FaultDrawer_FillScreen();
+ FaultDrawer_SetCharPad(-2, 4);
+ FaultDrawer_SetCursor(22, 20);
+
+ threadCtx = &thread->context;
+ FaultDrawer_Printf("THREAD:%d (%d:%s)\n", thread->id, causeStrIdx, sCpuExceptions[causeStrIdx]);
+ FaultDrawer_SetCharPad(-1, 0);
+
+ FaultDrawer_Printf("PC:%08xH SR:%08xH VA:%08xH\n", (u32)threadCtx->pc, (u32)threadCtx->sr,
+ (u32)threadCtx->badvaddr);
+ FaultDrawer_Printf("AT:%08xH V0:%08xH V1:%08xH\n", (u32)threadCtx->at, (u32)threadCtx->v0, (u32)threadCtx->v1);
+ FaultDrawer_Printf("A0:%08xH A1:%08xH A2:%08xH\n", (u32)threadCtx->a0, (u32)threadCtx->a1, (u32)threadCtx->a2);
+ FaultDrawer_Printf("A3:%08xH T0:%08xH T1:%08xH\n", (u32)threadCtx->a3, (u32)threadCtx->t0, (u32)threadCtx->t1);
+ FaultDrawer_Printf("T2:%08xH T3:%08xH T4:%08xH\n", (u32)threadCtx->t2, (u32)threadCtx->t3, (u32)threadCtx->t4);
+ FaultDrawer_Printf("T5:%08xH T6:%08xH T7:%08xH\n", (u32)threadCtx->t5, (u32)threadCtx->t6, (u32)threadCtx->t7);
+ FaultDrawer_Printf("S0:%08xH S1:%08xH S2:%08xH\n", (u32)threadCtx->s0, (u32)threadCtx->s1, (u32)threadCtx->s2);
+ FaultDrawer_Printf("S3:%08xH S4:%08xH S5:%08xH\n", (u32)threadCtx->s3, (u32)threadCtx->s4, (u32)threadCtx->s5);
+ FaultDrawer_Printf("S6:%08xH S7:%08xH T8:%08xH\n", (u32)threadCtx->s6, (u32)threadCtx->s7, (u32)threadCtx->t8);
+ FaultDrawer_Printf("T9:%08xH GP:%08xH SP:%08xH\n", (u32)threadCtx->t9, (u32)threadCtx->gp, (u32)threadCtx->sp);
+ FaultDrawer_Printf("S8:%08xH RA:%08xH LO:%08xH\n\n", (u32)threadCtx->s8, (u32)threadCtx->ra, (u32)threadCtx->lo);
+
+ Fault_PrintFPCR(threadCtx->fpcsr);
+ FaultDrawer_Printf("\n");
+ Fault_PrintFReg(0, &threadCtx->fp0.f.f_even);
+ Fault_PrintFReg(2, &threadCtx->fp2.f.f_even);
+ FaultDrawer_Printf("\n");
+ Fault_PrintFReg(4, &threadCtx->fp4.f.f_even);
+ Fault_PrintFReg(6, &threadCtx->fp6.f.f_even);
+ FaultDrawer_Printf("\n");
+ Fault_PrintFReg(8, &threadCtx->fp8.f.f_even);
+ Fault_PrintFReg(0xA, &threadCtx->fp10.f.f_even);
+ FaultDrawer_Printf("\n");
+ Fault_PrintFReg(0xC, &threadCtx->fp12.f.f_even);
+ Fault_PrintFReg(0xE, &threadCtx->fp14.f.f_even);
+ FaultDrawer_Printf("\n");
+ Fault_PrintFReg(0x10, &threadCtx->fp16.f.f_even);
+ Fault_PrintFReg(0x12, &threadCtx->fp18.f.f_even);
+ FaultDrawer_Printf("\n");
+ Fault_PrintFReg(0x14, &threadCtx->fp20.f.f_even);
+ Fault_PrintFReg(0x16, &threadCtx->fp22.f.f_even);
+ FaultDrawer_Printf("\n");
+ Fault_PrintFReg(0x18, &threadCtx->fp24.f.f_even);
+ Fault_PrintFReg(0x1A, &threadCtx->fp26.f.f_even);
+ FaultDrawer_Printf("\n");
+ Fault_PrintFReg(0x1C, &threadCtx->fp28.f.f_even);
+ Fault_PrintFReg(0x1E, &threadCtx->fp30.f.f_even);
+ FaultDrawer_Printf("\n");
+ FaultDrawer_SetCharPad(0, 0);
+
+ if (sFaultTimeTotal != 0.0f) {
+ FaultDrawer_DrawText(160, 216, "%5.2f sec\n", sFaultTimeTotal);
+ }
+}
+
+void osSyncPrintfThreadContext(OSThread* thread) {
+ __OSThreadContext* threadCtx;
+ s16 causeStrIdx = _SHIFTR((u32)thread->context.cause, 2, 5);
+
+ if (causeStrIdx == 23) { // Watchpoint
+ causeStrIdx = 16;
+ }
+ if (causeStrIdx == 31) { // Virtual coherency on data
+ causeStrIdx = 17;
+ }
+
+ threadCtx = &thread->context;
+ osSyncPrintf("\n");
+ osSyncPrintf("THREAD ID:%d (%d:%s)\n", thread->id, causeStrIdx, sCpuExceptions[causeStrIdx]);
+
+ osSyncPrintf("PC:%08xH SR:%08xH VA:%08xH\n", (u32)threadCtx->pc, (u32)threadCtx->sr, (u32)threadCtx->badvaddr);
+ osSyncPrintf("AT:%08xH V0:%08xH V1:%08xH\n", (u32)threadCtx->at, (u32)threadCtx->v0, (u32)threadCtx->v1);
+ osSyncPrintf("A0:%08xH A1:%08xH A2:%08xH\n", (u32)threadCtx->a0, (u32)threadCtx->a1, (u32)threadCtx->a2);
+ osSyncPrintf("A3:%08xH T0:%08xH T1:%08xH\n", (u32)threadCtx->a3, (u32)threadCtx->t0, (u32)threadCtx->t1);
+ osSyncPrintf("T2:%08xH T3:%08xH T4:%08xH\n", (u32)threadCtx->t2, (u32)threadCtx->t3, (u32)threadCtx->t4);
+ osSyncPrintf("T5:%08xH T6:%08xH T7:%08xH\n", (u32)threadCtx->t5, (u32)threadCtx->t6, (u32)threadCtx->t7);
+ osSyncPrintf("S0:%08xH S1:%08xH S2:%08xH\n", (u32)threadCtx->s0, (u32)threadCtx->s1, (u32)threadCtx->s2);
+ osSyncPrintf("S3:%08xH S4:%08xH S5:%08xH\n", (u32)threadCtx->s3, (u32)threadCtx->s4, (u32)threadCtx->s5);
+ osSyncPrintf("S6:%08xH S7:%08xH T8:%08xH\n", (u32)threadCtx->s6, (u32)threadCtx->s7, (u32)threadCtx->t8);
+ osSyncPrintf("T9:%08xH GP:%08xH SP:%08xH\n", (u32)threadCtx->t9, (u32)threadCtx->gp, (u32)threadCtx->sp);
+ osSyncPrintf("S8:%08xH RA:%08xH LO:%08xH\n", (u32)threadCtx->s8, (u32)threadCtx->ra, (u32)threadCtx->lo);
+ osSyncPrintf("\n");
+ Fault_LogFPCSR(threadCtx->fpcsr);
+ osSyncPrintf("\n");
+ Fault_LogFReg(0, &threadCtx->fp0.f.f_even);
+ Fault_LogFReg(2, &threadCtx->fp2.f.f_even);
+ osSyncPrintf("\n");
+ Fault_LogFReg(4, &threadCtx->fp4.f.f_even);
+ Fault_LogFReg(6, &threadCtx->fp6.f.f_even);
+ osSyncPrintf("\n");
+ Fault_LogFReg(8, &threadCtx->fp8.f.f_even);
+ Fault_LogFReg(10, &threadCtx->fp10.f.f_even);
+ osSyncPrintf("\n");
+ Fault_LogFReg(12, &threadCtx->fp12.f.f_even);
+ Fault_LogFReg(14, &threadCtx->fp14.f.f_even);
+ osSyncPrintf("\n");
+ Fault_LogFReg(16, &threadCtx->fp16.f.f_even);
+ Fault_LogFReg(18, &threadCtx->fp18.f.f_even);
+ osSyncPrintf("\n");
+ Fault_LogFReg(20, &threadCtx->fp20.f.f_even);
+ Fault_LogFReg(22, &threadCtx->fp22.f.f_even);
+ osSyncPrintf("\n");
+ Fault_LogFReg(24, &threadCtx->fp24.f.f_even);
+ Fault_LogFReg(26, &threadCtx->fp26.f.f_even);
+ osSyncPrintf("\n");
+ Fault_LogFReg(28, &threadCtx->fp28.f.f_even);
+ Fault_LogFReg(30, &threadCtx->fp30.f.f_even);
+ osSyncPrintf("\n");
+}
+
+/**
+ * Iterates through the active thread queue for a user thread with either
+ * the CPU break or Fault flag set.
+ */
+OSThread* Fault_FindFaultedThread(void) {
+ OSThread* iter = __osGetActiveQueue();
+
+ while (iter->priority != OS_PRIORITY_THREADTAIL) {
+ if ((iter->priority > OS_PRIORITY_IDLE) && (iter->priority < OS_PRIORITY_APPMAX) &&
+ (iter->flags & (OS_FLAG_CPU_BREAK | OS_FLAG_FAULT))) {
+ return iter;
+ }
+ iter = iter->tlnext;
+ }
+
+ return NULL;
+}
+void Fault_Wait5Seconds(void) {
+ s32 pad;
+ OSTime start = osGetTime();
+
+ do {
+ Fault_Sleep(1000 / 60);
+ } while ((osGetTime() - start) <= OS_SEC_TO_CYCLES(5));
+
+ sFaultInstance->autoScroll = true;
+}
+
+/**
+ * Waits for the following button combination to be entered before returning:
+ *
+ * (DPad-Left & L & R & C-Right) & Start
+ */
+void Fault_WaitForButtonCombo(void) {
+ Input* input = &sFaultInstance->inputs[0];
+
+ FaultDrawer_SetForeColor(GPACK_RGBA5551(255, 255, 255, 1));
+ FaultDrawer_SetBackColor(GPACK_RGBA5551(0, 0, 0, 1));
+
+ do {
+ do {
+ Fault_Sleep(1000 / 60);
+ Fault_UpdatePadImpl();
+ } while (!CHECK_BTN_ALL(input->press.button, BTN_RESET));
+ } while (!CHECK_BTN_ALL(input->cur.button, BTN_DLEFT | BTN_L | BTN_R | BTN_CRIGHT));
+}
+
+void Fault_DrawMemDumpContents(const char* title, uintptr_t addr, u32 param_3) {
+ uintptr_t alignedAddr = addr;
+ u32* writeAddr;
+ s32 y;
+ s32 x;
+
+ // Ensure address is within the bounds of RDRAM (Fault_DrawMemDump has already done this)
+ if (alignedAddr < K0BASE) {
+ alignedAddr = K0BASE;
+ }
+ // 8MB RAM, leave room to display 0x100 bytes on the final page
+ //! @bug The loop below draws 22 * 4 * 4 = 0x160 bytes per page. Due to this, by scrolling further than
+ //! 0x807FFEA0 some invalid bytes are read from outside of 8MB RDRAM space. This does not cause a crash,
+ //! however the values it displays are meaningless. On N64 hardware these invalid addresses are read as 0.
+ if (alignedAddr > (K0BASE + 0x800000 - 0x100)) {
+ alignedAddr = K0BASE + 0x800000 - 0x100;
+ }
+
+ // Ensure address is word-aligned
+ alignedAddr &= ~3;
+ writeAddr = (u32*)alignedAddr;
+
+ Fault_FillScreenBlack();
+ FaultDrawer_SetCharPad(-2, 0);
+
+ FaultDrawer_DrawText(36, 18, "%s %08x", title ? title : "PrintDump", alignedAddr);
+
+ if (alignedAddr >= K0BASE && alignedAddr < K2BASE) {
+ for (y = 0; y < 22; y++) {
+ FaultDrawer_DrawText(24, 28 + y * 9, "%06x", writeAddr);
+ for (x = 0; x < 4; x++) {
+ FaultDrawer_DrawText(82 + x * 52, 28 + y * 9, "%08x", *writeAddr++);
+ }
+ }
+ }
+
+ FaultDrawer_SetCharPad(0, 0);
+}
+
+/**
+ * Draws the memory dump page.
+ *
+ * DPad-Up scrolls up.
+ * DPad-Down scrolls down.
+ * Holding A while scrolling speeds up scrolling by a factor of 0x10.
+ * Holding B while scrolling speeds up scrolling by a factor of 0x100.
+ *
+ * L toggles auto-scrolling pages.
+ * START and A move on to the next page.
+ *
+ * @param pc Program counter, pressing C-Up jumps to this address
+ * @param sp Stack pointer, pressing C-Down jumps to this address
+ * @param cLeftJump Unused parameter, pressing C-Left jumps to this address
+ * @param cRightJump Unused parameter, pressing C-Right jumps to this address
+ */
+void Fault_DrawMemDump(uintptr_t pc, uintptr_t sp, uintptr_t cLeftJump, uintptr_t cRightJump) {
+ s32 scrollCountdown;
+ s32 off;
+ Input* input = &sFaultInstance->inputs[0];
+ uintptr_t addr = pc;
+
+ do {
+ scrollCountdown = 0;
+ // Ensure address is within the bounds of RDRAM
+ if (addr < K0BASE) {
+ addr = K0BASE;
+ }
+ // 8MB RAM, leave room to display 0x100 bytes on the final page
+ if (addr > (K0BASE + 0x800000 - 0x100)) {
+ addr = K0BASE + 0x800000 - 0x100;
+ }
+
+ // Align down the address to 0x10 bytes and draw the page contents
+ addr &= ~0xF;
+ Fault_DrawMemDumpContents("Dump", addr, 0);
+
+ scrollCountdown = 600;
+ while (sFaultInstance->autoScroll) {
+ // Count down until it's time to move on to the next page
+ if (scrollCountdown == 0) {
+ return;
+ }
+
+ scrollCountdown--;
+
+ Fault_Sleep(1000 / 60);
+ Fault_UpdatePadImpl();
+
+ if (CHECK_BTN_ALL(input->press.button, BTN_L)) {
+ sFaultInstance->autoScroll = false;
+ }
+ }
+
+ // Wait for input
+ do {
+ Fault_Sleep(1000 / 60);
+ Fault_UpdatePadImpl();
+ } while (input->press.button == 0);
+
+ // Move to next page
+ if (CHECK_BTN_ALL(input->press.button, BTN_START)) {
+ return;
+ }
+
+ // Memory dump controls
+
+ off = 0x10;
+ if (CHECK_BTN_ALL(input->cur.button, BTN_A)) {
+ off *= 0x10;
+ }
+ if (CHECK_BTN_ALL(input->cur.button, BTN_B)) {
+ off *= 0x100;
+ }
+ if (CHECK_BTN_ALL(input->press.button, BTN_DUP)) {
+ addr -= off;
+ }
+ if (CHECK_BTN_ALL(input->press.button, BTN_DDOWN)) {
+ addr += off;
+ }
+ if (CHECK_BTN_ALL(input->press.button, BTN_CUP)) {
+ addr = pc;
+ }
+ if (CHECK_BTN_ALL(input->press.button, BTN_CDOWN)) {
+ addr = sp;
+ }
+ if (CHECK_BTN_ALL(input->press.button, BTN_CLEFT)) {
+ addr = cLeftJump;
+ }
+ if (CHECK_BTN_ALL(input->press.button, BTN_CRIGHT)) {
+ addr = cRightJump;
+ }
+
+ } while (!CHECK_BTN_ALL(input->press.button, BTN_L));
+
+ // Resume auto-scroll and move to next page
+ sFaultInstance->autoScroll = true;
+}
+
+/**
+ * Searches a single function's stack frame for the function it was called from.
+ * There are two cases that must be covered: Leaf and non-leaf functions.
+ *
+ * A leaf function is one that does not call any other function, in this case the
+ * return address need not be saved to the stack. Since a leaf function does not
+ * call other functions, only the function the stack trace begins in could possibly
+ * be a leaf function, in which case the return address is in the thread context's
+ * $ra already, as it never left.
+ *
+ * The procedure is therefore
+ * - Iterate instructions
+ * - Once jr $ra is found, set pc to $ra
+ * - Done after delay slot
+ *
+ * A non-leaf function calls other functions, it is necessary for the return address
+ * to be saved to the stack. In these functions, it is important to keep track of the
+ * stack frame size of each function.
+ *
+ * The procedure is therefore
+ * - Iterate instructions
+ * - If lw $ra <imm>($sp) is found, fetch the saved $ra from stack memory
+ * - If addiu $sp, $sp, <imm> is found, modify $sp by the immediate value
+ * - If jr $ra is found, set pc to $ra
+ * - Done after delay slot
+ *
+ * Note that searching for one jr $ra is sufficient, as only leaf functions can have
+ * multiple jr $ra in the same function.
+ *
+ * There is also additional handling for eret and j. Neither of these instructions
+ * appear in IDO compiled C, however do show up in the exception handler. It is not
+ * possible to backtrace through an eret as an interrupt can occur at any time, so
+ * there is no choice but to give up here. For j instructions, they can be followed
+ * and the backtrace may continue as normal.
+ */
+void Fault_WalkStack(uintptr_t* spPtr, uintptr_t* pcPtr, uintptr_t* raPtr) {
+ uintptr_t sp = *spPtr;
+ uintptr_t pc = *pcPtr;
+ uintptr_t ra = *raPtr;
+ u32 lastInsn;
+ u16 insnHi;
+ s16 insnLo;
+ u32 imm;
+
+ if ((sp % 4 != 0) || (sp < K0BASE) || (sp >= K2BASE) || (ra % 4 != 0) || (ra < K0BASE) || (ra >= K2BASE)) {
+ *spPtr = 0;
+ *pcPtr = 0;
+ *raPtr = 0;
+ return;
+ }
+
+ if ((pc % 4 != 0) || (pc < K0BASE) || (pc >= K2BASE)) {
+ *pcPtr = ra;
+ return;
+ }
+
+ lastInsn = 0;
+ while (true) {
+ insnHi = *(uintptr_t*)pc >> 16;
+ insnLo = *(uintptr_t*)pc & 0xFFFF;
+ imm = insnLo;
+
+ if (insnHi == 0x8FBF) {
+ // lw $ra, <imm>($sp)
+ // read return address saved on the stack
+ ra = *(uintptr_t*)(sp + imm);
+ } else if (insnHi == 0x27BD) {
+ // addiu $sp, $sp, <imm>
+ // stack pointer increment or decrement
+ sp += imm;
+ } else if (*(uintptr_t*)pc == 0x42000018) {
+ // eret
+ // cannot backtrace through an eret, give up
+ sp = 0;
+ pc = 0;
+ ra = 0;
+ goto done;
+ }
+ if (lastInsn == 0x3E00008) {
+ // jr $ra
+ // return to previous function
+ pc = ra;
+ goto done;
+ } else if (lastInsn >> 26 == 2) {
+ // j <target>
+ // extract jump target
+ pc = (pc >> 28 << 28) | (lastInsn << 6 >> 4);
+ goto done;
+ }
+
+ lastInsn = *(uintptr_t*)pc;
+ pc += sizeof(u32);
+ }
+
+done:
+ *spPtr = sp;
+ *pcPtr = pc;
+ *raPtr = ra;
+}
+
+/**
+ * Draws the stack trace page contents for the specified thread
+ */
+void Fault_DrawStackTrace(OSThread* thread, u32 flags) {
+ s32 line;
+ uintptr_t sp = thread->context.sp;
+ uintptr_t ra = thread->context.ra;
+ uintptr_t pc = thread->context.pc;
+ s32 pad;
+ uintptr_t addr;
+
+ Fault_FillScreenBlack();
+ FaultDrawer_DrawText(120, 16, "STACK TRACE");
+ FaultDrawer_DrawText(36, 24, "SP PC (VPC)");
+
+ for (line = 1; (line < 22) && (((ra != 0) || (sp != 0)) && (pc != (uintptr_t)__osCleanupThread)); line++) {
+ FaultDrawer_DrawText(0x24, line * 8 + 24, "%08x %08x", sp, pc);
+
+ if (flags & 1) {
+ // Try to convert the relocated program counter to the corresponding unrelocated virtual address
+ addr = Fault_ConvertAddress(pc);
+ if (addr != 0) {
+ FaultDrawer_Printf(" -> %08x", addr);
+ }
+ } else {
+ FaultDrawer_Printf(" -> ????????");
+ }
+
+ Fault_WalkStack(&sp, &pc, &ra);
+ }
+}
+
+void Fault_LogStackTrace(OSThread* thread, u32 flags) {
+ s32 line;
+ uintptr_t sp = thread->context.sp;
+ uintptr_t ra = thread->context.ra;
+ uintptr_t pc = thread->context.pc;
+ uintptr_t addr;
+
+ osSyncPrintf("STACK TRACE");
+ osSyncPrintf("SP PC (VPC)\n");
+
+ for (line = 1; (line < 22) && (((ra != 0) || (sp != 0)) && (pc != (uintptr_t)__osCleanupThread)); line++) {
+ osSyncPrintf("%08x %08x", sp, pc);
+
+ if (flags & 1) {
+ // Try to convert the relocated program counter to the corresponding unrelocated virtual address
+ addr = Fault_ConvertAddress(pc);
+ if (addr != 0) {
+ osSyncPrintf(" -> %08x", addr);
+ }
+ } else {
+ osSyncPrintf(" -> ????????");
+ }
+ osSyncPrintf("\n");
+
+ Fault_WalkStack(&sp, &pc, &ra);
+ }
+}
+
+void Fault_ResumeThread(OSThread* thread) {
+ thread->context.cause = 0;
+ thread->context.fpcsr = 0;
+ thread->context.pc += sizeof(u32);
+ *(u32*)thread->context.pc = 0x0000000D; // write in a break instruction
+ osWritebackDCache((void*)thread->context.pc, 4);
+ osInvalICache((void*)thread->context.pc, 4);
+ osStartThread(thread);
+}
+
+void Fault_DisplayFrameBuffer(void) {
+ void* fb;
+
+ osViSetYScale(1.0f);
+ osViSetMode(&osViModeNtscLan1);
+ osViSetSpecialFeatures(OS_VI_GAMMA_OFF | OS_VI_DITHER_FILTER_ON);
+ osViBlack(false);
+
+ if (sFaultInstance->fb) {
+ fb = sFaultInstance->fb;
+ } else {
+ fb = osViGetNextFramebuffer();
+ if ((uintptr_t)fb == K0BASE) {
+ fb = (void*)(PHYS_TO_K0(osMemSize) - SCREEN_HEIGHT * SCREEN_WIDTH * sizeof(u16));
+ }
+ }
+
+ osViSwapBuffer(fb);
+ FaultDrawer_SetDrawerFrameBuffer(fb, SCREEN_WIDTH, SCREEN_HEIGHT);
+}
+
+/**
+ * Runs all registered fault clients. Each fault client displays a page
+ * on the crash screen.
+ */
+void Fault_ProcessClients(void) {
+ FaultClient* client = sFaultInstance->clients;
+ s32 idx = 0;
+
+ while (client != NULL) {
+ if (client->callback != NULL) {
+ Fault_FillScreenBlack();
+ FaultDrawer_SetCharPad(-2, 0);
+ FaultDrawer_Printf(FAULT_COLOR(DARK_GRAY) "CallBack (%d) %08x %08x %08x\n" FAULT_COLOR(WHITE), idx++,
+ client, client->arg0, client->arg1);
+ FaultDrawer_SetCharPad(0, 0);
+ client->callback(client->arg0, client->arg1);
+ Fault_WaitForInput();
+ Fault_DisplayFrameBuffer();
+ }
+ client = client->next;
+ }
+}
+
+void Fault_SetOptionsFromController3(void) {
+ static u32 faultCustomOptions;
+ Input* input3 = &sFaultInstance->inputs[3];
+ u32 pad;
+ uintptr_t pc;
+ uintptr_t ra;
+ uintptr_t sp;
+
+ // BTN_RESET is the "neutral reset". Corresponds to holding L+R and pressing S
+ if (CHECK_BTN_ALL(input3->press.button, BTN_RESET)) {
+ faultCustomOptions = !faultCustomOptions;
+ }
+
+ if (faultCustomOptions) {
+ pc = gGraphThread.context.pc;
+ ra = gGraphThread.context.ra;
+ sp = gGraphThread.context.sp;
+ if (CHECK_BTN_ALL(input3->cur.button, BTN_R)) {
+ static u32 faultCopyToLog;
+
+ faultCopyToLog = !faultCopyToLog;
+ FaultDrawer_SetOsSyncPrintfEnabled(faultCopyToLog);
+ }
+ if (CHECK_BTN_ALL(input3->cur.button, BTN_A)) {
+ osSyncPrintf("GRAPH PC=%08x RA=%08x STACK=%08x\n", pc, ra, sp);
+ }
+ if (CHECK_BTN_ALL(input3->cur.button, BTN_B)) {
+ FaultDrawer_SetDrawerFrameBuffer(osViGetNextFramebuffer(), 0x140, 0xF0);
+ Fault_DrawRec(0, 0xD7, 0x140, 9, 1);
+ FaultDrawer_SetCharPad(-2, 0);
+ FaultDrawer_DrawText(0x20, 0xD8, "GRAPH PC %08x RA %08x SP %08x", pc, ra, sp);
+ }
+ }
+}
+
+void Fault_UpdatePad(void) {
+ Fault_UpdatePadImpl();
+ Fault_SetOptionsFromController3();
+}
+
+#define FAULT_MSG_CPU_BREAK ((OSMesg)1)
+#define FAULT_MSG_FAULT ((OSMesg)2)
+#define FAULT_MSG_UNK ((OSMesg)3)
+
+void Fault_ThreadEntry(void* arg) {
+ OSMesg msg;
+ u32 pad;
+ OSThread* faultedThread;
+
+ // Direct OS event messages to the fault event queue
+ osSetEventMesg(OS_EVENT_CPU_BREAK, &sFaultInstance->queue, FAULT_MSG_CPU_BREAK);
+ osSetEventMesg(OS_EVENT_FAULT, &sFaultInstance->queue, FAULT_MSG_FAULT);
+
+ while (true) {
+ do {
+ // Wait for a thread to hit a fault
+ osRecvMesg(&sFaultInstance->queue, &msg, OS_MESG_BLOCK);
+
+ if (msg == FAULT_MSG_CPU_BREAK) {
+ sFaultInstance->msgId = (u32)FAULT_MSG_CPU_BREAK;
+ // "Fault manager: OS_EVENT_CPU_BREAK received"
+ osSyncPrintf("フォルトマネージャ:OS_EVENT_CPU_BREAKを受信しました\n");
+ } else if (msg == FAULT_MSG_FAULT) {
+ sFaultInstance->msgId = (u32)FAULT_MSG_FAULT;
+ // "Fault manager: OS_EVENT_FAULT received"
+ osSyncPrintf("フォルトマネージャ:OS_EVENT_FAULTを受信しました\n");
+ } else if (msg == FAULT_MSG_UNK) {
+ Fault_UpdatePad();
+ faultedThread = NULL;
+ continue;
+ } else {
+ sFaultInstance->msgId = (u32)FAULT_MSG_UNK;
+ // "Fault manager: received an unknown message"
+ osSyncPrintf("フォルトマネージャ:不明なメッセージを受信しました\n");
+ }
+
+ faultedThread = __osGetCurrFaultedThread();
+ osSyncPrintf("__osGetCurrFaultedThread()=%08x\n", faultedThread);
+
+ if (faultedThread == NULL) {
+ faultedThread = Fault_FindFaultedThread();
+ osSyncPrintf("FindFaultedThread()=%08x\n", faultedThread);
+ }
+ } while (faultedThread == NULL);
+
+ __osSetFpcCsr(__osGetFpcCsr() & ~(FPCSR_EV | FPCSR_EZ | FPCSR_EO | FPCSR_EU | FPCSR_EI));
+ sFaultInstance->faultedThread = faultedThread;
+
+ while (!sFaultInstance->faultHandlerEnabled) {
+ Fault_Sleep(1000);
+ }
+ Fault_Sleep(1000 / 2);
+
+ // Show fault framebuffer
+ Fault_DisplayFrameBuffer();
+
+ if (sFaultInstance->autoScroll) {
+ Fault_Wait5Seconds();
+ } else {
+ // Draw error bar signifying the crash screen is available
+ Fault_DrawCornerRec(GPACK_RGBA5551(255, 0, 0, 1));
+ Fault_WaitForButtonCombo();
+ }
+
+ // Set auto-scrolling and default colors
+ sFaultInstance->autoScroll = true;
+ FaultDrawer_SetForeColor(GPACK_RGBA5551(255, 255, 255, 1));
+ FaultDrawer_SetBackColor(GPACK_RGBA5551(0, 0, 0, 0));
+
+ // Draw pages
+ do {
+ // Thread context page
+ Fault_PrintThreadContext(faultedThread);
+ osSyncPrintfThreadContext(faultedThread);
+ Fault_WaitForInput();
+
+ // Stack trace page
+ Fault_DrawStackTrace(faultedThread, 0);
+ Fault_LogStackTrace(faultedThread, 0);
+ Fault_WaitForInput();
+
+ // Client pages
+ Fault_ProcessClients();
+
+ // Memory dump page
+ Fault_DrawMemDump((u32)(faultedThread->context.pc - 0x100), (u32)faultedThread->context.sp, 0, 0);
+ Fault_DrawStackTrace(faultedThread, 1);
+ Fault_LogStackTrace(faultedThread, 1);
+ Fault_WaitForInput();
+
+ // End page
+ Fault_FillScreenRed();
+ FaultDrawer_DrawText(64, 80, " CONGRATURATIONS! ");
+ FaultDrawer_DrawText(64, 90, "All Pages are displayed.");
+ FaultDrawer_DrawText(64, 100, " THANK YOU! ");
+ FaultDrawer_DrawText(64, 110, " You are great debugger!");
+ Fault_WaitForInput();
+ } while (!sFaultInstance->exit);
+
+ while (!sFaultInstance->exit) {}
+
+ Fault_ResumeThread(faultedThread);
+ }
+}
+
+void Fault_SetFrameBuffer(void* fb, u16 w, u16 h) {
+ sFaultInstance->fb = fb;
+ FaultDrawer_SetDrawerFrameBuffer(fb, w, h);
+}
+
+STACK(sFaultStack, 0x600);
+StackEntry sFaultStackInfo;
+FaultMgr gFaultMgr;
+
+void Fault_Init(void) {
+ sFaultInstance = &gFaultMgr;
+ bzero(sFaultInstance, sizeof(FaultMgr));
+ FaultDrawer_Init();
+ FaultDrawer_SetInputCallback(Fault_WaitForInput);
+ sFaultInstance->exit = false;
+ sFaultInstance->msgId = 0;
+ sFaultInstance->faultHandlerEnabled = false;
+ sFaultInstance->faultedThread = NULL;
+ sFaultInstance->padCallback = Fault_PadCallback;
+ sFaultInstance->clients = NULL;
+ sFaultInstance->autoScroll = false;
+ gFaultMgr.faultHandlerEnabled = true;
+ osCreateMesgQueue(&sFaultInstance->queue, sFaultInstance->msg, ARRAY_COUNT(sFaultInstance->msg));
+ StackCheck_Init(&sFaultStackInfo, sFaultStack, STACK_TOP(sFaultStack), 0, 0x100, "fault");
+ osCreateThread(&sFaultInstance->thread, Z_THREAD_ID_FAULT, Fault_ThreadEntry, NULL, STACK_TOP(sFaultStack),
+ Z_PRIORITY_FAULT);
+ osStartThread(&sFaultInstance->thread);
+}
+
+/**
+ * Fault page for Hungup crashes. Displays the thread id and two messages
+ * specified in arguments to `Fault_AddHungupAndCrashImpl`.
+ */
+void Fault_HangupFaultClient(const char* exp1, const char* exp2) {
+ osSyncPrintf("HungUp on Thread %d\n", osGetThreadId(NULL));
+ osSyncPrintf("%s\n", exp1 != NULL ? exp1 : "(NULL)");
+ osSyncPrintf("%s\n", exp2 != NULL ? exp2 : "(NULL)");
+ FaultDrawer_Printf("HungUp on Thread %d\n", osGetThreadId(NULL));
+ FaultDrawer_Printf("%s\n", exp1 != NULL ? exp1 : "(NULL)");
+ FaultDrawer_Printf("%s\n", exp2 != NULL ? exp2 : "(NULL)");
+}
+
+/**
+ * Immediately crashes the current thread, for cases where an irrecoverable
+ * error occurs. The parameters specify two messages detailing the error, one
+ * or both may be NULL.
+ */
+void Fault_AddHungupAndCrashImpl(const char* exp1, const char* exp2) {
+ FaultClient client;
+ s32 pad;
+
+ Fault_AddClient(&client, (void*)Fault_HangupFaultClient, (void*)exp1, (void*)exp2);
+ *(u32*)0x11111111 = 0; // trigger an exception via unaligned memory access
+}
+
+/**
+ * Like `Fault_AddHungupAndCrashImpl`, however provides a fixed message containing
+ * file and line number
+ */
+void Fault_AddHungupAndCrash(const char* file, s32 line) {
+ char msg[0x100];
+
+ sprintf(msg, "HungUp %s:%d", file, line);
+ Fault_AddHungupAndCrashImpl(msg, NULL);
+}
diff --git a/src/boot/fault_drawer.c b/src/boot/fault_drawer.c
new file mode 100644
index 000000000..6031ccaa6
--- /dev/null
+++ b/src/boot/fault_drawer.c
@@ -0,0 +1,310 @@
+/**
+ * @file fault_drawer.c
+ *
+ * Implements routines for drawing text with a fixed font directly to a framebuffer, used in displaying
+ * the crash screen implemented by fault.c
+ */
+
+#include "fault.h"
+#include "fault_internal.h"
+#include "global.h"
+#include "vt.h"
+
+typedef struct {
+ /* 0x00 */ u16* frameBuffer;
+ /* 0x04 */ u16 w;
+ /* 0x06 */ u16 h;
+ /* 0x08 */ u16 yStart;
+ /* 0x0A */ u16 yEnd;
+ /* 0x0C */ u16 xStart;
+ /* 0x0E */ u16 xEnd;
+ /* 0x10 */ u16 foreColor;
+ /* 0x12 */ u16 backColor;
+ /* 0x14 */ u16 cursorX;
+ /* 0x16 */ u16 cursorY;
+ /* 0x18 */ const u32* fontData;
+ /* 0x1C */ u8 charW;
+ /* 0x1D */ u8 charH;
+ /* 0x1E */ s8 charWPad;
+ /* 0x1F */ s8 charHPad;
+ /* 0x20 */ u16 printColors[10];
+ /* 0x34 */ u8 escCode; // bool
+ /* 0x35 */ u8 osSyncPrintfEnabled;
+ /* 0x38 */ FaultDrawerCallback inputCallback;
+} FaultDrawer; // size = 0x3C
+
+extern const u32 sFaultDrawerFont[];
+
+FaultDrawer sFaultDrawer;
+
+FaultDrawer* sFaultDrawerInstance = &sFaultDrawer;
+
+#define FAULT_DRAWER_CURSOR_X 22
+#define FAULT_DRAWER_CURSOR_Y 16
+
+FaultDrawer sFaultDrawerDefault = {
+ FAULT_FB_ADDRESS, // frameBuffer
+ SCREEN_WIDTH, // w
+ SCREEN_HEIGHT, // h
+ FAULT_DRAWER_CURSOR_Y, // yStart
+ SCREEN_HEIGHT - FAULT_DRAWER_CURSOR_Y - 1, // yEnd
+ FAULT_DRAWER_CURSOR_X, // xStart
+ SCREEN_WIDTH - FAULT_DRAWER_CURSOR_X - 1, // xEnd
+ GPACK_RGBA5551(255, 255, 255, 255), // foreColor
+ GPACK_RGBA5551(0, 0, 0, 0), // backColor
+ FAULT_DRAWER_CURSOR_X, // cursorX
+ FAULT_DRAWER_CURSOR_Y, // cursorY
+ sFaultDrawerFont, // fontData
+ 8, // charW
+ 8, // charH
+ 0, // charWPad
+ 0, // charHPad
+ {
+ // printColors
+ GPACK_RGBA5551(0, 0, 0, 1), // BLACK
+ GPACK_RGBA5551(255, 0, 0, 1), // RED
+ GPACK_RGBA5551(0, 255, 0, 1), // GREEN
+ GPACK_RGBA5551(255, 255, 0, 1), // YELLOW
+ GPACK_RGBA5551(0, 0, 255, 1), // BLUE
+ GPACK_RGBA5551(255, 0, 255, 1), // MAGENTA
+ GPACK_RGBA5551(0, 255, 255, 1), // CYAN
+ GPACK_RGBA5551(255, 255, 255, 1), // WHITE
+ GPACK_RGBA5551(120, 120, 120, 1), // DARK GRAY
+ GPACK_RGBA5551(176, 176, 176, 1), // LIGHT GRAY
+ },
+ false, // escCode
+ false, // osSyncPrintfEnabled
+ NULL, // inputCallback
+};
+
+//! TODO: Needs to be extracted
+#pragma GLOBAL_ASM("asm/non_matchings/boot/fault_drawer/sFaultDrawerFont.s")
+
+void FaultDrawer_SetOsSyncPrintfEnabled(u32 enabled) {
+ sFaultDrawerInstance->osSyncPrintfEnabled = enabled;
+}
+
+void FaultDrawer_DrawRecImpl(s32 xStart, s32 yStart, s32 xEnd, s32 yEnd, u16 color) {
+ u16* frameBuffer;
+ s32 x;
+ s32 y;
+ s32 xDiff = sFaultDrawerInstance->w - xStart;
+ s32 yDiff = sFaultDrawerInstance->h - yStart;
+ s32 xSize = xEnd - xStart + 1;
+ s32 ySize = yEnd - yStart + 1;
+
+ if ((xDiff > 0) && (yDiff > 0)) {
+ if (xDiff < xSize) {
+ xSize = xDiff;
+ }
+
+ if (yDiff < ySize) {
+ ySize = yDiff;
+ }
+
+ frameBuffer = sFaultDrawerInstance->frameBuffer + sFaultDrawerInstance->w * yStart + xStart;
+ for (y = 0; y < ySize; y++) {
+ for (x = 0; x < xSize; x++) {
+ *frameBuffer++ = color;
+ }
+ frameBuffer += sFaultDrawerInstance->w - xSize;
+ }
+
+ osWritebackDCacheAll();
+ }
+}
+
+void FaultDrawer_DrawChar(char c) {
+ s32 x;
+ s32 y;
+ u32 data;
+ s32 cursorX = sFaultDrawerInstance->cursorX;
+ s32 cursorY = sFaultDrawerInstance->cursorY;
+ s32 shift = c % 4;
+ const u32* dataPtr = &sFaultDrawerInstance->fontData[(((c / 8) * 16) + ((c & 4) >> 2))];
+ u16* frameBuffer = sFaultDrawerInstance->frameBuffer + (sFaultDrawerInstance->w * cursorY) + cursorX;
+
+ if ((sFaultDrawerInstance->xStart <= cursorX) &&
+ ((sFaultDrawerInstance->charW + cursorX - 1) <= sFaultDrawerInstance->xEnd) &&
+ (sFaultDrawerInstance->yStart <= cursorY) &&
+ ((sFaultDrawerInstance->charH + cursorY - 1) <= sFaultDrawerInstance->yEnd)) {
+ for (y = 0; y < sFaultDrawerInstance->charH; y++) {
+ u32 mask = 0x10000000 << shift;
+
+ data = *dataPtr;
+ for (x = 0; x < sFaultDrawerInstance->charW; x++) {
+ if (mask & data) {
+ frameBuffer[x] = sFaultDrawerInstance->foreColor;
+ } else if (sFaultDrawerInstance->backColor & 1) {
+ frameBuffer[x] = sFaultDrawerInstance->backColor;
+ }
+ mask >>= 4;
+ }
+ frameBuffer += sFaultDrawerInstance->w;
+ dataPtr += 2;
+ }
+ }
+}
+
+s32 FaultDrawer_ColorToPrintColor(u16 color) {
+ s32 i;
+
+ for (i = 0; i < ARRAY_COUNT(sFaultDrawerInstance->printColors); i++) {
+ if (color == sFaultDrawerInstance->printColors[i]) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+void FaultDrawer_UpdatePrintColor(void) {
+ s32 index;
+
+ if (sFaultDrawerInstance->osSyncPrintfEnabled) {
+ osSyncPrintf(VT_RST);
+
+ index = FaultDrawer_ColorToPrintColor(sFaultDrawerInstance->foreColor);
+ if ((index >= 0) && (index < 8)) {
+ osSyncPrintf(VT_SGR("3%d"), index);
+ }
+
+ index = FaultDrawer_ColorToPrintColor(sFaultDrawerInstance->backColor);
+ if ((index >= 0) && (index < 8)) {
+ osSyncPrintf(VT_SGR("4%d"), index);
+ }
+ }
+}
+
+void FaultDrawer_SetForeColor(u16 color) {
+ sFaultDrawerInstance->foreColor = color;
+ FaultDrawer_UpdatePrintColor();
+}
+
+void FaultDrawer_SetBackColor(u16 color) {
+ sFaultDrawerInstance->backColor = color;
+ FaultDrawer_UpdatePrintColor();
+}
+
+void FaultDrawer_SetFontColor(u16 color) {
+ FaultDrawer_SetForeColor(color | 1); // force alpha to be set
+}
+
+void FaultDrawer_SetCharPad(s8 padW, s8 padH) {
+ sFaultDrawerInstance->charWPad = padW;
+ sFaultDrawerInstance->charHPad = padH;
+}
+
+void FaultDrawer_SetCursor(s32 x, s32 y) {
+ if (sFaultDrawerInstance->osSyncPrintfEnabled) {
+ osSyncPrintf(
+ VT_CUP("%d", "%d"),
+ (y - sFaultDrawerInstance->yStart) / (sFaultDrawerInstance->charH + sFaultDrawerInstance->charHPad),
+ (x - sFaultDrawerInstance->xStart) / (sFaultDrawerInstance->charW + sFaultDrawerInstance->charWPad));
+ }
+ sFaultDrawerInstance->cursorX = x;
+ sFaultDrawerInstance->cursorY = y;
+}
+
+void FaultDrawer_FillScreen() {
+ if (sFaultDrawerInstance->osSyncPrintfEnabled) {
+ osSyncPrintf(VT_CLS);
+ }
+
+ FaultDrawer_DrawRecImpl(sFaultDrawerInstance->xStart, sFaultDrawerInstance->yStart, sFaultDrawerInstance->xEnd,
+ sFaultDrawerInstance->yEnd, sFaultDrawerInstance->backColor | 1);
+ FaultDrawer_SetCursor(sFaultDrawerInstance->xStart, sFaultDrawerInstance->yStart);
+}
+
+void* FaultDrawer_FormatStringFunc(void* arg, const char* str, size_t count) {
+ for (; count != 0; count--, str++) {
+ if (sFaultDrawerInstance->escCode) {
+ sFaultDrawerInstance->escCode = false;
+ if (*str >= '1' && *str <= '9') {
+ FaultDrawer_SetForeColor(sFaultDrawerInstance->printColors[*str - '0']);
+ }
+ } else {
+ switch (*str) {
+ case '\n':
+ if (sFaultDrawerInstance->osSyncPrintfEnabled) {
+ osSyncPrintf("\n");
+ }
+
+ sFaultDrawerInstance->cursorX = sFaultDrawerInstance->w;
+ break;
+
+ case FAULT_ESC:
+ sFaultDrawerInstance->escCode = true;
+ break;
+
+ default:
+ if (sFaultDrawerInstance->osSyncPrintfEnabled) {
+ osSyncPrintf("%c", *str);
+ }
+
+ FaultDrawer_DrawChar(*str);
+ sFaultDrawerInstance->cursorX += sFaultDrawerInstance->charW + sFaultDrawerInstance->charWPad;
+ }
+ }
+
+ if (sFaultDrawerInstance->cursorX >= (sFaultDrawerInstance->xEnd - sFaultDrawerInstance->charW)) {
+ sFaultDrawerInstance->cursorX = sFaultDrawerInstance->xStart;
+ sFaultDrawerInstance->cursorY += sFaultDrawerInstance->charH + sFaultDrawerInstance->charHPad;
+ if (sFaultDrawerInstance->yEnd - sFaultDrawerInstance->charH <= sFaultDrawerInstance->cursorY) {
+ if (sFaultDrawerInstance->inputCallback != NULL) {
+ sFaultDrawerInstance->inputCallback();
+ FaultDrawer_FillScreen();
+ }
+ sFaultDrawerInstance->cursorY = sFaultDrawerInstance->yStart;
+ }
+ }
+ }
+
+ osWritebackDCacheAll();
+
+ return arg;
+}
+
+const char D_80099080[] = "(null)";
+
+s32 FaultDrawer_VPrintf(const char* fmt, va_list ap) {
+ return _Printf(FaultDrawer_FormatStringFunc, sFaultDrawerInstance, fmt, ap);
+}
+
+s32 FaultDrawer_Printf(const char* fmt, ...) {
+ s32 ret;
+ va_list args;
+
+ va_start(args, fmt);
+
+ ret = FaultDrawer_VPrintf(fmt, args);
+
+ va_end(args);
+
+ return ret;
+}
+
+void FaultDrawer_DrawText(s32 x, s32 y, const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+
+ FaultDrawer_SetCursor(x, y);
+ FaultDrawer_VPrintf(fmt, args);
+
+ va_end(args);
+}
+
+void FaultDrawer_SetDrawerFrameBuffer(void* frameBuffer, u16 w, u16 h) {
+ sFaultDrawerInstance->frameBuffer = frameBuffer;
+ sFaultDrawerInstance->w = w;
+ sFaultDrawerInstance->h = h;
+}
+
+void FaultDrawer_SetInputCallback(FaultDrawerCallback callback) {
+ sFaultDrawerInstance->inputCallback = callback;
+}
+
+void FaultDrawer_Init() {
+ sFaultDrawerInstance = &sFaultDrawer;
+ bcopy(&sFaultDrawerDefault, sFaultDrawerInstance, sizeof(FaultDrawer));
+ sFaultDrawerInstance->frameBuffer = (u16*)(PHYS_TO_K0(osMemSize) - SCREEN_HEIGHT * SCREEN_WIDTH * sizeof(u16));
+}
diff --git a/src/boot/idle.c b/src/boot/idle.c
new file mode 100644
index 000000000..ccdf98695
--- /dev/null
+++ b/src/boot/idle.c
@@ -0,0 +1,127 @@
+#include "irqmgr.h"
+#include "main.h"
+#include "stack.h"
+#include "stackcheck.h"
+#include "z64thread.h"
+
+// Variables are put before most headers as a hacky way to bypass bss reordering
+IrqMgr gIrqMgr;
+STACK(sIrqMgrStack, 0x500);
+StackEntry sIrqMgrStackInfo;
+OSThread sMainThread;
+STACK(sMainStack, 0x900);
+StackEntry sMainStackInfo;
+OSMesg sPiMgrCmdBuff[50];
+OSMesgQueue gPiMgrCmdQueue;
+OSViMode gViConfigMode;
+u8 gViConfigModeType;
+
+#include "global.h"
+#include "buffers.h"
+#include "idle.h"
+
+u8 D_80096B20 = 1;
+vu8 gViConfigUseBlack = true;
+u8 gViConfigAdditionalScanLines = 0;
+u32 gViConfigFeatures = OS_VI_DITHER_FILTER_ON | OS_VI_GAMMA_OFF;
+f32 gViConfigXScale = 1.0f;
+f32 gViConfigYScale = 1.0f;
+
+void Main_ClearMemory(void* begin, void* end) {
+ if (begin < end) {
+ bzero(begin, (uintptr_t)end - (uintptr_t)begin);
+ }
+}
+
+void Main_InitFramebuffer(u32* framebuffer, size_t numBytes, u32 value) {
+ for (; numBytes > 0; numBytes -= sizeof(u32)) {
+ *framebuffer++ = value;
+ }
+}
+
+void Main_InitScreen(void) {
+ Main_InitFramebuffer((u32*)gFramebuffer1, sizeof(gFramebuffer1),
+ (GPACK_RGBA5551(0, 0, 0, 1) << 16) | GPACK_RGBA5551(0, 0, 0, 1));
+ ViConfig_UpdateVi(false);
+ osViSwapBuffer(gFramebuffer1);
+ osViBlack(false);
+}
+
+void Main_InitMemory(void) {
+ void* memStart = (void*)0x80000400;
+ void* memEnd = OS_PHYSICAL_TO_K0(osMemSize);
+
+ Main_ClearMemory(memStart, gFramebuffer1);
+ Main_ClearMemory(D_80025D00, bootproc);
+ Main_ClearMemory(gGfxSPTaskYieldBuffer, memEnd);
+}
+
+void Main_Init(void) {
+ DmaRequest dmaReq;
+ OSMesgQueue mq;
+ OSMesg msg[1];
+ size_t prevSize;
+
+ osCreateMesgQueue(&mq, msg, ARRAY_COUNT(msg));
+
+ prevSize = gDmaMgrDmaBuffSize;
+ gDmaMgrDmaBuffSize = 0;
+
+ DmaMgr_SendRequestImpl(&dmaReq, SEGMENT_START(code), SEGMENT_ROM_START(code),
+ SEGMENT_ROM_END(code) - SEGMENT_ROM_START(code), 0, &mq, NULL);
+ Main_InitScreen();
+ Main_InitMemory();
+ osRecvMesg(&mq, NULL, OS_MESG_BLOCK);
+
+ gDmaMgrDmaBuffSize = prevSize;
+
+ Main_ClearMemory(SEGMENT_BSS_START(code), SEGMENT_BSS_END(code));
+}
+
+void Main_ThreadEntry(void* arg) {
+ StackCheck_Init(&sIrqMgrStackInfo, sIrqMgrStack, STACK_TOP(sIrqMgrStack), 0, 0x100, "irqmgr");
+ IrqMgr_Init(&gIrqMgr, STACK_TOP(sIrqMgrStack), Z_PRIORITY_IRQMGR, 1);
+ DmaMgr_Start();
+ Main_Init();
+ Main(arg);
+ DmaMgr_Stop();
+}
+
+void Idle_InitVideo(void) {
+ osCreateViManager(OS_PRIORITY_VIMGR);
+
+ gViConfigFeatures = OS_VI_DITHER_FILTER_ON | OS_VI_GAMMA_OFF;
+ gViConfigXScale = 1.0f;
+ gViConfigYScale = 1.0f;
+
+ switch (osTvType) {
+ case OS_TV_NTSC:
+ gViConfigModeType = OS_VI_NTSC_LAN1;
+ gViConfigMode = osViModeNtscLan1;
+ break;
+
+ case OS_TV_MPAL:
+ gViConfigModeType = OS_VI_MPAL_LAN1;
+ gViConfigMode = osViModeMpalLan1;
+ break;
+
+ case OS_TV_PAL:
+ gViConfigModeType = OS_VI_FPAL_LAN1;
+ gViConfigMode = osViModeFpalLan1;
+ gViConfigYScale = 0.833f;
+ break;
+ }
+
+ D_80096B20 = 1;
+}
+
+void Idle_ThreadEntry(void* arg) {
+ Idle_InitVideo();
+ osCreatePiManager(OS_PRIORITY_PIMGR, &gPiMgrCmdQueue, sPiMgrCmdBuff, ARRAY_COUNT(sPiMgrCmdBuff));
+ StackCheck_Init(&sMainStackInfo, sMainStack, STACK_TOP(sMainStack), 0, 0x400, "main");
+ osCreateThread(&sMainThread, Z_THREAD_ID_MAIN, Main_ThreadEntry, arg, STACK_TOP(sMainStack), Z_PRIORITY_MAIN);
+ osStartThread(&sMainThread);
+ osSetThreadPri(NULL, OS_PRIORITY_IDLE);
+
+ for (;;) {}
+}
diff --git a/src/boot/irqmgr.c b/src/boot/irqmgr.c
new file mode 100644
index 000000000..9a773f279
--- /dev/null
+++ b/src/boot/irqmgr.c
@@ -0,0 +1,170 @@
+#include "global.h"
+#include "stackcheck.h"
+#include "z64thread.h"
+
+vs32 gIrqMgrResetStatus = 0;
+volatile OSTime sIrqMgrResetTime = 0;
+volatile OSTime sIrqMgrRetraceTime = 0;
+s32 sIrqMgrRetraceCount = 0;
+
+void IrqMgr_AddClient(IrqMgr* irqmgr, IrqMgrClient* client, OSMesgQueue* msgQueue) {
+ u32 saveMask;
+
+ saveMask = osSetIntMask(1);
+
+ client->queue = msgQueue;
+ client->next = irqmgr->callbacks;
+ irqmgr->callbacks = client;
+
+ osSetIntMask(saveMask);
+
+ if (irqmgr->prenmiStage > 0) {
+ osSendMesg(client->queue, &irqmgr->prenmiMsg.type, OS_MESG_NOBLOCK);
+ }
+ if (irqmgr->prenmiStage > 1) {
+ osSendMesg(client->queue, &irqmgr->nmiMsg.type, OS_MESG_NOBLOCK);
+ }
+}
+
+void IrqMgr_RemoveClient(IrqMgr* irqmgr, IrqMgrClient* remove) {
+ IrqMgrClient* iter;
+ IrqMgrClient* last;
+ u32 saveMask;
+
+ iter = irqmgr->callbacks;
+ last = NULL;
+
+ saveMask = osSetIntMask(1);
+
+ while (iter != NULL) {
+ if (iter == remove) {
+ if (last != NULL) {
+ last->next = remove->next;
+ } else {
+ irqmgr->callbacks = remove->next;
+ }
+ break;
+ }
+ last = iter;
+ iter = iter->next;
+ }
+
+ osSetIntMask(saveMask);
+}
+
+void IrqMgr_SendMesgForClient(IrqMgr* irqmgr, OSMesg msg) {
+ IrqMgrClient* iter = irqmgr->callbacks;
+
+ while (iter != NULL) {
+ osSendMesg(iter->queue, msg, OS_MESG_NOBLOCK);
+ iter = iter->next;
+ }
+}
+
+void IrqMgr_JamMesgForClient(IrqMgr* irqmgr, OSMesg msg) {
+ IrqMgrClient* iter = irqmgr->callbacks;
+
+ while (iter != NULL) {
+ if (iter->queue->validCount < iter->queue->msgCount) {
+ osSendMesg(iter->queue, msg, OS_MESG_NOBLOCK);
+ }
+ iter = iter->next;
+ }
+}
+
+void IrqMgr_HandlePreNMI(IrqMgr* irqmgr) {
+ gIrqMgrResetStatus = 1;
+ irqmgr->prenmiStage = 1;
+
+ sIrqMgrResetTime = irqmgr->lastPrenmiTime = osGetTime();
+
+ // Wait .45 seconds then generate a stage 2 prenmi interrupt
+ osSetTimer(&irqmgr->prenmiTimer, OS_USEC_TO_CYCLES(450000), 0, &irqmgr->irqQueue, (OSMesg)0x29F);
+
+ IrqMgr_JamMesgForClient(irqmgr, &irqmgr->prenmiMsg.type);
+}
+
+void IrqMgr_CheckStack(void) {
+ StackCheck_Check(NULL);
+}
+
+void IrqMgr_HandlePRENMI450(IrqMgr* irqmgr) {
+ gIrqMgrResetStatus = 2;
+ irqmgr->prenmiStage = 2;
+
+ // Wait .03 seconds then generate a stage 3 prenmi interrupt
+ osSetTimer(&irqmgr->prenmiTimer, OS_USEC_TO_CYCLES(30000), 0, &irqmgr->irqQueue, (OSMesg)0x2A0);
+
+ IrqMgr_SendMesgForClient(irqmgr, &irqmgr->nmiMsg.type);
+}
+
+void IrqMgr_HandlePRENMI480(IrqMgr* irqmgr) {
+ // Wait .52 seconds. After this we will have waited an entire second
+ osSetTimer(&irqmgr->prenmiTimer, OS_USEC_TO_CYCLES(520000), 0, &irqmgr->irqQueue, (OSMesg)0x2A1);
+
+ osAfterPreNMI();
+}
+
+void IrqMgr_HandlePRENMI500(IrqMgr* irqmgr) {
+ IrqMgr_CheckStack();
+}
+void IrqMgr_HandleRetrace(IrqMgr* irqmgr) {
+ if (sIrqMgrRetraceTime == 0) {
+ if (irqmgr->lastFrameTime == 0) {
+ irqmgr->lastFrameTime = osGetTime();
+ } else {
+ sIrqMgrRetraceTime = osGetTime() - irqmgr->lastFrameTime;
+ }
+ }
+
+ sIrqMgrRetraceCount += 1;
+ IrqMgr_SendMesgForClient(irqmgr, irqmgr);
+}
+
+void IrqMgr_ThreadEntry(IrqMgr* irqmgr) {
+ u32 interrupt;
+ u32 stop;
+
+ interrupt = 0;
+ stop = 0;
+ while (stop == 0) {
+ if (stop) {
+ ;
+ }
+
+ osRecvMesg(&irqmgr->irqQueue, (OSMesg*)&interrupt, OS_MESG_BLOCK);
+ switch (interrupt) {
+ case 0x29A:
+ IrqMgr_HandleRetrace(irqmgr);
+ break;
+ case 0x29D:
+ IrqMgr_HandlePreNMI(irqmgr);
+ break;
+ case 0x29F:
+ IrqMgr_HandlePRENMI450(irqmgr);
+ break;
+ case 0x2A0:
+ IrqMgr_HandlePRENMI480(irqmgr);
+ break;
+ case 0x2A1:
+ IrqMgr_HandlePRENMI500(irqmgr);
+ break;
+ }
+ }
+}
+
+void IrqMgr_Init(IrqMgr* irqmgr, void* stack, OSPri pri, u8 retraceCount) {
+ irqmgr->callbacks = NULL;
+ irqmgr->verticalRetraceMesg.type = 1;
+ irqmgr->prenmiMsg.type = 4;
+ irqmgr->nmiMsg.type = 3;
+ irqmgr->prenmiStage = 0;
+ irqmgr->lastPrenmiTime = 0;
+
+ osCreateMesgQueue(&irqmgr->irqQueue, (OSMesg*)irqmgr->irqBuffer, ARRAY_COUNT(irqmgr->irqBuffer));
+ osSetEventMesg(OS_EVENT_PRENMI, &irqmgr->irqQueue, (OSMesg)0x29D);
+ osViSetEvent(&irqmgr->irqQueue, (OSMesg)0x29A, retraceCount);
+
+ osCreateThread(&irqmgr->thread, Z_THREAD_ID_IRQMGR, IrqMgr_ThreadEntry, irqmgr, stack, pri);
+ osStartThread(&irqmgr->thread);
+}
diff --git a/src/boot/syncprintf.c b/src/boot/syncprintf.c
new file mode 100644
index 000000000..7ceb04bb1
--- /dev/null
+++ b/src/boot/syncprintf.c
@@ -0,0 +1,10 @@
+#include "global.h"
+
+void osSyncPrintfUnused(const char* fmt, ...) {
+}
+
+void osSyncPrintf(const char* fmt, ...) {
+}
+
+void rmonPrintf(const char* fmt, ...) {
+}
diff --git a/src/boot/viconfig.c b/src/boot/viconfig.c
new file mode 100644
index 000000000..ccba4e932
--- /dev/null
+++ b/src/boot/viconfig.c
@@ -0,0 +1,57 @@
+#include "global.h"
+#include "idle.h"
+
+void ViConfig_UpdateVi(u32 black) {
+ if (black) {
+ switch (osTvType) {
+ case OS_TV_MPAL:
+ osViSetMode(&osViModeMpalLan1);
+ break;
+
+ case OS_TV_PAL:
+ osViSetMode(&osViModePalLan1);
+ break;
+
+ case OS_TV_NTSC:
+ default:
+ osViSetMode(&osViModeNtscLan1);
+ break;
+ }
+
+ if (gViConfigFeatures != 0) {
+ osViSetSpecialFeatures(gViConfigFeatures);
+ }
+
+ if (gViConfigYScale != 1) {
+ osViSetYScale(1);
+ }
+ } else {
+ osViSetMode(&gViConfigMode);
+
+ if (gViConfigAdditionalScanLines != 0) {
+ osViExtendVStart(gViConfigAdditionalScanLines);
+ }
+
+ if (gViConfigFeatures != 0) {
+ osViSetSpecialFeatures(gViConfigFeatures);
+ }
+
+ if (gViConfigXScale != 1) {
+ osViSetXScale(gViConfigXScale);
+ }
+
+ if (gViConfigYScale != 1) {
+ osViSetYScale(gViConfigYScale);
+ }
+ }
+
+ gViConfigUseBlack = black;
+}
+
+void ViConfig_UpdateBlack(void) {
+ if (gViConfigUseBlack) {
+ osViBlack(true);
+ } else {
+ osViBlack(false);
+ }
+}
diff --git a/src/boot/yaz0.c b/src/boot/yaz0.c
new file mode 100644
index 000000000..edb7fc5cc
--- /dev/null
+++ b/src/boot/yaz0.c
@@ -0,0 +1,150 @@
+#include "global.h"
+#include "fault.h"
+
+u8 sYaz0DataBuffer[0x400];
+u8* sYaz0CurDataEnd;
+uintptr_t sYaz0CurRomStart;
+u32 sYaz0CurSize;
+u8* sYaz0MaxPtr;
+void* gYaz0DecompressDstEnd;
+
+void* Yaz0_FirstDMA() {
+ u32 pad0;
+ u32 dmaSize;
+ u32 curSize;
+
+ sYaz0MaxPtr = sYaz0CurDataEnd - 0x19;
+
+ curSize = (u32)sYaz0CurDataEnd - (u32)sYaz0DataBuffer;
+ dmaSize = (curSize > sYaz0CurSize) ? sYaz0CurSize : curSize;
+
+ DmaMgr_DmaRomToRam(sYaz0CurRomStart, sYaz0DataBuffer, dmaSize);
+ sYaz0CurRomStart += dmaSize;
+ sYaz0CurSize -= dmaSize;
+ return sYaz0DataBuffer;
+}
+
+void* Yaz0_NextDMA(void* curSrcPos) {
+ u8* dst;
+ u32 restSize;
+ u32 dmaSize;
+ OSPri oldPri;
+
+ restSize = (u32)sYaz0CurDataEnd - (u32)curSrcPos;
+
+ dst = (restSize & 7) ? (sYaz0DataBuffer - (restSize & 7)) + 8 : sYaz0DataBuffer;
+
+ bcopy(curSrcPos, dst, restSize);
+ dmaSize = ((u32)sYaz0CurDataEnd - (u32)dst) - restSize;
+ if (sYaz0CurSize < dmaSize) {
+ dmaSize = sYaz0CurSize;
+ }
+
+ if (dmaSize != 0) {
+ DmaMgr_DmaRomToRam(sYaz0CurRomStart, dst + restSize, dmaSize);
+ sYaz0CurRomStart += dmaSize;
+ sYaz0CurSize -= dmaSize;
+ if (!sYaz0CurSize) {
+ sYaz0MaxPtr = dst + restSize + dmaSize;
+ }
+ } else {
+ oldPri = osGetThreadPri(NULL);
+ osSetThreadPri(NULL, 0x7F);
+ osSyncPrintf("圧縮展開異常\n");
+ osSetThreadPri(NULL, oldPri);
+ }
+
+ return dst;
+}
+
+typedef struct {
+ /* 0x0 */ u32 magic; // Yaz0
+ /* 0x4 */ u32 decSize;
+ /* 0x8 */ u32 compInfoOffset; // only used in mio0
+ /* 0xC */ u32 uncompDataOffset; // only used in mio0
+} Yaz0Header; // size = 0x10
+
+#define YAZ0_MAGIC 0x59617A30 // "Yaz0"
+
+s32 Yaz0_DecompressImpl(u8* src, u8* dst) {
+ u32 bitIdx = 0;
+ u8* dstEnd;
+ u32 chunkHeader = 0;
+ u32 nibble;
+ u8* backPtr;
+ s32 chunkSize;
+ u32 off;
+ u32 magic;
+
+ magic = ((Yaz0Header*)src)->magic;
+
+ if (magic != YAZ0_MAGIC) {
+ return -1;
+ }
+
+ dstEnd = dst + ((Yaz0Header*)src)->decSize;
+ src = src + sizeof(Yaz0Header);
+
+ do {
+ if (bitIdx == 0) {
+ if ((sYaz0MaxPtr < src) && (sYaz0CurSize != 0)) {
+ src = Yaz0_NextDMA(src);
+ }
+
+ chunkHeader = *src++;
+ bitIdx = 8;
+ }
+
+ if (chunkHeader & (1 << 7)) { // uncompressed
+ *dst = *src;
+ dst++;
+ src++;
+ } else { // compressed
+ off = ((*src & 0xF) << 8 | *(src + 1));
+ nibble = *src >> 4;
+ backPtr = dst - off;
+ src += 2;
+
+ chunkSize = (nibble == 0) // N = chunkSize; B = back offset
+ ? (u32)(*src++ + 0x12) // 3 bytes 0B BB NN
+ : nibble + 2; // 2 bytes NB BB
+
+ do {
+ *dst++ = *(backPtr++ - 1);
+ chunkSize--;
+ } while (chunkSize != 0);
+ }
+ chunkHeader <<= 1;
+ bitIdx--;
+ } while (dst != dstEnd);
+
+ gYaz0DecompressDstEnd = dstEnd;
+
+ return 0;
+}
+
+void Yaz0_Decompress(uintptr_t romStart, void* dst, size_t size) {
+ s32 status;
+ u32 pad;
+ char sp80[0x50];
+ char sp30[0x50];
+
+ if (sYaz0CurDataEnd != NULL) {
+ while (sYaz0CurDataEnd != NULL) {
+ Sleep_Usec(10);
+ }
+ }
+
+ sYaz0CurDataEnd = sYaz0DataBuffer + sizeof(sYaz0DataBuffer);
+ sYaz0CurRomStart = romStart;
+ sYaz0CurSize = size;
+ status = Yaz0_DecompressImpl(Yaz0_FirstDMA(), dst);
+
+ if (status != 0) {
+ sprintf(sp80, "slidma slidstart_szs ret=%d", status);
+ sprintf(sp30, "src:%08lx dst:%08lx siz:%08lx", romStart, dst, size);
+ Fault_AddHungupAndCrashImpl(sp80, sp30);
+ }
+
+ sYaz0CurDataEnd = NULL;
+}
diff --git a/src/boot/z_std_dma.c b/src/boot/z_std_dma.c
new file mode 100644
index 000000000..ac4992264
--- /dev/null
+++ b/src/boot/z_std_dma.c
@@ -0,0 +1,242 @@
+#include "prevent_bss_reordering.h"
+#include "global.h"
+#include "fault.h"
+#include "stack.h"
+#include "stackcheck.h"
+#include "z64thread.h"
+
+size_t gDmaMgrDmaBuffSize = 0x2000;
+
+StackEntry sDmaMgrStackInfo;
+u16 sNumDmaEntries;
+OSMesgQueue sDmaMgrMsgQueue;
+OSMesg sDmaMgrMsgs[32];
+OSThread sDmaMgrThread;
+STACK(sDmaMgrStack, 0x500);
+
+s32 DmaMgr_DmaRomToRam(uintptr_t rom, void* ram, size_t size) {
+ OSIoMesg ioMsg;
+ OSMesgQueue queue;
+ OSMesg msg[1];
+ s32 ret;
+ size_t buffSize = gDmaMgrDmaBuffSize;
+
+ osInvalDCache(ram, size);
+ osCreateMesgQueue(&queue, msg, ARRAY_COUNT(msg));
+
+ if (buffSize != 0) {
+ while (buffSize < size) {
+ ioMsg.hdr.pri = 0;
+ ioMsg.hdr.retQueue = &queue;
+ ioMsg.devAddr = rom;
+ ioMsg.dramAddr = ram;
+ ioMsg.size = buffSize;
+ ret = osEPiStartDma(gCartHandle, &ioMsg, 0);
+ if (ret) {
+ goto END;
+ }
+
+ osRecvMesg(&queue, NULL, OS_MESG_BLOCK);
+ size -= buffSize;
+ rom = rom + buffSize;
+ ram = (u8*)ram + buffSize;
+ }
+ }
+ ioMsg.hdr.pri = 0;
+ ioMsg.hdr.retQueue = &queue;
+ ioMsg.devAddr = rom;
+ ioMsg.dramAddr = ram;
+ ioMsg.size = size;
+ ret = osEPiStartDma(gCartHandle, &ioMsg, 0);
+ if (ret) {
+ goto END;
+ }
+
+ osRecvMesg(&queue, NULL, OS_MESG_BLOCK);
+
+ osInvalDCache(ram, size);
+
+END:
+ return ret;
+}
+
+s32 DmaMgr_DmaHandler(OSPiHandle* pihandle, OSIoMesg* mb, s32 direction) {
+ return osEPiStartDma(pihandle, mb, direction);
+}
+
+DmaEntry* DmaMgr_FindDmaEntry(uintptr_t vrom) {
+ DmaEntry* curr;
+
+ for (curr = dmadata; curr->vromEnd != 0; curr++) {
+ if (vrom < curr->vromStart) {
+ continue;
+ }
+ if (vrom >= curr->vromEnd) {
+ continue;
+ }
+
+ return curr;
+ }
+
+ return NULL;
+}
+
+u32 DmaMgr_TranslateVromToRom(uintptr_t vrom) {
+ DmaEntry* entry = DmaMgr_FindDmaEntry(vrom);
+
+ if (entry != NULL) {
+ if (entry->romEnd == 0) {
+ return vrom + entry->romStart - entry->vromStart;
+ }
+
+ if (vrom == entry->vromStart) {
+ return entry->romStart;
+ }
+
+ return -1;
+ }
+
+ return -1;
+}
+
+s32 DmaMgr_FindDmaIndex(uintptr_t vrom) {
+ DmaEntry* entry = DmaMgr_FindDmaEntry(vrom);
+
+ if (entry != NULL) {
+ return entry - dmadata;
+ }
+
+ return -1;
+}
+
+const char* func_800809F4(u32 a0) {
+ return "??";
+}
+
+void DmaMgr_ProcessMsg(DmaRequest* req) {
+ uintptr_t vrom;
+ void* ram;
+ size_t size;
+ uintptr_t romStart;
+ size_t romSize;
+ DmaEntry* dmaEntry;
+ s32 index;
+
+ vrom = req->vromAddr;
+ ram = req->dramAddr;
+ size = req->size;
+
+ index = DmaMgr_FindDmaIndex(vrom);
+
+ if ((index >= 0) && (index < sNumDmaEntries)) {
+ dmaEntry = &dmadata[index];
+ if (dmaEntry->romEnd == 0) {
+ if (dmaEntry->vromEnd < (vrom + size)) {
+ Fault_AddHungupAndCrash("../z_std_dma.c", 499);
+ }
+ DmaMgr_DmaRomToRam((dmaEntry->romStart + vrom) - dmaEntry->vromStart, (u8*)ram, size);
+ return;
+ }
+
+ romSize = dmaEntry->romEnd - dmaEntry->romStart;
+ romStart = dmaEntry->romStart;
+
+ if (vrom != dmaEntry->vromStart) {
+ Fault_AddHungupAndCrash("../z_std_dma.c", 518);
+ }
+
+ if (size != (dmaEntry->vromEnd - dmaEntry->vromStart)) {
+ Fault_AddHungupAndCrash("../z_std_dma.c", 525);
+ }
+
+ osSetThreadPri(NULL, 10);
+ Yaz0_Decompress(romStart, ram, romSize);
+ osSetThreadPri(NULL, 17);
+ } else {
+ Fault_AddHungupAndCrash("../z_std_dma.c", 558);
+ }
+}
+
+void DmaMgr_ThreadEntry(void* a0) {
+ OSMesg msg;
+ DmaRequest* req;
+
+ while (1) {
+ osRecvMesg(&sDmaMgrMsgQueue, &msg, OS_MESG_BLOCK);
+
+ if (msg == NULL) {
+ break;
+ }
+
+ req = (DmaRequest*)msg;
+
+ DmaMgr_ProcessMsg(req);
+ if (req->notifyQueue) {
+ osSendMesg(req->notifyQueue, req->notifyMsg, OS_MESG_NOBLOCK);
+ }
+ }
+}
+
+s32 DmaMgr_SendRequestImpl(DmaRequest* request, void* vramStart, uintptr_t vromStart, size_t size, UNK_TYPE4 unused,
+ OSMesgQueue* queue, OSMesg msg) {
+ if (gIrqMgrResetStatus >= 2) {
+ return -2;
+ }
+
+ request->vromAddr = vromStart;
+ request->dramAddr = vramStart;
+ request->size = size;
+ request->unk14 = 0;
+ request->notifyQueue = queue;
+ request->notifyMsg = msg;
+
+ osSendMesg(&sDmaMgrMsgQueue, request, OS_MESG_BLOCK);
+
+ return 0;
+}
+
+s32 DmaMgr_SendRequest0(void* vramStart, uintptr_t vromStart, size_t size) {
+ DmaRequest req;
+ OSMesgQueue queue;
+ OSMesg msg[1];
+ s32 ret;
+
+ osCreateMesgQueue(&queue, msg, ARRAY_COUNT(msg));
+
+ ret = DmaMgr_SendRequestImpl(&req, vramStart, vromStart, size, 0, &queue, NULL);
+
+ if (ret == -1) {
+ return ret;
+ } else {
+ osRecvMesg(&queue, NULL, OS_MESG_BLOCK);
+ }
+
+ return 0;
+}
+
+void DmaMgr_Start(void) {
+ DmaMgr_DmaRomToRam(SEGMENT_ROM_START(dmadata), dmadata, SEGMENT_ROM_SIZE(dmadata));
+
+ {
+ DmaEntry* iter = dmadata;
+ u32 idx = 0;
+
+ while (iter->vromEnd != 0) {
+ iter++;
+ idx++;
+ }
+
+ sNumDmaEntries = idx;
+ }
+
+ osCreateMesgQueue(&sDmaMgrMsgQueue, sDmaMgrMsgs, ARRAY_COUNT(sDmaMgrMsgs));
+ StackCheck_Init(&sDmaMgrStackInfo, sDmaMgrStack, STACK_TOP(sDmaMgrStack), 0, 0x100, "dmamgr");
+ osCreateThread(&sDmaMgrThread, Z_THREAD_ID_DMAMGR, DmaMgr_ThreadEntry, NULL, STACK_TOP(sDmaMgrStack),
+ Z_PRIORITY_DMAMGR);
+
+ osStartThread(&sDmaMgrThread);
+}
+
+void DmaMgr_Stop(void) {
+ osSendMesg(&sDmaMgrMsgQueue, NULL, OS_MESG_BLOCK);
+}