mupen64plus-core icon indicating copy to clipboard operation
mupen64plus-core copied to clipboard

Knife Edge Nose Gunner - Frame Rate issues

Open Meerkov opened this issue 3 years ago • 20 comments

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).

Meerkov avatar Feb 13 '22 22:02 Meerkov

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.

loganmc10 avatar Feb 14 '22 04:02 loganmc10

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.

haydenmc avatar Apr 02 '23 03:04 haydenmc

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.

loganmc10 avatar Apr 02 '23 04:04 loganmc10

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!

haydenmc avatar Apr 03 '23 04:04 haydenmc

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!

Haven-Kek avatar Jan 10 '24 22:01 Haven-Kek

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?

haydenmc avatar Jan 11 '24 00:01 haydenmc

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.

theboy181 avatar Jan 11 '24 02:01 theboy181

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.

theboy181 avatar Jan 11 '24 02:01 theboy181

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?

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.

Haven-Kek avatar Jan 11 '24 13:01 Haven-Kek

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.

Haven-Kek avatar Jan 11 '24 13:01 Haven-Kek

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.

Haven-Kek avatar Jan 11 '24 17:01 Haven-Kek

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.

haydenmc avatar Jan 11 '24 18:01 haydenmc

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

Meerkov avatar Jan 11 '24 18:01 Meerkov

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.

theboy181 avatar Jan 12 '24 02:01 theboy181

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!

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

theboy181 avatar Feb 22 '24 03:02 theboy181

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?
image
^ 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.

haydenmc avatar Feb 22 '24 19:02 haydenmc

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

theboy181 avatar Feb 23 '24 02:02 theboy181

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.

theboy181 avatar Mar 11 '24 17:03 theboy181

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?

haydenmc avatar Mar 12 '24 01:03 haydenmc

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

theboy181 avatar Mar 13 '24 14:03 theboy181