From 8c86b91097ebb3f0b9bdb2c60bc10bae7cf34898 Mon Sep 17 00:00:00 2001 From: PurpleHato Date: Fri, 29 May 2026 13:34:21 +0200 Subject: [PATCH 1/2] Waoooooh --- CMakeLists.txt | 2 + src/game/game_init.c | 5 +- src/game/rendering_graph_node.c | 19 +++ src/game/skybox.c | 13 ++- src/menu/intro_geo.c | 1 + src/port/mods/PortEnhancements.cpp | 10 ++ src/port/mods/mirror/MirrorMode.cpp | 135 ++++++++++++++++++++++ src/port/mods/mirror/MirrorMode.h | 40 +++++++ src/port/ui/GhostshipMenuEnhancements.cpp | 10 ++ 9 files changed, 233 insertions(+), 2 deletions(-) create mode 100644 src/port/mods/mirror/MirrorMode.cpp create mode 100644 src/port/mods/mirror/MirrorMode.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ead8568b0..01dd23514 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -231,6 +231,8 @@ file(GLOB_RECURSE ALL_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/port/Enhancements/game-interactor/*.cpp" "src/port/importer/*.cpp" "src/port/importer/types/*.cpp" + "src/port/mods/mirror/*.h" + "src/port/mods/mirror/*.cpp" "src/port/net/*.h" "src/port/net/*.cpp" "text/*.c" diff --git a/src/game/game_init.c b/src/game/game_init.c index 4f3a56eed..37d588a96 100644 --- a/src/game/game_init.c +++ b/src/game/game_init.c @@ -22,6 +22,8 @@ #include "port/ui/cvar_prefixes.h" #include "port/interpolation/FrameInterpolation.h" +extern void mirror_mode_invert_input(void); + // First 3 controller slots struct Controller gControllers[3]; @@ -746,7 +748,8 @@ void thread5_iteration(void){ select_gfx_pool(); CALL_CANCELLABLE_EVENT(GameReadInput) { read_controller_inputs(); - }; + } + mirror_mode_invert_input(); if (CVarGetInteger("gFrameAdvance", 0) == 1) { bool shouldTick = CVarGetInteger("gFrameAdvanceTick", 0); if (shouldTick) { diff --git a/src/game/rendering_graph_node.c b/src/game/rendering_graph_node.c index a6d0d2546..8e5f91341 100644 --- a/src/game/rendering_graph_node.c +++ b/src/game/rendering_graph_node.c @@ -6,6 +6,7 @@ #include "gfx_dimensions.h" #include "main.h" #include "memory.h" +#include "model_ids.h" #include "print.h" #include "rendering_graph_node.h" #include "shadow.h" @@ -13,6 +14,17 @@ #include "port/interpolation/FrameInterpolation.h" #include "port/Matrix.h" +extern void mirror_mode_apply_projection(void); +extern int mirror_mode_is_enabled(void); +extern void mirror_mode_undo_projection(void); +extern int mirror_mode_is_active(void); +extern s16 gCurrLevelNum; +extern s16 sCurrPlayMode; +extern s32 gCurrCreditsEntry; +extern struct MarioState *gMarioState; + +#define PLAY_MODE_NORMAL 0 + /** * This file contains the code that processes the scene graph for rendering. * The scene graph is responsible for drawing everything except the HUD / text boxes. @@ -265,6 +277,12 @@ static void geo_process_perspective(struct GraphNodePerspective *node) { gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtx), G_MTX_PROJECTION | G_MTX_LOAD | G_MTX_NOPUSH); + // Apply mirror mode transform after perspective projection + // Mirror during: normal gameplay, ending cutscenes, but NOT during credits text rendering + if (sCurrPlayMode == PLAY_MODE_NORMAL && gMarioState != NULL && gMarioState->action != 0 && gCurrCreditsEntry == NULL) { + mirror_mode_apply_projection(); + } + gCurGraphNodeCamFrustum = node; geo_process_node_and_siblings(node->fnNode.node.children); gCurGraphNodeCamFrustum = NULL; @@ -917,6 +935,7 @@ static void geo_process_object(struct Object *node) { mtxf_scale_vec3f(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex + 1], node->header.gfx.scale); + node->header.gfx.throwMatrix = &gMatStack[++gMatStackIndex]; node->header.gfx.cameraToObject[0] = gMatStack[gMatStackIndex][3][0]; node->header.gfx.cameraToObject[1] = gMatStack[gMatStackIndex][3][1]; diff --git a/src/game/skybox.c b/src/game/skybox.c index 8f3c9f474..2b31c13a7 100644 --- a/src/game/skybox.c +++ b/src/game/skybox.c @@ -21,6 +21,7 @@ #include "assets/textures/skyboxes/water.h" #include "assets/textures/skyboxes/wdw.h" #include "port/interpolation/FrameInterpolation.h" +#include "port/mods/mirror/MirrorMode.h" /** * @file skybox.c @@ -140,12 +141,22 @@ f32 calculate_skybox_scaled_x(s8 player, f32 fov) { //! double literals are used instead of floats f32 yawScaled = SCREEN_WIDTH * 360.0 * yaw / (fov * 65536.0); - // Round the scaled yaw. Since yaw is a u16, it doesn't need to check for < 0 f32 scaledX = yawScaled; if (scaledX > SKYBOX_WIDTH) { scaledX -= (s32) scaledX / SKYBOX_WIDTH * SKYBOX_WIDTH; } + + // Apply mirror mode AFTER the wrap calculation + scaledX = SKYBOX_WIDTH - scaledX; + + if (mirror_mode_is_enabled()) { + // In mirror mode, invert the skybox position + scaledX = SKYBOX_WIDTH - scaledX; + } + + return scaledX; + return SKYBOX_WIDTH - scaledX; } diff --git a/src/menu/intro_geo.c b/src/menu/intro_geo.c index cfefcc976..c30589dcb 100644 --- a/src/menu/intro_geo.c +++ b/src/menu/intro_geo.c @@ -11,6 +11,7 @@ #include "buffers/framebuffers.h" #include "game/game_init.h" #include "audio/external.h" +#include "port/mods/mirror/MirrorMode.h" #include "gfx_dimensions.h" #include "game/rendering_graph_node.h" #include "assets/bin/title_screen_bg.h" diff --git a/src/port/mods/PortEnhancements.cpp b/src/port/mods/PortEnhancements.cpp index ba790ee13..17fc89dc1 100644 --- a/src/port/mods/PortEnhancements.cpp +++ b/src/port/mods/PortEnhancements.cpp @@ -1,9 +1,11 @@ #include "PortEnhancements.h" +#include "mirror/MirrorMode.h" #define INIT_EVENT_IDS #include "sm64.h" #include "game/level_update.h" +#include "game/game_init.h" #include "port/events/Events.h" #include "assets/bin/segment2.h" #include "port/Rando/Rando.h" @@ -52,6 +54,14 @@ void PortEnhancements_Init() { PortEnhancements_Register(); PatchSetupDList(); + // Initialize mirror mode + mirror_mode_init(); + + // Register event listeners + REGISTER_LISTENER(RenderHud, EVENT_PRIORITY_NORMAL, [](IEvent* event) { + mirror_mode_undo_projection(); + }); + // Register event listeners REGISTER_LISTENER(PlayerHealthChange, EVENT_PRIORITY_NORMAL, [](IEvent* event) { PlayerHealthChange* ev = (PlayerHealthChange*)event; diff --git a/src/port/mods/mirror/MirrorMode.cpp b/src/port/mods/mirror/MirrorMode.cpp new file mode 100644 index 000000000..27593ea49 --- /dev/null +++ b/src/port/mods/mirror/MirrorMode.cpp @@ -0,0 +1,135 @@ +#include "MirrorMode.h" + +#include +#include "../../ui/cvar_prefixes.h" +#include "sm64.h" +#include "game/game_init.h" + +#define CVAR_MIRROR_MODE CVAR_ENHANCEMENT("Modes.MirroredWorld.Mode") + +static int sMirrorEnabled = 0; +static int sMirrorActive = 0; +static int sCounterMirror = 0; +static int sExcludeForFrame = 0; + +int mirror_mode_is_enabled(void) { + return CVarGetInteger(CVAR_MIRROR_MODE, 0); +} + +int mirror_mode_is_active(void) { + return sMirrorActive; +} + +void mirror_mode_apply_projection(void) { + if (!mirror_mode_is_enabled()) { + return; + } + + // Check if we should exclude mirroring for this frame + if (sExcludeForFrame) { + sExcludeForFrame = 0; + return; + } + + Mtx* mtx = (Mtx*)alloc_display_list(sizeof(Mtx)); + if (mtx == NULL) { + return; + } + + // Apply horizontal scale flip + guScale(mtx, -1.0f, 1.0f, 1.0f); + gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtx), G_MTX_PROJECTION | G_MTX_MUL | G_MTX_NOPUSH); + + // Set invert culling flag for proper face sorting + gSPSetExtraGeometryMode(gDisplayListHead++, G_EX_INVERT_CULLING); + + sMirrorActive = 1; +} + +void mirror_mode_undo_projection(void) { + if (!sMirrorActive) { + return; + } + + // Clear invert culling flag before HUD renders + gSPClearExtraGeometryMode(gDisplayListHead++, G_EX_INVERT_CULLING); + + sMirrorActive = 0; +} + +void mirror_mode_register(void) { + // Events will be registered in mirror_mode_init via PortEnhancements +} + +void mirror_mode_init(void) { + // Update enabled state from CVAR + sMirrorEnabled = mirror_mode_is_enabled(); +} + +void mirror_mode_set_counter_mirror(void) { + sCounterMirror = 1; +} + +void mirror_mode_clear_counter_mirror(void) { + sCounterMirror = 0; +} + +int mirror_mode_should_counter_mirror(void) { + return sCounterMirror && sMirrorActive; +} + +// Exclude mirroring for the current frame (for file select, etc.) +void mirror_mode_exclude_for_frame(void) { + sExcludeForFrame = 1; +} + +// Apply counter-mirror scale to cancel projection mirror for text/numbers +void mirror_mode_apply_counter_scale(void) { + if (!mirror_mode_should_counter_mirror()) { + return; + } + + Mtx* mtx = (Mtx*)alloc_display_list(sizeof(Mtx)); + if (mtx == NULL) { + return; + } + + // Apply counter-scale to cancel the projection mirror + guScale(mtx, -1.0f, 1.0f, 1.0f); + gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtx), G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH); +} + +// Patch display lists to add counter-mirror for number sprites +void mirror_mode_patch_number_dl(void) { + // This function would patch the MODEL_NUMBER display list + // to include a counter-mirror transform at the beginning + // However, in the PC port, display lists are loaded dynamically + // and the patching would need to happen at runtime + // + // The approach would be: + // 1. Load the number_geo display list + // 2. Inject a counter-mirror matrix at the beginning + // 3. This would make all number sprites appear unmirrored + // + // This is currently a placeholder for future implementation + // as it requires deeper integration with the display list system +} + +void mirror_mode_invert_input(void) { + if (!mirror_mode_is_enabled()) { + return; + } + + struct Controller* ctrl = gPlayer1Controller; + if (ctrl == NULL) { + return; + } + + // Invert BOTH rawStickX and processed stickX + // rawStickX is used for some purposes, but stickX is what mario.c uses for movement + ctrl->rawStickX = -ctrl->rawStickX; + ctrl->stickX = -ctrl->stickX; + + // Also need to update magnitude + ctrl->stickMag = sqrtf(ctrl->stickX * ctrl->stickX + ctrl->stickY * ctrl->stickY); +} diff --git a/src/port/mods/mirror/MirrorMode.h b/src/port/mods/mirror/MirrorMode.h new file mode 100644 index 000000000..a059c2ca1 --- /dev/null +++ b/src/port/mods/mirror/MirrorMode.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Core state access +int mirror_mode_is_enabled(void); +int mirror_mode_is_active(void); + +// Projection manipulation +void mirror_mode_apply_projection(void); +void mirror_mode_undo_projection(void); + +// Counter-mirror for text-bearing elements (title, star doors, etc.) +void mirror_mode_set_counter_mirror(void); +void mirror_mode_clear_counter_mirror(void); +int mirror_mode_should_counter_mirror(void); + +// Apply counter-mirror scale to cancel projection mirror for text/numbers +void mirror_mode_apply_counter_scale(void); + +// Exclude mirroring for the current frame (for file select, etc.) +void mirror_mode_exclude_for_frame(void); + +// Patch display lists for number sprites and star doors +void mirror_mode_patch_number_dl(void); + +// Event registration +void mirror_mode_register(void); +void mirror_mode_init(void); + +// Input inversion for mirror mode controls +void mirror_mode_invert_input(void); + +#ifdef __cplusplus +} +#endif diff --git a/src/port/ui/GhostshipMenuEnhancements.cpp b/src/port/ui/GhostshipMenuEnhancements.cpp index b2a555875..dc8f7a29b 100644 --- a/src/port/ui/GhostshipMenuEnhancements.cpp +++ b/src/port/ui/GhostshipMenuEnhancements.cpp @@ -223,6 +223,16 @@ void GhostshipMenu::AddMenuEnhancements() { .RaceDisable(false) .Options(CheckboxOptions().Tooltip("Fixes the Koopa race music on Bob-omb Battlefield and Tiny-Huge Island.")); + path = { "Enhancements", "Modes", SECTION_COLUMN_1 }; + AddSidebarEntry("Enhancements", path.sidebarName, 1); + path.column = SECTION_COLUMN_1; + + AddWidget(path, "Mirrored World", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("Modes.MirroredWorld.Mode")) + .RaceDisable(false) + .Options(CheckboxOptions().Tooltip( + "Mirrors the world horizontally. Inverts left/right controls to match.")); + path = { "Enhancements", "Cheats", SECTION_COLUMN_1 }; AddSidebarEntry("Enhancements", path.sidebarName, 1); path.column = SECTION_COLUMN_1; From 5fd7f01f4295a3753559bb15e60135b573c641c1 Mon Sep 17 00:00:00 2001 From: PurpleHato Date: Fri, 29 May 2026 14:31:21 +0200 Subject: [PATCH 2/2] clang --- src/game/skybox.c | 1 + src/port/mods/PortEnhancements.cpp | 4 +--- src/port/ui/GhostshipMenuEnhancements.cpp | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/game/skybox.c b/src/game/skybox.c index 2b31c13a7..05847d682 100644 --- a/src/game/skybox.c +++ b/src/game/skybox.c @@ -141,6 +141,7 @@ f32 calculate_skybox_scaled_x(s8 player, f32 fov) { //! double literals are used instead of floats f32 yawScaled = SCREEN_WIDTH * 360.0 * yaw / (fov * 65536.0); + // Round the scaled yaw. Since yaw is a u16, it doesn't need to check for < 0 f32 scaledX = yawScaled; if (scaledX > SKYBOX_WIDTH) { diff --git a/src/port/mods/PortEnhancements.cpp b/src/port/mods/PortEnhancements.cpp index 17fc89dc1..7e87fcd4a 100644 --- a/src/port/mods/PortEnhancements.cpp +++ b/src/port/mods/PortEnhancements.cpp @@ -58,9 +58,7 @@ void PortEnhancements_Init() { mirror_mode_init(); // Register event listeners - REGISTER_LISTENER(RenderHud, EVENT_PRIORITY_NORMAL, [](IEvent* event) { - mirror_mode_undo_projection(); - }); + REGISTER_LISTENER(RenderHud, EVENT_PRIORITY_NORMAL, [](IEvent* event) { mirror_mode_undo_projection(); }); // Register event listeners REGISTER_LISTENER(PlayerHealthChange, EVENT_PRIORITY_NORMAL, [](IEvent* event) { diff --git a/src/port/ui/GhostshipMenuEnhancements.cpp b/src/port/ui/GhostshipMenuEnhancements.cpp index dc8f7a29b..240b565e5 100644 --- a/src/port/ui/GhostshipMenuEnhancements.cpp +++ b/src/port/ui/GhostshipMenuEnhancements.cpp @@ -230,8 +230,7 @@ void GhostshipMenu::AddMenuEnhancements() { AddWidget(path, "Mirrored World", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("Modes.MirroredWorld.Mode")) .RaceDisable(false) - .Options(CheckboxOptions().Tooltip( - "Mirrors the world horizontally. Inverts left/right controls to match.")); + .Options(CheckboxOptions().Tooltip("Mirrors the world horizontally. Inverts left/right controls to match.")); path = { "Enhancements", "Cheats", SECTION_COLUMN_1 }; AddSidebarEntry("Enhancements", path.sidebarName, 1);