Toggle menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

NX-FPS Switch: Difference between revisions

From GameBrew
(Created page with "{{Infobox Switch Homebrews |title=NX-FPS |image=nxfpsswitch.png |description=SaltyNX Plugin collecting FPS data |author=masagrator |lastupdated=2023/04/02 |type=Plugins |version=1.2.1 |license=MIT |download=https://dlhb.gamebrew.org/switchhomebrews/nxfpsswitch.7z |website=https://gbatemp.net/threads/nx-fps-saltynx-plugin-collecting-fps-data.559065/ |source=https://github.com/masagrator/NX-FPS |donation=https://ko-fi.com/masagrator }} {{#seo: |title=Switch Homebrew Apps (...")
 
No edit summary
Line 23: Line 23:
Once you've installed the necessary software, simply put the NX-FPS.elf file into <code>/SaltySD/plugins</code>. This plugin currently supports graphics APIs such as NVN, EGL, and Vulkan.
Once you've installed the necessary software, simply put the NX-FPS.elf file into <code>/SaltySD/plugins</code>. This plugin currently supports graphics APIs such as NVN, EGL, and Vulkan.


To read FPS data from the plugin, you can either use the Status Monitor Overlay 0.8.1+ from [https://github.com/masagrator/Status-Monitor-Overlay] or write your own code. If you're interested in writing your own code, I've provided instructions based on the Status Monitor Overlay code:
To read FPS data from the plugin, you can either use the Status Monitor Overlay 0.8.1+ from [https://github.com/masagrator/Status-Monitor-Overlay here] or write your own code. If you're interested in writing your own code, I've provided instructions based on the Status Monitor Overlay code:


# You'll need to include the old ipc.h file from [https://github.com/masagrator/ReverseNX-RT/blob/master/Overlay/include/ipc.h] as well as SaltyNX.h from [https://github.com/masagrator/ReverseNX-RT/blob/master/Overlay/include/SaltyNX.h].
# You'll need to include the old ipc.h file from [https://github.com/masagrator/ReverseNX-RT/blob/master/Overlay/include/ipc.h here] as well as SaltyNX.h from [https://github.com/masagrator/ReverseNX-RT/blob/master/Overlay/include/SaltyNX.h here].
# Connect to SaltyNX SharedMemory by connecting to SaltyNX, retrieving the SharedMemory handle, terminating the connection with SaltyNX, and then assigning SharedMemory to your homebrew.
# Connect to SaltyNX SharedMemory by connecting to SaltyNX, retrieving the SharedMemory handle, terminating the connection with SaltyNX, and then assigning SharedMemory to your homebrew.


Line 32: Line 32:
<youtube>0svgC4wuB18</youtube>
<youtube>0svgC4wuB18</youtube>


==Screenshots==
==Media==
https://dlhb.gamebrew.org/switchhomebrews/nxfpsswitch-01.pnga
'''Resident Evil 5 (Nintendo Switch) - Status Monitor Overlay and NX-FPS - ([https://www.youtube.com/watch?v=9RqGEk4talI Marek])'''<br>
https://dlhb.gamebrew.org/switchhomebrews/nxfpsswitch-02.pnga
<youtube>9RqGEk4talI</youtube>
 
==Helper==
<pre>Handle remoteSharedMemory = 1;
SharedMemory _sharedmemory = {};
bool SharedMemoryUsed = false;
 
//Function pinging SaltyNX InjectServ port to check if it's alive.
bool CheckPort () {
    Handle saltysd;
    for (int i = 0; i &lt; 67; i++) {
        if (R_SUCCEEDED(svcConnectToNamedPort(&amp;saltysd, &quot;InjectServ&quot;))) {
            svcCloseHandle(saltysd);
            break;
        }
        else {
            if (i == 66) return false;
            svcSleepThread(1'000'000);
        }
    }
    for (int i = 0; i &lt; 67; i++) {
        if (R_SUCCEEDED(svcConnectToNamedPort(&amp;saltysd, &quot;InjectServ&quot;))) {
            svcCloseHandle(saltysd);
            return true;
        }
        else svcSleepThread(1'000'000);
    }
    return false;
}
 
bool LoadSharedMemory() {
    //Connect to main SaltyNX port. On failed attempt return false
    if (SaltySD_Connect())
      return false;
    //Retrieve handle necessary to get access to SaltyNX SharedMemory
    SaltySD_GetSharedMemoryHandle(&amp;remoteSharedMemory);
    //Terminate SaltyNX main port connection
    SaltySD_Term();
   
    //Prepare struct that will be passed to shmemMap to get access to SaltyNX SharedMemory
    shmemLoadRemote(&amp;_sharedmemory, remoteSharedMemory, 0x1000, Perm_Rw);
    //Try to get access to SaltyNX SharedMemory
    if (!shmemMap(&amp;_sharedmemory)) {
      //On success change bool of SharedMemoryUsed to true and return true
      SharedMemoryUsed = true;
      return true;
    }
    //On failed attemp return false
    return false;
}
 
//By connecting to InjectServ port we can check if SaltyNX is alive and is not stuck anywhere.
bool SaltyNX = CheckPort();
//Check if SaltyNX signaled anything, if yes then use LoadSharedMemory() to get access to SaltyNX SharedMemory
if (SaltyNX) LoadSharedMemory();</pre>
 
* Next we need to find where our plugin is inside SharedMemory. For this our plugin stores magic 0x465053 as uint32_t. We need to find magic and based on that we know where our plugin is.
 
<pre>uint32_t* MAGIC_shared = 0;
uint8_t* FPS_shared = 0;
float* FPSavg_shared = 0;
bool* pluginActive = 0;
 
//Function searching for NX-FPS magic through SharedMemory.
//SaltyNX is page aligning any SharedMemory reservation request to 4, that's why we check every 4th byte for MAGIC.
ptrdiff_t searchSharedMemoryBlock(uintptr_t base) {
    ptrdiff_t search_offset = 0;
    while(search_offset &lt; 0x1000) {
        MAGIC_shared = (uint32_t*)(base + search_offset);
        if (*MAGIC_shared == 0x465053) {
            return search_offset;
        }
        else search_offset += 4;
    }
    return -1;
}
 
//Get virtual address of SaltyNX SharedMemory
uintptr_t base = (uintptr_t)shmemGetAddr(&amp;_sharedmemory);
//Pass retrieved virtual address of SaltyNX SharedMemory to function that will search for NX-FPS magic
ptrdiff_t rel_offset = searchSharedMemoryBlock(base);
//If magic will be found, you will get offset that starts before magic. It cannot be lower than 0.
if (rel_offset &gt; -1) {
  //Pass correct addresses inside SharedMemory to our pointers
  //It shows how many frames passed in one second
  FPS_shared = (uint8_t*)(base + rel_offset + 4);
  //It calculates average FPS based on last 10 readings
  FPSavg_shared = (float*)(base + rel_offset + 5);
  //Pointer where plugin writes true for every frame.
  pluginActive = (bool*)(base + rel_offset + 9);
  ///By passing false to *pluginActive and checking later if it has changed to true we can be sure plugin is working.
  *pluginActive = false;
}</pre>
 
<blockquote>WARNING
</blockquote>
Plugin brings some instability to boot process for some games. It is recommended to not close game before ~10 seconds have passed from showing Nintendo logo, otherwise you risk Kernel panic, which results in crashing OS.
 
-----
 
Not working games with this plugin (You can find games not compatible with SaltyNX [https://github.com/masagrator/SaltyNX/blob/master/README.md here])
 
{| class="wikitable"
! Title
! Version(s)
! Why?
|-
| Final Fantasy VIII Remastered
| all
| Framework stuff is included in NROs which SaltyNX doesn't support
|-
| Final Fantasy X/X-2
| all
| Framework stuff is included in NROs which SaltyNX doesn't support
|}


== Troubleshooting ==
== Troubleshooting ==

Revision as of 07:55, 2 April 2023

NX-FPS
File:Nxfpsswitch.png
General
Authormasagrator
TypePlugins
Version1.2.1
LicenseMIT License
Last Updated2023/04/02
Links
Download
Website
Source
Support Author

If you're looking to collect FPS data in Nintendo Switch games, NX-FPS is a great SaltyNX plugin to consider. To get started, you'll need to install my fork of SaltyNX 0.5.0+ from here.

Once you've installed the necessary software, simply put the NX-FPS.elf file into /SaltySD/plugins. This plugin currently supports graphics APIs such as NVN, EGL, and Vulkan.

To read FPS data from the plugin, you can either use the Status Monitor Overlay 0.8.1+ from here or write your own code. If you're interested in writing your own code, I've provided instructions based on the Status Monitor Overlay code:

  1. You'll need to include the old ipc.h file from here as well as SaltyNX.h from here.
  2. Connect to SaltyNX SharedMemory by connecting to SaltyNX, retrieving the SharedMemory handle, terminating the connection with SaltyNX, and then assigning SharedMemory to your homebrew.

Media

How to overclock the Nintendo Switch and view FPS - Sysclk & NX FPS Homebrew mods Atmosphere CFW - (Nevercholt)

Media

Resident Evil 5 (Nintendo Switch) - Status Monitor Overlay and NX-FPS - (Marek)

Helper

Handle remoteSharedMemory = 1;
SharedMemory _sharedmemory = {};
bool SharedMemoryUsed = false;

//Function pinging SaltyNX InjectServ port to check if it's alive.
bool CheckPort () {
    Handle saltysd;
    for (int i = 0; i < 67; i++) {
        if (R_SUCCEEDED(svcConnectToNamedPort(&saltysd, "InjectServ"))) {
            svcCloseHandle(saltysd);
            break;
        }
        else {
            if (i == 66) return false;
            svcSleepThread(1'000'000);
        }
    }
    for (int i = 0; i < 67; i++) {
        if (R_SUCCEEDED(svcConnectToNamedPort(&saltysd, "InjectServ"))) {
            svcCloseHandle(saltysd);
            return true;
        }
        else svcSleepThread(1'000'000);
    }
    return false;
}

bool LoadSharedMemory() {
    //Connect to main SaltyNX port. On failed attempt return false
    if (SaltySD_Connect())
      return false;
    //Retrieve handle necessary to get access to SaltyNX SharedMemory
    SaltySD_GetSharedMemoryHandle(&remoteSharedMemory);
    //Terminate SaltyNX main port connection
    SaltySD_Term();
    
    //Prepare struct that will be passed to shmemMap to get access to SaltyNX SharedMemory
    shmemLoadRemote(&_sharedmemory, remoteSharedMemory, 0x1000, Perm_Rw);
    //Try to get access to SaltyNX SharedMemory
    if (!shmemMap(&_sharedmemory)) {
      //On success change bool of SharedMemoryUsed to true and return true
      SharedMemoryUsed = true;
      return true;
    }
    //On failed attemp return false
    return false;
}

//By connecting to InjectServ port we can check if SaltyNX is alive and is not stuck anywhere.
bool SaltyNX = CheckPort();
//Check if SaltyNX signaled anything, if yes then use LoadSharedMemory() to get access to SaltyNX SharedMemory
if (SaltyNX) LoadSharedMemory();
  • Next we need to find where our plugin is inside SharedMemory. For this our plugin stores magic 0x465053 as uint32_t. We need to find magic and based on that we know where our plugin is.
uint32_t* MAGIC_shared = 0;
uint8_t* FPS_shared = 0;
float* FPSavg_shared = 0;
bool* pluginActive = 0;

//Function searching for NX-FPS magic through SharedMemory.
//SaltyNX is page aligning any SharedMemory reservation request to 4, that's why we check every 4th byte for MAGIC.
ptrdiff_t searchSharedMemoryBlock(uintptr_t base) {
    ptrdiff_t search_offset = 0;
    while(search_offset < 0x1000) {
        MAGIC_shared = (uint32_t*)(base + search_offset);
        if (*MAGIC_shared == 0x465053) {
            return search_offset;
        }
        else search_offset += 4;
    }
    return -1;
}

//Get virtual address of SaltyNX SharedMemory
uintptr_t base = (uintptr_t)shmemGetAddr(&_sharedmemory);
//Pass retrieved virtual address of SaltyNX SharedMemory to function that will search for NX-FPS magic
ptrdiff_t rel_offset = searchSharedMemoryBlock(base);
//If magic will be found, you will get offset that starts before magic. It cannot be lower than 0.
if (rel_offset > -1) {
  //Pass correct addresses inside SharedMemory to our pointers
  //It shows how many frames passed in one second
  FPS_shared = (uint8_t*)(base + rel_offset + 4);
  //It calculates average FPS based on last 10 readings
  FPSavg_shared = (float*)(base + rel_offset + 5);
  //Pointer where plugin writes true for every frame.
  pluginActive = (bool*)(base + rel_offset + 9);
  ///By passing false to *pluginActive and checking later if it has changed to true we can be sure plugin is working.
  *pluginActive = false;
}

WARNING

Plugin brings some instability to boot process for some games. It is recommended to not close game before ~10 seconds have passed from showing Nintendo logo, otherwise you risk Kernel panic, which results in crashing OS.


Not working games with this plugin (You can find games not compatible with SaltyNX here)

Title Version(s) Why?
Final Fantasy VIII Remastered all Framework stuff is included in NROs which SaltyNX doesn't support
Final Fantasy X/X-2 all Framework stuff is included in NROs which SaltyNX doesn't support

Troubleshooting

Q: Why I got constantly 255?
A: 255 is default value before plugin starts counting frames. This may be a sign that:

  • Game is using different API or function than what is currently supported
  • Plugin missed symbol when initializing (for whatever reason)

Try first to run game again few times. If it's still 255, make an issue and state name of game. Next updates will include support for other graphics APIs.

Changelog

1.2.1

  • Fix "main" address detection

1.2

  • Add support for nvnQueueAcquireTexture that was used with some old games (for example Outlast) before deprecation.

1.1

  • Allow loading LOCK bins produced by FPSLocker 1.1+

1.0

  • Allow manipulating FPS

0.4

  • Move writing data into SaltyNX SharedMemory. Requires SaltyNX 0.5.0+ to work.
  • If you use Status Monitor Overlay, you must update it to version 0.8.1+ to use it.

0.3

  • Added support for Vulkan, so games like The Talos Principle and DOOM 64 are now supported.

0.2

  • Added support for EGL, which means games like The Unholy Society and Layton's Mystery Journey: Katrielle and the Millionaires' Conspiracy are now supported.

0.1

  • First Release.

Thanks to

  • RetroNX channel for help with coding stuff,
  • CTCaer for providing many useful informations and convincing me to the end that I should target high precision,
  • Herbaciarz for providing video footage.

External links

Advertising: