From e1ac320f8916b4ae7db93d7c497ffeb7e1f975d7 Mon Sep 17 00:00:00 2001 From: Crementif <26669564+Crementif@users.noreply.github.com> Date: Tue, 26 Jul 2022 01:13:50 +0200 Subject: [PATCH] [XCX] Port FPS++ to 60FPS mod, dynamic FPS limits and fixed cutscenes Fixes https://github.com/ActualMandM/cemu_graphic_packs/issues/475. Thanks to @MetrosexualGarbodor for the generous bounty! --- .../Mods/60FPS/patch_60fps.asm | 153 -------- .../Mods/60FPS/patch_60fps_overdrive.asm | 18 - .../Mods/60FPS/patch_60fps_qte.asm | 40 -- .../Mods/60FPS/patch_Cutscene.asm | 98 +++++ .../Mods/60FPS/patch_GameSpeed.asm | 369 ++++++++++++++++++ .../Mods/60FPS/patch_Overdrive.asm | 24 ++ .../Mods/60FPS/patch_QTE.asm | 51 +++ src/XenobladeChroniclesX/Mods/60FPS/rules.txt | 91 ++++- 8 files changed, 629 insertions(+), 215 deletions(-) delete mode 100644 src/XenobladeChroniclesX/Mods/60FPS/patch_60fps.asm delete mode 100644 src/XenobladeChroniclesX/Mods/60FPS/patch_60fps_overdrive.asm delete mode 100644 src/XenobladeChroniclesX/Mods/60FPS/patch_60fps_qte.asm create mode 100644 src/XenobladeChroniclesX/Mods/60FPS/patch_Cutscene.asm create mode 100644 src/XenobladeChroniclesX/Mods/60FPS/patch_GameSpeed.asm create mode 100644 src/XenobladeChroniclesX/Mods/60FPS/patch_Overdrive.asm create mode 100644 src/XenobladeChroniclesX/Mods/60FPS/patch_QTE.asm diff --git a/src/XenobladeChroniclesX/Mods/60FPS/patch_60fps.asm b/src/XenobladeChroniclesX/Mods/60FPS/patch_60fps.asm deleted file mode 100644 index fc24e8b17..000000000 --- a/src/XenobladeChroniclesX/Mods/60FPS/patch_60fps.asm +++ /dev/null @@ -1,153 +0,0 @@ -[XCX60fpsV16J] ; ######################################################## -moduleMatches = 0x785CA8A9 - -;Default as of 0.51 - Limit logic/2d animation to 30 fps Less timing issues, more jitter -;0x100D0604 = .float 0.5 ; .float 1.0 ; GUI animations -0x100CFAE8 = .float 0.5 ; .float 1.0 ; GUI animations * -0x10059514 = .float 0.5 ; .float 1.0 ;Title screen cutscene - -0x100144F0 = .float 0.1 ; controller acceleration -0x10171070 = .float 2.0 ; arbitrary 2.0 float -0x1017117C = .float 0.5 ; .5 float -0x10171070 = _halfRate: -0x1017117C = _fullRate: - -0x027370B8 = lis r11, _halfRate@ha ; Double updateEventParam cutscene -0x027370C4 = lfs f1, _halfRate@l(r11) ; - -0x027A0180 = lis r10, _fullRate@ha ; half SyncFrame cinematic cutscene, fixes timing issues with cinematic cutscenes - -0x027A0184 = lfs f1, _fullRate@l(r10) ; but introduces Shake and stutter issue. CODE XREF: ev::CEvtManager::startPage((float,bool))+6C j - - -0x025F149C = lis r12, _fullRate@h ;;; Move__11CfSceneTaskFv ;Filter CPU, 30 fps logic -0x025F14A4 = lfs f31, _fullRate@l(r12) ;;; Move__11CfSceneTaskFv ; -0x02768064 = lis r8, _fullRate@ha ; sync in game cut scene -0x02768068 = lfs f31, _fullRate@l(r8) ; -0x0273BBD0 = lis r7, _fullRate@ha ; sync elevator, vehicles etc -0x0273BBD4 = lfs f31, _fullRate@l(r7) ; Ç - -; swapInterval 60 -0x02FCEB9C = li r3, 1 - -[XCX60fpsV48J] ; ######################################################## -moduleMatches = 0x7672271D - -;Default as of 0.51 - Limit logic/2d animation to 30 fps Less timing issues, more jitter -;0x100D0604 = .float 0.5 ; .float 1.0 ; GUI animations * -;0x1005989C = .float 0.5 ; .float 1.0 ;Title screen cutscene - -;0x10014528 = .float 0.1 ; controller acceleration - -;0x10171980 = .float 2.0 ; arbitrary 2.0 float - -0x100D0070 = .float 0.5 -0x1005989C = .float 0.5 -0x10014528 = .float 0.1 -0x10171570 = .float 2.0 - -0x10171570 = _halfRate: -0x100D0070 = _fullRate: - -0x0273802C = lis r11, _halfRate@ha ; Double updateEventParam cutscene, ver -0x02738038 = lfs f1, _halfRate@l(r11) ; - -0x027A1120 = lis r10, _fullRate@ha ; half SyncFrame cinematic cutscene, fixes timing issues with cinematic cutscenes - -0x027A1124 = lfs f1, _fullRate@l(r10) ; but introduces Shake and stutter issue. CODE XREF: ev::CEvtManager::startPage((float,bool))+6C j - - -0x025F1F78 = lis r12, _fullRate@h ;;; Move__11CfSceneTaskFv ;Filter CPU, 30 fps logic -0x025F1F80 = lfs f31, _fullRate@l(r12) ;;; Move__11CfSceneTaskFv ; -0x02768FD4 = lis r8, _fullRate@ha ; sync in game cut scene -0x02768FD8 = lfs f31, _fullRate@l(r8) ; -0x0273CB44 = lis r7, _fullRate@ha ; sync elevator, vehicles etc -0x0273CB48 = lfs f31, _fullRate@l(r7) ; Ç - -; swapInterval 60 -0x02FD3F5C= li r3, 1 - -[XCX60fpsV100E_V101E] ; ######################################################## -moduleMatches = 0x218F6E07, 0xF882D5CF - -;Default as of 0.51 - Limit logic/2d animation to 30 fps Less timing issues -0x100D03E8 = .float 0.5 ; .float 1.0 ; GUI animations * -0x100598E4 = .float 0.5 ; .float 1.0 ;Title screen cutscene - -0x10014528 = .float 0.05 ; controller acceleration - -0x10171980 = .float 2.0 ; arbitrary 2.0 float - -0x10171980 = _halfRate: -0x100D03E8 = _fullRate: - -0x027398B4 = lis r11, _halfRate@ha ; Double updateEventParam cutscene - -0x027398C0 = lfs f1, _halfRate@l(r11) ; - -0x027A33D8 = lis r10, _fullRate@ha ; half SyncFrame cinematic cutscene, fixes timing issues with cinematic cutscenes - -0x027A33DC = lfs f1, _fullRate@l(r10) ; but introduces Shake and stutter issue. CODE XREF: ev::CEvtManager::startPage((float,bool))+6Cj - - -;0x02707478 = lis r7, _tmp@ha ; ALT calcAdxSkip__Q2_2ev13CFrameManagerFv -;0x0270747C = lfs f1, _tmp@l(r7) ; calcAdxSkip__Q2_2ev13CFrameManagerFv -;0x02707660 = lis r9, _tmp@ha ; calcCpuSkip__Q2_2ev13CFrameManagerFv ; SLOW down scene -;0x02707668 = lfs f31, _tmp@l(r9) ; calcCpuSkip__Q2_2ev13CFrameManagerFv - - -0x025F299C = lis r12, _fullRate@h ;;; Move__11CfSceneTaskFv ;Filter CPU, 30 fps logic -0x025F29A4 = lfs f31, _fullRate@l(r12) ;;; Move__11CfSceneTaskFv ; -0x0276A85C = lis r8, _fullRate@ha ; sync in game cut scene -0x0276A860 = lfs f31, _fullRate@l(r8) ; -0x0273E3CC = lis r7, _fullRate@ha ; sync elevator, vehicles etc -0x0273E3D0 = lfs f31, _fullRate@l(r7) ; Ç - -; swapInterval 60 -0x02FD8A94 = li r3, 1 - -0x10012644 = .float 15.0 ; fix for soulvoices (not sure it's safe) - -[XCX60fpsV102U] ; ######################################################## -moduleMatches = 0x30B6E091 - -;Default as of 0.51 - Limit logic/2d animation to 30 fps Less timing issues -0x100D03D0 = .float 0.5 ; .float 1.0 ; GUI animations -0x100598E4 = .float 0.5 ; .float 1.0 ;Title screen cutscene -0x10014528 = .float 0.05 ; controller acceleration -0x10171980 = .float 2.0 ; arbitrary 2.0 float - -0x10171980 = _halfRate: -0x100D03D0 = _fullRate: - -0x027398B4 = lis r11, _halfRate@ha ; Double updateEventParam cutscene -0x027398C0 = lfs f1, _halfRate@l(r11) ; -0x027A33D8 = lis r10, _fullRate@ha ; half SyncFrame cinematic cutscene, fixes timing issues with cinematic cutscenes -0x027A33DC = lfs f1, _fullRate@l(r10) ; but introduces Shake and stutter issue. -0x025F299C = lis r12, _fullRate@h ;;; Move__11CfSceneTaskFv ;Filter CPU, 30 fps logic -0x025F29A4 = lfs f31, _fullRate@l(r12) ;;; Move__11CfSceneTaskFv ; -0x0276A85C = lis r8, _fullRate@ha ; sync in game cut scene -0x0276A860 = lfs f31, _fullRate@l(r8) ; -0x0273E3CC = lis r7, _fullRate@ha ; sync elevator, vehicles etc -0x0273E3D0 = lfs f31, _fullRate@l(r7) ; Ç - - -;Disabled, original per feature approach, severe timing issues -;0x101231F0 = .float 0.5 ; .float 1.0 ; ingame animation timing -;0x100D03D0 = .float 0.5 ; .float 1.0 ; GUI animations -;0x100598E4 = .float 0.5 ; .float 1.0 ;Title screen cutscene -;0x1003C3A0 = .float 15.0 ; .float 30.0 fight 3d animations -;0x1000F7A8 = .float 0.1 ; RegistDamage_ButtonChallenge -;0x10014528 = .float 0.05 ; controller acceleration -;0x100211D8 = .float 15.0 ; .float 30.0 fade in -;0x10034804 = .float 15.0 ; .float 30.0 bullets -;0x10035D84 = .float 15.0 ; walk acceleration -;0x1003C3A0 = .float 15.0 ; arts frame to sec -;0x10012368 = .float 45.0 ; respawn -;0x10012644 = .float 15.0 ; soulvoice -;0x1000C448 = .float 15.0 ; init battle -;0x1000CB90 = .float 15.0 ; init battle -;0x1003E538 = .float 0.33333335 ;birds, falling leaves -;0x10171980 = .float 2.0 ; arbitrary 2.0 float -;0x100955F0 = .float 60.0 ; 30.0 Create avatar cam rotation - -; JFF -;0x101123B4 = .float 0.03333335 ; .float 0.016666668 superfast (vsync) -;0x10035D7C = .float 0.25 ; .float 1.0 fast run -;0x10035E00 = .float 2.0 ; jump high.float 0.5 -;0x10190C7C = .float 0.75 ; master FOV -;0x10012368 = .float 45.0 ; seconds before respawn "cheat" - -; swapInterval 60 -0x02FD8A34 = li r3, 1 - -0x10012644 = .float 15.0 ; fix for soulvoices (not sure it's safe) - -;SNESticleNGCVERIONPP71Copyright - Sardu you magnificent bastard, we salute you! - - diff --git a/src/XenobladeChroniclesX/Mods/60FPS/patch_60fps_overdrive.asm b/src/XenobladeChroniclesX/Mods/60FPS/patch_60fps_overdrive.asm deleted file mode 100644 index 3a304b63b..000000000 --- a/src/XenobladeChroniclesX/Mods/60FPS/patch_60fps_overdrive.asm +++ /dev/null @@ -1,18 +0,0 @@ -[XCX_60FPS_OVERDRIVE] ; ######################################################## -moduleMatches = 0xF882D5CF, 0x30B6E091, 0xAB97DE6B ; 1.0.1E, 1.0.2U, 1.0.1U - -.origin = codecave - -_timePassed: - .float 0.5 - -_over: - fmr f31, f1 - lis r31, _timePassed@ha - lfs f1, _timePassed@l(r31) - fmuls f1, f31, f1 - blr - -0x021BC904 = bla _over - -0x021E2020 = bla _over ; Skell Overdrive \ No newline at end of file diff --git a/src/XenobladeChroniclesX/Mods/60FPS/patch_60fps_qte.asm b/src/XenobladeChroniclesX/Mods/60FPS/patch_60fps_qte.asm deleted file mode 100644 index 12bbf4852..000000000 --- a/src/XenobladeChroniclesX/Mods/60FPS/patch_60fps_qte.asm +++ /dev/null @@ -1,40 +0,0 @@ -[XCX_60FPS_QTE] ; ######################################################## -moduleMatches = 0xF882D5CF, 0x30B6E091 ; 1.0.1E, 1.0.2U - -.origin = codecave - -_setup: - .float 15.0 ; reduces speed of animation - -_justFrame1: - lwz r12, 0x47C(r29) - mulli r12, r12, 2 - blr - -_justFrame2: - lwz r0, 0x478(r29) - mulli r0, r0, 2 - blr - -[XCX_60FPS_QTE_1E] ; ######################################################## -moduleMatches = 0xF882D5CF ; 1.0.1E - -; menu::MenuButtonChallenge::setup -0x02ACE40C = lis r7, _setup@ha -0x02ACE414 = lfs f0, _setup@l(r7) - -; menu::MenuButtonChallenge::move -0x02ACE6E4 = bla _justFrame1 -0x02ACE700 = bla _justFrame2 - -[XCX_60FPS_QTE_2U] ; ######################################################## -moduleMatches = 0x30B6E091 ; 1.0.2U - -; menu::MenuButtonChallenge::setup -0x02ACE3FC = lis r7, _setup@ha -0x02ACE404 = lfs f0, _setup@l(r7) - -; menu::MenuButtonChallenge::move -0x02ACE6D4 = bla _justFrame1 -0x02ACE6F0 = bla _justFrame2 - diff --git a/src/XenobladeChroniclesX/Mods/60FPS/patch_Cutscene.asm b/src/XenobladeChroniclesX/Mods/60FPS/patch_Cutscene.asm new file mode 100644 index 000000000..22081150c --- /dev/null +++ b/src/XenobladeChroniclesX/Mods/60FPS/patch_Cutscene.asm @@ -0,0 +1,98 @@ +[XCX_FPS++_Cutscene] +moduleMatches = 0x218F6E07, 0xF882D5CF, 0x30B6E091, 0x7672271D ; 1.0.0E, 1.0.1E, 1.0.2U, 1.0.2J + +.origin = codecave + +forceCutsceneLimit: +.int 0 + +_useCutsceneLimit: +li r10, 1 +lis r9, forceCutsceneLimit@ha +stw r10, forceCutsceneLimit@l(r9) + +lfs f11, -0x303C(r8) +blr + +_resetCutsceneLimit: +li r10, 0 +lis r9, forceCutsceneLimit@ha +stw r10, forceCutsceneLimit@l(r9) +b _restoreRegisters + +cutsceneFloatConv: +cutsceneFloatConvHa: +.uint 0 +cutsceneFloatConvL: +.uint 0 + + +; Check if a cutscene is ongoing with a set FPS limit +_waitTillCutsceneLimit: +li r10, $cutsceneFPSLimit +cmpwi r10, 1 +bne _calculateFPS +lis r10, forceCutsceneLimit@ha +lwz r10, forceCutsceneLimit@l(r10) +cmpwi r10, 1 +bne _calculateFPS + +; If a cutscene FPS limit is set, lower FPS +_lowerCutsceneFPS: +lis r10, const_30@ha +lfs f12, const_30@l(r10) + +; Calculate how many ticks a frame has to take to render at the given FPS limit +lis r10, const_1@ha +lfs f7, const_1@l(r10) +fdivs f12, f7, f12 +lis r10, timerTickSpeed@ha +lfs f7, timerTickSpeed@l(r10) +fmuls f12, f7, f12 + +; Subtract the time that it took for the actual frame to render and make sure that it's not negative (which means a frame already took longer to render then the FPS limit) +fsubs f12, f12, f10 +lis r10, const_0.0@ha +lfs f7, const_0.0@l(r10) +fcmpu cr0, f7, f12 +ble .+0x8 +fmr f12, f7 + +; Convert the remaining ticks that should be spend idling to an integer (but it requires storing it in memory) +fctiwz f12, f12 +lis r10, cutsceneFloatConvHa@ha +stfd f12, cutsceneFloatConvHa@l(r10) + +# ; Load the converted integer ticks and sleep for the given amount of time +mr r10, r4 +lis r9, cutsceneFloatConvL@ha +lwz r4, cutsceneFloatConvL@l(r9) +mr r11, r3 +# li r3, 0 +lis r9, cutsceneFloatConvHa@ha +lwz r3, cutsceneFloatConvHa@l(r9) +mflr r9 +bl import.coreinit.OSSleepTicks +mtlr r9 +mr r3, r11 +mr r4, r10 + +_reacquireTime: +mr r11, r3 +mr r10, r4 +mflr r9 +bl import.coreinit.OSGetSystemTime +mtlr r9 + +lis r9, prevFrameTime_Up32Bit@ha +stw r3, prevFrameTime_Up32Bit@l(r9) +lis r9, prevFrameTime_Low32Bit@ha +stw r4, prevFrameTime_Low32Bit@l(r9) +mr r3, r11 +mr r4, r10 + +; Directly set the game speed to 30FPS when cutscenes are active +lis r10, const_30@ha +lfs f10, const_30@l(r10) + +b _setGameSpeed \ No newline at end of file diff --git a/src/XenobladeChroniclesX/Mods/60FPS/patch_GameSpeed.asm b/src/XenobladeChroniclesX/Mods/60FPS/patch_GameSpeed.asm new file mode 100644 index 000000000..25158ae66 --- /dev/null +++ b/src/XenobladeChroniclesX/Mods/60FPS/patch_GameSpeed.asm @@ -0,0 +1,369 @@ +[XCX_FPS++_GameSpeed] +moduleMatches = 0x218F6E07, 0xF882D5CF, 0x30B6E091, 0x7672271D ; 1.0.0E, 1.0.1E, 1.0.2U, 1.0.2J + +.origin = codecave + +# Constants + +timerTickSpeed: +busSpeed: +.float 62156250 + +convSub: +.uint 0x43300000 +.uint 0x80000000 + +floatConv: +floatConvHa: +.uint 0 +floatConvL: +.uint 0 + +prevFrameTime: +prevFrameTime_Up32Bit: +.uint 0 +prevFrameTime_Low32Bit: +.uint 0 + +const_0.0: +.float 0.0 +const_0.1: +.float 0.1 +const_0.25: +.float 0.25 +const_0.5: +.float 0.5 +const_1: +.float 1.0 +const_1.5: +.float 1.5 +const_2.0: +.float 2.0 +const_15: +.float 15.0 +const_30: +.float 30.0 +const_60: +.float 60.0 + +const_roundUpModifier: +.float $roundUpModifier + + +# Variables + +fpsLimit: +.float $fpsLimit + +lowFPSLimit: +.float $lowFPSLimit + +bufferSizeDivider: +.float $frameAverageAmount + +averageFPS30: +.float $fpsLimit + +averageFPS30Inv: +.float (900/$fpsLimit) + +averageFPS1: +.float ($fpsLimit/30.0) + +averageFPS1Inv: +.float (30.0/$fpsLimit) + +averageFPS0.1: +.float ($fpsLimit/300.0) + +averageFPS1IntInv: +averageFPS1IntInvHa: +.uint 0 +averageFPS1IntInvL: +.uint 1 + +buffer: +.float 30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30 ; buffer can only store a max length of 32 frames + +bufferIndex: +.int 0 + +bufferIndexEnd: +.int (4*$frameAverageAmount) + + +; free registers: r10, r8, r9, r0 +; backed-up registers: r3, r4 +_calculateGamespeed: + +; TODO: Maybe filter out 0 +_loadPreviousTicks: +; Load current previous tick before overwriting it +lis r9, prevFrameTime_Up32Bit@ha +lwz r8, prevFrameTime_Up32Bit@l(r9) +lis r9, prevFrameTime_Low32Bit@ha +lwz r10, prevFrameTime_Low32Bit@l(r9) +_storeCurrentTicks: +; Store current ticks +lis r9, prevFrameTime_Up32Bit@ha +stw r3, prevFrameTime_Up32Bit@l(r9) +lis r9, prevFrameTime_Low32Bit@ha +stw r4, prevFrameTime_Low32Bit@l(r9) + +_getGameTicks: +.uint 0x7D2A2010 ; subfc r9, r10, r4 +.uint 0x7C081910 ; subfe r0, r8, r3 + +_convertTicksToFrametime: +xoris r9, r9, 0x8000 +lis r10, floatConv@ha +stw r9, floatConv@l+0x4(r10) +lis r9, 0x4330 +stw r9, floatConv@l+0x0(r10) +lfd f10, floatConv@l+0x0(r10) +lis r10, convSub@ha +lfd f12, convSub@l(r10) +fsub f10, f10, f12 +frsp f10, f10 + +; Call externalized cutscene FPS function +_callWaitTillCutsceneLimit: +b _waitTillCutsceneLimit + +_calculateFPS: +lis r10, const_1@ha +lfs f12, const_1@l(r10) +fdivs f10, f12, f10 +lis r10, timerTickSpeed@ha +lfs f12, timerTickSpeed@l(r10) +fmuls f10, f12, f10 + +; Calculate the rolling average FPS over the last N amount of frames which are stored in the circular buffer +_calcAverageFPS: +; Store FPS value from this frame into the buffer first +lis r10, buffer@ha +addi r10, r10, buffer@l +lis r9, bufferIndex@ha +lwz r9, bufferIndex@l(r9) +.int 0x7D4A4D2E +;stfsx f10, r10, r9 + +; Then store the offset to the next buffer entry +lis r8, bufferIndexEnd@ha +lwz r8, bufferIndexEnd@l(r8) +addi r9, r9, 0x04 +cmpw r8, r9 +bgt .+0x08 +li r9, 0 +lis r10, bufferIndex@ha +stw r9, bufferIndex@l(r10) + +; Finally, loop over the whole buffer and create an average sum of FPS values +_calculateBuffer: +lis r9, const_0.0@ha +lfs f10, const_0.0@l(r9) +lis r9, buffer@ha +addi r9, r9, buffer@l +lis r8, bufferIndexEnd@ha ; technically unncessary +lwz r8, bufferIndexEnd@l(r8) +li r10, 0 + +startCalculateBufferLoop: +.int 0x7D89542E +; lfsx f12, r9, r10 +fadds f10, f10, f12 +addi r10, r10, 0x04 +cmpw r10, r8 +blt startCalculateBufferLoop +lis r10, bufferSizeDivider@ha +lfs f7, bufferSizeDivider@l(r10) +fdivs f10, f10, f7 + +_setGameSpeed: +; Set game speed (30 range) +lis r10, averageFPS30@ha +stfs f10, averageFPS30@l(r10) + +; Set game speed (inverted 30 range) +lis r10, const_30@ha +lfs f12, const_30@l(r10) +fmuls f12, f12, f12 +fdivs f7, f12, f10 +lis r10, averageFPS30Inv@ha +stfs f7, averageFPS30Inv@l(r10) + +; Set game speed (1.0 range) +lis r10, const_30@ha +lfs f12, const_30@l(r10) +fdivs f7, f10, f12 +lis r10, averageFPS1@ha +stfs f7, averageFPS1@l(r10) + +; Set game speed (inverted 1.0 range) +fdivs f7, f12, f10 +lis r10, averageFPS1Inv@ha +stfs f7, averageFPS1Inv@l(r10) + +; Set game speed (inverted 0.1 range) +lis r10, averageFPS1@ha +lfs f7, averageFPS1@l(r10) +fdivs f7, f12, f10 +lis r10, const_0.1@ha +lfs f12, const_0.1@l(r10) +fmuls f7, f12, f7 +lis r10, averageFPS0.1@ha +stfs f7, averageFPS0.1@l(r10) + +; Set title FPS manually instead of editing 10 places that read this +lis r10, averageFPS1Inv@ha +lfs f7, averageFPS1Inv@l(r10) +lis r10, titleScreenSpeed@ha +stfs f7, titleScreenSpeed@l(r10) + +; Set soul voice FPS manually +lis r10, averageFPS30Inv@ha +lfs f7, averageFPS30Inv@l(r10) +lis r10, soulVoiceSpeed@ha +stfs f7, soulVoiceSpeed@l(r10) + +; Set havok half-speed +lis r10, averageFPS30@ha +lfs f7, averageFPS30@l(r10) +lis r10, havokHalfSpeed@ha +stfs f7, havokHalfSpeed@l(r10) + +; Calculate the FPS speed +_calculateAverageFPSInt: +lis r10, averageFPS1@ha +lfs f7, averageFPS1@l(r10) +lis r10, const_roundUpModifier@ha +lfs f12, const_roundUpModifier@l(r10) +fadds f7, f7, f12 +fctiwz f7, f7 +lis r10, averageFPS1IntInv@ha +stfd f7, averageFPS1IntInv@l(r10) + +; Reset the cutscene FPS limit each frame +_callResetCutsceneLimit: +b _resetCutsceneLimit + +; Restore register state +_restoreRegisters: +lis r9, prevFrameTime_Up32Bit@ha +lwz r3, prevFrameTime_Up32Bit@l(r9) +lis r9, prevFrameTime_Low32Bit@ha +lwz r4, prevFrameTime_Low32Bit@l(r9) + +lwz r10, 0x14(r1) +blr + + +[XCX_FPS++_GameSpeed_V100E_V101E] +moduleMatches = 0x218F6E07, 0xF882D5CF + +; Global data patch +0x10171980 = havokHalfSpeed: +0x100598E4 = titleScreenSpeed: +0x10012644 = soulVoiceSpeed: + +; Instruction-specific patches +0x02228274 = lis r5, averageFPS0.1@ha ; Controller acceleration +0x0222827C = lfs f30, averageFPS0.1@l(r5) ; Controller acceleration +0x0273E3CC = lis r7, averageFPS1Inv@ha ; Sync elevator, vehicles etc +0x0273E3D0 = lfs f31, averageFPS1Inv@l(r7) ; Sync elevator, vehicles etc +0x0276A85C = lis r8, averageFPS1Inv@ha ; Sync in-game cutscenes +0x0276A860 = lfs f31, averageFPS1Inv@l(r8) ; Sync in-game cutscenes +0x025F299C = lis r12, averageFPS1Inv@ha ; Move__11CfSceneTaskFv ; Filter CPU, 30FPS logic +0x025F29A4 = lfs f31, averageFPS1Inv@l(r12) ; Move__11CfSceneTaskFv +0x02D20328 = lis r12, averageFPS1Inv@ha ; MenuObject::playEvent +0x02D2032C = lfs f31, averageFPS1Inv@l(r12) ; MenuObject::playEvent +0x02D203F4 = lis r12, averageFPS1Inv@ha ; MenuObject::playEventFrame +0x02D203F8 = lfs f31, averageFPS1Inv@l(r12) ; MenuObject::playEventFrame + +; Call GX2SetSwapInterval with 0 which removes any vsync +0x02FD8A94 = li r3, 0 +; Use FPS waiting logic even with swap interval being 0 +0x02FD5A14 = li r3, 1 + +0x02FD5AB4 = bla _calculateGamespeed +0x027685B0 = bla _useCutsceneLimit + +# These patches are replaced by lowering the framerate to prevent side-effects +# 0x027398B4 = lis r11, averageFPS1@ha ; Double updateEventParam cutscenes +# 0x027398C0 = lfs f1, averageFPS1@l(r11) ; Double updateEventParam cutscenes +# 0x027A33D8 = lis r10, averageFPS1Inv@ha ; Half SyncFrame cinematic cutscene, fixes timing issues with cinematic cutscenes +# 0x027A33DC = lfs f1, averageFPS1Inv@l(r10) ; But introduces shake and stutter issue. CODE XREF: ev::CEvtManager::startPage((float,bool))+6Cj + + +[XCX_FPS++_GameSpeed_V102J] +moduleMatches = 0x7672271D + +; Global data patch +0x10171570 = havokHalfSpeed: +0x1005989C = titleScreenSpeed: +0x10012644 = soulVoiceSpeed: + +; Instruction-specific patches +0x02227D40 = lis r5, averageFPS0.1@ha ; Controller acceleration +0x02227D48 = lfs f30, averageFPS0.1@l(r5) ; Controller acceleration +0x0273CB44 = lis r7, averageFPS1Inv@ha ; Sync elevator, vehicles etc +0x0273CB48 = lfs f31, averageFPS1Inv@l(r7) ; Sync elevator, vehicles etc +0x02768FD4 = lis r8, averageFPS1Inv@ha ; Sync in-game cutscenes +0x02768FD8 = lfs f31, averageFPS1Inv@l(r8) ; Sync in-game cutscenes +0x025F1F78 = lis r12, averageFPS1Inv@ha ; Move__11CfSceneTaskFv ; Filter CPU, 30FPS logic +0x025F1F80 = lfs f31, averageFPS1Inv@l(r12) ; Move__11CfSceneTaskFv +0x02D1B818 = lis r12, averageFPS1Inv@ha ; MenuObject::playEvent +0x02D1B81C = lfs f31, averageFPS1Inv@l(r12) ; MenuObject::playEvent +0x02D1B8E4 = lis r12, averageFPS1Inv@ha ; MenuObject::playEventFrame +0x02D1B8E8 = lfs f31, averageFPS1Inv@l(r12) ; MenuObject::playEventFrame + +; Call GX2SetSwapInterval with 0 which removes any vsync +0x02FD3F5C = li r3, 0 +; Use FPS waiting logic even with swap interval being 0 +0x02FD0EDC = li r3, 1 + +0x02FD0F7C = bla _calculateGamespeed +0x02766D28 = bla _useCutsceneLimit + +# These patches are replaced by lowering the framerate to prevent side-effects +# 0x0273802C = lis r11, averageFPS1@ha ; Double updateEventParam cutscenes +# 0x02738038 = lfs f1, averageFPS1@l(r11) ; Double updateEventParam cutscenes +# 0x027A1120 = lis r10, averageFPS1Inv@ha ; Half SyncFrame cinematic cutscene, fixes timing issues with cinematic cutscenes +# 0x027A1124 = lfs f1, averageFPS1Inv@l(r10) ; But introduces shake and stutter issue. CODE XREF: ev::CEvtManager::startPage((float,bool))+6Cj + + +[XCX_FPS++_GameSpeed_V102U] +moduleMatches = 0x30B6E091 + +; Global data patch +0x10171980 = havokHalfSpeed: +0x100598E4 = titleScreenSpeed: +0x10012644 = soulVoiceSpeed: + +; Instruction-specific patches +0x02228274 = lis r5, averageFPS0.1@ha ; Controller acceleration +0x0222827C = lfs f30, averageFPS0.1@l(r5) ; Controller acceleration +0x0273E3CC = lis r7, averageFPS1Inv@ha ; Sync elevator, vehicles etc +0x0273E3D0 = lfs f31, averageFPS1Inv@l(r7) ; Sync elevator, vehicles etc +0x0276A85C = lis r8, averageFPS1Inv@ha ; Sync in-game cutscenes +0x0276A860 = lfs f31, averageFPS1Inv@l(r8) ; Sync in-game cutscenes +0x025F299C = lis r12, averageFPS1Inv@ha ; Move__11CfSceneTaskFv ; Filter CPU, 30FPS logic +0x025F29A4 = lfs f31, averageFPS1Inv@l(r12) ; Move__11CfSceneTaskFv +0x02D202C8 = lis r12, averageFPS1Inv@ha ; MenuObject::playEvent +0x02D202CC = lfs f31, averageFPS1Inv@l(r12) ; MenuObject::playEvent +0x02D20394 = lis r12, averageFPS1Inv@ha ; MenuObject::playEventFrame +0x02D20398 = lfs f31, averageFPS1Inv@l(r12) ; MenuObject::playEventFrame + +; Call GX2SetSwapInterval with 0 which removes any vsync +0x02FD8A34 = li r3, 0 +; Use FPS waiting logic even with swap interval being 0 +0x02FD59B4 = li r3, 1 + +0x02FD5A54 = bla _calculateGamespeed +0x027685B0 = bla _useCutsceneLimit + +# These patches are replaced by lowering the framerate to prevent side-effects +# 0x027398B4 = lis r11, averageFPS1@ha ; Double updateEventParam cutscenes +# 0x027398C0 = lfs f1, averageFPS1@l(r11) ; Double updateEventParam cutscenes +# 0x027A33D8 = lis r10, averageFPS1Inv@ha ; Half SyncFrame cinematic cutscene, fixes timing issues with cinematic cutscenes +# 0x027A33DC = lfs f1, averageFPS1Inv@l(r10) ; But introduces shake and stutter issue. CODE XREF: ev::CEvtManager::startPage((float,bool))+6Cj diff --git a/src/XenobladeChroniclesX/Mods/60FPS/patch_Overdrive.asm b/src/XenobladeChroniclesX/Mods/60FPS/patch_Overdrive.asm new file mode 100644 index 000000000..d2e6ff86c --- /dev/null +++ b/src/XenobladeChroniclesX/Mods/60FPS/patch_Overdrive.asm @@ -0,0 +1,24 @@ +[XCX_FPS++_Overdrive_General] ; ######################################################## +moduleMatches = 0xF882D5CF, 0x30B6E091, 0xAB97DE6B, 0x7672271D ; 1.0.1E, 1.0.2U, 1.0.1U, 1.0.2J + +.origin = codecave + +_over: + fmr f31, f1 + lis r31, averageFPS1@ha + lfs f1, averageFPS1@l(r31) + fmuls f1, f31, f1 + blr + + +[XCX_FPS++_Overdrive_NonJ] ; ######################################################## +moduleMatches = 0xF882D5CF, 0x30B6E091, 0xAB97DE6B ; 1.0.1E, 1.0.2U, 1.0.1U + +0x021BC904 = bla _over +0x021E2020 = bla _over ; Skell Overdrive + +[XCX_FPS++_Overdrive_2J] ; ######################################################## +moduleMatches = 0x7672271D ; 1.0.2J + +0x021BC3D0 = bla _over +0x021E1AEC = bla _over ; Skell Overdrive \ No newline at end of file diff --git a/src/XenobladeChroniclesX/Mods/60FPS/patch_QTE.asm b/src/XenobladeChroniclesX/Mods/60FPS/patch_QTE.asm new file mode 100644 index 000000000..d93cd2a8e --- /dev/null +++ b/src/XenobladeChroniclesX/Mods/60FPS/patch_QTE.asm @@ -0,0 +1,51 @@ +[XCX_FPS++_QTE_General] ; ######################################################## +moduleMatches = 0x218F6E07, 0xF882D5CF, 0x30B6E091, 0x7672271D ; 1.0.1E, 1.0.2U, 1.0.1U, 1.0.2J + +.origin = codecave + +_justFrame1: + lwz r12, 0x47C(r29) + lis r10, averageFPS1IntInv@ha + lbz r10, averageFPS1IntInv@l(r10) + mullw r12, r12, r10 + blr + +_justFrame2: + lwz r0, 0x478(r29) + lis r8, averageFPS1IntInv@ha + lbz r8, averageFPS1IntInv@l(r8) + mullw r0, r0, r8 + blr + +[XCX_FPS++_QTE_1E] ; ######################################################## +moduleMatches = 0xF882D5CF ; 1.0.1E + +; menu::MenuButtonChallenge::setup +0x02ACE40C = lis r7, averageFPS1Inv@ha +0x02ACE414 = lfs f0, averageFPS1Inv@l(r7) + +; menu::MenuButtonChallenge::move +0x02ACE6E4 = bla _justFrame1 +0x02ACE700 = bla _justFrame2 + +[XCX_FPS++_QTE_2U] ; ######################################################## +moduleMatches = 0x30B6E091 ; 1.0.2U + +; menu::MenuButtonChallenge::setup +0x02ACE3FC = lis r7, averageFPS1Inv@ha +0x02ACE404 = lfs f0, averageFPS1Inv@l(r7) + +; menu::MenuButtonChallenge::move +0x02ACE6D4 = bla _justFrame1 +0x02ACE6F0 = bla _justFrame2 + +[XCX_FPS++_QTE_2J] +moduleMatches = 0x7672271D ; 1.0.2J + +; menu::MenuButtonChallenge::setup +0x02ACAA38 = lis r7, averageFPS1Inv@ha +0x02ACAA40 = lfs f0, averageFPS1Inv@l(r7) + +; menu::MenuButtonChallenge::move +0x02ACAD10 = bla _justFrame1 +0x02ACAD2C = bla _justFrame2 \ No newline at end of file diff --git a/src/XenobladeChroniclesX/Mods/60FPS/rules.txt b/src/XenobladeChroniclesX/Mods/60FPS/rules.txt index e42a60cdf..bf0aeb5b0 100644 --- a/src/XenobladeChroniclesX/Mods/60FPS/rules.txt +++ b/src/XenobladeChroniclesX/Mods/60FPS/rules.txt @@ -1,6 +1,89 @@ [Definition] titleIds = 0005000010116100,00050000101C4C00,00050000101C4D00 -name = 60FPS -path = "Xenoblade Chronicles X/Mods/60FPS" -description = Important: This patches the gameplay to be in 60FPS. Getting under 60FPS will slow down the game's speed. -version = 5 +name = FPS++ For Xenoblade Chronicles X +path = "Xenoblade Chronicles X/Mods/FPS++ For XCX" +description = Allows you to change the FPS and the speed that most things run at.|Some bugs might still occur if you run games at 60FPS. +version = 6 + +[Default] +$fpsLimit = 60 +$cutsceneFPSLimit:int = 1 +$lowFPSLimit = 20 +$frameAverageAmount = 8 +$roundUpModifier = 0.8 + +[Preset] +name = 240FPS Limit +category = FPS Limit +$fpsLimit = 240 + +[Preset] +name = 165FPS Limit +category = FPS Limit +$fpsLimit = 165 + +[Preset] +name = 144FPS Limit +category = FPS Limit +$fpsLimit = 144 + +[Preset] +name = 120FPS Limit +category = FPS Limit +$fpsLimit = 120 + +[Preset] +name = 90FPS Limit +category = FPS Limit +$fpsLimit = 90 + +[Preset] +name = 75FPS Limit +category = FPS Limit +$fpsLimit = 75 + +[Preset] +name = 72FPS Limit +category = FPS Limit +$fpsLimit = 72 + +[Preset] +name = 60FPS Limit (Default) +category = FPS Limit +default = 1 +$fpsLimit = 60 + +[Preset] +name = 55FPS Limit +category = FPS Limit +$fpsLimit = 55 + +[Preset] +name = 45FPS Limit +category = FPS Limit +$fpsLimit = 45 + +[Preset] +name = 30FPS Limit +category = FPS Limit +$fpsLimit = 30 + +[Preset] +name = 20FPS Limit +category = FPS Limit +$fpsLimit = 20 + + +# [Preset] +# name = Enabled (Limit Stuttery Cutscenes To 30FPS) +# category = Cutscene FPS Limit +# default = 1 +# $cutsceneFPSLimit:int = 1 +# +# [Preset] +# name = Disabled (Allow Jittery Cutscenes) +# category = Cutscene FPS Limit +# $cutsceneFPSLimit:int = 0 + +[Control] +vsyncFrequency = $fpsLimit