How to make 60FPS .ips Patch for Nintendo Switch Game Ghidra Tutorial

Use FPSLocker on actual hardware or uncap framerate emulator and/or limit game speed to 200% to achieve the same result as this guide. No Ghidra or guide required now.

This is a basic guide to create an .ips that patches 'nvnWindowBuilderSetPresentInterval' and/or 'nvnWindowSetPresentInterval' for a game in order to make the game run at 60FPS. Some games need additional patches specific to that game for 60FPS and not only this method.
If you want to follow along the game I am using is Story of Seasons: A Wonderful Life v1.1 (JP) which I have made a patch for https://gbatemp.net/threads/story-of-seasons-a-wonderful-life-60fps-ips-patch.625672/

Thanks @systemdev for helping me with this game and indirectly this guide.

Instructions
1. Obtain the main of the game - you want to dump the one from the latest update of the game. For this I dump the exeFS of the game using nxdumptool after I've installed the game on my system although there are other ways. https://github.com/DarkMatterCore/nxdumptool/releases

2. Install https://github.com/NationalSecurityAgency/ghidra and https://github.com/Adubbz/Ghidra-Switch-Loader

3. Make a new project, put the main into Ghidra and analyze it (this will take a while)

4. On Switch two variables usually control the framerate target which are
'nvnWindowBuilderSetPresentInterval'
'nvnWindowSetPresentInterval'
Click Search->Search Memory (Search Program Text is an alternate option) and search for one or both of these - in my case I searched for 'nvnWindowSetPresentInterval'
Click on the search result that should come up and you should have found it!
1675544037305.png


5. Double click on the function circled in red - Ghidra should now analyze the function on the right side of the screen in a window. This might take a little bit.
1675544179425.png


6. Find nvnWindowSetPresentInterval and its corresponding DAT
1675544286652.png

7. Click on DAT_71010cb520 and you'll be taken a screen that looks this
1675545732259.png

You'll have to click on each function and examine if you can find the right ASM block. For me, it was the second one which is highlighted which looks like this when clicked on
1675545867720.png

8. Look at the highlight block above. We are going to edit the w1, #0x2. Right click on it and then click 'Patch Instruction'. Change the #0x2 to #0x1. For other games that #0x2 may be a w20 or another value, however in that case still change it to a #0x1.
1675546086229.png

9. IPS patch time! We are first going to make the .pchtxt file which will be turned into the IPS patch which are formatted like this

The NSOBID part contains the BID of the game. I use Tinfoil to easily find the full BID (Build ID) of a game. https://tinfoil.io/Title/0100936018EB4000
Include the flag offset_shift 0x100 so the patch is applied to the right place
Under @enabled your modified ASM will go. The left part is the address and the right part are the values you have modified at that address. Make sure you get rid of the first two digits '71' from the Ghidra address. Save this file as a .pchtxt file.
1675546158433.png


10. Install IPSwitch on your Switch. Copy the .pchtxt to sdmc:/switch/ipswitch/{patch_description}/{whatever_name}.pchtxt
Run IPSwitch and select the patch located into the folder and turn it into a .ips file which will be located at atmosphere/exefs_patches/{patch_description}/{BID of Patch}.ips

