mupen64plus-core
mupen64plus-core copied to clipboard
Knife Edge Nose Gunner - Frame Rate issues
Knife Edge Nose Gunner seems to have issues with framerates in the Mupen64 core. The game goes too fast, and the frame rate suffers from major changes during gameplay, getting faster and slower.
Tested in BizHawk 2.7 (November-ish build).
This is an issue as old as time, I believe it affects pretty much every N64 emulator (https://github.com/project64/project64/issues/284)
But I don't think we have an issue for it, so it's good to have this.
Starting from the cheat @theboy181 contributed in https://github.com/project64/project64/issues/284, I was able to develop a patch that keeps the game at a reasonable frame rate throughout, and does not soft lock after the Dead City rescue cinematic.
The intro and menus still run too fast, though. Ideally there'd be a solution at the emulator level.
EDIT: My original patch was busted, see this comment for a working one.
It would be helpful to describe what the patch does, and why, to help us understand what may need to be changed on the emulator side to make it work without a patch.
Totally!
I started with the cheat originally described by @theboy181, which nop'd out this instruction at 0x8019D02C
:
beqz $t6, 0x80197B44
Here register t6
contains a value loaded from 0x8011D298
that appears to indicate whether there is dialogue playing on-screen. From some experimenting, it looks like value 0x1
means dialogue is playing, 0x2
means dialogue is starting (an intro animation plays), and 0x3
means dialogue is ending (an outro animation plays).
So the instructions immediately following this beqz
are only meant to run when dialogue is showing on-screen, and appear to handle things like preparing the characters that are shown in the dialogue message window.
But it also includes a portion that spin locks until a specific count value is reached, effectively limiting the computation speed - this starts at memory location 0x80197AE0
.
The problem with the original cheat is that the game will eventually soft-lock at the Dead City level after a cinematic plays and some dialogue is meant to start; presumably because there is some bad state being set in memory by this section of code running when it shouldn't. To fix this, instead of nop'ing the instruction, I patch it to jump straight to the spin-lock section of code:
beqz $t6, 0x80197AE0
And now the game happily spin-locks to keep things moving at the proper speed without getting stuck (at least through the ice level, which is as far as I've played so far 😁). Again, this fix doesn't apply to the menu or intro sequence, but presumably you could find a place to patch this bit of code in and jump to it in the game loop somewhere for these portions..?
My relatively uninformed guess as to what's happening is that the game does not have any effective clock functionality and relies on the processing speed of the N64 CPU to throttle things. Anecdotally, there are areas of the game that appear to slow down or speed up, and the "uneven" play speed would seem to support this hypothesis. However, the game throttles to a specific rate when dialogue is showing, presumably because having an inconsistent time scale could make the text display at irregular rates, or perhaps show too quickly to read.
You can actually see the frame rate/game speed subtly increase after the dialogue is dismissed on the real N64 hardware, like here.
Hope this helps and let me know if I can add detail somewhere.
Thanks!
Hello!
I currently looking into getting Knife Edge to work at the proper speed (or at least for the most part, meaning the gameplay), to play the game and record a podcast about it hopefully. I tried to apply the patch @haydenmc supplied per https://www.romhacking.net/patch/ without success. I also tried different emulators as I wasn't sure if this can make a difference. I don't know anything about ROM hacking/patching, so its hard for me to figure the issue. I also tried looking into different N64 emulators if one of the fixed the speed issue, but that is not the case as far as I can tell. Would be great if someone could point me in the directions of some resources for example, that could help me apply the patch.
Thanks!
Hi @Haven-Kek, can you describe what problem you run into when you attempt to patch? Is the source ROM you are using the USA version of Knife Edge in z64 format? The file I am working with has a MD5 hash of ea434c7e292c6631eca5dd8d460b1963
and SHA-1 hash of 986baf02b1be05a2325292e3ddec0ef58c45f2ca
. Do yours match?
Knife Edge - Nose Gunner (U) FPS Fix (30fps hack)
D012AABB 0000 800E0BDF 0003 D012AABB 0001 800E0BDF 0000
Note: Counter Factor = 1 , Fixed Audio Timing = On Makes the game run at its proper FPS
Can you add the updated code to this @Haven-Kek then he can just run it as a cheat, and avoid the patching all together. If its been added to PJ64 just do a PR to have them update it. Add your notes.. Great stuff on fixing it! Was a long time ago I made this patch.
Side Note, The intro to Goldeneye64 also plays to fast on emulator, but is find on real HW. I think the real HW is limited by CPU here too, however it seems like its a perfect 30fps to my eyes.
I would rather that get fixed than Knifes Edge.
Hi @Haven-Kek, can you describe what problem you run into when you attempt to patch? Is the source ROM you are using the USA version of Knife Edge in z64 format? The file I am working with has a MD5 hash of
ea434c7e292c6631eca5dd8d460b1963
and SHA-1 hash of986baf02b1be05a2325292e3ddec0ef58c45f2ca
. Do yours match?
Thank you for your reply @haydenmc. Turns out my MD5 hash is indeed different: 8043d829fcd4f8f72dd81e5c6dde916f The file is z64 and titled as an USA Rom (and it is different from the EU ROM I have; the one difference I could spot ingame is that the cutscenes are only skippable on the USA ROM; apart from different hashed ofc). So I will look for a ROM with your MD5 hash and test it with that.
Side Note, The intro to Goldeneye64 also plays to fast on emulator, but is find on real HW. I think the real HW is limited by CPU here too, however it seems like its a perfect 30fps to my eyes.
I would rather that get fixed than Knifes Edge.
Haha true, GoldenEye64 is no doubt the more important game.
Unfortunately I was unable to find any ROMs matching your MD5 @haydenmc, only one totally different one.
@theboy181 I have no idea how to apply that myself, are there any resources I can teach myself with? I also have not found where to set Counter Factor or Fixed Audio Timing in any emulators.
Hi @Haven-Kek,
Per the suggestion from @theboy181 I'll work on converting my patch to a cheat that can be applied at runtime.
In the meantime if you'd like you can shoot me an email (you should be able to find it in any of my public commits) and I can help you with patching.
Still kind of curious if there is a more clear root cause. Obviously it seems like modern computers are calculating too much per frame. However it was mentioned this problem has existed in every N64 emulator. Modern hardware doesn't explain that.
Could it be something like a quirk of the hardware? I remember reading old NES hardware would trigger a next frame of you called a certain opcode 3x in a row. It seems like the executed code must be requesting a new frame, but the emulators don't know the hardware quirk
I have a theory! This game theory!
The game seems to be a fully unlocked fps and was designed to go as fast as the HW could push it.
During the speeches sections of the game it caps the fps.
My original patch just leverages that fact and sets the fps based on this mode.
The mystery seems to be why does this game and other games on CEN still have these issues. They are supposed to be cycle accurate, right? ;)
I think someone also theorized that it could have been held back by the fill rate. It’s unclear If emulators would emulate that type of HW limitation.
Totally!
I started with the cheat originally described by @theboy181, which nop'd out this instruction at
0x8019D02C
:beqz $t6, 0x80197B44
Here register
t6
contains a value loaded from0x8011D298
that appears to indicate whether there is dialogue playing on-screen. From some experimenting, it looks like value0x1
means dialogue is playing,0x2
means dialogue is starting (an intro animation plays), and0x3
means dialogue is ending (an outro animation plays).So the instructions immediately following this
beqz
are only meant to run when dialogue is showing on-screen, and appear to handle things like preparing the characters that are shown in the dialogue message window.But it also includes a portion that spin locks until a specific count value is reached, effectively limiting the computation speed - this starts at memory location
0x80197AE0
.The problem with the original cheat is that the game will eventually soft-lock at the Dead City level after a cinematic plays and some dialogue is meant to start; presumably because there is some bad state being set in memory by this section of code running when it shouldn't. To fix this, instead of nop'ing the instruction, I patch it to jump straight to the spin-lock section of code:
beqz $t6, 0x80197AE0
And now the game happily spin-locks to keep things moving at the proper speed without getting stuck (at least through the ice level, which is as far as I've played so far 😁). Again, this fix doesn't apply to the menu or intro sequence, but presumably you could find a place to patch this bit of code in and jump to it in the game loop somewhere for these portions..?
My relatively uninformed guess as to what's happening is that the game does not have any effective clock functionality and relies on the processing speed of the N64 CPU to throttle things. Anecdotally, there are areas of the game that appear to slow down or speed up, and the "uneven" play speed would seem to support this hypothesis. However, the game throttles to a specific rate when dialogue is showing, presumably because having an inconsistent time scale could make the text display at irregular rates, or perhaps show too quickly to read.
You can actually see the frame rate/game speed subtly increase after the dialogue is dismissed on the real N64 hardware, like here.
Hope this helps and let me know if I can add detail somewhere.
Thanks!
I was reviewing this Cheat I made years ago, and I am a little confused now after reading your explanation of how it worked. You stated that I NOP'd an instruction, however I looks like I actually leverage a place in memory that each frame changes between a 0 or a 1. If its one it sets 0 to the part in memory that sets the condition for the mission overview, and if its 1 it sets it to 3. Alternating between this seems to trigger the fps where is locks to 30fps.
Its super hacky! I was unable to locate the place in the active memory to set the branch like you stated. Can you re-explain what you did on your end? I would like to fix this in the cheat if possible.
I think the softlock will happen later in the game is because the place in memory I am using to detect the 0/1 doesnt operate the same later in the game, or the memory shifts and the address is no longer available.
Knife Edge - Nose Gunner (U) FPS Fix (30fps hack)
D012AABB 0000 800E0BDF 0003 D012AABB 0001 800E0BDF 0000
Howdy, thanks for bumping this thread - offline I had to prepare a new patch for @Haven-Kek since my old one wasn't working, not sure what went wrong with it. I'm attaching the fixed one here. Hopefully the old one didn't lead you on a wild goose chase.
Speed Fix Knife Edge - Nose Gunner (USA).zip
In response to your questions about how I developed my fix, it's been a long time so I'm not "fresh" on what I did, but I'll reference some notes I took that will maybe help.
The "original fix" I cite is in my notes here:
Original FPS Hack
811978D0 0000 -> nop @ 0x901978D0
Instruction that is nop'd is located at 0x8023b780 in ROM memory space:
BEQZ T6, 0x80197B44
Which looks sourced from this comment. Maybe there are a couple of different patches floating around with different approaches?
Here are the notes I took while mucking about with the enum value that seems to influence the FPS limiter:
0x8011d298 / address of data that is loaded into t6 that determines whether FPS limit is applied?
^ Switch case?
Looks like there might be 3 modes -
0x8011d298: (0, 1, 2, 3)
0 is gameplay mode?
1 is ???
2 seems to start dialogue...
3 seems to be exiting dialogue…
Seems like a dialogue state enum.
I reached those conclusions by modifying the value at runtime and observing the mission dialogue UI coming up and getting dismissed (along with the FPS limiting behavior).
Here are the remainder of my notes where I zero in on the function that actually spin-locks to apply the FPS limit (some of the notes reference a Ghidra decomp I was using to try to make sense of the assembly):
0x80197B18 appears to be the branch that controls the speed
Or FUNC_800E0C80
New fix:
@ 0x801978D0 replace
BEQZ T6, 0x80197B44
with
BEQZ T6, 0x80197AE0
func_800CD230 on physical rom
/* BE40 800CD240 11C00079 */ beqz $t6, .L800CD428 --> /* C028 800CD428 8FBF001C */ lw $ra, 0x1C($sp)
0x800CD428 - 0x40 = 0x800CD3E8
CHANGE FROM:
beqz $t6, 0x274
nop
CHANGE TO:
beqz $t6, 0x210
nop
I remember that there was a tight loop in this function that waited on a "count" register to increment to a certain value - I recall looking up the register here: https://n64.readthedocs.io/
Hopefully that is helpful - please let me know if I can clarify any more!
I'm away from my desk at the moment but when I get a chance I can try to crack open the ROM again and see if I can find the exact code I'm referencing.
Those are both my patches, the first one is the older one. I see why I am confused now. The newer one is actually 30fps, and it has an issue where I leverage a single byte that is either a 0 or a 1 for each frame, then change a branch. This needs to be frame perfect.
There are places in the game where this doesn't work well as it can soft lock if the 0/1 have any pulsing issues. This is why its best to unlock the fps, use a frame-limiter f 60fps, and set a CF of 1.
I looked for other areas in the game that always pulse between two numbers but have not had any luck.
Here is your approach in a GameShark friendly manner.
D012AAB7 0080 800E0BDF 0002 D012AAB7 0028 800E0BDF 0000
Knife Edge - Nose Gunner (U) FPS Fix (30fps hack)
D00ECED3 00A0 800E0BDF 0002 D00ECED3 0070 800E0BDF 0000 810E0CF0 2402 810E0CF2 0001
Note: Counter Factor = 1 , Fixed Audio Timing = On
Makes the game run at its proper FPS
This cheat will let you play the game from start to finish, and will run at 30fps in the menus, and in game.
Awesome! Thanks for the share!
Curious on the background if you have the time to give context, does this leverage the same "throttling" mechanism that the other patch did?
Awesome! Thanks for the share!
Curious on the background if you have the time to give context, does this leverage the same "throttling" mechanism that the other patch did?
This is a different approach to this game. The first approach leveraged the throttling that you mentioned, but the 2nd and 3rd approach leverages the frame buffer swapping as a timer, then I use it to disable and enable a branch so that the frames are doubled resulting in 30fps. Super Hacky!
I have done this for a few games that run too fast, and the mods can be found on Discord 64 https://discord.gg/HpRpD5uVT3