Author Topic: [FF7 PS1] Memory Addresses for framerate cap/limiter  (Read 4790 times)

MemorySeeker

  • *
  • Posts: 1
    • View Profile
Greetings! I am trying to find which memory addresses store the framerate in each module of the PlayStation version of FF7. There exist codes for other PSX games to raise the framerate cap/uncap the framerate, so I figured FF7 would be a good showcase for some framerate analysis on real PlayStation hardware (think Digital Foundry) since there is likely a lot of headroom in the fields.

As you know, the menu model operates at 60FPS, the battle model at 15FPS, and the other models at 30FPS.

So, I booted up the game with Duckstation, and used Duckstation's memory viewer to have a look through what's going on at the very start of the game (fields and battles). From searching 1E and F (30 and 15), I was unable to find any memory addresses that stored the framerate target.

I then wondered whether a module's framerate cap was instead given as a divisor (of the refresh rate), so I looked for 2's and 4's that remained fixed throughout a field/throughout a battle. Unfortunately, none of these dictated the framerate either.

I must conclude that the framerate/game speed is obscured somehow. If anyone has more knowledge about the framerate controller, please let me know, thanks!
« Last Edit: 2024-04-16 05:02:49 by MemorySeeker »

RoSoDude

  • *
  • Posts: 5
    • View Profile
Re: [FF7 PS1] Memory Addresses for framerate cap/limiter
« Reply #1 on: 2025-02-06 19:53:50 »
Hi there. I don't have particular information about FF7, but I think it is likely that it works just like in FF9, for which I recently made a ROMhack that increases the 3D scene framerate in battle from 15 fps to 20 fps. So I should be able to help, and this may serve as usual documentation for others.

In FF9, the game runs at 60fps (really 59.94 Hz), but the 3D scene only updates every 4th frame. This is accomplished using a frame counter at RAM address 0x80104E84 that ticks up on every menu frame (there are many many frame counters all over RAM; this is the relevant one). Here's the code to increment that frame counter:

Code: [Select]
000e9b70: 3c058010 lui r5,0x8010 ; r5 = 0x80100000
000e9b74: 24a54e68 addiu r5,r5,0x4e68 ; r5 = r5 + 0x4E68 = 0x80104E68
000e9b78: 3c038007 lui r3,0x8007
000e9b7c: 8ca2001c lw r2,0x001c(r5)
000e9b80: 24637148 addiu r3,r3,0x7148
000e9b84: 24420001 addiu r2,r2,0x0001
000e9b88: aca2001c sw r2,0x001c(r5)
000e9b8c: 8c620008 lw r2,0x0008(r3)
000e9b90: 8ca40018 lw r4,0x0018(r5) ; r4 = $(r5 +0x0018) = $0x80104E68 [load frame counter]
000e9b94: 24420001 addiu r2,r2,0x0001
000e9b98: 24840001 addiu r4,r4,0x0001 ; r4 = r4 + 1 [increment frame counter]
000e9b9c: ac620008 sw r2,0x0008(r3)
000e9ba0: aca40018 sw r4,0x0018(r5) ; store r4 at $(r5 +0x0018) = $0x80104E68 [store frame counter]

This frame counter is then compared with a buffer frame limit that's stored at RAM address 0x8008B009. If the frame count is equal to the buffer frame limit minus 1, the game code tells the processor to update the 3D scene and resets the frame counter to 0x00. So we need to modify the buffer frame limit to get faster battle animations.

Code: [Select]
000e9ee0: 3c038010 lui r3,0x8010 ; r3 = 0x80100000
000e9ee4: 3c028006 lui r2,0x8006 ; r2 = 0x80060000
000e9ee8: 8c42794c lw r2,0x794c(r2) ; r2 = $(r2+0x794c) = 0x8008B000
000e9eec: 24704e68 addiu r16,r3,0x4e68 ; r16 = r3 + 0x4E68 = 0x80104E68
000e9ef0: 90430009 lbu r3,0x0009(r2) ; r3 = $(r2 + 0x0009) = $0x8008B009 [load buffer frame limit]
000e9ef4: 8e02001c lw r2,0x001c(r16) ; r2 = $(r3 + 0x001C) = $80104E84 [load frame counter]
000e9ef8: 2464ffff addiu r4,r3,0xffff ; r4 = r3 - 1 [buffer frame limit - 1]
000e9efc: 0044102b sltu r2,r2,r4 ; r2 = (r2 < r4)
000e9f00: 10400003 beq r2,r0,0x000e9f10 ; branch if r2 == 0 [frame counter was less than buffer frame limit, skip updating 3D scene]
000e9f04: 00000000 nop
000e9f08: 0c005716 jal 0x00015c58 ; jump to subroutine to update 3D scene
000e9f0c: 00000000 nop
000e9f10: 0c03d0e1 jal 0x000f4384
000e9f14: 00000000 nop