11. Run the game and see if its at 60FPS. Make sure you overclock using sys-clk (some games when modded to 60FPS, if they can't run at 60FPS they will drop to 30FPS, and need the extra performance from overclocking) or another program and check the FPS using Status Monitor. Congratulations, hopefully your game is at a silky smooth 60FPS like the example below!

1675546728064.png
 

Attachments

  • 1675545793968.png
    1675545793968.png
    11 KB · Views: 126
Last edited by ChanseyIsTheBest,

binkinator

Garfield’s Fitness Coach
Member
GBAtemp Patron
Joined
Mar 29, 2021
Messages
6,511
Trophies
2
XP
6,155
Country
United States
This is a basic guide to create an .ips that patches 'nvnWindowBuilderSetPresentInterval' and/or 'nvnWindowSetPresentInterval' for a game in order to make the game run at 60FPS. Some games need additional patches specific to that game for 60FPS and not only this method.
If you want to follow along the game I am using is Story of Seasons: A Wonderful Life v1.1 (JP) which I have made a patch for https://gbatemp.net/threads/story-of-seasons-a-wonderful-life-60fps-ips-patch.625672/

Thanks @systemdev for helping me with this game and indirectly this guide.

Instructions
1. Obtain the main of the game - you want to dump the one from the latest update of the game. For this I dump the exeFS of the game using nxdumptool after I've installed the game on my system although there are other ways. https://github.com/DarkMatterCore/nxdumptool/releases

2. Install https://github.com/NationalSecurityAgency/ghidra and https://github.com/Adubbz/Ghidra-Switch-Loader

3. Make a new project, put the main into Ghidra and analyze it (this will take a while)

4. On Switch two variables usually control the framerate target which are
'nvnWindowBuilderSetPresentInterval'
'nvnWindowSetPresentInterval'
Click Search->Search Memory (Search Program Text is an alternate option) and search for one or both of these - in my case I searched for 'nvnWindowSetPresentInterval'
Click on the search result that should come up and you should have found it!
View attachment 351146

5. Double click on the function circled in red - Ghidra should now analyze the function on the right side of the screen in a window. This might take a little bit.
View attachment 351147

6. Find nvnWindowSetPresentInterval and its corresponding DAT
View attachment 351148
7. Click on DAT_71010cb520 and you'll be taken a screen that looks this
View attachment 351151
You'll have to click on each function and examine if you can find the right ASM block. For me, it was the second one which is highlighted which looks like this when clicked on
View attachment 351155
8. Look at the highlight block above. We are going to edit the w1, #0x2. Right click on it and then click 'Patch Instruction'. Change the #0x2 to #0x1. For other games that #0x2 may be a w20 or another value, however in that case still change it to a #0x1.
View attachment 351157
9. IPS patch time! We are first going to make the .pchtxt file which will be turned into the IPS patch which are formatted like this

The NSOBID part contains the BID of the game. I use Tinfoil to easily find the full BID (Build ID) of a game. https://tinfoil.io/Title/0100936018EB4000
Include the flag offset_shift 0x100 so the patch is applied to the right place
Under @enabled your modified ASM will go. The right part is the address and the left part are the values you have modified at that address. Make sure you get rid of the first two digits '71' from the Ghidra address. Save this file as a .pchtxt file.
View attachment 351158

10. Install IPSwitch on your Switch. Copy the .pchtxt to sdmc:/switch/ipswitch/{patch_description}/{whatever_name}.pchtxt
Run IPSwitch and select the patch located into the folder and turn it into a .ips file which will be located at atmosphere/exefs_patches/{patch_description}/{BID of Patch}.ips

11. Run the game and see if its at 60FPS. Make sure you overclock using sys-clk (some games when modded to 60FPS, if they can't run at 60FPS they will drop to 30FPS, and need the extra performance from overclocking) or another program and check the FPS using Status Monitor. Congratulations, hopefully your game is at a silky smooth 60FPS like the example below!
View attachment 351164
This is the shit! I absolutely love it. I’m always blown away by you guys putting out all these 60FPS hax. Thanks for the tut!
 

masagrator

The patches guy
Developer
Joined
Oct 14, 2018
Messages
6,270
Trophies
3
XP
12,037
Country
Poland
Wow, Ghidra finally supports referencing nvn strings into nvnloadcproc (talking about first screenshot). This didn't work at least until 10.0.2. :P
I was just searching what function was calling nvnBootstrapLoader and always next call was nvnLoadCProc.
Post automatically merged:

Adding some tidbits to this - just search first instruction that writes something into x1 or w1 before calling SetPresentInterval function. It's not always hardcoded, so it may try to load it from some pointer or other register (LDR x1/w1 or any MOV x1/w1).
And just change it to mov w1, #1
Interval is always set to w1/x1 closely before call (on this screen BLR x8 is used to call function).
Post automatically merged:

I would add note that this method is only for games using NVN graphics API.
Plus for 32-bit games finding correct place is slightly different.
 
Last edited by masagrator,
  • Like
Reactions: binkinator

ChanseyIsTheBest

Well-Known Member
OP
Member
Joined
Aug 26, 2022
Messages
390
Trophies
0
Location
Australia
XP
1,051
Country
Australia
Wow, Ghidra finally supports referencing nvn strings into nvnloadcproc (talking about first screenshot). This didn't work at least until 10.0.2. :P
I was just searching what function was calling nvnBootstrapLoader and always next call was nvnLoadCProc.
Post automatically merged:

Adding some tidbits to this - just search first instruction that writes something into x1 or w1 before calling SetPresentInterval function. It's not always hardcoded, so it may try to load it from some pointer or other register (LDR x1/w1 or any MOV x1/w1).
And just change it to mov w1, #1
Interval is always set to w1/x1 closely before call (on this screen BLR x8 is used to call function).
Post automatically merged:

I would add note that this method is only for games using NVN graphics API.
Plus for 32-bit games finding correct place is slightly different.
I'll add this stuff in. To tell you the truth I don't understand most of it.
How can you tell which graphics API a game is using? I know that NVN is most common.
I have a feeling if it has all the NVN strings like 'nvnWindowSetPresentInterval' you can tell.
 

Hyper_2979

Member
Newcomer
Joined
May 10, 2023
Messages
5
Trophies
0
XP
73
Country
United States
Resources that explain how to use Ghidra for switch modding are very scarce. Thank you for sharing your knowledge on this subject.
 

B1anYu

New Member
Newbie
Joined
Jul 23, 2023
Messages
1
Trophies
0
Age
23
XP
13
Country
United States
I want to create a patch for Ring Fit Adventure to limit the frame rate to 30, but I encountered a problem in step 7. I can't find a place that looks like the "right ASM block." Specifically, I can't find anything similar to "w1, #0x1" (the game runs at 60 frames, so I guess it's like this). I've tried the previous steps for both nvnWindowBuilderSetPresentInterval and nvnWindowSetPresentInterval, but neither of them seem to have the right results.
I wonder if there are any other characteristics related to the "right ASM block"?
BTW I am running the game on Yuzu, so I cannot use FPSLocker.
 

masagrator

The patches guy
Developer
Joined
Oct 14, 2018
Messages
6,270
Trophies
3
XP
12,037
Country
Poland
I want to create a patch for Ring Fit Adventure to limit the frame rate to 30, but I encountered a problem in step 7. I can't find a place that looks like the "right ASM block." Specifically, I can't find anything similar to "w1, #0x1" (the game runs at 60 frames, so I guess it's like this). I've tried the previous steps for both nvnWindowBuilderSetPresentInterval and nvnWindowSetPresentInterval, but neither of them seem to have the right results.
I wonder if there are any other characteristics related to the "right ASM block"?
BTW I am running the game on Yuzu, so I cannot use FPSLocker.
You don't want to do that. People found that limiting FPS actually increases CPU usage in this game.

And you can't use this tutorial in many 60 FPS games to limit to 30 FPS. Solely because game doesn't need to do anything to run game at 60 FPS. That's why you can't find those functions actually being used.
 
  • Like
Reactions: B1anYu

K-Eye085

Member
Newcomer
Joined
Jul 25, 2023
Messages
11
Trophies
0
Age
26
XP
85
Country
Brazil
if I want to create a resolution patch to play a game at 720p in handheld mode (reverseNX doesnt work), what shoud I search for?
 

RichardTheKing

Honestly XC2>XC3...
Member
Joined
Mar 18, 2020
Messages
1,045
Trophies
1
Age
26
XP
3,203
Country
Australia
4. On Switch two variables usually control the framerate target which are
'nvnWindowBuilderSetPresentInterval'
'nvnWindowSetPresentInterval'
Click Search->Search Memory (Search Program Text is an alternate option) and search for one or both of these - in my case I searched for 'nvnWindowSetPresentInterval'
Click on the search result that should come up and you should have found it!
I've analysed Main, and there are no labels; searching for these leads to zero results. Am I doing something wrong here?
 
Last edited by RichardTheKing,

StevensND

Well-Known Member
Member
Joined
Dec 26, 2017
Messages
128
Trophies
0
Age
27
XP
456
Country
Spain
I've analysed Main, and there are no labels; searching for these leads to zero results. Am I doing something wrong here?

If you're seeing a lot of ???? symbols or maybe missing labels even after the analysis has been completed test the following: click on Analysis -> One Shot -> Aggressive Instruction Finder. Only do this if you're sure that the first analysis is complete. Let it run again and wait until it's finished. Then try to do the tutorial again.
 

masagrator

The patches guy
Developer
Joined
Oct 14, 2018
Messages
6,270
Trophies
3
XP
12,037
Country
Poland
If you're seeing a lot of ???? symbols or maybe missing labels even after the analysis has been completed test the following: click on Analysis -> One Shot -> Aggressive Instruction Finder. Only do this if you're sure that the first analysis is complete. Let it run again and wait until it's finished. Then try to do the tutorial again.
This won't help since that method works on binary level.

If they didn't screw up, answer is pretty simple - game doesn't use NVN. Tutorial lacks answers to question what if game turns out to be using EGL (so eglInterval must be patched) or Vulkan (API doesn't support setting intervals ).

In case of EGL matter complicates fact that games are using two approaches to link to subsdk functions: by implementing weak symbol, or like NVN's LoadCProcs it calls other function to get address of eglInterval and stores it somewhere.
 

StevensND

Well-Known Member
Member
Joined
Dec 26, 2017
Messages
128
Trophies
0
Age
27
XP
456
Country
Spain
This won't help since that method works on binary level.

If they didn't screw up, answer is pretty simple - game doesn't use NVN. Tutorial lacks answers to question what if game turns out to be using EGL (so eglInterval must be patched) or Vulkan (API doesn't support setting intervals ).

In case of EGL matter complicates fact that games are using two approaches to link to subsdk functions: by implementing weak symbol, or like NVN's LoadCProcs it calls other function to get address of eglInterval and stores it somewhere.

Well then I guess this is weird or I might be lucky: I tested a game (I don't remember which as I tested more games last week so it could be latest Dragon Quest or Fate Samurai) and I had that problem: Missing labels and lot of ?. So I did that and suddenly I saw the missing labels that I needed (no problems using IDA).

I've been having issues since I moved to Ghidra 10.3 (sometimes infinite loop and showing something like: "Examining code flow") when the main size is more than ~30 MB and then it took ~3-4 hours to finish the complete analysis ...)

However yeah I suggested it just in case the same thing could happened. Ofc that doesn't work all the time.
 

masagrator

The patches guy
Developer
Joined
Oct 14, 2018
Messages
6,270
Trophies
3
XP
12,037
Country
Poland
@StevensND this is because if analyze fails in finding references to those strings or it won't recognize them as strings, instead of search results showing normal strings they show only first byte of that string or other shit. But if there are no results at all (which OP is suggesting), it means that nvn is not used.
 
  • Like
Reactions: StevensND

Site & Scene News

Popular threads in this forum

General chit-chat
Help Users
    rvtr @ rvtr: Spam bots again.