PSP:Cheat Codes/60 FPS
A detailed explanation of the 60 FPS code and how it was found.
The code
This illustrates the raw code, US region. (The usable CWCheat code can be found at PSP:Cheat_Codes#60_FPS.)
line address orig_val new_val 1 08975900 24050002 24050001 2 0897C47C 3C023F38 3C023EB8 3 089504C4 8C8501C8 340505DC 4 0892C79C 3C023E4C 3C023DCC 5 0892C7EC 3C023E4C 3C023DCC 6 0892C8D0 3C023ECC 3C023E4C 7 0899FBB8 44800000 46002805 8 089A4000 44800000 4600B005
address
- The offset into PSP memory; game stuff starts at 0x08800000. (To get the address used in the CWCheat code, you subtract 0x08800000, then change the first digit to a 2 to signify that you wish to write a 32-bit value to that address.)
orig_val
- The original value that was at this address. The Disable codes use this, reverting back to original values to disable the 60 FPS effect.
new_val
- The 60 FPS code changes the original values to these values.
What do the values mean?
They are MIPS assembly instructions, but instead of text like li a1, 0x2
they are hex like 24050002
. I just use the emulator or ps2dis to convert between the two.
Explanation
Format:
[PSP memory address of this instruction] original: old instruction (old instruction as hex) 60 FPS: new instruction (new instruction as hex)
Some terms and stuff:
- alpha: transparency. Higher is more visible, lower is less visible.
- delta/delta value: Value that is added to or subtracted from something to get a new value. For example, if you decrease volume by 3000 every frame, 3000 is the delta value.
- immediate: A type of hardcoded value. (Specifically, it's a hardcoded value stored right inside of the MIPS assembly instruction that uses it.)
- You see these with instructions like li (load immediate) and lui (load upper immediate).
- float values: It will also help to understand that floating-point values in hex exist and are not very intuitive, e.g.
3f800000
= 1.0. (Here's a Float/Hex converter if you want to mess around with that.)
Set frameskip in stages (line 1)
[at address 08975900] original: li a1, 0x2 (as hex: 24050002) 60 FPS: li a1, 0x1 (as hex: 24050001)
Result: Stages run at 60 fps with fewer side effects than the previous attempt at this code. (Some things still happen 2x too fast as a result of hardcoded values, many of which are fixed by other codes below.)
What the code does:
- Original instruction: This instruction executes during stage load, setting a frameskip value of 2.
- 60 FPS instruction: Same, but sets a value of 1 instead.
(For those curious, the value loaded here gets stored in memory at 0x08A1FC08. It may be useful to change this value in memory when experimenting, e.g. to see which animations are affected by the frameskip value.)
How I found it: The previous attempt at a 60 FPS code (by GamerzHell9137 on the PPSSPP forums) had various issues. I tried to solve only the problem of game-over scenes proceeding too quickly, but I accidentally solved a bunch of other stuff as well.
- First I hacked in weird values for start and end frames of the Lost Pre scene in the .CHC file
- Then searched for those values in memory, found where the end Lost Pre frame is stored in memory.
- Using PPSSPP's debugger, I set a read breakpoint on that memory to find which instructions read from memory containing the Lost Pre end frame.
- Stopping at one of these instructions and stepping forward one instruction at a time, I found where the Lost Pre end frame value is used: the game compares the currently-playing Lost Pre frame to the Lost Pre end frame, presumably to decide whether it should play the Lose Post scene yet.
- Starting from where the currently-playing Lost Pre frame is compared, I made my way backwards through the instructions to find where its value comes from: (currently-playing Lost Pre frame) = (previous currently-playing Lost Pre frame) + (some delta value)
- My thought process: "If I can halve that delta value, lose scenes will proceed half as quickly."
- So I went backwards from there to find where it gets that delta value: delta value = something * 2
That 2 is loaded from somewhere memory; finding the source of this value and changing it made me realize it was the frameskip value, and it affected tons of stuff, not just lose scenes. Setting it to 1 fixed not just the problem I was trying to solve, but a number of other problems as well (such as non-tempo animations playing too fast).
Fix game over animations too fast (line 2)
[at address 0897C47C] original: lui v0,0x3f38 (as hex: 3C023F38) 60 FPS: lui v0,0x3eb8 (as hex: 3C023EB8)
Result: During game-over scenes, tempo animations play at the correct speed (instead of 2x too fast)
What the code does:
- Original instruction: During gameplay, tempo animations have a speed determined by the current SongSection's tempo. Right as you lose, the instruction above executes, replacing every tempo animation's speed with a hardcoded value of 0.72.
- 60 FPS instruction: Same, but the hardcoded value is 0.36.
How I found it:
- Found an affected animation (Stage 4's Ben-K animation brk_x2), changed its length to some weird value by hacking the .GMO file, then searched for that value in memory.
- Using breakpoints in PPSSPP's debugger, found a number of instructions that read back that anim length
- Found where current anim frame is compared to anim length, traced back current anim frame to find where it's determined, found the anim frame delta
- Found out how anim frame delta is determined, traced back the values that determine that, eventually found a hardcoded 0.72 that would affect those anims if I changed it
Fix Gitaroo audio fading too fast (line 3)
[at address 089504C4] original: lw a1,0x1c8(a0) (as hex: 8C8501C8) 60 FPS: li a1,0x5DC (as hex: 340505DC)
Result: When you release or hit the end a note, Gitaroo audio fades normally instead of 2x too fast.
What the code does:
- Original instruction: This instruction executes when Gitaroo audio fades, whether from releasing or hitting the end of a note. (Unfortunately it also executes for audio fades in the main menu and stuff, leading to a limitation described below.) It loads a value from memory, which in practice is always
0xBB8
(3000). - 60 FPS instruction: Same, but instead of loading a value from memory, it sets hardcoded value of
0x5DC
(1500).
(I could instead change the instruction that determines the value in memory in the first place, but it's only executed at boot, and I'd rather have something I can enable and disable without rebooting the game.)
Limitation of this code: Audio fades happen 2x too slow outside of stages. (To fix this, I'd have write some code that checks whether you're in a stage or not.)
How I found it:
- Finally got around to using Cheat Engine. I hit a note, advanced frames one at a time and released the note, then searched for decreasing values in memory via Cheat Engine. I found two promising values and tested them to be sure (turned out to be left and right channel volume for Gitaroo audio).
- Then I set a breakpoint in PPSSPP's debugger on these values, finding the instruction that wrote to them, then worked backwards to find how the new values were calculated, and looked for a delta value subtracted each frame.
- That delta value was loaded from memory rather than created from an immediate. By replacing this value, I could change how quickly or slowly audio fades out.
Fix radar fading in/out and rotating too fast (lines 4-6)
By "radar," I mean the blue fan-shaped zone you control when playing the game. (The graphic for it is named RADER.GIM.) Also, when I talk about radar fading in and out, I don't just mean how transparent it is, I also mean the little animation where the arrows open and close.
[at address 0892C79C] original: lui v0,0x3E4C (as hex: 3C023E4C) 60 FPS: lui v0,0x3DCC (as hex: 3C023DCC) [at address 0892C7EC] original: lui v0,0x3E4C (as hex: 3C023E4C) 60 FPS: lui v0,0x3DCC (as hex: 3C023DCC)
Result: The radar fades in and out at the correct speed instead of 2x too fast.
What the code does:
- Original instruction: The first instruction above executes as the radar fades in, the second one executes as it fades out. Both instructions load a hardcoded value of 0.2, which is then added to or subtracted from the current radar fade value. (Radar fade value: 0.0 is invisible, 1.0 is fully visible.)
- 60 FPS instruction: Same, but the hardcoded value is 0.1.
How I found it:
- I found the radar fade value in memory accident while using Cheat Engine to find what makes phrase bars fade out when you miss them. (I was letting go of the analog stick at the same time, which makes the radar fade out.)
- From there, I set a write breakpoint to find which instruction writes to that memory, then worked backwards from that instruction to find when the radar fade value is added to or subtracted from, then further backwards to where the value to add/subtract exists in hardcoded form.
[at address 0892C8D0] original: lui v0,0x3ECC (as hex: 3C023ECC) 60 FPS: lui v0,0x3E4C (as hex: 3C023E4C)
Result: The radar rotates at the correct speed instead of 2x too fast.
What the code does:
- Original instruction: This instruction loads a hardcoded value of 0.4. Later on (not shown here), this value helps determines the radar rotation delta value (amount to add to/subtract from current radar rotation).
- 60 FPS instruction: Same, but the hardcoded value is 0.2.
How I found it:
- I set a breakpoint somewhere near the stuff above, advanced a frame at a time while rotating the analog stick to different positions, and paid attention to the contents of the floating-point registers.
- There were several registers that contained the current angle of the analog stick (in radians, not degrees), but it took several breakpoints to find a register containing the current angle of the radar itself, which gradually increases from the previous analog stick rotation value to the current one.
- A note on finding stuff in radians: Try rotating your analog stick in 90-degree increments, like straight up or right. When you do this, approximate float values to look for (since they won't be exact) are 0, half of pi (1.5707...), pi (3.1415...), 3/2 of pi (4.712...), and maybe
tau2*pi (6.283...).- These values are equivalent to 0/90/180/270/360 degrees (counterclockwise starting from 3 o'clock, though Gitaroo Man might start from 9 o'clock for some reason...)
- Be aware, rotation values of higher than 6.283 and less than 0 are also valid, just as 720 degrees is. The angle of the radar was like this, making it hard to find it at first.
- A note on finding stuff in radians: Try rotating your analog stick in 90-degree increments, like straight up or right. When you do this, approximate float values to look for (since they won't be exact) are 0, half of pi (1.5707...), pi (3.1415...), 3/2 of pi (4.712...), and maybe
Fix letter ghost on results screens (line 7-8)
By "letter ghost" I mean that on the results screens, when the letter grade pops up, it has a "ghost" graphic behind it that quickly grows as it fades away to nothing.
[at address 0899FBB8] original: mtc1 zero,f0 (as hex: 44800000) 60 FPS: abs.s f0,f5 (as hex: 46002805)
Result: Makes the letter ghost fade normally on the single-player results screen. Without this code, at 60 FPS the letter reappears when it's done fading.
[at address 089A4000] original: mtc1 zero,f0 (as hex: 44800000) 60 FPS: abs.s f0,f22 (as hex: 4600B005)
Result: Same as above, but fixes it on the multiplayer results screens (VS and Duet).
What the codes do:
- Original instructions: The instruction executes during the letter ghost's animation. It sets a hardcoded value of 0.0, and later instructions (not shown here) compare the letter ghosts's transparency value to that 0.0 to decide when to end the animation.
- 60 FPS instructions: Same, but instead of 0.0 it uses 0.01. (Specifically, it takes the absolute value of another floating-point register, which is always -0.01 at this point.)
but why:
- At 60 FPS, the game already knows to fade the ghost by half as much per frame. This is the correct speed and should be fine, except...
- If you start with 1.0 and repeatedly subtract 0.02 from it, you will end up with exactly 0.0 and everything is fine. However, if you repeatedly subtract 0.01 (which the game does at 60 FPS), you will end up with very slightly above 0.0 due to floating-point inaccuracy.
- So since the ghost transparency isn't 0.0 yet, the game fades it one more time, giving it a negative transparency. Which makes it visible again.
- So ideally, instead of checking if ghost transparency is 0.0 or less, we'd check if it's
very slightly above 0.0
or less. - As it so happens, at the time this instruction executes, one of the floating-point registers contains a value of -0.01. The absolute value of that, 0.01, would be perfect for our
very slightly above 0.0
value.- Working backwards, I found that in both cases, the value was the result of other hardcoded values, meaning it would always be this -0.01 at this instruction.
- So instead letting the game load 0.0, I make it load the absolute value of that other register, i.e. 0.01. As a result, the ghost stops fading when its transparency reaches less than 0.01, leading to correct behavior.
- By the way, this means the letter ghost does not fade out completely—it ends with a transparency of very slightly above 0.0. But this results in no actual difference in the pixels shown onscreen, so the nitpicking part of my personality can go suck it!!!
How I found it:
- Used Cheat Engine, waited for the the letter ghost to appear and start fading, then advanced frame by frame while searching memory for decreasing values
- Once I found that value (ghost alpha) in memory, I set a breakpoint on it to find the instructions that used it
- found a compare instruction that would make the ghost stop fading after a certain point (in this case, when it reaches 0)
- set a breakpoint at the compare instruction, played the ghost fade animation again frame by frame while paying attention to the value of ghost alpha
- (the rest of the process from here on out was already described above, i.e. realizing the ghost alpha became negative and finding a way to prevent that)
Current issues
- Audio fades 2x too slow outside of stages (e.g. when you enter or exit menus)
- Current code has no effect on 30 FPS animations outside of stages (i.e. spinning faces in main menu, growing/shrinking backgrounds in submenus)
- Note: menus already animate at 60 FPS, something else is making those anims 30 FPS
Things that animate 2x too fast:
- health bars (flashing and gain/lose)
- phase indicators (CHARGE/BATTLE/etc)
- the words GREAT/GOOD/OK/MISS
- fade-out of the phrase bar when you drop a note partway through
- the big green fading circle when you hit a note
- fade-in/fade-out of tutorial stuff like word bubbles
Things I haven't tested but probably should:
- any effect on health gained/lost when you release a note early?