The buffer frame limit is set in two different places. At the start of battle, it's set to 0x04 by this code (60 fps / 4 = 15 fps).

Code: [Select]
000a8d08: 240400da addiu r4,r0,0x00da
000a8d0c: 8e23794c lw r3,0x794c(r17)
000a8d10: 24020004 addiu r2,r0,0x0004 ; wait 4 frames for update = 15fps
000a8d14: 0c007341 jal 0x0001cd04
000a8d18: a0620009 sb r2,0x0009(r3) ; store buffer frame limit at $0x8008B009

I was able to set the 3D scene framerate by changing "addiu r2,r0,0x0004" to "addiu r2,r0,0x0003", i.e. setting the buffer frame limit to 3 (60 fps / 3 = 20 fps)

The buffer frame limit is also set during some spell animation sequences. Some animations will temporarily change the framerate e.g. to 20 fps and then back to 15 fps later in the animation sequence. The animation sequence uses the opcode 0x2C for this, for example a target framerate of 20 = 0x14 would be set using the opcode "2C 14 00". This target framerate is read by a subroutine that divides 60 by the target framerate to calculate the buffer frame limit as an integer value (so only 60 / x framerates are possible).

Code: [Select]
000e45d0: 27bdffe8 addiu r29,r29,0xffe8 ; SUBROUTINE: set 3D battle framerate
000e45d4: 24020001 addiu r2,r0,0x0001
000e45d8: 14820012 bne r4,r2,0x000e4624
000e45dc: afbf0010 sw r31,0x0010(r29)
000e45e0: 8fa20028 lw r2,0x0028(r29)
000e45e4: 00000000 nop
000e45e8: 8c420000 lw r2,0x0000(r2) ; load target framerate
000e45ec: 2404003c addiu r4,r0,0x003c ; r4 = 60 (global framerate)
000e45f0: 0082001a div r4,r2 ; divide r4/r2 (buffer frame count)
000e45f4: 14400002 bne r2,r0,0x000e4600 ; make sure we didn't divide by 0
000e45f8: 00000000 nop
000e45fc: 0007000d break 0x00001c00
000e4600: 2401ffff addiu r1,r0,0xffff
000e4604: 14410004 bne r2,r1,0x000e4618 ; make sure we didn't divide by 0xFFFF
000e4608: 3c018000 lui r1,0x8000
000e460c: 14810002 bne r4,r1,0x000e4618 ; make sure we didn't divide from 0x80000000 (?)
000e4610: 00000000 nop
000e4614: 0006000d break 0x00001800
000e4618: 00002012 mflo r4 ; get result of divide
000e461c: 0c03a3d0 jal 0x000e8f40 ; store buffer frame limit in r4
000e4620: 00000000 nop
000e4624: 8fbf0010 lw r31,0x0010(r29)
000e4628: 24020001 addiu r2,r0,0x0001
000e462c: 03e00008 jr r31
000e4630: 27bd0018 addiu r29,r29,0x0018

I modified this code to subtract 1 from the buffer frame limit if it was 0x04 or greater (15 fps -> 20 fps, 7.5 fps -> 8.58 fps, etc.) so that animations would return to the correct default of 20 fps from the "2C 0F 00" opcode.

I would imagine there is similar code in FF7, but it would take a lot of searching through RAM in the debugger to chase it down. I was actually considering doing this work for FF7 and FF8 myself to make patches to speed up their battles, though it isn't as pressing an issue as with FF9 and its sluggish battle animations.