mirror of https://github.com/n64decomp/mk64.git
1234 lines
40 KiB
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();
|
|
}
|
|
}
|