FPSCap - NosHook Addon - Set The Games FPS Divisor

08/23/2016 09:33 atom0s#1
You will need NosHook for this to work!
Get it here:
[Only registered and activated users can see links. Click Here To Register...]

Addon Author: atom0s
Addon Name: FPSCap
Addon Version: 1.0.0
Addon Repository: [Only registered and activated users can see links. Click Here To Register...]

FPSCap is a simple addon that allows you to change the FPS divisor (cap) of the game.

Installation and Usage
  • Load up the new NosHook injector and download the addon via the Addons page.
  • Edit your Default.txt to include the addon if you wish to auto-load it via: /addon load fpscap
  • Load the addon manually via the console: /addon load fpscap

This addon is auto-load friendly, meaning you can add it and use it within your Default.txt such as:
Code:
/addon load fpscap
/fpscap 0
Commands
  • /fpscap # (Where # is the number you wish to set the divisor to.)

Warning: Changing the FPS divisor can increase the CPU usage of the game. Older machines may have issues while using a lower divisor value. The lower the value, the higher your fps will go.

18 is the default divisor value.
0 is uncapped, allowing the FPS to go as high as your system can handle.

Download
FPSCap can be downloaded via the NosHook injector on the Addons page.

More info for this addon here:
[Only registered and activated users can see links. Click Here To Register...]
08/23/2016 10:38 WalrossGreat#2
18 is not the default value... at least not always
08/23/2016 11:01 atom0s#3
Quote:
Originally Posted by WalrossGreat View Post
18 is not the default value... at least not always
For me, it has been 18 all day today. So I deemed it the default value as a recommended value to set it back to if people want to 'undo' their edit.
08/23/2016 11:13 BladeTiger12#4
Soooo much threads x.x.
Maybe it's better doing 1 Collection-Thread...
Just a suggestion :P.
08/23/2016 12:11 atom0s#5
Quote:
Originally Posted by BladeTiger12 View Post
Soooo much threads x.x.
Maybe it's better doing 1 Collection-Thread...
Just a suggestion :P.

I'd rather have separate threads per-project to keep questions, suggestions, etc. separated. Easier to manage everything and less annoying with the merging posts if you double post etc.


Quote:
Originally Posted by WalrossGreat View Post
18 is not the default value... at least not always

To go into more detail, here is where the game handles the config file loading the fps data:
Code:
  if ( (unsigned __int8)(byte_81B605 - 1) >= 7u )
  {
    dword_68D59C = 18;
  }
  else
  {
    v14 = (int *)(5 * (unsigned __int8)byte_81B605 + 25);
    v11 = 5 * (unsigned __int8)byte_81B605 + 25;
    result = 0x384 / v11;
    dword_68D59C = 0x384 / v11;
  }
byte_81B605 is the loaded configuration files FPS value.
7 is the configurations max value for the fps slider (it goes from 1 to 7).
7 or higher defaults the fps divisor to 18.


Lower is a calculated amount but in general it is still not a big deal based on what is/isn't the overall 'default'. Nothing else writes to the address besides an internal configuration.ini when the game is told to load from an ini config for testing.
08/23/2016 12:31 WalrossGreat#6
I knew it, that's why I said, just got interesting how you find that address without looking in debugger at the beginning
08/23/2016 13:21 atom0s#7
Quote:
Originally Posted by WalrossGreat View Post
I knew it, that's why I said, just got interesting how you find that address without looking in debugger at the beginning
It's easy to find in via two ways:


1. Via the config file since the value of the fps divisor is written to it and loaded within the client.


2. Most games that have FPS limiting use a few API calls to handle the delay, which usually includes Sleep()/SleepEx(), GetTickCount(), time(), QueryPerformanceCounter(), etc. Looking for references to those can lead to the handler pretty fast. You can also look for PeekMessage/GetMessage/TranslateMessage/DispatchMessage to find the applications usual main message loop which tends to call the function(s) that handle the fps capping and so on.
08/23/2016 13:45 WalrossGreat#8
1. I don't agree. The value of divisor is not save to config file, only the value of slider lvl
08/23/2016 20:55 atom0s#9
Quote:
Originally Posted by WalrossGreat View Post
1. I don't agree. The value of divisor is not save to config file, only the value of slider lvl
Lol.. sorry for not being 'omg' literal. Yes the value of the slider is written to the config, but it is what is used for the divisor calculation. Either way, the value is useful to find the calculation inside of the game client.


