Files
goldeneye-pc-port/src/boss.c
T
James Dansereau 36de85e789 D339 (#92): GE_PERFSTAT perf probe + measured CPU budget (CPU likely not the low-end bottleneck; GPU suspect)
Env-gated per-frame timing: render phases, tris/batches, game-thread busy,
GL_TIME_ELAPSED GPU timer. Measured ~2.1 ms/frame total CPU on the heaviest
levels here; the GPU timer is only meaningful when GPU-bound. Target-hardware
numbers needed before a low-end preset. Golden 3/3; Win + Linux builds.
2026-09-24 11:11:47 -04:00

957 lines
35 KiB
C

#include <os_extension.h>
#include <PR/os.h>
#ifdef PORT
#include <stdlib.h>
#include <stdio.h>
#endif
#include "bondview.h"
#include <bondconstants.h>
#include <bondgame.h>
#include "boss.h"
#include "deb.h"
#include "debugmenu.h"
#include "game/dyn.h"
#include "game/game_debug.h"
#include "game/file.h"
#include "game/file2.h"
#include "game/indy_comms.h"
#include "init.h"
#include "joy.h"
#include "game/lv.h"
#include "game/front.h"
#include "mema.h"
#include "memp.h"
#include "music.h"
#include "game/ob.h"
#include "ramrom.h"
#include "random.h"
#include "rmon.h" /*<PR/rmon.h>*/
#include "sched.h"
#include "speed_graph.h"
#include "token.h"
#include "tlb_manage.h"
#include "fr.h"
#include "game/image.h"
#include "vi.h"
#include "game/bg.h"
#include "game/debugmenu_handler.h"
#include "game/lv.h"
#include "game/ramromreplay.h"
#include "game/rsp.h"
#include "game/stan.h"
#include "game/textrelated.h"
#include "game/player.h"
#include "game/frametiming.h"
#include "PR/R4300.h"
/**
* @file boss.c
* @brief Main game loop and initialization functions.
*
* This file contains the core functions that drive the game loop, manage stages, and handle debug operations.
*/
// #define OS_CLOCK_RATE 62500000LL
// #define OS_CPU_COUNTER (OS_CLOCK_RATE * 3 / 4)
#ifdef REFRESH_PAL
#define CYCLES_PER_FRAME ((u32) OS_CPU_COUNTER / 50U) // 937,500
#define INTERVAL_INTER_MATH (CYCLES_PER_FRAME / 2U) // 468,750
// this is: frameDelay * 931050 - 465525
#define MAIN_LOOP_TICK_INTERVAL frameDelay * (CYCLES_PER_FRAME - 6450U) - (INTERVAL_INTER_MATH - 3225U)
#else
#define CYCLES_PER_FRAME ((u32) OS_CPU_COUNTER / 60U) // 781,250
#define INTERVAL_INTER_MATH (CYCLES_PER_FRAME / 2U) // 390,625
// note: 3225U * 5/6 = 2687.5
#define MAIN_LOOP_TICK_INTERVAL (INTERVAL_INTER_MATH - 2688U) // 387,937
#endif
/**
* Copied from n64devkit\ultra\usr\src\pr\demos_old\simple\gfx.h
*/
typedef union {
struct {
short type;
} gen;
struct {
short type;
} done;
OSScMsg app;
} GFXMsg;
// forward declarations
void bossMainloop(void);
/* data */
u32 g_BossDebugNoticeEntry = 0;
s32 g_DebugAndUpdateStageFlag = FALSE;
s32 g_StageNum = LEVELID_TITLE;
u32 g_CurentMMallocValue = 0x234800;
u32 g_CurentMaMallocValue = 0x4B000;
s32 g_ShowMemUseFlag = FALSE;
s32 g_ShowMemBarsFlag = FALSE;
struct memallocstring memallocstringtable[] = {
{ LEVELID_DAM, "-ml0 -me0 -mgfx70 -mvtx50 -mt625 -ma275"},
{ LEVELID_FACILITY, "-ml0 -me0 -mgfx70 -mvtx50 -mt650 -ma160"},
{ LEVELID_RUNWAY, "-ml0 -me0 -mgfx100 -mvtx50 -mt610 -ma300"},
{ LEVELID_SURFACE, "-ml0 -me0 -mgfx70 -mvtx50 -mt600 -ma300"},
{ LEVELID_BUNKER1, "-ml0 -me0 -mgfx100 -mvtx50 -mt700 -ma150"},
{ LEVELID_SILO, "-ml0 -me0 -mgfx70 -mvtx50 -mt660 -ma150"},
{ LEVELID_FRIGATE, "-ml0 -me0 -mgfx70 -mvtx50 -mt750 -ma225"},
{ LEVELID_SURFACE2, "-ml0 -me0 -mgfx100 -mvtx50 -mt550 -ma350"},
{ LEVELID_BUNKER2, "-ml0 -me0 -mgfx100 -mvtx50 -mt725 -ma150"},
{ LEVELID_STATUE, "-ml0 -me0 -mgfx70 -mvtx50 -mt750 -ma220"},
{ LEVELID_ARCHIVES, "-ml0 -me0 -mgfx70 -mvtx50 -mt600 -ma250"},
{ LEVELID_STREETS, "-ml0 -me0 -mgfx60 -mvtx40 -mt635 -ma290"},
{ LEVELID_DEPOT, "-ml0 -me0 -mgfx60 -mvtx50 -mt710 -ma300"},
{ LEVELID_TRAIN, "-ml0 -me0 -mgfx100 -mvtx50 -mt600 -ma200"},
{ LEVELID_JUNGLE, "-ml0 -me0 -mgfx70 -mvtx50 -mt500 -ma200"},
{ LEVELID_CONTROL, "-ml0 -me0 -mgfx70 -mvtx50 -mt671 -ma200"},
{ LEVELID_CRADLE, "-ml0 -me0 -mgfx100 -mvtx50 -mt650 -ma250"},
{ LEVELID_CAVERNS, "-ml0 -me0 -mgfx100 -mvtx50 -mt530 -ma250"},
{ LEVELID_AZTEC, "-ml0 -me0 -mgfx60 -mvtx40 -mt855 -ma135"},
{ LEVELID_EGYPT, "-ml0 -me0 -mgfx100 -mvtx50 -mt600 -ma250"},
{ LEVELID_CITADEL, "-ml0 -me0 -mgfx100 -mvtx50 -mt650 -ma150"},
{ LEVELID_CUBA, "-ml0 -me0 -mgfx100 -mvtx50 -mt300 -ma300"},
{ LEVELID_TITLE, "-ml0 -me0 -mgfx80 -mvtx20 -mt646 -ma001"},
{ 0x5B, "-ml0 -me0 -mgfx60 -mvtx20 -mt500 -ma001"},
{ 0x63, "-ml0 -me0 -mgfx60 -mvtx20 -mt500 -ma001"},
{ LEVELID_TEMPLE, "-ml0 -me0 -mgfx130 -mvtx100 -mt390 -ma100"},
{ LEVELID_COMPLEX, "-ml0 -me0 -mgfx130 -mvtx100 -mt400 -ma100"},
{ LEVELID_LIBRARY, "-ml0 -me0 -mgfx130 -mvtx100 -mt400 -ma100"},
{ LEVELID_BASEMENT, "-ml0 -me0 -mgfx130 -mvtx100 -mt400 -ma100"},
{ LEVELID_STACK, "-ml0 -me0 -mgfx130 -mvtx100 -mt400 -ma100"},
{ LEVELID_CAVES, "-ml0 -me0 -mgfx130 -mvtx100 -mt400 -ma300"},
{ LEVELID_BUNKER2_MP , "-ml0 -me0 -mgfx130 -mvtx100 -mt550 -ma170"},
{ LEVELID_ARCHIVES_MP , "-ml0 -me0 -mgfx80 -mvtx100 -mt550 -ma250"},
{ LEVELID_CAVERNS_MP , "-ml0 -me0 -mgfx130 -mvtx100 -mt440 -ma220"},
{ LEVELID_FACILITY_MP , "-ml0 -me0 -mgfx90 -mvtx100 -mt550 -ma230"},
{ LEVELID_EGYPT_MP , "-ml0 -me0 -mgfx110 -mvtx100 -mt350 -ma400"},
{ LEVELID_DEFAULT, "-ml0 -me0 -mgfx100 -mvtx50 -mt700 -ma400"},
{ 0x0, },
{ 0x0, },
{ 0x0, }
};
s32 g_MainStageNum = LEVELID_NONE;
#ifdef PORT
static s32 g_d235ReloadStage = -1, g_d235ReloadWait = 0; /* TEMP D235 verification */
#endif
s32 g_BossIsDebugMenuOpen = FALSE;
OSScMsg g_bossGfxDoneMsg = { OS_SC_DONE_MSG };
// extern declarations
extern struct player *g_CurrentPlayer;
/**
* 6930 70005D30
* ??? - uses "-level_", "-m" strings
*/
void bossInitMainthreadData(void)
{
OSMesg bossmsg;
OSTimer bosstimer;
OSMesgQueue bossmq;
u32 start;
u32 unused;
s32 i;
debInit();
romCreateMesgQueue();
tlbmanageEstablishManagementTable();
image_entries_load();
mempInit();
memaInit();
bgInit();
viInit();
viInitVideoSettings();
indycommInit();
g_DebugAndUpdateStageFlag = rmonGetToken();
obInit();
rspInit();
dynInit();
joyInit();
osCreateMesgQueue(&bossmq, &bossmsg, 1);
for (i = 0; i != MAXCONTROLLERS; i++)
{
osSetTimer(&bosstimer, OS_USEC_TO_CYCLES(100000), 0, &bossmq, &bossmsg);
osRecvMesg(&bossmq, &bossmsg, OS_MESG_BLOCK);
if (i == 1)
{
joyCheckStatusThreadSafe();
}
else if (i >= 2)
{
joyConsumeSamplesWrapper();
}
}
if (tokenFind(1, "-level_") == 0)
{
g_DebugAndUpdateStageFlag = 1;
}
stanInit();
gameInit();
if (g_DebugAndUpdateStageFlag != 0)
{
tokenSetString(" -ml0 -me0 -mgfx100 -mvtx50 -mt700 -ma400");
}
#ifdef PORT
// D121 (WS1): a bare `-level_XX` on PC otherwise boots with default
// pools and OOM-crashes. On N64 the per-stage memallocstringtable[]
// loop (boss.c bossMainloop) supplies the right `-m*` row, but it is
// gated on g_DebugAndUpdateStageFlag, which `-level_` leaves to
// rmonGetToken() -> 0 on PC. Enabling the flag here instead is NOT
// equivalent (it routes boot through the title stage). So inject the
// stage's row directly, only when no `-m*` args were supplied by hand.
// Same lookup the N64 loop does; no control-flow / flag change.
// tokenSetString() REPLACES the whole token buffer, so the injected
// string must carry `-level_XX` (and `-hard N`) forward or bossMainloop
// loses them and boots the title stage instead.
else if (tokenFind(1, "-level_") != 0 && tokenFind(1, "-m") == 0)
{
const unsigned char *lvl = (const unsigned char *)tokenFind(1, "-level_");
const char *hard = tokenFind(1, "-hard");
s32 wantId = (((s32)(lvl[0] * 10) + (s32)lvl[1]) - 0x210);
s32 k;
for (k = 0; memallocstringtable[k].id != 0; k++)
{
if (memallocstringtable[k].id == wantId)
{
static char portTokenBuf[128];
if (hard != NULL && hard[0] != '\0')
{
sprintf(portTokenBuf, "-level_%c%c -hard%c %s",
lvl[0], lvl[1], hard[0],
memallocstringtable[k].string);
}
else
{
sprintf(portTokenBuf, "-level_%c%c %s",
lvl[0], lvl[1], memallocstringtable[k].string);
}
tokenSetString(portTokenBuf);
break;
}
}
}
#endif
if (tokenFind(1, "-m") != 0)
{
g_CurentMMallocValue = (s32) (strtol(tokenFind(1, "-m"), 0, 0) << 0xa);
}
start = (PHYS_TO_K0(osVirtualToPhysical(&_bssSegmentEnd)));
mempCheckMemflagTokens(start, ((u32)tlbmanageGetTlbAllocatedBlock() - (u32)start));
mempResetBank(MEMPOOL_PERMANENT);
langInit();
lvInit();
bossInit();
textInit();
debmenuInit();
default_player_perspective_and_height();
store_osgetcount();
null_init_main_1();
speedgraphInit();
set_gu_scale();
null_init_main_2();
casingsInit();
alloc_load_expand_ani_table();
init_weapon_animation_groups_maybe();
reset_counter_rand_body_head();
null_init_main_3();
init_player_gait_object();
initGameData();
fileResetRamRomSave();
clear_ramrom_block_buffer_heading_ptrs();
}
/**
* 6BF4 70005FF4
* 1 ->"show mem use" debug memory display [800241B4]; fry AT,T6
*/
void bossEnableShowMemUseFlag(void) {
g_ShowMemUseFlag=TRUE;
}
/**
* 6C04 70006004
* toggle "show mem bars" [800241B8]; fries V0,T6,T7
*/
void bossMemBarsFlagToggle(void) {
g_ShowMemBarsFlag = g_ShowMemBarsFlag ^ 1;
}
/**
* 6C1C 7000601C
* loads primary resources and starts main program loop
* this is infinite. Loops unconditionally: JAL 70006060
*/
void bossEntry(void) {
bossInitMainthreadData();
rspAllocateBuffers();
musicSeqPlayerInit();
while(1){
bossMainloop();
}
}
/**
* Main program loop.
*
* 6C60 70006060
*
* Seems to have been based on devkit example at one point,
* n64devkit\ultra\usr\src\pr\demos_old\simple\simple.c
*
* loop:
* 70006090 tests memstring for "-level_##"
* 700060DC if not title, tests memstring for "-hard#"
* 70006160 follows...
* 700061FC test if debug console unconnected [800241A4]
* 700062EC follows...
* 700062FC tests memstring for "-ma"
* 7000633C allocates "-ma" bytes to mem bank 4
* 7000635C reset player data pointers
* 70006364 offsets stage number based on number of players unless main menu
* 700063A0 parses and sets memory allocation, loads stage, etc.
* ...
* 70006708 displays memory usage when active
* 70006724 displays in-game debugger when active
* 7000674C writes a full sync, end display list combo
* 7000676C display mem use when active [800241B4]
* 700067A8 display mem bars when active [800241B8]
* 700067C0 follows...
* 700067D8 tests if "u64.taskgrab.#.core" activated and dumps memory
* 70006854 follows... (700068BC - stop demos)
*/
void bossMainloop(void)
{
// declarations
s32 done;
const unsigned char *tokenFindLevel;
GFXMsg *localGfxFrameMsg; // sp 468
OSScMsg localGfxDoneMsg; // sp 436
s32 stringIndex;
s32 toggleFlag; // sp 428
Gfx *gdl; // sp424
Gfx *firstGdl;
u32 nowCount;
s32 i;
s32 count;
s8 joyStickXPos;
s8 joyStickYPos;
u16 joyButtons;
struct player *localPlayer;
s32 localSelectedNumPlayers;
u32 pendingGfx = 0;
s32 freeGfx;
s32 mainTickElapsed;
s32 rspReplyMsg;
u32 unused_stackpadding_[56];
// end declarations
done = 0;
reset_mem_bank_5();
if (tokenFind(1, "-level_") != NULL)
{
tokenFindLevel = (const unsigned char *)tokenFind(1, "-level_");
// quick hack strltolon, converts the two digit ASCII level to 32bit int
// eg, "09" = 48*10 + 57 - 528 = 9
g_StageNum = (((s32)(tokenFindLevel[0] * 10) + (s32)tokenFindLevel[1]) - 0x210);
}
if (g_StageNum != LEVELID_TITLE)
{
fileValidateSaves();
fileSetCurrentFolder(FOLDER1);
set_selected_difficulty(DIFFICULTY_AGENT);
set_solo_and_ptr_briefing(g_StageNum);
if (tokenFind(1, "-hard"))
{
// convert ASCII difficulty value to int in set difficulty calls eg '1' = 49, 49-48 = 1
set_selected_difficulty(*(const unsigned char*)tokenFind(1, "-hard") - '0');
lvlSetSelectedDifficulty(*(const unsigned char*)tokenFind(1, "-hard") - '0');
}
}
nowCount = osGetCount();
randomSetSeed(nowCount);
// 'done' value never changes, and control never breaks -- infinite loop
while (!done)
{
localGfxFrameMsg = NULL;
localGfxDoneMsg = g_bossGfxDoneMsg;
toggleFlag = 0;
pendingGfx = 0;
test_if_recording_demos_this_stage_load(g_StageNum, lvlGetSelectedDifficulty());
if (g_DebugAndUpdateStageFlag)
{
stringIndex = -1;
if (g_StageNum != LEVELID_TITLE && get_selected_num_players() >= 2)
{
stringIndex = 0;
while(memallocstringtable[stringIndex].id)
{
if (memallocstringtable[stringIndex].id == (g_StageNum + 400))
{
break;
}
stringIndex++;
}
if (memallocstringtable[stringIndex].id == 0)
{
stringIndex = -1;
}
}
if (stringIndex)
{
// empty
}
if (stringIndex < 0)
{
stringIndex = 0;
while(memallocstringtable[stringIndex].id)
{
if (memallocstringtable[stringIndex].id == g_StageNum)
{
break;
}
stringIndex++;
}
}
tokenSetString(memallocstringtable[stringIndex].string);
}
mempResetBank(MEMPOOL_STAGE);
obBlankResourcesLoadedInBank(MEMPOOL_STAGE);
if (tokenFind(1, "-ma"))
{
g_CurentMaMallocValue = (s32) (strtol(tokenFind(1, "-ma"), NULL, 0) * 1024);
}
#ifdef PORT
/* D294: PC visibility settings (Video.FovScale / WidescreenAuto /
* DrawDistance / LodDistance) keep more rooms on screen at once than
* the N64's fixed view, so the authored `-ma` room pool can exhaust;
* memaAlloc() then fails and undrawn rooms render as solid black.
* Scale the pool by portRoomPoolScale() (1.0 at all-N64-faithful
* settings, capped 2.0x) and never let it eat more than the STAGE
* bank minus 128 KB headroom for zbuf/object resources. */
{
extern f32 portRoomPoolScale(void);
f32 scale = portRoomPoolScale();
s32 avail;
u32 scaled;
if (scale < 0.9999f || scale > 1.0001f) {
scaled = (u32)(((s64)g_CurentMaMallocValue * (s64)(scale * 4.0f) + 3) / 4);
avail = mempGetBankSizeLeft(MEMPOOL_STAGE) - 0x20000;
if ((s32)scaled > avail && avail >= (s32)g_CurentMaMallocValue) {
scaled = (u32)avail;
}
g_CurentMaMallocValue = scaled;
}
}
#endif
#ifdef PORT
if (getenv("GE_D294")) { osSyncPrintf("D294 stage=%d ma=%d\n", (s32)g_StageNum, (s32)g_CurentMaMallocValue); }
#endif
memaReset(mempAllocBytesInBank(g_CurentMaMallocValue, MEMPOOL_STAGE), g_CurentMaMallocValue);
reset_play_data_ptrs();
#ifdef PORT
/* D235 (wrong textures after dying/restarting a level): the port-side
* GL texture cache (port/fast3d/gfx_pc.cpp TextureCacheMap) is keyed by
* (source address, palette addrs, fmt, siz, size_bytes, palette_hash)
* with NO content check. The MEMPOOL_STAGE arena those stage-texture
* source addresses live in was just reset above, so every entry
* pointing into it is stale. On a death/restart the texpool can place
* a DIFFERENT image at an address that held another one during the
* previous run of this level -> cache hit returns the old image =
* "wrong textures" that only ever appear on the reload path, never on
* a cold boot. The N64 has no such cache (the RSP re-reads DRAM every
* frame), so clearing it here is a port-only correctness fix; the
* re-uploads are pixel-identical and cost one-time during the loading
* screen. videoResetTextureCache() existed for exactly this and had
* zero callers -- this is the wiring. GE_D235=1 logs how many stale
* entries each transition actually purged (evidence for the finding;
* a cold boot into a level should log 0, a death/restart >0). */
{
static int ge_d235 = -1;
if (ge_d235 < 0) ge_d235 = getenv("GE_D235") != NULL;
extern void videoResetTextureCache(void);
extern int gfx_texture_cache_count(void);
if (ge_d235)
osSyncPrintf("D235: stage=%d texcache entries purged at load=%d\n",
(s32)g_StageNum, gfx_texture_cache_count());
if (!getenv("GE_D235_OFF")) /* TEMP: A/B switch for verification */
videoResetTextureCache();
/* TEMP (D235 verification, remove with the probe):
* GE_D235_RELOAD=<frames> re-enters the first level once, <frames>
* retraces after the title stage loads following it (e.g. after a
* watch Abort) -- so the front end has rendered its own textures
* into the reset stage arena first, like a real death/restart --
* via the same bossSetLoadedStage() call the front end's mission
* start makes (front.c). Inert when unset. */
{
static int s_reload = -2, s_firstLevel = -1;
if (s_reload == -2) {
const char *e = getenv("GE_D235_RELOAD");
s_reload = e ? atoi(e) : -1;
}
if (s_reload >= 0) {
if (g_StageNum != LEVELID_TITLE && s_firstLevel < 0)
s_firstLevel = g_StageNum;
else if (g_StageNum == LEVELID_TITLE && s_firstLevel >= 0 &&
g_d235ReloadStage < 0 && g_d235ReloadWait == 0) {
/* GE_D235_NEXT=<stage id>: load a DIFFERENT stage
* instead (level A -> title -> level B leaks). */
const char *nx = getenv("GE_D235_NEXT");
g_d235ReloadStage = (nx && *nx) ? (s32)strtol(nx, NULL, 0) : s_firstLevel;
g_d235ReloadWait = s_reload + 1;
}
}
}
}
#endif
localSelectedNumPlayers = 0;
if (g_StageNum != LEVELID_TITLE)
{
localSelectedNumPlayers = 1;
if (get_selected_num_players() >= 2)
{
localSelectedNumPlayers = get_selected_num_players();
}
}
init_player_data_ptrs_construct_viewports(localSelectedNumPlayers);
dynInitMemory();
joyCheckStatusThreadSafe();
lvlStageLoad(g_StageNum);
viInitBuffers();
debmenuRefresh();
waitForNextFrame();
speedgraphMarkerCommit();
if(1); // regalloc
if(1);
while (osRecvMesg(&gfxFrameMsgQ, (OSMesg *)&localGfxFrameMsg, OS_MESG_NOBLOCK) == 0)
{
// empty
}
if(1); // regalloc
if(1);
while (g_MainStageNum < 0 || pendingGfx != 0)
{
#ifdef PORT
/* #92 perf probe (GE_PERFSTAT=1): game-thread busy time between
* waking for a message and blocking again (logic + DL build). */
{
static int pon = -1; static u32 tw = 0; static f64 busy = 0; static s32 n = 0;
if (pon < 0) pon = getenv("GE_PERFSTAT") != NULL;
if (pon) {
u32 now = osGetCount();
if (tw) { busy += (f64)(u32)(now - tw); n++; }
if (n == 600) {
fprintf(stderr, "PERFSTAT game-thread busy=%.2fms/msg (avg over 600 msgs)\n",
busy / (f64)n / (f64)(OS_CPU_COUNTER / 1000));
busy = 0; n = 0;
}
}
osRecvMesg(&gfxFrameMsgQ, (OSMesg *)&localGfxFrameMsg, OS_MESG_BLOCK);
if (pon) tw = osGetCount();
}
#else
osRecvMesg(&gfxFrameMsgQ, (OSMesg *)&localGfxFrameMsg, OS_MESG_BLOCK);
#endif
switch (localGfxFrameMsg->gen.type)
{
case (OS_SC_RETRACE_MSG):
{
#ifdef DEBUG
/* debug logging from simple.c, I think this requires #include <ultralog.h>
* // if (logging)
* // osLogEvent(log, LOG_RETRACE, 1, pendingGfx);
*/
#endif
mainTickElapsed = (u32) (osGetCount() - copy_of_osgetcount_value_1);
if (mainTickElapsed < MAIN_LOOP_TICK_INTERVAL)
{
// nothing to do.
}
else
{
if (g_MainStageNum < 0 && pendingGfx < 2U)
{
#ifdef PORT
/* TEMP D235 verification (see GE_D235_RELOAD above) */
if (g_d235ReloadWait > 0 && --g_d235ReloadWait == 0) {
osSyncPrintf("D235: RELOAD re-entering stage %d\n", g_d235ReloadStage);
bossSetLoadedStage(g_d235ReloadStage);
}
#endif
if (get_is_ramrom_flag())
{
iterate_ramrom_entries_handle_camera_out();
}
else
{
waitForNextFrame();
}
speedgraphRenderGraph();
speedgraphMarkerCommit();
speedgraphMarkerHandler(0x20000);
joyConsumeSamplesWrapper();
permit_stderr(0);
gdl = firstGdl = dynGetMasterDisplayList();
#if defined(DEBUGMENU)
//ported from pd beta, official way to open debug menu
// If menu is open (?) or player has pressed C down + C up
if (g_BossIsDebugMenuOpen || joyGetButtons(0, U_CBUTTONS | D_CBUTTONS) == (U_CBUTTONS | D_CBUTTONS)) {
joyStickXPos = joyGetStickX(0);
joyStickYPos = joyGetStickY(0);
joyButtons = joyGetButtons(0, ANY_BUTTON);
g_BossIsDebugMenuOpen = debug_menu_processor(joyStickXPos, joyStickYPos, joyButtons, joyGetButtonsPressedThisFrame(0, ANY_BUTTON));
} else if (joyGetButtons(0, START_BUTTON) == 0) {
g_DebugMode = g_DebugHighlightedOption;
} else
{
joyStickXPos = joyGetStickX(0);
joyStickYPos = joyGetStickY(0);
joyButtons = joyGetButtons(0, ANY_BUTTON);
g_BossIsDebugMenuOpen = debug_menu_processor(joyStickXPos, joyStickYPos, joyButtons, joyGetButtonsPressedThisFrame(0, ANY_BUTTON));
}
#elif defined(PORT)
// GE_DEBUGMENU=1 -- diagnostic-only: enable leftover debug-menu open
// path (D243). Unset (default) reproduces the #ifndef DEBUGMENU
// behavior below exactly -- no behavior change.
static int ge_debugmenu = -1;
if (ge_debugmenu < 0) ge_debugmenu = getenv("GE_DEBUGMENU") != NULL;
if (ge_debugmenu && (g_BossIsDebugMenuOpen || joyGetButtons(0, U_CBUTTONS | D_CBUTTONS) == (U_CBUTTONS | D_CBUTTONS))) {
joyStickXPos = joyGetStickX(0);
joyStickYPos = joyGetStickY(0);
joyButtons = joyGetButtons(0, ANY_BUTTON);
g_BossIsDebugMenuOpen = debug_menu_processor(joyStickXPos, joyStickYPos, joyButtons, joyGetButtonsPressedThisFrame(0, ANY_BUTTON));
} else if (ge_debugmenu && joyGetButtons(0, START_BUTTON) == 0) {
g_DebugMode = g_DebugHighlightedOption;
} else if (ge_debugmenu) {
joyStickXPos = joyGetStickX(0);
joyStickYPos = joyGetStickY(0);
joyButtons = joyGetButtons(0, ANY_BUTTON);
g_BossIsDebugMenuOpen = debug_menu_processor(joyStickXPos, joyStickYPos, joyButtons, joyGetButtonsPressedThisFrame(0, ANY_BUTTON));
} else if (g_BossIsDebugMenuOpen)
{
joyStickXPos = joyGetStickX(0);
joyStickYPos = joyGetStickY(0);
joyButtons = joyGetButtons(0, ANY_BUTTON);
g_BossIsDebugMenuOpen = debug_menu_processor(joyStickXPos, joyStickYPos, joyButtons, joyGetButtonsPressedThisFrame(0, ANY_BUTTON));
}
#else
if (g_BossIsDebugMenuOpen)
{
joyStickXPos = joyGetStickX(0);
joyStickYPos = joyGetStickY(0);
joyButtons = joyGetButtons(0, ANY_BUTTON);
g_BossIsDebugMenuOpen = debug_menu_processor(joyStickXPos, joyStickYPos, joyButtons, joyGetButtonsPressedThisFrame(0, ANY_BUTTON));
}
#endif
lvlManageMpGame();
shuffle_player_ids();
if (g_StageNum != LEVELID_TITLE)
{
for (i = 0; i < getPlayerCount(); i++)
{
set_cur_player(get_nth_player_from_shuffled(i));
localPlayer = g_CurrentPlayer;
viSetViewSize(localPlayer->viewx, localPlayer->viewy);
localPlayer = g_CurrentPlayer;
viSetViewPosition(localPlayer->viewleft, localPlayer->viewtop);
lvlViewMoveTick();
}
}
#ifdef PORT
/* D250: isolate DL-building cost (culling,
* animation, scene traversal) from gfx_run's
* software-RSP interpretation cost, measured
* separately by libultra.c's "frame rendered in
* us" (D193 already showed that one stays well
* under budget while real fps still sags -- this
* finds out whether lvlRender is where the rest
* of the missing time goes). Env-gated, no-op
* unless GE_D250 is set. */
{
static int gd250 = -1;
static OSTime d250Sum = 0, d250WallStart = 0;
static int d250N = 0;
OSTime d250T0 = 0;
if (gd250 < 0) gd250 = getenv("GE_D250") != NULL;
if (gd250) d250T0 = osGetTime();
#endif
gdl = lvlRender(gdl);
#ifdef PORT
if (gd250) {
OSTime now = osGetTime();
d250Sum += now - d250T0;
d250N++;
if (!d250WallStart) d250WallStart = now;
if (now - d250WallStart >= 1000000) {
fprintf(stderr,
"[D250] lvlRender avg=%lluus n=%d\n",
(unsigned long long)(d250Sum / (d250N ? d250N : 1)),
d250N);
d250Sum = 0; d250N = 0; d250WallStart = now;
}
}
}
#endif
// Lets Visualise the Coverage Value used for Scilohete Anti-Ailising (edges)
// (done on the VI), also produces a cool looking linemode - providing AA is working.
if (get_debug_VisCVG_flag())
{
gDPPipeSync(gdl++); // 0xe7000000, 0x00000000
gDPSetCycleType(gdl++, G_CYC_1CYCLE); // 0xba001402, 0x00000000
gDPSetBlendColor(gdl++, 0xff, 0xff, 0xff, 0xff); // 0xf9000000, 0xffffffff
gDPSetPrimDepth(gdl++, 0xffff, 0xffff); // 0xee000000, 0xffffffff
gDPSetDepthSource(gdl++, G_ZS_PRIM); // 0xb9000201, 0x00000004
gDPSetRenderMode(gdl++, G_RM_VISCVG, G_RM_VISCVG2); // 0xb900031d, 0x0fa54040
gDPFillRectangle(gdl++, 0, 0, viGetX() - 1, viGetY() - 1);
}
gdl = debmenuDraw(gdl);
if (get_memusage_display_flag())
{
gdl = speedgraphDisplayMetrics(gdl);
}
if (g_BossIsDebugMenuOpen)
{
debugmenuUpdate();
gdl = debugmenuRender(gdl);
}
gDPFullSync(gdl++); // 0xe9000000, 0x00000000
gSPEndDisplayList(gdl++); // 0xb8000000, 0x00000000
if (g_ShowMemUseFlag)
{
nulled_mempLoopAllMemBanks();
memaDumpPrePostMerge();
dynRemovedFunc(gdl);
nullsub_41(0);
g_ShowMemUseFlag = 0;
}
if (g_ShowMemBarsFlag)
{
dynDrawMembars(gdl);
}
freeGfx = dynGetFreeGfx2(gdl);
dynSwapBuffers();
video_related_8();
if ((get_debug_taskgrab_val())
&& (joyGetButtonsPressedThisFrame(0, (A_BUTTON | B_BUTTON)))
&& (joyGetButtons(0, (A_BUTTON | B_BUTTON)) == (A_BUTTON | B_BUTTON)))
{
static s32 taskgrab_ramdump_num = 1;
u8 taskGrabBuffer[28];
s32 taskGrabFileSize;
while (1)
{
s32 unusedSprintf = sprintf(taskGrabBuffer, "u64.taskgrab.%d.core", taskgrab_ramdump_num);
if (indycommHostCheckFileExists(taskGrabBuffer, &taskGrabFileSize) != NULL)
{
taskgrab_ramdump_num++;
continue;
}
break;
}
indycommHostSendDump(taskGrabBuffer, (u8*)0x80000000, 0x400000);
}
rspReplyMsg = (s32)(&localGfxDoneMsg);
rspGfxTaskStart(firstGdl, gdl, 0, (s32*)rspReplyMsg);
pendingGfx++;
memaSingleDefragPass();
#ifdef VERSION_EU
eu_sub_7f0c00a4();
#endif
toggleFlag ^= 1;
speedgraphMarkerHandler(0x10000);
if(1);
}
}
}
break;
case (OS_SC_DONE_MSG):
pendingGfx--;
break;
case OS_SC_PRE_NMI_MSG: // message when the console's power is being cut off
pendingGfx = 4U; // this stops any further graphics processing
break;
}
}
lvlUnloadStageTextData();
stop_demo_playback();
mempNullNextEntryInBank(MEMPOOL_STAGE);
obBlankResourcesLoadedInBank(MEMPOOL_STAGE);
#if defined(VERSION_EU)
if(1);
#endif
g_StageNum = g_MainStageNum;
g_MainStageNum = LEVELID_NONE;
}
// if(gdl) also works here.
if (!gdl)
// removed ... or ido?
;
sub_GAME_7F0D1A7C();
}
/**
* 7530 70006930
* run title [0x5A->loaded stage#]; fry AT
* redirect to 70006950: A0=0x5A
*/
void bossRunTitleStage(void) {
bossSetLoadedStage(LEVELID_TITLE);
}
/**
* 7550 70006950
* A0->loaded stage# [800242FC]; fry AT
* 0x5A jumps to folder select
* 0x5B
* 0x63
*/
void bossSetLoadedStage(LEVELID stage){
g_MainStageNum = stage;
}
/**
* 755C 7000695C
* V0= stage# [800241A8]
*/
LEVELID bossGetStageNum() {
return g_StageNum;
}
/**
* 7568 70006968
* return to title screen from stage
*/
void bossReturnTitleStage(void) {
#ifdef BUGFIX_R1
display_objective_status_text_on_status_change();
objectivestatusDisableStatusDisplay();
#endif
#ifdef PORT
if (getenv("GE_D160")) {
osSyncPrintf("D160: bossReturnTitleStage ENTER stage=%d allComplete=%d caller=%p\n",
(int)bossGetStageNum(), (int)objectiveIsAllComplete(),
__builtin_return_address(0));
}
if (getenv("GE_SAVELOG")) {
extern int g_savelogObjOnce;
g_savelogObjOnce = 1;
osSyncPrintf("SAVELOG: bossReturnTitleStage stage=%d objectiveIsAllComplete=%d\n",
(int)bossGetStageNum(), (int)objectiveIsAllComplete());
g_savelogObjOnce = 0;
}
#endif
if ((bossGetStageNum() != LEVELID_CUBA) && (objectiveIsAllComplete() != 0x0)) {
end_of_mission_briefing();
}
bossRunTitleStage();
}
/**
* 75B4 700069B4
* V0=state of debug menu (1:on; 0:off) [80024300]
*/
s32 bossGetDebugParseFlag(void) {
return g_BossIsDebugMenuOpen;
}
/**
* 75C0 700069C0
* V0= p->debug.notice.list entry for boss_c_debug using data at 800241A0
*/
void bossInit(void) {
debTryAdd(&g_BossDebugNoticeEntry, "boss_c_debug");
}