mk64/src/main.c

1234 lines
40 KiB
C

#ifndef GCC
#define D_800DC510_AS_U16
#endif
#include <ultra64.h>
#include <PR/os.h>
#include <PR/ucode.h>
#include <macros.h>
#include <decode.h>
#include <mk64.h>
#include "profiler.h"
#include "main.h"
#include "racing/memory.h"
#include "menus.h"
#include <segments.h>
#include <common_structs.h>
#include <defines.h>
#include "buffers.h"
#include "camera.h"
#include "profiler.h"
#include "race_logic.h"
#include "skybox_and_splitscreen.h"
#include "render_objects.h"
#include "effects.h"
#include "code_80281780.h"
#include "audio/external.h"
#include "code_800029B0.h"
#include "code_80280000.h"
#include "podium_ceremony_actors.h"
#include "code_80091750.h"
#include "code_80057C60.h"
#include "profiler.h"
#include "player_controller.h"
#include "render_player.h"
#include "render_courses.h"
#include "actors.h"
#include "staff_ghosts.h"
#include <debug.h>
#include "crash_screen.h"
#include "buffers/gfx_output_buffer.h"
// Declarations (not in this file)
void func_80091B78(void);
void audio_init();
void create_debug_thread(void);
void start_debug_thread(void);
struct SPTask *create_next_audio_frame_task(void);
struct VblankHandler *gVblankHandler1 = NULL;
struct VblankHandler *gVblankHandler2 = NULL;
struct SPTask *gActiveSPTask = NULL;
struct SPTask *sCurrentAudioSPTask = NULL;
struct SPTask *sCurrentDisplaySPTask = NULL;
struct SPTask *sNextAudioSPTask = NULL;
struct SPTask *sNextDisplaySPTask = NULL;
struct Controller gControllers[NUM_PLAYERS];
struct Controller *gControllerOne = &gControllers[0];
struct Controller *gControllerTwo = &gControllers[1];
struct Controller *gControllerThree = &gControllers[2];
struct Controller *gControllerFour = &gControllers[3];
struct Controller *gControllerFive = &gControllers[4]; // All physical controllers combined.`
struct Controller *gControllerSix = &gControllers[5];
struct Controller *gControllerSeven = &gControllers[6];
struct Controller *gControllerEight = &gControllers[7];
Player gPlayers[NUM_PLAYERS];
Player *gPlayerOne = &gPlayers[0];
Player *gPlayerTwo = &gPlayers[1];
Player *gPlayerThree = &gPlayers[2];
Player *gPlayerFour = &gPlayers[3];
Player *gPlayerFive = &gPlayers[4];
Player *gPlayerSix = &gPlayers[5];
Player *gPlayerSeven = &gPlayers[6];
Player *gPlayerEight = &gPlayers[7];
Player *gPlayerOneCopy = &gPlayers[0];
Player *gPlayerTwoCopy = &gPlayers[1];
UNUSED Player *gPlayerThreeCopy = &gPlayers[2];
UNUSED Player *gPlayerFourCopy = &gPlayers[3];
UNUSED s32 D_800FD850[3];
struct GfxPool gGfxPools[2];
struct GfxPool *gGfxPool;
UNUSED s32 gfxPool_padding; // is this necessary?
struct VblankHandler gGameVblankHandler;
struct VblankHandler sSoundVblankHandler;
OSMesgQueue gDmaMesgQueue, gGameVblankQueue, gGfxVblankQueue, unused_gMsgQueue, gIntrMesgQueue, gSPTaskMesgQueue;
OSMesgQueue sSoundMesgQueue;
OSMesg sSoundMesgBuf[1];
OSMesg gDmaMesgBuf[1], gGameMesgBuf;
OSMesg gGfxMesgBuf[1];
UNUSED OSMesg D_8014F010, D_8014F014;
OSMesg gIntrMesgBuf[16], gSPTaskMesgBuf[16];
OSMesg gMainReceivedMesg;
OSIoMesg gDmaIoMesg;
OSMesgQueue gSIEventMesgQueue;
OSMesg gSIEventMesgBuf[3];
OSContStatus gControllerStatuses[4];
OSContPad gControllerPads[4];
u8 gControllerBits;
// Contains a 32x32 grid of indices into gCollisionIndices containing indices into gCollisionMesh
CollisionGrid gCollisionGrid[1024];
u16 gNumActors;
u16 gMatrixObjectCount;
s32 gTickSpeed;
f32 D_80150118;
u16 wasSoftReset;
u16 D_8015011E;
s32 D_80150120;
s32 gGotoMode;
UNUSED s32 D_80150128;
UNUSED s32 D_8015012C;
f32 gCameraZoom[4]; // look like to be the fov of each character
UNUSED s32 D_80150140;
UNUSED s32 D_80150144;
f32 gScreenAspect;
f32 D_8015014C;
f32 D_80150150;
UNUSED f32 D_80150154;
struct D_80150158 gD_80150158[16];
uintptr_t gSegmentTable[16];
Gfx *gDisplayListHead;
struct SPTask *gGfxSPTask;
s32 D_801502A0;
s32 D_801502A4;
u16 *gPhysicalFramebuffers[3];
uintptr_t gPhysicalZBuffer;
UNUSED u32 D_801502B8;
UNUSED u32 D_801502BC;
Mat4 D_801502C0;
s32 padding[2048];
u16 D_80152300[4];
u16 D_80152308;
UNUSED OSThread paddingThread;
OSThread gIdleThread;
ALIGNED8 u8 gIdleThreadStack[STACKSIZE]; // Based on sm64 and padding between bss symbols.
OSThread gVideoThread;
ALIGNED8 u8 gVideoThreadStack[STACKSIZE];
UNUSED OSThread D_80156820;
UNUSED ALIGNED8 u8 D_8015680_Stack[STACKSIZE];
OSThread gGameLoopThread;
ALIGNED8 u8 gGameLoopThreadStack[STACKSIZE];
OSThread gAudioThread;
ALIGNED8 u8 gAudioThreadStack[STACKSIZE];
UNUSED OSThread D_8015CD30;
UNUSED ALIGNED8 u8 D_8015CD30_Stack[STACKSIZE / 2];
ALIGNED8 u8 gGfxSPTaskYieldBuffer[4352];
ALIGNED8 u32 gGfxSPTaskStack[256];
OSMesg gPIMesgBuf[32];
OSMesgQueue gPIMesgQueue;
s32 gGamestate = 0xFFFF;
// D_800DC510 is externed as an s32 in other files. D_800DC514 is only used in main.c, likely a developer mistake.
u16 D_800DC510 = 0;
u16 D_800DC514 = 0;
u16 creditsRenderMode = 0; // Renders the whole track. Displays red if used in normal race mode.
u16 gDemoMode = DEMO_MODE_INACTIVE;
u16 gEnableDebugMode = DEBUG_MODE;
s32 gGamestateNext = 7; // = COURSE_DATA_MENU?;
UNUSED s32 D_800DC528 = 1;
s32 gActiveScreenMode = SCREEN_MODE_1P;
s32 gScreenModeSelection = SCREEN_MODE_1P;
UNUSED s32 D_800DC534 = 0;
s32 gPlayerCountSelection1 = 2;
s32 gModeSelection = GRAND_PRIX;
s32 D_800DC540 = 0;
s32 D_800DC544 = 0;
s32 gCCSelection = CC_50;
s32 gGlobalTimer = 0;
UNUSED s32 D_800DC550 = 0;
UNUSED s32 D_800DC554 = 0;
UNUSED s32 D_800DC558 = 0;
// Framebuffer rendering values (max 3)
u16 sRenderedFramebuffer = 0;
u16 sRenderingFramebuffer = 0;
UNUSED u16 D_800DC564 = 0;
s32 D_800DC568 = 0;
s32 D_800DC56C[8] = {0};
s16 sNumVBlanks = 0;
UNUSED s16 D_800DC590 = 0;
f32 gVBlankTimer = 0.0f;
f32 gCourseTimer = 0.0f;
void create_thread(OSThread *thread, OSId id, void (*entry)(void *), void *arg, void *sp, OSPri pri) {
thread->next = NULL;
thread->queue = NULL;
osCreateThread(thread, id, entry, arg, sp, pri);
}
void isPrintfInit(void);
void main_func(void) {
#ifdef VERSION_EU
osTvType = TV_TYPE_PAL;
#endif
osInitialize();
#ifdef DEBUG
isPrintfInit(); // init osSyncPrintf
#endif
create_thread(&gIdleThread, 1, &thread1_idle, NULL, gIdleThreadStack + ARRAY_COUNT(gIdleThreadStack), 100);
osStartThread(&gIdleThread);
}
/**
* Initialize hardware, start main thread, then idle.
*/
void thread1_idle(void *arg) {
osCreateViManager(OS_PRIORITY_VIMGR);
#ifdef VERSION_EU
osViSetMode(&osViModeTable[OS_VI_PAL_LAN1]);
#else // VERSION_US
if (osTvType == TV_TYPE_NTSC) {
osViSetMode(&osViModeTable[OS_VI_NTSC_LAN1]);
} else {
osViSetMode(&osViModeTable[OS_VI_MPAL_LAN1]);
}
#endif
osViBlack(true);
osViSetSpecialFeatures(OS_VI_GAMMA_OFF);
osCreatePiManager(OS_PRIORITY_PIMGR, &gPIMesgQueue, gPIMesgBuf, ARRAY_COUNT(gPIMesgBuf));
wasSoftReset = (s16) osResetType;
create_debug_thread();
start_debug_thread();
create_thread(&gVideoThread, 3, &thread3_video, arg, gVideoThreadStack + ARRAY_COUNT(gVideoThreadStack), 100);
osStartThread(&gVideoThread);
osSetThreadPri(NULL, 0);
// Halt
while (true);
}
void setup_mesg_queues(void) {
osCreateMesgQueue(&gDmaMesgQueue, gDmaMesgBuf, ARRAY_COUNT(gDmaMesgBuf));
osCreateMesgQueue(&gSPTaskMesgQueue, gSPTaskMesgBuf, ARRAY_COUNT(gSPTaskMesgBuf));
osCreateMesgQueue(&gIntrMesgQueue, gIntrMesgBuf, ARRAY_COUNT(gIntrMesgBuf));
osViSetEvent(&gIntrMesgQueue, (OSMesg) MESG_VI_VBLANK, 1);
osSetEventMesg(OS_EVENT_SP, &gIntrMesgQueue, (OSMesg) MESG_SP_COMPLETE);
osSetEventMesg(OS_EVENT_DP, &gIntrMesgQueue, (OSMesg) MESG_DP_COMPLETE);
}
void start_sptask(s32 taskType) {
if (taskType == M_AUDTASK) {
gActiveSPTask = sCurrentAudioSPTask;
} else {
gActiveSPTask = sCurrentDisplaySPTask;
}
osSpTaskLoad(&gActiveSPTask->task);
osSpTaskStartGo(&gActiveSPTask->task);
gActiveSPTask->state = SPTASK_STATE_RUNNING;
}
/**
* Initializes the Fast3D OSTask structure.
* Loads F3DEX or F3DLX based on the number of players
**/
void create_gfx_task_structure(void) {
gGfxSPTask->msgqueue = &gGfxVblankQueue;
gGfxSPTask->msg = (OSMesg) 2;
gGfxSPTask->task.t.type = M_GFXTASK;
gGfxSPTask->task.t.flags = OS_TASK_DP_WAIT;
gGfxSPTask->task.t.ucode_boot = rspF3DBootStart;
gGfxSPTask->task.t.ucode_boot_size = ((u8 *) rspF3DBootEnd - (u8 *) rspF3DBootStart);
// The split-screen multiplayer racing state uses F3DLX which has a simple subpixel calculation.
// Singleplayer race mode and all other game states use F3DEX.
// http://n64devkit.square7.ch/n64man/ucode/gspF3DEX.htm
if (gGamestate != RACING || gPlayerCountSelection1 == 1) {
gGfxSPTask->task.t.ucode = gspF3DEXTextStart;
gGfxSPTask->task.t.ucode_data = gspF3DEXDataStart;
} else {
gGfxSPTask->task.t.ucode = gspF3DLXTextStart;
gGfxSPTask->task.t.ucode_data = gspF3DLXDataStart;
}
gGfxSPTask->task.t.flags = 0;
gGfxSPTask->task.t.flags = OS_TASK_DP_WAIT;
gGfxSPTask->task.t.ucode_size = SP_UCODE_SIZE;
gGfxSPTask->task.t.ucode_data_size = SP_UCODE_DATA_SIZE;
gGfxSPTask->task.t.dram_stack = (u64 *) &gGfxSPTaskStack;
gGfxSPTask->task.t.dram_stack_size = SP_DRAM_STACK_SIZE8;
gGfxSPTask->task.t.output_buff = (u64 *) &gGfxSPTaskOutputBuffer;
gGfxSPTask->task.t.output_buff_size = (u64 *) ((u8 *) gGfxSPTaskOutputBuffer + sizeof(gGfxSPTaskOutputBuffer));
gGfxSPTask->task.t.data_ptr = (u64 *) gGfxPool->gfxPool;
gGfxSPTask->task.t.data_size = (gDisplayListHead - gGfxPool->gfxPool) * sizeof(Gfx);
func_8008C214();
gGfxSPTask->task.t.yield_data_ptr = (u64 *) &gGfxSPTaskYieldBuffer;
gGfxSPTask->task.t.yield_data_size = OS_YIELD_DATA_SIZE;
}
void init_controllers(void) {
osCreateMesgQueue(&gSIEventMesgQueue, &gSIEventMesgBuf[0], ARRAY_COUNT(gSIEventMesgBuf));
osSetEventMesg(OS_EVENT_SI, &gSIEventMesgQueue, (OSMesg) 0x33333333);
osContInit(&gSIEventMesgQueue, &gControllerBits, gControllerStatuses);
if ((gControllerBits & 1) == 0) {
sIsController1Unplugged = true;
} else {
sIsController1Unplugged = false;
}
}
void update_controller(s32 index) {
struct Controller *controller = &gControllers[index];
u16 stick;
if (sIsController1Unplugged) {
return;
}
controller->rawStickX = gControllerPads[index].stick_x;
controller->rawStickY = gControllerPads[index].stick_y;
if ((gControllerPads[index].button & 4) != 0) {
gControllerPads[index].button |= Z_TRIG;
}
controller->buttonPressed = gControllerPads[index].button & (gControllerPads[index].button ^ controller->button);
controller->buttonDepressed = controller->button & (gControllerPads[index].button ^ controller->button);
controller->button = gControllerPads[index].button;
stick = 0;
if (controller->rawStickX < -50) {
stick |= L_JPAD;
}
if (controller->rawStickX > 50) {
stick |= R_JPAD;
}
if (controller->rawStickY < -50) {
stick |= D_JPAD;
}
if (controller->rawStickY > 50) {
stick |= U_JPAD;
}
controller->stickPressed = stick & (stick ^ controller->stickDirection);
controller->stickDepressed = controller->stickDirection & (stick ^ controller->stickDirection);
controller->stickDirection = stick;
}
void read_controllers(void) {
OSMesg msg;
osContStartReadData(&gSIEventMesgQueue);
osRecvMesg(&gSIEventMesgQueue, &msg, OS_MESG_BLOCK);
osContGetReadData(gControllerPads);
update_controller(0);
update_controller(1);
update_controller(2);
update_controller(3);
gControllerFive->button = (s16) (((gControllerOne->button | gControllerTwo->button) | gControllerThree->button) | gControllerFour->button);
gControllerFive->buttonPressed = (s16) (((gControllerOne->buttonPressed | gControllerTwo->buttonPressed) | gControllerThree->buttonPressed) | gControllerFour->buttonPressed);
gControllerFive->buttonDepressed = (s16) (((gControllerOne->buttonDepressed | gControllerTwo->buttonDepressed) | gControllerThree->buttonDepressed) | gControllerFour->buttonDepressed);
gControllerFive->stickDirection = (s16) (((gControllerOne->stickDirection | gControllerTwo->stickDirection) | gControllerThree->stickDirection) | gControllerFour->stickDirection);
gControllerFive->stickPressed = (s16) (((gControllerOne->stickPressed | gControllerTwo->stickPressed) | gControllerThree->stickPressed) | gControllerFour->stickPressed);
gControllerFive->stickDepressed = (s16) (((gControllerOne->stickDepressed | gControllerTwo->stickDepressed) | gControllerThree->stickDepressed) | gControllerFour->stickDepressed);
}
void func_80000BEC(void) {
gPhysicalZBuffer = VIRTUAL_TO_PHYSICAL(&gZBuffer);
}
void dispatch_audio_sptask(struct SPTask *spTask) {
osWritebackDCacheAll();
osSendMesg(&gSPTaskMesgQueue, spTask, OS_MESG_NOBLOCK);
}
void exec_display_list(struct SPTask *spTask) {
osWritebackDCacheAll();
spTask->state = SPTASK_STATE_NOT_STARTED;
if (sCurrentDisplaySPTask == NULL) {
sCurrentDisplaySPTask = spTask;
sNextDisplaySPTask = NULL;
osSendMesg(&gIntrMesgQueue, (OSMesg) MESG_START_GFX_SPTASK, OS_MESG_NOBLOCK);
}
else{
sNextDisplaySPTask = spTask;
}
}
/**
* Set default RCP (Reality Co-Processor) settings.
*/
void init_rcp(void) {
move_segment_table_to_dmem();
init_rdp();
set_viewport();
select_framebuffer();
init_z_buffer();
}
/**
* End the master display list and initialize the graphics task structure for the next frame to be rendered.
*/
void end_master_display_list(void) {
gDPFullSync(gDisplayListHead++);
gSPEndDisplayList(gDisplayListHead++);
create_gfx_task_structure();
}
// clear_frame_buffer from SM64, with a few edits
//! @todo Why did void* work for matching
void *clear_framebuffer(s32 color) {
gDPPipeSync(gDisplayListHead++);
gDPSetRenderMode(gDisplayListHead++, G_RM_OPA_SURF, G_RM_OPA_SURF2);
gDPSetCycleType(gDisplayListHead++, G_CYC_FILL);
gDPSetFillColor(gDisplayListHead++, color);
gDPFillRectangle(gDisplayListHead++, 0, 0, SCREEN_WIDTH - 1,
SCREEN_HEIGHT - 1);
gDPPipeSync(gDisplayListHead++);
gDPSetCycleType(gDisplayListHead++, G_CYC_1CYCLE);
}
void rendering_init(void) {
gGfxPool = &gGfxPools[0];
set_segment_base_addr(1, gGfxPool);
gGfxSPTask = &gGfxPool->spTask;
gDisplayListHead = gGfxPool->gfxPool;
init_rcp();
clear_framebuffer(0);
end_master_display_list();
exec_display_list(&gGfxPool->spTask);
sRenderingFramebuffer++;
gGlobalTimer++;
}
void config_gfx_pool(void) {
gGfxPool = &gGfxPools[gGlobalTimer & 1];
set_segment_base_addr(1, gGfxPool);
gDisplayListHead = gGfxPool->gfxPool;
gGfxSPTask = &gGfxPool->spTask;
}
/**
* Send current master display list for rendering.
* Tell the VI which colour framebuffer to display.
* Yields to the VI framerate twice, locking the game at 30 FPS.
* Selects the next framebuffer to be rendered and displayed.
*/
void display_and_vsync(void) {
profiler_log_thread5_time(BEFORE_DISPLAY_LISTS);
osRecvMesg(&gGfxVblankQueue, &gMainReceivedMesg, OS_MESG_BLOCK);
exec_display_list(&gGfxPool->spTask);
profiler_log_thread5_time(AFTER_DISPLAY_LISTS);
osRecvMesg(&gGameVblankQueue, &gMainReceivedMesg, OS_MESG_BLOCK);
osViSwapBuffer((void *) PHYSICAL_TO_VIRTUAL(gPhysicalFramebuffers[sRenderedFramebuffer]));
profiler_log_thread5_time(THREAD5_END);
osRecvMesg(&gGameVblankQueue, &gMainReceivedMesg, OS_MESG_BLOCK);
crash_screen_set_framebuffer(gPhysicalFramebuffers[sRenderedFramebuffer]);
if (++sRenderedFramebuffer == 3) {
sRenderedFramebuffer = 0;
}
if (++sRenderingFramebuffer == 3) {
sRenderingFramebuffer = 0;
}
gGlobalTimer++;
}
void init_segment_ending_sequences(void) {
bzero((void *) SEG_ENDING, SEG_ENDING_SIZE);
osWritebackDCacheAll();
dma_copy((u8 *) SEG_ENDING, (u8 *) SEG_ENDING_ROM_START, SEG_ENDING_ROM_SIZE);
osInvalICache((void *) SEG_ENDING, SEG_ENDING_SIZE);
osInvalDCache((void *) SEG_ENDING, SEG_ENDING_SIZE);
}
void init_segment_racing(void) {
bzero((void *) SEG_RACING, SEG_RACING_SIZE);
osWritebackDCacheAll();
dma_copy((u8 *) SEG_RACING, (u8 *) SEG_RACING_ROM_START, SEG_RACING_ROM_SIZE);
osInvalICache((void *) SEG_RACING, SEG_RACING_SIZE);
osInvalDCache((void *) SEG_RACING, SEG_RACING_SIZE);
}
void dma_copy(u8 *dest, u8 *romAddr, size_t size) {
osInvalDCache(dest, size);
while(size > 0x100) {
osPiStartDma(&gDmaIoMesg, 0, 0, (uintptr_t) romAddr, dest, 0x100, &gDmaMesgQueue);
osRecvMesg(&gDmaMesgQueue, &gMainReceivedMesg, 1);
size -= 0x100;
romAddr += 0x100;
dest += 0x100;
}
if (size != 0) {
osPiStartDma(&gDmaIoMesg, 0, 0, (uintptr_t) romAddr, dest, size, &gDmaMesgQueue);
osRecvMesg(&gDmaMesgQueue, &gMainReceivedMesg, 1);
}
}
/**
* Setup main segments and framebuffers.
*/
void setup_game_memory(void) {
UNUSED u32 pad[2];
ptrdiff_t commonCourseDataSize; // Compressed mio0 size
uintptr_t textureSegSize;
ptrdiff_t textureSegStart;
uintptr_t allocatedMemory;
UNUSED s32 unknown_padding;
init_segment_racing();
gHeapEndPtr = SEG_RACING;
set_segment_base_addr(0, (void *) SEG_START);
// Memory pool size of 0xAB630
initialize_memory_pool(MEMORY_POOL_START, MEMORY_POOL_END);
func_80000BEC();
// Initialize trig tables segment
osInvalDCache((void *) TRIG_TABLES, TRIG_TABLES_SIZE);
osPiStartDma(&gDmaIoMesg, 0, 0, TRIG_TABLES_ROM_START, (void *) TRIG_TABLES, TRIG_TABLES_SIZE, &gDmaMesgQueue);
osRecvMesg(&gDmaMesgQueue, &gMainReceivedMesg, OS_MESG_BLOCK);
set_segment_base_addr(2, (void *) load_data(SEG_DATA_START, SEG_DATA_END));
commonCourseDataSize = COMMON_TEXTURES_SIZE;
commonCourseDataSize = ALIGN16(commonCourseDataSize);
#ifdef AVOID_UB
textureSegStart = (ptrdiff_t) SEG_RACING - commonCourseDataSize;
#else
textureSegStart = SEG_RACING - commonCourseDataSize;
#endif
osPiStartDma(&gDmaIoMesg, 0, 0, COMMON_TEXTURES_ROM_START, (void *) textureSegStart, commonCourseDataSize, &gDmaMesgQueue);
osRecvMesg(&gDmaMesgQueue, &gMainReceivedMesg, OS_MESG_BLOCK);
textureSegSize = *(uintptr_t *)(textureSegStart + 4);
textureSegSize = ALIGN16(textureSegSize);
allocatedMemory = gNextFreeMemoryAddress;
mio0decode((u8 *) textureSegStart, (u8 *) allocatedMemory);
set_segment_base_addr(0xD, (void *) allocatedMemory);
gNextFreeMemoryAddress += textureSegSize;
// Common course data does not get reloaded when the race state resets.
// Therefore, only reset the memory ptr to after the common course data.
gFreeMemoryResetAnchor = gNextFreeMemoryAddress;
}
/**
* @brief
*
*/
void game_init_clear_framebuffer(void) {
gGamestateNext = 0; // = START_MENU_FROM_QUIT?
clear_framebuffer(0);
}
void race_logic_loop(void) {
s16 i;
u16 rotY;
gMatrixObjectCount = 0;
gMatrixEffectCount = 0;
if (gIsGamePaused != 0) {
func_80290B14();
}
if (gIsInQuitToMenuTransition != 0) {
func_802A38B4(); return;
}
if (sNumVBlanks >= 6) {
sNumVBlanks = 5;
}
if (sNumVBlanks < 0) {
sNumVBlanks = 1;
}
func_802A4EF4();
switch(gActiveScreenMode) {
case SCREEN_MODE_1P:
gTickSpeed = 2;
staff_ghosts_loop();
if (gIsGamePaused == 0) {
for (i = 0; i < gTickSpeed; i++) {
if (D_8015011E) {
gCourseTimer += COURSE_TIMER_ITER;
}
func_802909F0();
evaluate_collision_for_players_and_actors();
func_800382DC();
func_8001EE98(gPlayerOneCopy, camera1, 0);
func_80028F70();
func_8028F474();
func_80059AC8();
update_course_actors();
func_802966A0();
func_8028FCBC();
}
func_80022744();
}
func_8005A070();
sNumVBlanks = 0;
profiler_log_thread5_time(LEVEL_SCRIPT_EXECUTE);
D_8015F788 = 0;
render_player_one_1p_screen();
if (!gEnableDebugMode) {
D_800DC514 = false;
} else {
if (D_800DC514) {
if ((gControllerOne->buttonPressed & R_TRIG) &&
(gControllerOne->button & A_BUTTON) &&
(gControllerOne->button & B_BUTTON)) {
D_800DC514 = false;
}
rotY = camera1->rot[1];
gDebugPathCount = D_800DC5EC->pathCounter;
if (rotY < 0x2000) {
func_80057A50(40, 100, "SOUTH ", gDebugPathCount);
} else if (rotY < 0x6000) {
func_80057A50(40, 100, "EAST ", gDebugPathCount);
} else if (rotY < 0xA000) {
func_80057A50(40, 100, "NORTH ", gDebugPathCount);
} else if (rotY < 0xE000) {
func_80057A50(40, 100, "WEST ", gDebugPathCount);
} else {
func_80057A50(40, 100, "SOUTH ", gDebugPathCount);
}
} else {
if ((gControllerOne->buttonPressed & L_TRIG) &&
(gControllerOne->button & A_BUTTON) &&
(gControllerOne->button & B_BUTTON)) {
D_800DC514 = true;
}
}
}
break;
case SCREEN_MODE_2P_SPLITSCREEN_VERTICAL:
if (gCurrentCourseId == COURSE_DK_JUNGLE) {
gTickSpeed = 3;
} else {
gTickSpeed = 2;
}
if (gIsGamePaused == 0) {
for (i = 0; i < gTickSpeed; i++) {
if (D_8015011E != 0) {
gCourseTimer += COURSE_TIMER_ITER;
}
func_802909F0();
evaluate_collision_for_players_and_actors();
func_800382DC();
func_8001EE98(gPlayerOneCopy, camera1, 0);
func_80029060();
func_8001EE98(gPlayerTwoCopy, camera2, 1);
func_80029150();
func_8028F474();
func_80059AC8();
update_course_actors();
func_802966A0();
func_8028FCBC();
}
func_80022744();
}
func_8005A070();
profiler_log_thread5_time(LEVEL_SCRIPT_EXECUTE);
sNumVBlanks = 0;
move_segment_table_to_dmem();
init_rdp();
if (D_800DC5B0 != 0) {
select_framebuffer();
}
D_8015F788 = 0;
if (gPlayerWinningIndex == 0) {
render_player_two_2p_screen_vertical();
render_player_one_2p_screen_vertical();
} else {
render_player_one_2p_screen_vertical();
render_player_two_2p_screen_vertical();
}
break;
case SCREEN_MODE_2P_SPLITSCREEN_HORIZONTAL:
if (gCurrentCourseId == COURSE_DK_JUNGLE) {
gTickSpeed = 3;
} else {
gTickSpeed = 2;
}
if (gIsGamePaused == 0) {
for (i = 0; i < gTickSpeed; i++) {
if (D_8015011E != 0) {
gCourseTimer += COURSE_TIMER_ITER;
}
func_802909F0();
evaluate_collision_for_players_and_actors();
func_800382DC();
func_8001EE98(gPlayerOneCopy, camera1, 0);
func_80029060();
func_8001EE98(gPlayerTwoCopy, camera2, 1);
func_80029150();
func_8028F474();
func_80059AC8();
update_course_actors();
func_802966A0();
func_8028FCBC();
}
func_80022744();
}
profiler_log_thread5_time(LEVEL_SCRIPT_EXECUTE);
sNumVBlanks = (u16)0;
func_8005A070();
move_segment_table_to_dmem();
init_rdp();
if (D_800DC5B0 != 0) {
select_framebuffer();
}
D_8015F788 = 0;
if (gPlayerWinningIndex == 0) {
render_player_two_2p_screen_horizontal();
render_player_one_2p_screen_horizontal();
} else {
render_player_one_2p_screen_horizontal();
render_player_two_2p_screen_horizontal();
}
break;
case SCREEN_MODE_3P_4P_SPLITSCREEN:
if (gPlayerCountSelection1 == 3) {
switch(gCurrentCourseId) {
case COURSE_BOWSER_CASTLE:
case COURSE_MOO_MOO_FARM:
case COURSE_SKYSCRAPER:
case COURSE_DK_JUNGLE:
gTickSpeed = 3;
break;
default:
gTickSpeed = 2;
break;
}
} else {
// Four players
switch(gCurrentCourseId) {
case COURSE_BLOCK_FORT:
case COURSE_DOUBLE_DECK:
case COURSE_BIG_DONUT:
gTickSpeed = 2;
break;
case COURSE_DK_JUNGLE:
gTickSpeed = 4;
break;
default:
gTickSpeed = 3;
break;
}
}
if (gIsGamePaused == 0) {
for (i = 0; i < gTickSpeed; i++) {
if (D_8015011E != 0) {
gCourseTimer += COURSE_TIMER_ITER;
}
func_802909F0();
evaluate_collision_for_players_and_actors();
func_800382DC();
func_8001EE98(gPlayerOneCopy, camera1, 0);
func_80029158();
func_8001EE98(gPlayerTwo, camera2, 1);
func_800291E8();
func_8001EE98(gPlayerThree, camera3, 2);
func_800291F0();
func_8001EE98(gPlayerFour, camera4, 3);
func_800291F8();
func_8028F474();
func_80059AC8();
update_course_actors();
func_802966A0();
func_8028FCBC();
}
func_80022744();
}
func_8005A070();
sNumVBlanks = 0;
profiler_log_thread5_time(LEVEL_SCRIPT_EXECUTE);
move_segment_table_to_dmem();
init_rdp();
if (D_800DC5B0 != 0) {
select_framebuffer();
}
D_8015F788 = 0;
if (gPlayerWinningIndex == 0) {
render_player_two_3p_4p_screen();
render_player_three_3p_4p_screen();
render_player_four_3p_4p_screen();
render_player_one_3p_4p_screen();
} else if (gPlayerWinningIndex == 1) {
render_player_one_3p_4p_screen();
render_player_three_3p_4p_screen();
render_player_four_3p_4p_screen();
render_player_two_3p_4p_screen();
} else if (gPlayerWinningIndex == 2) {
render_player_one_3p_4p_screen();
render_player_two_3p_4p_screen();
render_player_four_3p_4p_screen();
render_player_three_3p_4p_screen();
} else {
render_player_one_3p_4p_screen();
render_player_two_3p_4p_screen();
render_player_three_3p_4p_screen();
render_player_four_3p_4p_screen();
}
break;
}
if (!gEnableDebugMode) {
gEnableResourceMeters = 0;
} else {
if (gEnableResourceMeters) {
resource_display();
if ((!(gControllerOne->button & L_TRIG)) &&
(gControllerOne->button & R_TRIG) &&
(gControllerOne->buttonPressed & B_BUTTON)) {
gEnableResourceMeters = 0;
}
} else {
if ((!(gControllerOne->button & L_TRIG)) &&
(gControllerOne->button & R_TRIG) &&
(gControllerOne->buttonPressed & B_BUTTON)) {
gEnableResourceMeters = 1;
}
}
}
func_802A4300();
func_800591B4();
func_80093E20();
#if DVDL
display_dvdl();
#endif
gDPFullSync(gDisplayListHead++);
gSPEndDisplayList(gDisplayListHead++);
}
/**
* mk64's game loop depends on a series of states.
* It runs a wide branching series of code based on these states.
* State 1) Clear framebuffer
* State 2) Run menus
* State 3) Process race related logic
* State 4) Ending sequence
* State 5) Credits
*
* Note that the state doesn't flip-flop at random but is permanent
* until the state changes (ie. Exit menus and start a race).
*/
void game_state_handler(void) {
#if DVDL
if ((gControllerOne->button & L_TRIG) &&
(gControllerOne->button & R_TRIG) &&
(gControllerOne->button & Z_TRIG) &&
(gControllerOne->button & A_BUTTON)) {
gGamestateNext = CREDITS_SEQUENCE;
} else if ((gControllerOne->button & L_TRIG) &&
(gControllerOne->button & R_TRIG) &&
(gControllerOne->button & Z_TRIG) &&
(gControllerOne->button & B_BUTTON)) {
gGamestateNext = ENDING;
}
#endif
switch (gGamestate) {
case 7:
game_init_clear_framebuffer();
break;
case START_MENU_FROM_QUIT:
case MAIN_MENU_FROM_QUIT:
case PLAYER_SELECT_MENU_FROM_QUIT:
case COURSE_SELECT_MENU_FROM_QUIT:
// Display black
osViBlack(0);
update_menus();
init_rcp();
func_80094A64(gGfxPool);
#if DVDL
display_dvdl();
#endif
break;
case RACING:
race_logic_loop();
break;
case ENDING:
podium_ceremony_loop();
break;
case CREDITS_SEQUENCE:
credits_loop();
break;
}
}
void interrupt_gfx_sptask(void) {
if (gActiveSPTask->task.t.type == M_GFXTASK) {
gActiveSPTask->state = SPTASK_STATE_INTERRUPTED;
osSpTaskYield();
}
}
void receive_new_tasks(void) {
UNUSED s32 pad;
struct SPTask *spTask;
while(osRecvMesg(&gSPTaskMesgQueue, (OSMesg *) &spTask, OS_MESG_NOBLOCK) != -1) {
spTask->state = SPTASK_STATE_NOT_STARTED;
switch(spTask->task.t.type) {
case 2:
sNextAudioSPTask = spTask;
break;
case 1:
sNextDisplaySPTask = spTask;
break;
}
}
if (sCurrentAudioSPTask == NULL && sNextAudioSPTask != NULL) {
sCurrentAudioSPTask = sNextAudioSPTask;
sNextAudioSPTask = NULL;
}
if (sCurrentDisplaySPTask == NULL && sNextDisplaySPTask != NULL) {
sCurrentDisplaySPTask = sNextDisplaySPTask;
sNextDisplaySPTask = NULL;
}
}
void set_vblank_handler(s32 index, struct VblankHandler *handler, OSMesgQueue *queue, OSMesg *msg) {
handler->queue = queue;
handler->msg = msg;
switch (index) {
case 1:
gVblankHandler1 = handler;
break;
case 2:
gVblankHandler2 = handler;
break;
}
}
void start_gfx_sptask(void) {
if (gActiveSPTask == NULL && sCurrentDisplaySPTask != NULL
&& sCurrentDisplaySPTask->state == SPTASK_STATE_NOT_STARTED) {
profiler_log_gfx_time(TASKS_QUEUED);
start_sptask(M_GFXTASK);
}
}
void handle_vblank(void) {
gVBlankTimer += V_BlANK_TIMER_ITER;
sNumVBlanks++;
receive_new_tasks();
// First try to kick off an audio task. If the gfx task is currently
// running, we need to asynchronously interrupt it -- handle_sp_complete
// will pick up on what we're doing and start the audio task for us.
// If there is already an audio task running, there is nothing to do.
// If there is no audio task available, try a gfx task instead.
if (sCurrentAudioSPTask != NULL) {
if (gActiveSPTask != NULL) {
interrupt_gfx_sptask();
} else {
profiler_log_vblank_time();
start_sptask(M_AUDTASK);
}
} else {
if (gActiveSPTask == NULL && sCurrentDisplaySPTask != NULL
&& sCurrentDisplaySPTask->state != SPTASK_STATE_FINISHED) {
profiler_log_gfx_time(TASKS_QUEUED);
start_sptask(M_GFXTASK);
}
}
/* This is where I would put my rumble code... If I had any. */
#if ENABLE_RUMBLE
rumble_thread_update_vi();
#endif
if (gVblankHandler1 != NULL) {
osSendMesg(gVblankHandler1->queue, gVblankHandler1->msg, OS_MESG_NOBLOCK);
}
if (gVblankHandler2 != NULL) {
osSendMesg(gVblankHandler2->queue, gVblankHandler2->msg, OS_MESG_NOBLOCK);
}
}
void handle_dp_complete(void) {
// Gfx SP task is completely done.
if (sCurrentDisplaySPTask->msgqueue != NULL) {
osSendMesg(sCurrentDisplaySPTask->msgqueue, sCurrentDisplaySPTask->msg, OS_MESG_NOBLOCK);
}
profiler_log_gfx_time(RDP_COMPLETE);
sCurrentDisplaySPTask->state = SPTASK_STATE_FINISHED_DP;
sCurrentDisplaySPTask = NULL;
}
void handle_sp_complete(void) {
struct SPTask *curSPTask = gActiveSPTask;
gActiveSPTask = NULL;
if (curSPTask->state == SPTASK_STATE_INTERRUPTED) {
// handle_vblank tried to start an audio task while there was already a
// gfx task running, so it had to interrupt the gfx task. That interruption
// just finished.
if (osSpTaskYielded((OSTask *) curSPTask) == 0) {
// The gfx task completed before we had time to interrupt it.
// Mark it finished, just like below.
curSPTask->state = SPTASK_STATE_FINISHED;
profiler_log_gfx_time(RSP_COMPLETE);
}
// Start the audio task, as expected by handle_vblank.
profiler_log_vblank_time();
start_sptask(M_AUDTASK);
} else {
curSPTask->state = SPTASK_STATE_FINISHED;
if (curSPTask->task.t.type == M_AUDTASK) {
// After audio tasks come gfx tasks.
profiler_log_vblank_time();
if (sCurrentDisplaySPTask != NULL) {
if (sCurrentDisplaySPTask->state != SPTASK_STATE_FINISHED) {
if (sCurrentDisplaySPTask->state != SPTASK_STATE_INTERRUPTED) {
profiler_log_gfx_time(TASKS_QUEUED);
}
start_sptask(M_GFXTASK);
}
}
sCurrentAudioSPTask = NULL;
if (curSPTask->msgqueue != NULL) {
osSendMesg(curSPTask->msgqueue, curSPTask->msg, OS_MESG_NOBLOCK);
}
} else {
// The SP process is done, but there is still a Display Processor notification
// that needs to arrive before we can consider the task completely finished and
// null out sCurrentDisplaySPTask. That happens in handle_dp_complete.
profiler_log_gfx_time(RSP_COMPLETE);
}
};
}
void thread3_video(UNUSED void *arg0) {
s32 i;
u64 *framebuffer1;
OSMesg msg;
UNUSED s32 pad[4];
gPhysicalFramebuffers[0] = (u16 *) &gFramebuffer0;
gPhysicalFramebuffers[1] = (u16 *) &gFramebuffer1;
gPhysicalFramebuffers[2] = (u16 *) &gFramebuffer2;
// Clear framebuffer.
framebuffer1 = (u64 *) &gFramebuffer1;
for (i = 0; i < 19200; i++) {
framebuffer1[i] = 0;
}
setup_mesg_queues();
setup_game_memory();
create_thread(&gAudioThread, 4, &thread4_audio, 0, gAudioThreadStack + ARRAY_COUNT(gAudioThreadStack), 20);
osStartThread(&gAudioThread);
create_thread(&gGameLoopThread, 5, &thread5_game_loop, 0, gGameLoopThreadStack + ARRAY_COUNT(gGameLoopThreadStack), 10);
osStartThread(&gGameLoopThread);
while (true) {
osRecvMesg(&gIntrMesgQueue, &msg, OS_MESG_BLOCK);
switch ((u32) msg) {
case MESG_VI_VBLANK:
handle_vblank();
break;
case MESG_SP_COMPLETE:
handle_sp_complete();
break;
case MESG_DP_COMPLETE:
handle_dp_complete();
break;
case MESG_START_GFX_SPTASK:
start_gfx_sptask();
break;
}
}
}
void func_800025D4(void) {
func_80091B78();
gActiveScreenMode = SCREEN_MODE_1P;
func_802A4D18();
}
void func_80002600(void) {
func_80091B78();
gActiveScreenMode = SCREEN_MODE_1P;
func_802A4D18();
}
void func_8000262C(void) {
func_80091B78();
gActiveScreenMode = SCREEN_MODE_1P;
func_802A4D18();
}
void func_80002658(void) {
func_80091B78();
gActiveScreenMode = SCREEN_MODE_1P;
func_802A4D18();
}
/**
* Sets courseId to NULL if
*
*
*/
void update_gamestate(void) {
switch (gGamestate) {
case START_MENU_FROM_QUIT:
func_80002658();
gCurrentlyLoadedCourseId = COURSE_NULL;
break;
case MAIN_MENU_FROM_QUIT:
func_800025D4();
gCurrentlyLoadedCourseId = COURSE_NULL;
break;
case PLAYER_SELECT_MENU_FROM_QUIT:
func_80002600();
gCurrentlyLoadedCourseId = COURSE_NULL;
break;
case COURSE_SELECT_MENU_FROM_QUIT:
func_8000262C();
gCurrentlyLoadedCourseId = COURSE_NULL;
break;
case RACING:
/**
* @bug Reloading this segment makes random_u16() deterministic for player spawn order.
* In laymens terms, random_u16() outputs the same value every time.
*/
init_segment_racing();
setup_race();
break;
case ENDING:
gCurrentlyLoadedCourseId = COURSE_NULL;
init_segment_ending_sequences();
load_ceremony_cutscene();
break;
case CREDITS_SEQUENCE:
gCurrentlyLoadedCourseId = COURSE_NULL;
init_segment_racing();
init_segment_ending_sequences();
load_credits();
break;
}
}
void thread5_game_loop(UNUSED void *arg) {
osCreateMesgQueue(&gGfxVblankQueue, gGfxMesgBuf, 1);
osCreateMesgQueue(&gGameVblankQueue, &gGameMesgBuf, 1);
init_controllers();
if (!wasSoftReset) {
clear_nmi_buffer();
}
set_vblank_handler(2, &gGameVblankHandler, &gGameVblankQueue, (OSMesg) OS_EVENT_SW2);
// These variables track stats such as player wins.
// In the event of a console reset, it remembers them.
gNmiUnknown1 = &pAppNmiBuffer[0]; // 2 u8's, tracks number of times player 1/2 won a VS race
gNmiUnknown2 = &pAppNmiBuffer[2]; // 9 u8's, 3x3, tracks number of times player 1/2/3 has placed in 1st/2nd/3rd in a VS race
gNmiUnknown3 = &pAppNmiBuffer[11]; // 12 u8's, 4x3, tracks number of times player 1/2/3/4 has placed in 1st/2nd/3rd in a VS race
gNmiUnknown4 = &pAppNmiBuffer[23]; // 2 u8's, tracking number of Battle mode wins by player 1/2
gNmiUnknown5 = &pAppNmiBuffer[25]; // 3 u8's, tracking number of Battle mode wins by player 1/2/3
gNmiUnknown6 = &pAppNmiBuffer[28]; // 4 u8's, tracking number of Battle mode wins by player 1/2/3/4
rendering_init();
read_controllers();
func_800C5CB8();
while(true) {
func_800CB2C4();
// Update the gamestate if it has changed (racing, menus, credits, etc.).
if (gGamestateNext != gGamestate) {
gGamestate = gGamestateNext;
update_gamestate();
}
profiler_log_thread5_time(THREAD5_START);
config_gfx_pool();
read_controllers();
game_state_handler();
end_master_display_list();
display_and_vsync();
}
}
/**
* Sound processing thread. Runs at 50 or 60 FPS according to osTvType.
*/
void thread4_audio(UNUSED void *arg) {
UNUSED u32 unused[3];
audio_init();
osCreateMesgQueue(&sSoundMesgQueue, sSoundMesgBuf, ARRAY_COUNT(sSoundMesgBuf));
set_vblank_handler(1, &sSoundVblankHandler, &sSoundMesgQueue, (OSMesg) 512);
while (true) {
OSMesg msg;
struct SPTask *spTask;
osRecvMesg(&sSoundMesgQueue, &msg, OS_MESG_BLOCK);
profiler_log_thread4_time();
spTask = create_next_audio_frame_task();
if (spTask != NULL) {
dispatch_audio_sptask(spTask);
}
profiler_log_thread4_time();
}
}