You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1159 lines
41 KiB
C

// The Digital Grove Codebase
// Copyright (c) Ryan Fleury. All rights reserved.
// This code was adapted from the excellent work by the authors of the
// libdragon project (https://libdragon.dev). libdragon is licensed under
// The Unlicense:
//
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or distribute
// this software, either in source code form or as a compiled binary, for any
// purpose, commercial or non-commercial, and by any means.
//
// In jurisdictions that recognize copyright laws, the author or authors of
// this software dedicate any and all copyright interest in the software to the
// public domain. We make this dedication for the benefit of the public at
// large and to the detriment of our heirs and successors. We intend this
// dedication to be an overt act of relinquishment in perpetuity of all present
// and future rights to this software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to http://unlicense.org
#ifndef N64_H
#define N64_H
////////////////////////////////
//~ rjf: Console Kinds
typedef enum N64_ConsoleKind
{
N64_ConsoleKind_N64,
N64_ConsoleKind_IQue,
N64_ConsoleKind_Analogue3D,
N64_ConsoleKind_ModRetroM64,
}
N64_ConsoleKind;
////////////////////////////////
//~ rjf: Boot Info (defined by libdragon's IPL3)
typedef enum N64_TVKind
{
N64_TVKind_PAL = 0,
N64_TVKind_NTSC = 1,
N64_TVKind_MPAL = 2,
N64_TVKind_COUNT = 3,
}
N64_TVKind;
typedef U32 N64_BootInfoFlags;
enum
{
N64_BootInfoFlag_IsIQue = (1<<0),
N64_BootInfoFlags_ROMKindMask = 0xff000000u,
N64_BootInfoFlags_ROMKindShift = 24,
N64_BootInfoFlags_TVKindMask = 0x00ff0000u,
N64_BootInfoFlags_TVKindShift = 16,
N64_BootInfoFlags_ResetKindMask = 0x0000ff00u,
N64_BootInfoFlags_ResetKindShift= 8,
};
typedef struct N64_BootInfo N64_BootInfo;
struct N64_BootInfo
{
U32 memory_size;
U32 entropy;
N64_BootInfoFlags flags;
U32 rom_addr;
};
StaticAssert(sizeof(N64_BootInfo) == 16, n64_boot_info_size_check);
#define N64_BOOT_INFO ((volatile N64_BootInfo *)0xA4000000)
////////////////////////////////
//~ rjf: Exceptions
typedef U32 N64_ExceptionCode;
enum
{
N64_ExceptionCode_Interrupt,
N64_ExceptionCode_TLBModification,
N64_ExceptionCode_TLBLoadMiss,
N64_ExceptionCode_TLBStoreMiss,
N64_ExceptionCode_AddressLoadError,
N64_ExceptionCode_AddressStoreError,
N64_ExceptionCode_BusFetchError,
N64_ExceptionCode_BusLoadStoreError,
N64_ExceptionCode_Syscall,
N64_ExceptionCode_Breakpoint,
N64_ExceptionCode_ReservedInstruction,
N64_ExceptionCode_CoprocessorUnusable,
N64_ExceptionCode_ArithmeticOverflow,
N64_ExceptionCode_Trap,
N64_ExceptionCode_Reserved0,
N64_ExceptionCode_FloatingPoint,
N64_ExceptionCode_Reserved1,
N64_ExceptionCode_Reserved2,
N64_ExceptionCode_Reserved3,
N64_ExceptionCode_Reserved4,
N64_ExceptionCode_Reserved5,
N64_ExceptionCode_Reserved6,
N64_ExceptionCode_Reserved7,
N64_ExceptionCode_Watch,
N64_ExceptionCode_Emux,
N64_ExceptionCode_Reserved8,
N64_ExceptionCode_Reserved9,
N64_ExceptionCode_Reserved10,
N64_ExceptionCode_Reserved11,
N64_ExceptionCode_Reserved12,
N64_ExceptionCode_Reserved13,
N64_ExceptionCode_Reserved14,
};
global String8 n64_string_from_exception_code_table[] =
{
Str8LitComp("Interrupt"), // 0
Str8LitComp("TLB Modification"), // 1
Str8LitComp("TLB Miss (load/instruction fetch)"), // 2
Str8LitComp("TLB Miss (store)"), // 3
Str8LitComp("Address Error (load/instruction fetch)"), // 4
Str8LitComp("Address Error (store)"), // 5
Str8LitComp("Bus Error (instruction fetch)"), // 6
Str8LitComp("Bus Error (load/store)"), // 7
Str8LitComp("Syscall"), // 8
Str8LitComp("Breakpoint"), // 9
Str8LitComp("Reserved Instruction"), // 10
Str8LitComp("Coprocessor Unusable"), // 11
Str8LitComp("Arithmetic Overflow"), // 12
Str8LitComp("Trap"), // 13
Str8LitComp("Reserved"), // 14
Str8LitComp("Floating-Point"), // 15
Str8LitComp("Reserved"), // 16
Str8LitComp("Reserved"), // 17
Str8LitComp("Reserved"), // 18
Str8LitComp("Reserved"), // 19
Str8LitComp("Reserved"), // 20
Str8LitComp("Reserved"), // 21
Str8LitComp("Reserved"), // 22
Str8LitComp("Watch"), // 23
Str8LitComp("Emux"), // 24
Str8LitComp("Reserved"), // 25
Str8LitComp("Reserved"), // 26
Str8LitComp("Reserved"), // 27
Str8LitComp("Reserved"), // 28
Str8LitComp("Reserved"), // 29
Str8LitComp("Reserved"), // 30
Str8LitComp("Reserved"), // 31
};
typedef struct N64_ExceptionRegState N64_ExceptionRegState;
struct N64_ExceptionRegState
{
//- rjf: GPRs 1-32
union
{
U64 gpr[32];
struct
{
U64 zr, at, v0, v1, a0, a1, a2, a3;
U64 t0, t1, t2, t3, t4, t5, t6, t7;
U64 s0, s1, s2, s3, s4, s5, s6, s7;
U64 t8, t9, k0, k1, gp, sp, fp, ra;
};
};
//- rjf: hi/lo/sr/cr/epc
U64 lo;
U64 hi;
U32 sr;
U32 cr;
U32 epc;
//- rjf: FC31
U32 fc31;
//- rjf: FPRs 1-32
U64 fpr[32];
}
__attribute__((packed));
StaticAssert(sizeof(N64_ExceptionRegState) == 544, n64_exception_reg_state_size_check);
////////////////////////////////
//~ rjf: Cache Instruction Specifications
//- rjf: cache size defines
#define N64_CACHE_INST_SIZE (16 * 1024)
#define N64_CACHE_INST_LINESIZE (32)
#define N64_CACHE_DATA_SIZE (8 * 1024)
#define N64_CACHE_DATA_LINESIZE (16)
//- rjf: cache kinds
typedef enum N64_CacheKind
{
N64_CacheKind_Inst = 0,
N64_CacheKind_Data = 1,
}
N64_CacheKind;
//- rjf: cache op kind (VR4300 manual, pg 404)
typedef enum N64_CacheOpKind
{
N64_CacheOpKind_IndexInvalidate = 0,
N64_CacheOpKind_IndexWritebackInvalidate = 0,
N64_CacheOpKind_LoadTag = 1,
N64_CacheOpKind_StoreTag = 2,
N64_CacheOpKind_CreateDirty = 3,
N64_CacheOpKind_HitInvalidate = 4,
N64_CacheOpKind_HitWritebackInvalidate = 5,
N64_CacheOpKind_Fill = 5,
N64_CacheOpKind_HitWriteback = 6,
}
N64_CacheOpKind;
//- rjf: cache opcode builder. from libdragon:
//
// operation is encoded in 5 bits where bits 4..2 contain the operation type
// and bits 0..1 determine the cache that is to be operated on.
//
#define N64_CacheOpcode(op_kind, cache_kind) (((op_kind) << 2) | (cache_kind))
//- rjf: cache op implementation helper
#define N64_CacheOp(op_kind, cache_kind) ({\
if(length)\
{\
int linesize = (cache_kind == N64_CacheKind_Data) ? N64_CACHE_DATA_LINESIZE : N64_CACHE_INST_LINESIZE;\
void *cur = (void*)((unsigned long)addr & ~(linesize-1));\
int count = (int)length + (addr-cur);\
for(int i = 0; i < count; i += linesize)\
{\
asm ("\tcache %0,(%1)\n"::"i" ((((op_kind) << 2) | (cache_kind))), "r" (cur+i));\
}\
} \
})
////////////////////////////////
//~ rjf: Intrinsics
#define N64_MemoryBarrier() asm volatile ("" : : : "memory")
////////////////////////////////
//~ rjf: PIF Specs
#define N64_PIF_RAM (void *)0x1fc007c0
////////////////////////////////
//~ rjf: Serial Interface Specs
//- rjf: SI status bits
#define N64_SI_STATUS_DMA_BUSY (1<<0)
#define N64_SI_STATUS_IO_BUSY (1<<1)
//- rjf: SI register layout
typedef struct N64_SI_Regs N64_SI_Regs;
struct N64_SI_Regs
{
U32 dram_addr;
U32 pif_addr_read;
U32 reserved_0;
U32 reserved_1;
U32 pif_addr_write;
U32 reserved_2;
U32 status;
};
//- rjf: SI register pointer
#define N64_SI_REGS ((volatile N64_SI_Regs *)0xA4800000u)
////////////////////////////////
//~ rjf: JoyBus Specs
#define N64_JOYBUS_PORT_COUNT 5 // [0, 4) -> Controller Slots, 5 -> EEPROM / real-time clock
#define N64_JOYBUS_COMMAND_SKIP_SIZE 1 // Size of a Joybus command to "skip this port" in bytes.
#define N64_JOYBUS_COMMAND_METADATA_SIZE 2 // Size of the Joybus command metadata fields in bytes.
#define N64_JOYBUS_COMMAND_OFFSET_SEND_LEN 0 // Offset of the Joybus command "send length" field in bytes.
#define N64_JOYBUS_COMMAND_OFFSET_RECV_LEN 1 // Offset of the Joybus command "receive length" field in bytes.
#define N64_JOYBUS_COMMAND_OFFSET_COMMAND_ID 2 // Offset of the Joybus command "command ID" field in bytes.
typedef U8 N64_JoyBus_CmdID;
typedef enum N64_JoyBus_CmdIDEnum
{
N64_JoyBus_CmdID_Reset = 0xFF, // "Reset" Joybus command identifier.
N64_JoyBus_CmdID_Identify = 0x00, // "Identify" Joybus command identifier.
N64_JoyBus_CmdID_N64ControllerRead = 0x01, // "N64 Controller Read" Joybus command identifier.
N64_JoyBus_CmdID_N64AccessoryRead = 0x02, // "N64 Accessory Read" Joybus command identifier.
N64_JoyBus_CmdID_N64AccessoryWrite = 0x03, // "N64 Accessory Write" Joybus command identifier.
N64_JoyBus_CmdID_EEPROMReadBlock = 0x04, // "EEPROM Read Block" Joybus command identifier.
N64_JoyBus_CmdID_EEPROMWriteBlock = 0x05, // "EEPROM Write Block" Joybus command identifier.
N64_JoyBus_CmdID_RTCIdentify = 0x06, // "Real-Time Clock Identify" Joybus command identifier.
N64_JoyBus_CmdID_RTCReadBlock = 0x07, // "Real-Time Clock Read Block" Joybus command identifier.
N64_JoyBus_CmdID_RTCWriteBlock = 0x08, // "Real-Time Clock Write Block" Joybus command identifier.
N64_JoyBus_CmdID_N64RandnetKeyboardRead = 0x13, // "N64 Randnet Keyboard Read" Joybus command identifier.
N64_JoyBus_CmdID_64GBLinkCableRead = 0x13, // "64GB Link Cable Read" Joybus command identifier.
N64_JoyBus_CmdID_GBALinkCableRead = 0x14, // "GBA Link Cable Read" Joybus command identifier.
N64_JoyBus_CmdID_64GBLinkCableWrite = 0x14, // "64GB Link Cable Write" Joybus command identifier.
N64_JoyBus_CmdID_GBALinkCableWrite = 0x15, // "GBA Link Cable Write" Joybus command identifier.
N64_JoyBus_CmdID_PixelFXN64GameID = 0x1D, // "PixelFX N64 Game ID" Joybus command identifier. (https://gitlab.com/pixelfx-public/n64-game-id#how-to-integrate-on-n64)
N64_JoyBus_CmdID_GCNControllerRead = 0x40, // "GameCube Controller Read" Joybus command identifier.
N64_JoyBus_CmdID_GCNControllerOrigin = 0x41, // "GameCube Controller Read Origins" Joybus command identifier.
N64_JoyBus_CmdID_GCNControllerRecalibrate = 0x42, // "GameCube Controller Recalibrate" Joybus command identifier. Invalidates the origins of the GameCube controller.
N64_JoyBus_CmdID_GCNControllerReadLong = 0x43, // "GameCube Controller 'Long Read'" Joybus command identifier. Used by GameCube mode 0, mode 1, mode 2, and mode 4 to read extended controller data, including analog A and B button values.
}
N64_JoyBus_CmdIDEnum;
typedef struct N64_JoyBus_CmdIDInfo N64_JoyBus_CmdIDInfo;
struct N64_JoyBus_CmdIDInfo
{
U8 send_size;
U8 recv_size;
};
typedef U16 N64_JoyBus_ID;
enum
{
N64_JoyBus_ID_None = 0x0000,
N64_JoyBus_ID_N64VoiceRecognition = 0x0001,
N64_JoyBus_ID_N64RandnetKeyboard = 0x0002,
N64_JoyBus_ID_64GBLinkCable = 0x0003,
N64_JoyBus_ID_GBALinkCable = 0x0004,
N64_JoyBus_ID_CartRTC = 0x0010,
N64_JoyBus_ID_CartEEProm4KBit = 0x0080,
N64_JoyBus_ID_CartEEProm16KBit = 0x00C0,
N64_JoyBus_ID_N64Controller = 0x0500,
N64_JoyBus_ID_N64Mouse = 0x0200,
//
// NOTE(rjf): some IDs are excluded, for GCN controllers etc. - at that point,
// this ID becomes not just an enum, but a bitfield. check joybus.h in
// libdragon for more info, if that becomes relevant.
//
};
typedef U8 N64_JoyBus_PortStatus;
enum
{
N64_JoyBus_PortStatus_AccessoryUnsupported = 0x00, // Joybus identify status for an N64 controller that does not support accessories. Some third-party controllers incorrectly use this status to mean absence of an accessory.
N64_JoyBus_PortStatus_AccessoryPresent = 0x01, // Joybus identify status for an N64 controller with an accessory present.
N64_JoyBus_PortStatus_AccessoryAbsent = 0x02, // Joybus identify status for an N64 controller with no accessory present.
N64_JoyBus_PortStatus_AccessoryChanged = 0x03, // Joybus identify status for an N64 controller with an accessory present that has changed since it was last identified.
N64_JoyBus_PortStatus_AccessoryMask = 0x03, // Joybus identify status byte mask for N64 accessory presence values.
N64_JoyBus_PortStatus_VoiceRecognitionReady = 0x01, // Joybus identify status bit for a VRU/VRS that is initialized and ready.
N64_JoyBus_PortStatus_CommandChecksumError = 0x04, // Joybus identify status bit that signifies the previous accessory command had a checksum error.
N64_JoyBus_PortStatus_GCNRumbleActive = 0x08, // Joybus identify status bit for GameCube controllers that indicates whether the rumble motor is currently active.
N64_JoyBus_PortStatus_EEPromBusy = 0x80, // Joybus identify status bit for EEPROM devices that indicates a write is in-progress.
};
pack_begin
typedef struct N64_JoyBus_CmdPayload_Identify N64_JoyBus_CmdPayload_Identify;
struct N64_JoyBus_CmdPayload_Identify
{
struct
{
N64_JoyBus_CmdID cmd_id;
} send;
struct
{
N64_JoyBus_ID id;
N64_JoyBus_PortStatus status;
} recv;
};
typedef N64_JoyBus_CmdPayload_Identify N64_JoyBus_CmdPayload_Reset;
typedef struct N64_JoyBus_CmdPayload_N64ControllerRead N64_JoyBus_CmdPayload_N64ControllerRead;
struct N64_JoyBus_CmdPayload_N64ControllerRead
{
struct
{
N64_JoyBus_CmdID cmd_id;
} send;
struct
{
U32 a : 1; // A button
U32 b : 1; // B button
U32 z : 1; // Z button
U32 start : 1; // Start button
U32 d_up : 1; // D-Pad up
U32 d_down : 1; // D-Pad down
U32 d_left : 1; // D-Pad left
U32 d_right : 1; // D-Pad right
U32 reset : 1; // Reset flag
U32 padding : 1; // Unused padding
U32 l : 1; // L shoulder button
U32 r : 1; // R shoulder button
U32 c_up : 1; // C-Pad up
U32 c_down : 1; // C-Pad down
U32 c_left : 1; // C-Pad left
U32 c_right : 1; // C-Pad right
S32 stick_x : 8; // Analog stick X-axis
S32 stick_y : 8; // Analog stick Y-axis
} recv;
};
pack_end
////////////////////////////////
//~ rjf: JoyBus Interaction State Types
typedef enum N64_JoyBus_Status
{
N64_JoyBus_Status_Idle,
N64_JoyBus_Status_Sending,
N64_JoyBus_Status_Recving,
}
N64_JoyBus_Status;
typedef struct N64_JoyBus_PortState N64_JoyBus_PortState;
struct N64_JoyBus_PortState
{
U32 connect_gen; // incremented on every connection change
N64_JoyBus_ID id;
U8 status;
};
typedef struct N64_JoyBus_Cmd N64_JoyBus_Cmd;
struct align(64) N64_JoyBus_Cmd
{
U8 send_payload[64];
U8 recv_payload[64];
N64_JoyBus_CmdID cmd_id;
};
////////////////////////////////
//~ rjf: Timing Presets For TV Kinds
typedef struct N64_TimingPreset N64_TimingPreset;
struct N64_TimingPreset
{
U32 h_total;
U32 h_total_leap;
U32 v_total;
U32 burst;
U32 v_burst;
U32 display_x0;
U32 display_y0;
U32 display_width;
U32 display_height;
};
////////////////////////////////
//~ rjf: MIPS Interface Specs
typedef U32 N64_MI_MaskCmd;
enum
{
N64_MI_MaskCmd_SetMD_iQue = 0x08000000, // Enable memory card interrupt (iQue)
N64_MI_MaskCmd_ClrMD_iQue = 0x04000000, // Disable memory card interrupt (iQue)
N64_MI_MaskCmd_SetBtn_iQue = 0x02000000, // Enable power button interrupt (iQue)
N64_MI_MaskCmd_ClrBtn_iQue = 0x01000000, // Disable power button interrupt (iQue)
N64_MI_MaskCmd_SetUSB1_iQue = 0x00800000, // Enable USB1 interrupt (iQue)
N64_MI_MaskCmd_ClrUSB1_iQue = 0x00400000, // Disable USB1 interrupt (iQue)
N64_MI_MaskCmd_SetUSB0_iQue = 0x00200000, // Enable USB0 interrupt (iQue)
N64_MI_MaskCmd_ClrUSB0_iQue = 0x00100000, // Disable USB0 interrupt (iQue)
N64_MI_MaskCmd_SetPIErr_iQue = 0x00080000, // Enable PI error interrupt (iQue)
N64_MI_MaskCmd_ClrPIErr_iQue = 0x00040000, // Disable PI error interrupt (iQue)
N64_MI_MaskCmd_SetIDE_iQue = 0x00020000, // Enable IDE interrupt (iQue)
N64_MI_MaskCmd_ClrIDE_iQue = 0x00010000, // Disable IDE interrupt (iQue)
N64_MI_MaskCmd_SetAES_iQue = 0x00008000, // Enable AES interrupt (iQue)
N64_MI_MaskCmd_ClrAES_iQue = 0x00004000, // Disable AES interrupt (iQue)
N64_MI_MaskCmd_SetFlash_iQue = 0x00002000, // Enable flash interrupt (iQue)
N64_MI_MaskCmd_ClrFlash_iQue = 0x00001000, // Disable flash interrupt (iQue)
N64_MI_MaskCmd_SetDP = 0x00000800, // Enable RDP SYNC_FULL interrupt
N64_MI_MaskCmd_ClrDP = 0x00000400, // Disable RDP SYNC_FULL interrupt
N64_MI_MaskCmd_SetPI = 0x00000200, // Enable PI transfer interrupt
N64_MI_MaskCmd_ClrPI = 0x00000100, // Disable PI transfer interrupt
N64_MI_MaskCmd_SetVI = 0x00000080, // Enable VI line match interrupt
N64_MI_MaskCmd_ClrVI = 0x00000040, // Disable VI line match interrupt
N64_MI_MaskCmd_SetAI = 0x00000020, // Enable AI buffer playback interrupt
N64_MI_MaskCmd_ClrAI = 0x00000010, // Disable AI buffer playback interrupt
N64_MI_MaskCmd_SetSI = 0x00000008, // Enable SI transfer interrupt
N64_MI_MaskCmd_ClrSI = 0x00000004, // Disable SI transfer interrupt
N64_MI_MaskCmd_SetSP = 0x00000002, // Enable RSP interrupt
N64_MI_MaskCmd_ClrSP = 0x00000001, // Disable RSP interrupt
};
typedef U32 N64_MI_InterruptStatusMask;
enum
{
N64_MI_InterruptStatusMask_BBMDState = 0x02000000, // 1 if the memory card is present, 0 if missing (iQue)
N64_MI_InterruptStatusMask_BBBtnState = 0x01000000, // 1 if the power button is pressed, 0 if not (iQue)
N64_MI_InterruptStatusMask_BBMD = 0x00002000, // Pending interrupt: Memory card removed or inserted (iQue)
N64_MI_InterruptStatusMask_BBBtn = 0x00001000, // Pending interrupt: Power button pressed (iQue)
N64_MI_InterruptStatusMask_BBUSB1 = 0x00000800, // Pending interrupt: USB1 (iQue)
N64_MI_InterruptStatusMask_BBUSB0 = 0x00000400, // Pending interrupt: USB0 (iQue)
N64_MI_InterruptStatusMask_BBPIErr = 0x00000200, // Pending interrupt: PI error (iQue)
N64_MI_InterruptStatusMask_BBIDE = 0x00000100, // Pending interrupt: IDE (iQue)
N64_MI_InterruptStatusMask_BBAES = 0x00000080, // Pending interrupt: AES interrupt (iQue)
N64_MI_InterruptStatusMask_BBFlash = 0x00000040, // Pending interrupt: Flash (iQue)
N64_MI_InterruptStatusMask_DP = 0x00000020, // Pending interrupt: RDP SYNC_FULL has finished
N64_MI_InterruptStatusMask_PI = 0x00000010, // Pending interrupt: PI transfer finished
N64_MI_InterruptStatusMask_VI = 0x00000008, // Pending interrupt: VI line match (normally, vblank)
N64_MI_InterruptStatusMask_AI = 0x00000004, // Pending interrupt: AI buffer playback started
N64_MI_InterruptStatusMask_SI = 0x00000002, // Pending interrupt: SI transfer finished
N64_MI_InterruptStatusMask_SP = 0x00000001, // Pending interrupt: RSP interrupt requested
};
#define N64_MI_VERSION ((volatile U32 *)0xA4300004)
#define N64_MI_INTERRUPT ((volatile U32 *)0xA4300008)
#define N64_MI_MASK ((volatile U32 *)0xA430000C)
////////////////////////////////
//~ rjf: Visual Interface Specs
//- rjf: VI CTRL register definitions
typedef U32 N64_VI_CtrlReg;
#define N64_VI_CtrlRegVal(kind, aa_mode, pixel_advance, flags) ((kind) | (aa_mode) | (pixel_advance) | (flags))
typedef enum N64_VI_CtrlKind
{
N64_VI_CtrlKind_None = 0x0, // turn off VI - TVs show static, black, or no signal.
N64_VI_CtrlKind_16bpp = 0x2, // framebuffer source -> 16-bit, RGBA5551
N64_VI_CtrlKind_32bpp = 0x3, // framebuffer source -> 32-bit
}
N64_VI_CtrlKind;
typedef enum N64_VI_AAMode
{
N64_VI_AAMode_None = (0b11<<8), // disable. hardware bug on NTSC units; do not use this mode in 16bpp mode, or widths <= 320px.
N64_VI_AAMode_Resample = (0b10<<8), // enable bilinear filtering during resampling. not compatible with dedithering.
N64_VI_AAMode_ResampleFetchNeeded = (0b01<<8), // smooth external edges of polygons. can cause corruption in high bandwidth, e.g. high-res. use FetchAlways in those cases (at a performance cost).
N64_VI_AAMode_ResampleFetchAlways = (0b00<<8), // similar to FetchNeeded, but exact difference is unknown - it is slower, fixes some FetchNeeded artifacts, and is broken in 32bpp mode.
}
N64_VI_AAMode;
typedef enum N64_VI_PixelAdvance
{
N64_VI_PixelAdvance_Default = 0b11<<12, // default
N64_VI_PixelAdvance_BBPlayer = 0b01<<12, // default on iQue devices
}
N64_VI_PixelAdvance;
typedef U32 N64_VI_CtrlFlags;
enum
{
N64_VI_CtrlFlag_GammaDitherEnable = (1<<2),
N64_VI_CtrlFlag_GammaCorrect = (1<<3),
N64_VI_CtrlFlag_Divot = (1<<4), // "divot filter" - fixes 1 pixel holes after AA
N64_VI_CtrlFlag_Serrate = (1<<6), // interlaced output
N64_VI_CtrlFlag_Dedither = (1<<16),
};
//- rjf: VI register layout
typedef struct N64_VI_Regs N64_VI_Regs;
struct N64_VI_Regs
{
N64_VI_CtrlReg ctrl; // VI configuration register
U32 origin; // physical address of framebuffer
U32 width; // width in pixels of framebuffer
U32 v_intr; // vertical interrupt - which half-line you want the interrupt on?
U32 v_current; // current half-line
U32 burst; // sync/burst values (?)
U32 v_total; // total visible and non-visible lines (?)
U32 h_total; // total width of a line
U32 h_total_leap; // alternative scanline length for one scanline during vsync (?)
U32 h_video; // start/end of active video image, in screen pixels
U32 v_video; // start/end of active video image, in screen half-lines
U32 v_burst; // start/end of color burst enable, in half-lines (?)
U32 x_scale; // horizontal subpixel offset; 1/x-scale-up factor (?)
U32 y_scale; // vertical subpixel offset; 1/y-scale-up factor (?)
};
//- rjf: VI register pointer
#define N64_VI_REGS ((volatile N64_VI_Regs *)0xA4400000u)
////////////////////////////////
//~ rjf: Display Processor (DP, RDP) Specs
//
// https://n64brew.dev/wiki/Reality_Display_Processor
//
//- rjf: DP command IDs
typedef U8 N64_DP_CmdID;
enum
{
N64_DP_CmdID_NoOp = 0x00,
//- rjf: triangle fills [Z: depth, T -> texture, S -> shade]
N64_DP_CmdID_FillTriangle = 0x08,
N64_DP_CmdID_FillTriangleZ = 0x09,
N64_DP_CmdID_FillTriangleT = 0x0A,
N64_DP_CmdID_FillTriangleZT = 0x0B,
N64_DP_CmdID_FillTriangleS = 0x0C,
N64_DP_CmdID_FillTriangleSZ = 0x0D,
N64_DP_CmdID_FillTriangleST = 0x0E,
N64_DP_CmdID_FillTriangleSTZ = 0x0F,
//- rjf: textured rectangles
N64_DP_CmdID_TextureRectangle = 0x24,
N64_DP_CmdID_TextureRectangleFlip = 0x25,
//- rjf: syncs (partial syncs do not wait on any internal signal - they always wait/waste N GCLK cycles.)
N64_DP_CmdID_SyncLoad = 0x26, // Stalls the RDP pipeline for exactly 25 GCLK cycles. This guarantees that the loading pipeline will be available for use following any prior operation.
N64_DP_CmdID_SyncPipe = 0x27, // Stalls the RDP pipeline for exactly 50 GCLK cycles. This guarantees that any preceding primitives will be fully rendered and it is safe to modify rendering attributes such as color registers, othermodes and combine mode.
N64_DP_CmdID_SyncTile = 0x28, // Stalls the RDP pipeline for exactly 33 GCLK cycles. This guarantees that any preceding primitives will have finished using tile information and that it is now safe to modify tile descriptors without affecting prior primitives.
N64_DP_CmdID_SyncFull = 0x29, // Waits for all currently staged pipeline and memory operations to complete before halting the RDP pipeline counter and raising the DP interrupt in the MIPS Interface.
//- rjf: chroma keys / color conversions
N64_DP_CmdID_SetKeyGB = 0x2A,
N64_DP_CmdID_SetKeyR = 0x2B,
N64_DP_CmdID_SetConvert = 0x2C,
//- rjf: scissors
N64_DP_CmdID_SetScissor = 0x2D,
//- rjf: primitive depth
N64_DP_CmdID_SetPrimitiveDepth = 0x2E,
//- rjf: set other modes
N64_DP_CmdID_SetOtherModes = 0x2F,
//- rjf: texture settings
N64_DP_CmdID_LoadTLUT = 0x30,
N64_DP_CmdID_SetTileSize = 0x32,
N64_DP_CmdID_LoadBlock = 0x33,
N64_DP_CmdID_LoadTile = 0x34,
N64_DP_CmdID_SetTile = 0x35,
//- rjf: rectangle fills
N64_DP_CmdID_FillRectangle = 0x36,
//- rjf: color settings
N64_DP_CmdID_SetFillColor = 0x37,
N64_DP_CmdID_SetFogColor = 0x38,
N64_DP_CmdID_SetBlendColor = 0x39,
N64_DP_CmdID_SetPrimitiveColor = 0x3A,
N64_DP_CmdID_SetEnvironmentColor = 0x3B,
//- rjf: combine mode
N64_DP_CmdID_SetCombineMode = 0x3C,
//- rjf: RDRAM image addresses / settings
N64_DP_CmdID_SetTextureImage = 0x3D, // Sets the texture image, the location in RDRAM where the texture loading pipeline should source image data, and related properties.
N64_DP_CmdID_SetDepthImage = 0x3E, // Sets the depth image, the region in RDRAM where depth information will be stored. The depth image width is the same as the image width for the current color image. The depth buffer format is a fixed 18-bit-per-pixel format irrespective of the color image format.
N64_DP_CmdID_SetColorImage = 0x3F, // Sets the color image address and properties, the region in RDRAM where primitives will be rendered to as pixels.
};
//- rjf: DP command structures
pack_begin
StaticAssert(BIG_ENDIAN, n64_big_endian_check);
typedef U8 N64_DP_PixelFormat;
enum
{
N64_DP_PixelFormat_RGBA = 0,
N64_DP_PixelFormat_YUV = 1,
N64_DP_PixelFormat_CI = 2,
N64_DP_PixelFormat_IA = 3,
N64_DP_PixelFormat_I = 4,
};
typedef U8 N64_DP_PixelSizeKind;
enum
{
N64_DP_PixelSizeKind_4 = 0,
N64_DP_PixelSizeKind_8 = 1,
N64_DP_PixelSizeKind_16 = 2,
N64_DP_PixelSizeKind_32 = 3,
};
typedef union N64_DP_Cmd_SetColorImage N64_DP_Cmd_SetColorImage;
union N64_DP_Cmd_SetColorImage
{
U64 v[1];
struct
{
N64_DP_CmdID id;
N64_DP_PixelFormat format : 3;
N64_DP_PixelSizeKind pixel_size_kind : 2;
U8 padding : 3;
U16 width;
U32 image_paddr;
};
};
typedef union N64_DP_Cmd_SetDepthImage N64_DP_Cmd_SetDepthImage;
union N64_DP_Cmd_SetDepthImage
{
U64 v[1];
struct
{
N64_DP_CmdID id;
U32 flags : 24;
U32 image_paddr;
};
};
typedef union N64_DP_Cmd_SetScissor N64_DP_Cmd_SetScissor;
union N64_DP_Cmd_SetScissor
{
U64 v[1];
struct
{
N64_DP_CmdID id;
U16 upper_left_x : 12;
U16 upper_left_y : 12;
U8 padding : 6;
U8 field : 1; // enable interlaced rendering
U8 odd : 1; // when interlaced rendering, do odd lines or even?
U16 lower_right_x : 12;
U16 lower_right_y : 12;
};
};
typedef union N64_DP_Cmd_SetOtherModes N64_DP_Cmd_SetOtherModes;
union N64_DP_Cmd_SetOtherModes
{
U64 v[1];
struct
{
N64_DP_CmdID id;
U8 atomic_prim : 1; // Enables span buffer coherency, forces active span segments to be written to the frame buffer before reading new span segments
U8 padding_0 : 1;
U8 cycle_type : 2; // Determines pipeline mode. Either 1-Cycle (0), 2-Cycle (1), COPY (2), FILL (3)
U8 persp_tex_en : 1; // Enables perspective correction of texture coordinates
U8 detail_tex_en : 1; // Enables "detail texture" mode in Texture LOD
U8 sharpen_tex_en : 1; // Enables "sharpen texture" mode in Texture LOD
U8 tex_lod_en : 1; // Enables Texture Level of Detail (LOD)
U8 tlut_en : 1; // Enables Texture Look-Up Table (TLUT) sampling. Texels are first fetched from low TMEM that are then used to index a palette in high TMEM to find the final color values.
U8 tlut_type : 1; // Determines TLUT texel format. Either RGBA16 (0) or IA16 (1)
U8 sample_type : 1; // Determines texel sampling mode. Either point-sampled (0) or 2x2 bilinear (1)
U8 mid_texel : 1; // Determines bilinear filter mode. Either 3-point (0) or average mode (1)
U8 bi_lerp_0 : 1; // Determines texture filter mode for the first cycle. Either YUV to RGB conversion (See Set Convert) (0) or bilinear filter (1)
U8 bi_lerp_1 : 1; // Determines texture filter mode for the second cycle. Either YUV to RGB conversion (See Set Convert) (0) or bilinear filter (1)
U8 convert_one : 1; // Determines the input to the second texture filter stage. Either the sample from the second stage of texture sampling (0) or the result from the first texture filter cycle (1)
U8 key_en : 1; // Enables chroma keying following the Color Combiner stage
U8 rgb_dither_sel : 2; // Set RGB dither mode
U8 alpha_dither_sel : 2; // Set Alpha dither mode
U8 padding_1 : 4;
U8 bl_m1a_0 : 2; // Blender input P (first cycle)
U8 bl_m1a_1 : 2; // Blender input P (second cycle)
U8 bl_m1b_0 : 2; // Blender input A (first cycle)
U8 bl_m1b_1 : 2; // Blender input A (second cycle)
U8 bl_m2a_0 : 2; // Blender input M (first cycle)
U8 bl_m2a_1 : 2; // Blender input M (second cycle)
U8 bl_m2b_0 : 2; // Blender input B (first cycle)
U8 bl_m2b_1 : 2; // Blender input B (second cycle)
U8 padding_2 : 1;
U8 force_blend : 1; // Enables blending for all pixels rather than only edge pixels
U8 alpha_cvg_select : 1; // Use coverage (or coverage multiplied by CC alpha) for alpha input to blender rather than alpha output from CC
U8 cvg_x_alpha : 1; // Multiply coverage and alpha from CC (used in conjunction with alpha_cvg_sel)
U8 z_mode : 2; // Determines z-buffer comparator mode
U8 cvg_dest : 2; // Determines coverage output mode
U8 color_on_cvg : 1; // If enabled, writes the blender output only if coverage overflowed, otherwise write the 2B input verbatim
U8 image_read_en : 1; // Enable color image reading
U8 z_update_en : 1; // Enable z-buffer writing
U8 z_compare_en : 1; // Enable z-buffer reading and depth comparison
U8 antialias_en : 1; // Enable anti-aliasing, which may enable blending on edge pixels
U8 z_source_sel : 1; // Selects either per-pixel (0) or primitive (1) depth as depth source to compare against the z-buffer
U8 dither_alpha_en : 1; // Determines alpha compare threshold source. (0 blend color register alpha, 1 random)
U8 alpha_compare_en : 1; // Enables alpha compare, pixels below the alpha threshold (compared against CC alpha output) are not written
};
};
typedef U8 N64_DP_Cmd_CombineColorCode;
enum
{
N64_DP_Cmd_CombineColorCode_Combined = 0, // Combined color, from first cycle
N64_DP_Cmd_CombineColorCode_Tex0 = 1, // Texture color sampled from the tile set in the primitive command, after texture LOD if enabled
N64_DP_Cmd_CombineColorCode_Tex1 = 2, // Texture color sampled from the tile set in the primitive command, after texture LOD if enabled, plus one (mod 8, e.g. if TEX0 refers to tile 7, TEX1 refers to tile 0)
N64_DP_Cmd_CombineColorCode_Primitive = 3, // Primitive color register
N64_DP_Cmd_CombineColorCode_Shade = 4, // Shade color interpolated per-pixel from shade coefficients
N64_DP_Cmd_CombineColorCode_Environment = 5, // Environment color register
};
typedef union N64_DP_Cmd_SetCombineMode N64_DP_Cmd_SetCombineMode;
union N64_DP_Cmd_SetCombineMode
{
U64 v[1];
struct
{
N64_DP_CmdID id;
U8 rgb_a_0 : 4;
U8 rgb_c_0 : 5;
U8 alpha_a_0 : 3;
U8 alpha_c_0 : 3;
U8 rgb_a_1 : 4;
U8 rgb_c_1 : 5;
U8 rgb_b_0 : 4;
U8 rgb_b_1 : 4;
U8 alpha_a_1 : 3;
U8 alpha_c_1 : 3;
U8 rgb_d_0 : 3;
U8 alpha_b_0 : 3;
U8 alpha_d_0 : 3;
U8 rgb_d_1 : 3;
U8 alpha_b_1 : 3;
U8 alpha_d_1 : 3;
};
};
typedef union N64_DP_Cmd_SetFillColor N64_DP_Cmd_SetFillColor;
union N64_DP_Cmd_SetFillColor
{
U64 v[1];
struct
{
N64_DP_CmdID id;
U8 padding_0;
U16 padding_1;
union
{
struct
{
U16 rgba16_odd;
U16 rgba16_even;
};
struct
{
U32 rgba32;
};
};
};
};
typedef union N64_DP_Cmd_SetBlendColor N64_DP_Cmd_SetBlendColor;
union N64_DP_Cmd_SetBlendColor
{
U64 v[1];
struct
{
N64_DP_CmdID id;
U32 padding : 24;
U32 rgba;
};
};
typedef union N64_DP_Cmd_Stub N64_DP_Cmd_Stub;
union N64_DP_Cmd_Stub
{
U64 v[1];
struct
{
N64_DP_CmdID id;
U64 padding : 56;
};
};
typedef U8 N64_DP_Cmd_FillTriangleFlags;
enum
{
N64_DP_Cmd_FillTriangleFlag_BaseTileDescriptorIndexMask = 0x3,
N64_DP_Cmd_FillTriangleFlag_MaximumLODMask = 0xc,
N64_DP_Cmd_FillTriangleFlag_LeftMajor = 0x80,
};
typedef struct N64_DP_Cmd_FillTriangleBase N64_DP_Cmd_FillTriangleBase;
struct N64_DP_Cmd_FillTriangleBase
{
N64_DP_CmdID id;
N64_DP_Cmd_FillTriangleFlags flags;
U16 yl;
U16 ym;
U16 yh;
//-
U32 xl;
U32 dxldy;
//-
U32 xh;
U32 dxhdy;
//-
U32 xm;
U32 dxmdy;
};
typedef struct N64_DP_CmdExt_FillTriangleShading N64_DP_CmdExt_FillTriangleShading;
struct N64_DP_CmdExt_FillTriangleShading
{
U16 r_i;
U16 g_i;
U16 b_i;
U16 a_i;
//-
U16 drdx_i;
U16 dgdx_i;
U16 dbdx_i;
U16 dadx_i;
//-
U16 r_f;
U16 g_f;
U16 b_f;
U16 a_f;
//-
U16 drdx_f;
U16 dgdx_f;
U16 dbdx_f;
U16 dadx_f;
//-
U16 drde_i;
U16 dgde_i;
U16 dbde_i;
U16 dade_i;
//-
U16 drdy_i;
U16 dgdy_i;
U16 dbdy_i;
U16 dady_i;
//-
U16 drde_f;
U16 dgde_f;
U16 dbde_f;
U16 dade_f;
//-
U16 drdy_f;
U16 dgdy_f;
U16 dbdy_f;
U16 dady_f;
};
typedef struct N64_DP_CmdExt_FillTriangleTexturing N64_DP_CmdExt_FillTriangleTexturing;
struct N64_DP_CmdExt_FillTriangleTexturing
{
U16 s_i;
U16 t_i;
U16 w_i;
U16 padding_0;
//-
U16 dsdx_i;
U16 dtdx_i;
U16 dwdx_i;
U16 padding_1;
//-
U16 s_f;
U16 t_f;
U16 w_f;
U16 padding_2;
//-
U16 dsdx_f;
U16 dtdx_f;
U16 dwdx_f;
U16 padding_3;
//-
U16 dsde_i;
U16 dtde_i;
U16 dwde_i;
U16 padding_4;
//-
U16 dsdy_i;
U16 dtdy_i;
U16 dwdy_i;
U16 padding_5;
//-
U16 dsde_f;
U16 dtde_f;
U16 dwde_f;
U16 padding_6;
//-
U16 dsdy_f;
U16 dtdy_f;
U16 dwdy_f;
U16 padding_7;
};
typedef struct N64_DP_CmdExt_FillTriangleDepth N64_DP_CmdExt_FillTriangleDepth;
struct N64_DP_CmdExt_FillTriangleDepth
{
U32 z;
U32 dzdx;
U32 dzde;
U32 dzdy;
};
typedef union N64_DP_Cmd_FillRectangle N64_DP_Cmd_FillRectangle;
union N64_DP_Cmd_FillRectangle
{
U64 v[1];
struct
{
N64_DP_CmdID id;
U16 lower_right_x : 12;
U16 lower_right_y : 12;
U8 padding : 8;
U16 upper_left_x : 12;
U16 upper_left_y : 12;
};
};
pack_end
//- rjf: DP status register flags
typedef U32 N64_DP_StatusFlags;
enum
{
N64_DP_StatusFlag_DMemDMA = (1<<0), // DP is using DMEM DMA
N64_DP_StatusFlag_Freeze = (1<<1), // DP is frozen
N64_DP_StatusFlag_Flush = (1<<2), // DP is flushed
N64_DP_StatusFlag_GCLKAlive = (1<<3), // DP GCLK is alive
N64_DP_StatusFlag_TMemBusy = (1<<4), // DP TMEM is busy
N64_DP_StatusFlag_PipeBusy = (1<<5), // DP pipeline is busy
N64_DP_StatusFlag_Busy = (1<<6), // DP command unit is busy
N64_DP_StatusFlag_BufferReady = (1<<7), // DP command buffer is ready
N64_DP_StatusFlag_DMABusy = (1<<8), // DP DMA is busy
N64_DP_StatusFlag_EndValid = (1<<9), // DP command end register is valid
N64_DP_StatusFlag_StartValid = (1<<10), // DP command start register is valid
};
typedef U32 N64_DP_StatusCmd;
enum
{
N64_DP_StatusCmd_ResetXBusDMemDMA = (1<<0), // DP status flags write mask: clear status flags DMemDMA bit
N64_DP_StatusCmd_SetXBusDMemDMA = (1<<1), // DP status flags write mask: set status flags DMemDMA bit
N64_DP_StatusCmd_ResetFreeze = (1<<2), // DP status flags write mask: clear status flags Freeze bit
N64_DP_StatusCmd_SetFreeze = (1<<3), // DP status flags write mask: set status flags Freeze bit
N64_DP_StatusCmd_ResetFlush = (1<<4), // DP status flags write mask: clear status flags Flush bit
N64_DP_StatusCmd_SetFlush = (1<<5), // DP status flags write mask: set status flags Flush bit
N64_DP_StatusCmd_ResetTMemCounter = (1<<6), // DP status flags write mask: clear TMem counter
N64_DP_StatusCmd_ResetPipeCounter = (1<<7), // DP status flags write mask: clear Pipe counter
N64_DP_StatusCmd_ResetCmdCounter = (1<<8), // DP status flags write mask: clear Cmd counter
N64_DP_StatusCmd_ResetClockCounter = (1<<9), // DP status flags write mask: clear Clock counter
};
//- rjf: DP register layout
typedef struct N64_DP_Regs N64_DP_Regs;
struct N64_DP_Regs
{
U32 start;
U32 end;
U32 current;
N64_DP_StatusFlags status_flags;
U32 clock;
U32 busy;
U32 pipe_busy;
U32 tmem_busy;
};
//- rjf: DP register pointer
#define N64_DP_REGS ((volatile N64_DP_Regs *)0xA4100000u)
////////////////////////////////
//~ rjf: Framebuffer Dimensions
#define N64_FB_WIDTH 320
#define N64_FB_HEIGHT 240
////////////////////////////////
//~ rjf: Default Fonts
#define N64_BIG_DEFAULT_FONT_GLYPH_WIDTH 7
#define N64_BIG_DEFAULT_FONT_GLYPH_HEIGHT 14
#define N64_BIG_DEFAULT_FONT_GLYPH_ADVANCE 6
typedef struct N64_BigDefaultFontGlyph N64_BigDefaultFontGlyph;
struct N64_BigDefaultFontGlyph
{
U8 v[N64_BIG_DEFAULT_FONT_GLYPH_WIDTH*N64_BIG_DEFAULT_FONT_GLYPH_HEIGHT];
};
#define N64_SMALL_DEFAULT_FONT_GLYPH_WIDTH 5
#define N64_SMALL_DEFAULT_FONT_GLYPH_HEIGHT 11
#define N64_SMALL_DEFAULT_FONT_GLYPH_ADVANCE 4
typedef struct N64_SmallDefaultFontGlyph N64_SmallDefaultFontGlyph;
struct N64_SmallDefaultFontGlyph
{
U8 v[N64_SMALL_DEFAULT_FONT_GLYPH_WIDTH*N64_SMALL_DEFAULT_FONT_GLYPH_HEIGHT];
};
////////////////////////////////
//~ rjf: Main State Bundle
typedef struct N64_State N64_State;
struct N64_State
{
//- rjf: console kind
N64_ConsoleKind console_kind;
//- rjf: joybus interaction state
N64_JoyBus_Status joybus_status;
N64_JoyBus_PortState joybus_port_states[N64_JOYBUS_PORT_COUNT];
N64_JoyBus_Cmd joybus_cmds[8];
U32 joybus_cmds_write_pos;
U32 joybus_cmds_read_pos;
//- rjf: current controller states
GamePad game_pads[GAME_PAD_SLOT_COUNT];
};
////////////////////////////////
//~ rjf: Globals
extern U8 __bss_end[];
extern N64_BigDefaultFontGlyph n64_big_default_font_glyphs[256];
extern N64_SmallDefaultFontGlyph n64_small_default_font_glyphs[256];
extern U8 n64_dgtlgrove_logo_32x48[32*48];
extern U8 n64_controller_icon_32x32[32*32];
global U16 * volatile n64_fb = 0;
global N64_State n64_state = {0};
global ThreadCtx n64_tctx = {0};
////////////////////////////////
//~ rjf: Low-Level Post-IPL3 Entry Point
void _start(void);
////////////////////////////////
//~ rjf: Low-Level Interrupt Handler
void _interrupt_handler(void);
////////////////////////////////
//~ rjf: Detected Console Kind
function N64_ConsoleKind N64_GetConsoleKind(void);
////////////////////////////////
//~ rjf: JoyBus Specs
function N64_JoyBus_CmdIDInfo N64_JoyBus_InfoFromCmdID(N64_JoyBus_CmdID id);
////////////////////////////////
//~ rjf: Cache Operations
function void N64_DataCacheHitWriteback(volatile void *addr, UAddr length);
function void N64_DataCacheHitInvalidate(volatile void *addr, UAddr length);
function void N64_DataCacheHitWritebackInvalidate(volatile void *addr, UAddr length);
function void N64_DataCacheIndexWritebackInvalidate(volatile void *addr, UAddr length);
function void N64_InstCacheHitWriteback(volatile void *addr, UAddr length);
function void N64_InstCacheHitInvalidate(volatile void *addr, UAddr length);
function void N64_InstCacheFill(volatile void *addr, UAddr length);
function void N64_InstCacheIndexInvalidate(volatile void *addr, UAddr length);
////////////////////////////////
//~ rjf: JoyBus Command Processing
function B32 N64_JoyBusCmdPush(N64_JoyBus_CmdID id);
function void N64_JoyBusCmdSend(N64_JoyBus_Cmd *cmd);
////////////////////////////////
//~ rjf: Rendering Helpers
function U16 *N64_Framebuffer(void);
function void N64_DrawMonochromeBitmap(Vec2F32 pos, Vec4F32 color, U8 *ptr, U32 width, U32 height);
function Vec2F32 N64_DrawDebugString(B32 big, Vec2F32 pos, Vec4F32 color, String8 string);
////////////////////////////////
//~ rjf: High-Level Interrupt Handler
void N64_InterruptHandler(N64_ExceptionRegState *regs, N64_ExceptionCode exception_code);
////////////////////////////////
//~ rjf: High-Level N64 Program Entry Point
void N64_EntryPoint(void);
#endif // N64_H