diff --git a/lib/libpm-jp.a b/lib/libpm-jp.a index ebe7fdc..3fb0474 100644 --- a/lib/libpm-jp.a +++ b/lib/libpm-jp.a @@ -70,6 +70,8 @@ pm_audio_set_mono = 0x80055E30; pm_remove_effect = 0x8005A100; nuPiReadRom = 0x8005F140; nuContDataGet = 0x8005F300; +nuGfxTaskStart = 0x8002CF24; +nuGfxTaskAllEndWait = 0x8005EF80; __osPiGetAccess = 0x80061474; __osPiRelAccess = 0x800614E0; osWritebackDCacheAll = 0x800618A0; @@ -83,6 +85,7 @@ osStopThread = 0x80066110; osGetTime = 0x80066270; osSetTime = 0x80066300; __osGetActiveQueue = 0x80066260; +osViGetNextFramebuffer = 0x800669C0; osViSwapBuffer = 0x80066FF0; osViBlack = 0x80067350; osViRepeatLine = 0x800673B0; diff --git a/lib/libpm-us.a b/lib/libpm-us.a index 89ce680..10a7162 100644 --- a/lib/libpm-us.a +++ b/lib/libpm-us.a @@ -70,6 +70,8 @@ pm_audio_set_mono = 0x80056180; pm_remove_effect = 0x8005A450; nuPiReadRom = 0x8005F490; nuContDataGet = 0x8005F650; +nuGfxTaskStart = 0x8002CCC4; +nuGfxTaskAllEndWait = 0x8005F2D0; __osPiGetAccess = 0x800614A4; __osPiRelAccess = 0x80061510; osWritebackDCacheAll = 0x800618D0; @@ -83,6 +85,7 @@ osStopThread = 0x80066140; osGetTime = 0x800662A0; osSetTime = 0x80066330; __osGetActiveQueue = 0x80066290; +osViGetNextFramebuffer = 0x800669F0; osViSwapBuffer = 0x80067020; osViBlack = 0x80067380; osViRepeatLine = 0x800673E0; diff --git a/src/commands.c b/src/commands.c index 46927bf..0f64185 100644 --- a/src/commands.c +++ b/src/commands.c @@ -31,6 +31,8 @@ struct Command fpCommands[COMMAND_MAX] = { {"store ability", COMMAND_PRESS_ONCE, 0, commandStoreAbility }, {"ignore walls", COMMAND_PRESS_ONCE, 0, commandIgnoreWalls }, {"floor clip", COMMAND_PRESS_ONCE, 0, commandFloorClip }, + {"pause", COMMAND_PRESS_ONCE, 0, commandPauseProc }, + {"frame advance", COMMAND_PRESS, 0, commandFrameAdvanceProc }, }; void showMenu(void) { @@ -77,6 +79,11 @@ void fpSetInputMask(u32 pad, s8 x, s8 y) { } bool fpWarp(enum Areas area, u16 map, u16 entrance) { + if (pm_CurGameMode < 4 || pm_CurGameMode >= 12 || pm_gGameStatus.demoState) { + fpLog("can't warp right now"); + return FALSE; + } + pm_bgm_set_song(1, -1, 0, 0, 8); // clear secondary songs pm_snd_ambient_stop_all(0); pm_au_sfx_reset_players(pm_gSoundManager); @@ -334,3 +341,22 @@ void commandFloorClip(void) { } } } + +void commandPauseProc(void) { + if (fp.pendingFrames == 0) { + fp.pendingFrames = -1; + fpLog("unpaused"); + } else { + fp.pendingFrames = 0; + fpLog("paused"); + } +} + +void commandFrameAdvanceProc(void) { + if (fp.pendingFrames == 0) { + fp.pendingFrames++; + } else { + fp.pendingFrames = 0; + fpLog("paused"); + } +} diff --git a/src/commands.h b/src/commands.h index 28793f2..34a9f1f 100644 --- a/src/commands.h +++ b/src/commands.h @@ -25,6 +25,8 @@ enum Commands { COMMAND_STORE_ABILITY, COMMAND_IGNORE_WALLS, COMMAND_FLOOR_CLIP, + COMMAND_PAUSE, + COMMAND_FRAME_ADVANCE, COMMAND_MAX }; @@ -63,6 +65,8 @@ void commandClippyProc(void); void commandStoreAbility(void); void commandIgnoreWalls(void); void commandFloorClip(void); +void commandPauseProc(void); +void commandFrameAdvanceProc(void); extern struct Command fpCommands[COMMAND_MAX]; diff --git a/src/enums.h b/src/enums.h index 9ef9a76..1a1ea13 100644 --- a/src/enums.h +++ b/src/enums.h @@ -993,4 +993,12 @@ enum GameContext { CONTEXT_PAUSE = 2, }; +enum GlobalOverrides { + GLOBAL_OVERRIDES_DISABLE_DRAW_FRAME = 0x00000008, +}; + +enum BackgroundRenderState { + BACKGROUND_RENDER_STATE_MASK = 0xF0, +}; + #endif diff --git a/src/fp.c b/src/fp.c index 83d553f..b2211e1 100644 --- a/src/fp.c +++ b/src/fp.c @@ -12,12 +12,18 @@ #include "util/geometry.h" #include "util/watchlist.h" #include +#include #include #include #include #include -FpCtxt fp = {.savedArea = 0x1c, .camDistMin = 100, .camDistMax = 1000, .freeCamMoveSpeed = 250, .freeCamPanSpeed = 70}; +FpCtxt fp = {.savedArea = 0x1c, + .camDistMin = 100, + .camDistMax = 1000, + .freeCamMoveSpeed = 250, + .freeCamPanSpeed = 70, + .pendingFrames = -1}; // Initializes and uses new stack instead of using games main thread stack. static void initStack(void (*func)(void)) { @@ -29,8 +35,13 @@ static void initStack(void (*func)(void)) { "jalr %0;" "nop;" "lw $ra, 0($sp);" - "lw $sp, 4($sp);" ::"r"(func), - "i"(&stack[sizeof(stack)])); + "lw $sp, 4($sp);" + : + : "r"(func), "i"(&stack[sizeof(stack)]) + // assume that `func` clobbers every caller-saved register + : "memory", "v0", "v1", "a0", "a1", "a2", "a3", "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7", + "t8", "t9", "ra", "hi", "lo", "$f0", "$f1", "$f2", "$f3", "$f4", "$f5", "$f6", "$f7", "$f8", + "$f9", "$f10", "$f11", "$f12", "$f13", "$f14", "$f15", "$f16", "$f17", "$f18", "$f19"); } static void mainReturnProc(struct MenuItem *item, void *data) { @@ -196,9 +207,9 @@ void fpDrawVersion(struct GfxFont *font, s32 cellWidth, s32 cellHeight, u8 menuA } void fpDrawInputDisplay(struct GfxFont *font, s32 cellWidth, s32 cellHeight, u8 menuAlpha) { - u16 dPad = pm_gGameStatus.currentButtons[0].buttons; - s8 dX = pm_gGameStatus.stickX[0]; - s8 dY = pm_gGameStatus.stickY[0]; + u16 dPad = inputPad().buttons; + s8 dX = inputX(); + s8 dY = inputY(); struct GfxTexture *texture = resourceGet(RES_ICON_BUTTONS); struct GfxTexture *controlStick = resourceGet(RES_TEXTURE_CONTROL_STICK); @@ -572,6 +583,91 @@ void fpDraw(void) { gfxFlush(); } +#define PAUSE_DL_LENGTH 1024 +#define PAUSE_BLIT_ROWS 6 // 4KB tmem / (320 * 2) ~= 6 + +static struct { + bool frozen; + bool captured; + bool overrideWasSet; + s16 bgRenderState; + u16 *frame; + s32 dlIdx; + Gfx dl[2][PAUSE_DL_LENGTH]; +} pauseFrame; + +static void fpPauseBlit(const u16 *src, u16 *dst) { + gDPPipeSync(pm_gMainGfxPos++); + gSPTexture(pm_gMainGfxPos++, -1, -1, 0, G_TX_RENDERTILE, G_ON); + gDPSetScissor(pm_gMainGfxPos++, G_SC_NON_INTERLACE, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + gDPSetColorImage(pm_gMainGfxPos++, G_IM_FMT_RGBA, G_IM_SIZ_16b, SCREEN_WIDTH, MIPS_KSEG0_TO_PHYS(dst)); + gDPSetCycleType(pm_gMainGfxPos++, G_CYC_COPY); + gDPSetRenderMode(pm_gMainGfxPos++, G_RM_NOOP, G_RM_NOOP2); + gDPSetTexturePersp(pm_gMainGfxPos++, G_TP_NONE); + gDPSetTextureLUT(pm_gMainGfxPos++, G_TT_NONE); + gDPSetAlphaCompare(pm_gMainGfxPos++, G_AC_NONE); + for (s32 y = 0; y < SCREEN_HEIGHT; y += PAUSE_BLIT_ROWS) { + gDPLoadTextureTile(pm_gMainGfxPos++, &src[y * SCREEN_WIDTH], G_IM_FMT_RGBA, G_IM_SIZ_16b, SCREEN_WIDTH, + PAUSE_BLIT_ROWS, 0, 0, SCREEN_WIDTH - 1, PAUSE_BLIT_ROWS - 1, 0, G_TX_NOMIRROR | G_TX_CLAMP, + G_TX_NOMIRROR | G_TX_CLAMP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD); + gSPTextureRectangle(pm_gMainGfxPos++, qu102(0), qu102(y), qu102(SCREEN_WIDTH - 1), + qu102(y + PAUSE_BLIT_ROWS - 1), G_TX_RENDERTILE, 0, 0, 4 << 10, 1 << 10); + } +} + +// emitted into display list before fp so frozen frame doesn't contain stale fp overlay +static void fpPauseCapture(void) { + u16 *cfb = nuGfxCfb_ptr; + if (pauseFrame.frame == NULL) { + pauseFrame.frame = malloc(SCREEN_WIDTH * SCREEN_HEIGHT * sizeof(*pauseFrame.frame)); + } + fpPauseBlit(cfb, pauseFrame.frame); + gDPPipeSync(pm_gMainGfxPos++); + gDPSetColorImage(pm_gMainGfxPos++, G_IM_FMT_RGBA, G_IM_SIZ_16b, SCREEN_WIDTH, MIPS_KSEG0_TO_PHYS(cfb)); + pauseFrame.captured = TRUE; +} + +static void fpPauseFreeze(void) { + nuGfxTaskAllEndWait(); + if (!pauseFrame.captured) { + // game skipped draw pass so copy the fb ourselves + if (pauseFrame.frame == NULL) { + pauseFrame.frame = malloc(SCREEN_WIDTH * SCREEN_HEIGHT * sizeof(*pauseFrame.frame)); + } + memcpy(pauseFrame.frame, osViGetNextFramebuffer(), SCREEN_WIDTH * SCREEN_HEIGHT * sizeof(*pauseFrame.frame)); + } + pauseFrame.overrideWasSet = (pm_gOverrideFlags & GLOBAL_OVERRIDES_DISABLE_DRAW_FRAME) != 0; + pm_gOverrideFlags |= GLOBAL_OVERRIDES_DISABLE_DRAW_FRAME; + pauseFrame.bgRenderState = pm_gGameStatus.backgroundFlags & BACKGROUND_RENDER_STATE_MASK; + pm_gGameStatus.backgroundFlags &= ~BACKGROUND_RENDER_STATE_MASK; + pauseFrame.frozen = TRUE; +} + +static void fpPauseUnfreeze(void) { + if (!pauseFrame.overrideWasSet) { + pm_gOverrideFlags &= ~GLOBAL_OVERRIDES_DISABLE_DRAW_FRAME; + } + pm_gGameStatus.backgroundFlags |= pauseFrame.bgRenderState; + pauseFrame.frozen = FALSE; + pauseFrame.captured = FALSE; +} + +static void fpPauseDrawFrame(void) { + Gfx *dl = pauseFrame.dl[pauseFrame.dlIdx]; + pauseFrame.dlIdx ^= 1; + u16 *cfb = nuGfxCfb_ptr; + pm_gMainGfxPos = dl; + + gSPSegment(pm_gMainGfxPos++, 0x00, 0x0); + fpPauseBlit(pauseFrame.frame, cfb); + + initStack(fpDraw); + + gDPFullSync(pm_gMainGfxPos++); + gSPEndDisplayList(pm_gMainGfxPos++); + nuGfxTaskStart(dl, (u32)(pm_gMainGfxPos - dl) * sizeof(*dl), NU_GFX_UCODE_F3DEX2, NU_SC_SWAPBUFFER); +} + /* ========================== HOOK ENTRY POINTS ========================== */ ENTRY void fpUpdateEntry(void) { @@ -582,13 +678,30 @@ ENTRY void fpUpdateEntry(void) { PRINTF("\n**** fp initialized ****\n\n"); } - pm_step_game_loop(); - initStack(fpUpdate); + if (fp.pendingFrames) { + if (fp.pendingFrames > 0) { + fp.pendingFrames--; + } + if (pauseFrame.frozen) { + fpPauseUnfreeze(); + } + pm_step_game_loop(); + initStack(fpUpdate); + } else { + if (!pauseFrame.frozen) { + fpPauseFreeze(); + } + initStack(fpUpdate); + fpPauseDrawFrame(); + } } ENTRY void fpDrawEntry(void) { init_gp(); pm_state_render_frontUI(); + if (fp.pendingFrames == 0 && !pauseFrame.frozen) { + fpPauseCapture(); + } initStack(fpDraw); } diff --git a/src/fp.h b/src/fp.h index dd6fe20..6534913 100644 --- a/src/fp.h +++ b/src/fp.h @@ -72,6 +72,7 @@ typedef struct { u16 freeCamMoveSpeed; u16 freeCamPanSpeed; bool ignoreWalls; + s8 pendingFrames; } FpCtxt; extern FpCtxt fp; diff --git a/src/pm64.h b/src/pm64.h index 27eda07..aac9d3e 100644 --- a/src/pm64.h +++ b/src/pm64.h @@ -53,6 +53,9 @@ #define SCRIPT_BOWSER_FINAL_2_WAVE 0x8023226C #define SCRIPT_BOWSER_FINAL_2_LIGHTNING 0x802339F4 +#define NU_GFX_UCODE_F3DEX2 0 +#define NU_SC_SWAPBUFFER 1 + // NOLINTBEGIN typedef struct OSContPad { u16 button; @@ -1439,11 +1442,14 @@ void pm_audio_set_mono(void); void pm_remove_effect(pm_EffectInstance *effect); void nuPiReadRom(u32 rom_addr, void *buf_ptr, u32 size); void nuContDataGet(OSContPad *contpad, u32 cont_no); +void nuGfxTaskStart(Gfx *gfxListPtr, u32 gfxListSize, u32 ucode, u32 flags); +void nuGfxTaskAllEndWait(void); void osWritebackDCacheAll(void); s32 _Printf(PrintCallback pfn, void *arg, const char *fmt, va_list ap); u64 osGetTime(void); void osSetTime(u64 time); OSThread *__osGetActiveQueue(void); +void *osViGetNextFramebuffer(void); void osViSwapBuffer(void *vaddr); void osViBlack(u8 active); void osViRepeatLine(u8 active); diff --git a/src/sys/settings.c b/src/sys/settings.c index 8f615d7..7617394 100644 --- a/src/sys/settings.c +++ b/src/sys/settings.c @@ -89,6 +89,8 @@ void settingsLoadDefault(void) { d->binds[COMMAND_STORE_ABILITY] = bindMake(2, BUTTON_B, BUTTON_D_LEFT); d->binds[COMMAND_IGNORE_WALLS] = bindMake(2, BUTTON_B, BUTTON_D_UP); d->binds[COMMAND_FLOOR_CLIP] = bindMake(2, BUTTON_L, BUTTON_D_DOWN); + d->binds[COMMAND_PAUSE] = bindMake(0); + d->binds[COMMAND_FRAME_ADVANCE] = bindMake(0); d->cheatEnemyContact = 0; d->controlStickRange = 90; d->controlStick = 2; diff --git a/src/sys/settings.h b/src/sys/settings.h index b23dcb4..3754f49 100644 --- a/src/sys/settings.h +++ b/src/sys/settings.h @@ -7,7 +7,7 @@ #define SETTINGS_SAVE_FILE_SIZE 0x1380 #define SETTINGS_PROFILE_MAX 4 -#define SETTINGS_VERSION 9 +#define SETTINGS_VERSION 10 #define SETTINGS_FIO_PAGE 7 #define SETTINGS_WATCHES_MAX 18