This is where NtConfig.exe saves the configuration file:
Code:
int __fastcall sub_486B34(int a1)
{
  int v1; // ebx@1
  int v2; // esi@1
  int v3; // edi@1
  int v4; // eax@1
  int result; // eax@7


  v1 = a1;
  v2 = a1 + 932;
  v3 = a1 + 932;
  (*(void (__fastcall **)(_DWORD, _DWORD))(**(_DWORD **)(a1 + 868) + 220))(*(_DWORD *)(a1 + 868), *(_BYTE *)(a1 + 932));
  Extctrls::TCustomRadioGroup::SetItemIndex(*(Extctrls::TCustomRadioGroup **)(v1 + 872), *(_BYTE *)(v3 + 1) - 1);
  v4 = *(_DWORD *)(v1 + 872);
  if ( *(_DWORD *)(v4 + 608) >= 6u )
    Extctrls::TCustomRadioGroup::SetItemIndex((Extctrls::TCustomRadioGroup *)v4, 0);
  Extctrls::TCustomRadioGroup::SetItemIndex(*(Extctrls::TCustomRadioGroup **)(v1 + 876), *(_BYTE *)(v3 + 2));
  (*(void (__fastcall **)(_DWORD, _DWORD))(**(_DWORD **)(v1 + 884) + 220))(*(_DWORD *)(v1 + 884), *(_BYTE *)(v2 + 12));
  (*(void (__fastcall **)(_DWORD, _DWORD))(**(_DWORD **)(v1 + 888) + 220))(*(_DWORD *)(v1 + 888), *(_BYTE *)(v2 + 13));
  (*(void (__fastcall **)(_DWORD, _DWORD))(**(_DWORD **)(v1 + 896) + 220))(*(_DWORD *)(v1 + 896), *(_BYTE *)(v2 + 14));
  if ( *(_BYTE *)(v2 + 16) )
    Extctrls::TCustomRadioGroup::SetItemIndex(*(Extctrls::TCustomRadioGroup **)(v1 + 908), 1);
  else
    Extctrls::TCustomRadioGroup::SetItemIndex(*(Extctrls::TCustomRadioGroup **)(v1 + 908), 0);
    
    
  // Here is the FPS divisor slider value being saved..
  if ( (unsigned __int8)(*(_BYTE *)(v2 + 17) - 1) >= 7u )
    result = Comctrls::TTrackBar::SetPosition(*(Comctrls::TTrackBar **)(v1 + 916), 5);
  else
    result = Comctrls::TTrackBar::SetPosition(*(Comctrls::TTrackBar **)(v1 + 916), *(_BYTE *)(v2 + 17));
  return result;
}

The file layout is:
Code:
struct TNostaleConfig
{
     uint8_t IsFullscreenOn;    // 0 = Off, 1 = On
     uint8_t ScreenSize;        // 1 = 1024x768, etc.
     uint8_t ScreenBitCount;    // 0 = 16bit, 1 = 32bit
     uint8_t Unknown0000;
     uint8_t Unknown0001;
     uint8_t Unknown0002;
     uint8_t Unknown0003;
     uint8_t Unknown0004;
     uint8_t Unknown0005;
     uint8_t Unknown0006;
     uint8_t Unknown0007;
     uint8_t Unknown0008;
     uint8_t IsFXOn;
     uint8_t IsBGMOn;
     uint8_t Is3DOn;
     uint8_t Unknown000F;
     uint8_t RenderingEngine;   // 0 = Direct3D, 1 = OpenGL
     uint8_t FPS;               // 1 = Lowest, 7 = Highest
};

The actual client loads and reads this structure here:
Code:
char __usercall sub_4B33F0@<al>(int a1@<eax>)
{
  int v1; // ebx@1
  int v3; // [sp-Ch] [bp-18h]@2
  void *v4; // [sp-8h] [bp-14h]@2
  int *v5; // [sp-4h] [bp-10h]@2
  void *v6; // [sp+0h] [bp-Ch]@2
  _DWORD *v7; // [sp+4h] [bp-8h]@2
  char v8; // [sp+Bh] [bp-1h]@1
  int savedregs; // [sp+Ch] [bp+0h]@2


  v1 = a1;
  v8 = 0;
  if ( (unsigned __int8)sub_40C350() )
  {
    v7 = (_DWORD *)sub_41E558((int)off_41A374, 1, v1, 0);
    v5 = &savedregs;
    v4 = &loc_4B345A;
    __writefsdword(0, (unsigned int)&v3);
    (*(void (__fastcall **)(signed int, char *, unsigned __int32))(*v7 + 12))(1024, &byte_81B5F4, __readfsdword(0));
    sub_4B3010(); // <--- THIS CALL IS WHERE ITS LOADED
    v8 = 1;
    __writefsdword(0, (unsigned int)v4);
    v6 = &loc_4B3461;
    sub_403E54(v7);
    sub_4B2784(v6);
  }
  return v8;
}

You can find this function simply by looking for string references of Config.dat which leads to:
Code:
  sub_404F78(&v123, (int)"Config.dat");
  sub_4B33F0(v123);

The call 'sub_4B3010' contains the FPS divisor code at the bottom that I pasted above earlier. Which reads from the config file in the same offset as the NtConfig.exe wrote it.
08/23/2016 22:44 WalrossGreat#10
I was just talking that simple look on config file won't help without further looking at nostalex code, all what you are pasting now is exactly looking at nostalex(which I already did).

Just got confused when you were talking about "default" value, like you didn't look into debugger but still has the place in memory with the fps sec sleep value.

Just nvm, thought that there are any magic ways
05/16/2018 08:42 atom0s#11
Small update pushed to fix issues with latest client. Run the NosHook launcher and it will auto-update.