diff --git a/README.md b/README.md
index f19377f..4fea028 100644
--- a/README.md
+++ b/README.md
@@ -1,30 +1,35 @@
# Sekiro FPS Unlocker and more
-A small utility to remove frame rate limit, add custom resolutions with 21/9 widescreen support, increase field of view (FOV) (credits to jackfuste) and borderless window mode for [Sekiro: Shadows Die Twice](https://www.sekirothegame.com/) written in C#.
-Patches games memory while running, does not modify any game files. works with every game version (legit steam & oh-not-so-legit), should work with all future updates.
+A small utility to remove frame rate limit, add custom resolutions with 21/9 widescreen support, change field of view (FOV), borderless window mode and various game modifications for [Sekiro: Shadows Die Twice](https://www.sekirothegame.com/) written in C#.
+Patches games memory while running, does not modify any game files. Wrks with every game version (legit steam & oh-not-so-legit), should work with all future updates. Also available [Nexus Mods](https://www.nexusmods.com/sekiro/mods/13/).
## Download
**[Get the latest release here](https://github.com/uberhalit/SekiroFpsUnlockAndMore/releases)**
### See it in action:
-[](https://giant.gfycat.com/DevotedArtisticKingsnake.webm)
+[](https://giant.gfycat.com/PhonyReadyGreatargus.webm)
## Features
* does not modify any game files, RAM patches only
* works with legit, unmodified steam version as well as with unpacked, not-so-legit versions
* GSYNC and FreeSync support even in borderless window mode
-* unlock frame rate (remove FPS limit) by setting a new custom limit or setting lock to unlimited
-* add a custom resolution, 21/9 widescreen supported (will overwrite the default 1920x1080 resolution, HUD limited to 16/9)
-* increase field of view (FOV) (credits to jackfuste)
+* unlock frame rate (remove FPS limit) by setting a new custom limit
+* add a custom resolution, 21/9 widescreen supported (will overwrite the default 1920x1080 / 1280x720 resolution, HUD limited to 16/9)
+* increase and decrease field of view (FOV)
* set the game to borderless window mode
+* game modifications
+ * global game speed modifier
+ * player speed modifier
* automatically patch game on startup
* seamlessly switch between windowed, borderless and borderless fullscreen
* hotkey for patching while in (borderless) window mode
## Usage
+The following graphical guide has to be done if you want to unlock the game's framerate or play on a highter refresh rate in fullscreen. If you do not wish to use that feature you can scoll down further to the guides on all other features. The graphic setup has to be done only once but as the patcher hot-patches the memory **you have to start the patcher every time you want use any of its features**.
+
The game enforces VSYNC and forces 60 Hz in fullscreen even on 144 Hz monitors so we have to override these.
#### 60 Hz monitors: disable VSYNC via driver (use 'Enhanced Sync' on AMD) and use fullscreen, see guide below
#### high refresh rate monitors: use borderless or force monitor to always use highest available refresh rate and then use fullscreen, see guide below
@@ -35,20 +40,31 @@ The game enforces VSYNC and forces 60 Hz in fullscreen even on 144 Hz monitors s
3. **Make sure your monitor is set to the highest Refresh rate possible:**
4. [](#)
5. Navigate to `3D Settings -> Manage 3D settings -> Program Settings`
-6. Select Sekiro from the dropdown or add it manually if it's missing: `Add -> Select Sekiro -> Add Selected Program`
-7. **Set `Vertical sync` to `Off`**
-8. **Set `Preferred refresh rate` to `Highest available`**
-9. [](#)
-10. Hit apply and close Nvidia Control Panel
-11. Start `Sekiro FPS Unlocker and more` and set FPS lock to your desired framerate
-12. Start the game and use fullscreen or borderless window mode
-13. These steps will force disable vsync so it won't limit your fps to monitor refresh rate and also force the monitor to ignore the games request to run at 60 Hz if in fullscreen
+6. **Check if you already have a Sekiro Profile in dropdown and if so DELETE IT**
+7. Manually add Sekiro into a clean new profile: `Add -> Browse ->` Navigate to `sekiro.exe` and select it
+8. **Make sure that there is a file path to Sekiro and that it is indeed correct and you haven't loaded a premade (empty) profile**
+9. **Set `Preferred refresh rate` to `Highest available`**
+10. **Set `Vertical sync` to `Off`**
+11. [](#)
+12. Hit apply and close Nvidia Control Panel
+13. Start `Sekiro FPS Unlocker and more` and set FPS lock to your desired framerate
+14. Start the game and use fullscreen or borderless window mode
+15. These steps will force disable vsync so it won't limit your fps to monitor refresh rate and also force the monitor to ignore the games request to run at 60 Hz if in fullscreen
#### If you do not have 'Preferred refresh rate' or 'Vertical sync' follow these steps (Nvidia):
-1. Download and extract the [Nvidia Inspector](https://www.techpowerup.com/download/nvidia-inspector/)
-2. Start the Nvidia Profile Inspector
-3. Under `2 - Sync and Refresh` set `Prefered Refreshrate` to `Highest available` and `Vertical Sync` to `Force off`
-4. [](#)
-5. Hit `Apply changes` and you are good to go
+1. **Delete the Sekiro Profile in Nvidia Control panel as otherwise it will block all settings from Profile Inspector**
+2. Hit apply and close the Nvidia Control panel
+3. Download and extract the [Nvidia Inspector](https://www.techpowerup.com/download/nvidia-inspector/)
+4. Start the Nvidia Profile Inspector
+5. **Check if there already is a profile for Sekiro and if so DELETE IT using the red 'X' button**
+6. Press the yellow start icon in the menu bar to create a new Profile (1)
+7. [](#)
+8. Name it `Sekiro` and select it in dropdown
+9. Press the blue window icon with the plus symbol to add an application to this profile(2)
+10. Change file type to `Application Absolute Path`, navigate to your `sekiro.exe` and select it
+11. [](#)
+12. Make sure that the file path to the game is correct (3)
+13. Under `2 - Sync and Refresh` set `Prefered Refreshrate` to `Highest available` and `Vertical Sync` to `Force off` (4)
+14. Hit `Apply changes` and you are good to go (5)
### Follow these steps on AMD:
1. Right click on Desktop -> `Display settings`
@@ -64,34 +80,33 @@ The game enforces VSYNC and forces 60 Hz in fullscreen even on 144 Hz monitors s
11. **Launch the game in windowed mode, then switch to fullscreen once in game**
12. The last step is important as AMD somehow does not correctly disable VSYNC otherwise
#### If you do not have 'Enhanced Sync' follow these steps (AMD):
-1. Try setting `Wait for Vertical Refresh` to `Off` instead:
+1. Try setting `Wait for Vertical Refresh` to `Always off` instead:
2. [](#)
3. Be aware however that it seems like AMDs latest drivers are buggy in that regard
### To play the game with GSYNC do these additional steps (Nvidia):
1. Under Nvidia Control Panel navigate to `3D Settings -> Manage 3D settings -> Program Settings -> Sekiro`
2. Set `Monitor Technology` to `G-SYNC`
-3. Make sure that `Preferred refresh rate` is still set to `Highest available`
-4. Make sure that `Vertical sync` is still set to `Off`
+3. Set `Vertical sync` to `Off` again as enabling G-SYNC re-enables it
+4. Make sure that `Preferred refresh rate` is still set to `Highest available`
5. [](#)
-6. Don't forget to Apply and close Nvidia Control Panel
-7. Use a 3rd party frame rate limiter like [RTSS](https://www.guru3d.com/files-details/rtss-rivatuner-statistics-server-download.html) and set a frame rate limit just a few fps below your monitor refresh rate, on a 144Hz Monitor use 138
-8. Start `Sekiro FPS Unlocker and more` and set FPS lock to your monitors refresh rate
-9. Start the game and set it to Fullscreen
-10. Enjoy perfectly tearing free variable high refresh rates without VSYNC
-
-The graphic setup has to be done only once but as the patcher hot-patches the memory **you have to start the patcher every time you want to unlock frame rate etc**.
+6. If you do not have `Preferred refresh rate` or `Vertical sync` see the guide above on how to use the Nvidia Profile Inspector
+7. Don't forget to Apply and close Nvidia Control Panel
+8. Use a 3rd party frame rate limiter like [RTSS](https://www.guru3d.com/files-details/rtss-rivatuner-statistics-server-download.html) and set a frame rate limit just a few fps below your monitor refresh rate, on a 144Hz Monitor use 138
+9. Start `Sekiro FPS Unlocker and more` and set FPS lock to your monitors refresh rate
+10. Start the game and set it to Fullscreen
+11. Enjoy perfectly tearing free variable high refresh rates without VSYNC
### To add a custom resolution
1. Start the game
2. Start `Sekiro FPS Unlocker and more`, set you desired resolution and enable it by ticking the check box
-3. Set your custom resolution in the graphical settings, be aware that the ingame HUD will be limited to 16/9
+3. Set your custom resolution in the graphical settings
+4. Be aware that your monitor has to natively support this resolution and the ingame HUD will be limited to 16/9
### To use the FOV changer
1. Start the game
2. Load up your save game
3. Start `Sekiro FPS Unlocker and more`, set you desired FOV value and enable it by ticking the check box
-4. If you reload a save FOV will reset so patch game manually again
### To use borderless window mode:
1. Start the game
@@ -100,19 +115,25 @@ The graphic setup has to be done only once but as the patcher hot-patches the me
4. Start `Sekiro FPS Unlocker and more` and enable borderless window mode
5. If you want fullscreen borderless enable `Fullscreen stretch`
+### To use any of the game modifications
+1. Start the game
+2. Load up your save game
+3. Start `Sekiro FPS Unlocker and more` and expand `Game modifications`
+4. Set your desired values and then tick the checkbox you'd wish to enable
+
## Preview
-[](#)
+[](#)
### Unlocked framerate
-[](#)
+[](#)
-### Increased FOV and borderless window:
-[](#)
+### Increased FOV borderless window and 90% game speed:
+[](#)
## Prerequisites
-* .NET Framework 4.0
+* .NET Framework 4.5
* administrative privileges (for patching)
* 64 bit OS
@@ -130,7 +151,8 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
## Credits
-* jackfuste for FOV findings and running speed fix
+* Zullie the Witch#7202 for game speed and player speed modifier offsets
+* jackfuste for FOV offset and basic running speed fix
* TyChii93#2376 for AMD and widescreen testing
* [Darius Dan](http://www.dariusdan.com) for the icon
@@ -139,12 +161,19 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
* the game has forced VSYNC so unlocking the frame rate when your monitor has 60Hz will do nothing. You'll have to disable VSYNC in Nvidia Control Panel or AMD Radeon Settings first
* in fullscreen the game forces the monitor to 60 Hz so you'll have to handle this with driver override too, see Usage
* your monitor has to support your custom resolution otherwise it won't show up correctly
-* due to how the game renders altering FOV will not move the HUD
-* the HUD is limited to 16/9 even on 21/9 resolutions
+* due to how the game renders altering HUD is limited to 16/9 even on 21/9 resolutions
* the hotkey won't work if the game runs in exclusive, true fullscreen mode
## Version History
+* v1.1.0 (2019-03-30)
+ * Added game speed modifier (thanks to Zullie the Witch#7202 for offset)
+ * Added player speed modifier (thanks to Zullie the Witch#7202 for offset)
+ * Settings are saved and loaded from config file now
+ * FOV will now stick even after save game load
+ * Fixed a potential issue with unlimited frame rate unlock
+ * Fixed a potential issue when user tried to enable borderless while in minimized fullscreen
+ * Improved initial load time til game is patchable
* v1.0.2 (2019-03-26)
* Added option to reduce FOV (request)
* Added option to stretch borderless window to fullscreen regardless of window resolution
diff --git a/SekiroFpsUnlockAndMore/App.config b/SekiroFpsUnlockAndMore/App.config
index 74ade9d..d1428ad 100644
--- a/SekiroFpsUnlockAndMore/App.config
+++ b/SekiroFpsUnlockAndMore/App.config
@@ -1,6 +1,6 @@
-
+
diff --git a/SekiroFpsUnlockAndMore/GameData.cs b/SekiroFpsUnlockAndMore/GameData.cs
new file mode 100644
index 0000000..83d4a98
--- /dev/null
+++ b/SekiroFpsUnlockAndMore/GameData.cs
@@ -0,0 +1,187 @@
+using System;
+using System.Linq;
+using System.Collections.Generic;
+
+namespace SekiroFpsUnlockAndMore
+{
+ internal class GameData
+ {
+ internal const string PROCESS_NAME = "sekiro";
+ internal const string PROCESS_TITLE = "Sekiro";
+ internal const string PROCESS_DESCRIPTION = "Shadows Die Twice";
+
+
+ /**
+ fFrameTick determines default frame rate limit in seconds
+ 000000014116168D | C743 18 8988883C | mov dword ptr ds:[rbx+18],3C888889 | fFrameTick
+ 0000000141161694 | 4C:89AB 70020000 | mov qword ptr ds:[rbx+270],r13 |
+ */
+ internal const string PATTERN_FRAMELOCK = "88 88 3C 4C 89 AB"; // 88 88 3C 4C 89 AB // first byte (last in mem) can can be 88/90 instead of 89 due to precision loss on floating point numbers
+ internal const string PATTERN_FRAMELOCK_MASK = "xxxxxx"; // mask for frame rate limiter signature scanning
+ internal const int PATTERN_FRAMELOCK_OFFSET = -1; // offset to byte array from found position
+ internal const string PATTERN_FRAMELOCK_FUZZY = "C7 43 00 00 00 00 00 4C 89 AB"; // C7 43 ?? ?? ?? ?? ?? 4C 89 AB
+ internal const string PATTERN_FRAMELOCK_FUZZY_MASK = "xx?????xxx";
+ internal const int PATTERN_FRAMELOCK_FUZZY_OFFSET = 3;
+
+
+ /**
+ Reference pointer pFrametimeRunningSpeed to speed table entry that gets used in calculations. Add or remove multiplications of 4bytes to pFrametimeRunningSpeed address to use a higher or lower fFrametimeCriticalRunningSpeed from table
+ fFrametimeCriticalRunningSpeed should be roughly half the frame rate: 30 @ 60FPS limit, 50 @ 100FPS limit...
+ 00000001407D4E08 | F3:0F5905 90309202 | mulss xmm0,dword ptr ds:[1430F7EA0] | pFrametimeRunningSpeed->fFrametimeCriticalRunningSpeed
+ */
+ internal const string PATTERN_FRAMELOCK_SPEED_FIX = "F3 0F 59 05 00 30 92 02"; // F3 0F 59 05 ?? 30 92 02 | 0F 51 C2 F3 0F 59 05 ?? ?? ?? ?? 0F 2F F8
+ internal const string PATTERN_FRAMELOCK_SPEED_FIX_MASK = "xxxx?xxx";
+ internal const int PATTERN_FRAMELOCK_SPEED_FIX_OFFSET = 4;
+ /**
+ 00000001430F7E10
+ Key: Patch to pFrametimeRunningSpeed last byte
+ Value: Value resolve in float table from pFrametimeRunningSpeed->fFrametimeCriticalRunningSpeed
+ Hardcoded cause lazy -> if anyone knows how the table is calculated then tell me and I'll buy you a beer
+ */
+ internal static Dictionary PATCH_FRAMELOCK_SPEED_FIX_MATRIX = new Dictionary
+ {
+ { new byte[1] {0x68}, 15f },
+ { new byte[1] {0x6C}, 16f },
+ { new byte[1] {0x70}, 16.6667f },
+ { new byte[1] {0x74}, 18f },
+ { new byte[1] {0x78}, 18.6875f },
+ { new byte[1] {0x7C}, 18.8516f },
+ { new byte[1] {0x80}, 20f },
+ { new byte[1] {0x84}, 24f },
+ { new byte[1] {0x88}, 25f },
+ { new byte[1] {0x8C}, 27.5f },
+ { new byte[1] {0x90}, 30f },
+ { new byte[1] {0x94}, 32f },
+ { new byte[1] {0x98}, 38.5f },
+ { new byte[1] {0x9C}, 40f },
+ { new byte[1] {0xA0}, 48f },
+ { new byte[1] {0xA4}, 49.5f },
+ { new byte[1] {0xA8}, 50f },
+ { new byte[1] {0xAC}, 57.2958f },
+ { new byte[1] {0xB0}, 60f },
+ { new byte[1] {0xB4}, 64f },
+ { new byte[1] {0xB8}, 66.75f },
+ { new byte[1] {0xBC}, 67f },
+ { new byte[1] {0xC0}, 78.8438f },
+ { new byte[1] {0xC4}, 80f },
+ { new byte[1] {0xC8}, 84f },
+ { new byte[1] {0xCC}, 90f },
+ { new byte[1] {0xD0}, 93.8f },
+ { new byte[1] {0xD4}, 100f },
+ { new byte[1] {0xD8}, 120f },
+ { new byte[1] {0xDC}, 127f },
+ { new byte[1] {0xE0}, 128f },
+ { new byte[1] {0xE4}, 130f },
+ { new byte[1] {0xE8}, 140f },
+ { new byte[1] {0xEC}, 144f },
+ { new byte[1] {0xF0}, 150f }
+ };
+ internal static byte[] PATCH_FRAMELOCK_SPEED_FIX_DISABLE = new byte[1] { 0x90 }; // 30f
+
+ ///
+ /// Finds closest valid speed fix value for a frame rate limit.
+ ///
+ /// The set frame rate limit.
+ /// The byte patch of the closest speed fix.
+ internal static byte[] FindSpeedFixForRefreshRate(int frameLimit)
+ {
+ float idealSpeedFix = frameLimit / 2f;
+ KeyValuePair closestSpeedFix = new KeyValuePair(PATCH_FRAMELOCK_SPEED_FIX_DISABLE, 30f);
+ foreach (var speedFix in PATCH_FRAMELOCK_SPEED_FIX_MATRIX.OrderBy(kvp => kvp.Value))
+ {
+ if (Math.Abs(idealSpeedFix - speedFix.Value) < Math.Abs(idealSpeedFix - closestSpeedFix.Value))
+ closestSpeedFix = speedFix;
+ }
+ return closestSpeedFix.Key;
+ }
+
+
+ /**
+ Reference pointer pCurrentResolutionWidth to iInternalGameWidth (and iInternalGameHeight which is +4 bytes)
+ 000000014114AC85 | 0F57D2 | xorps xmm2,xmm2 |
+ 000000014114AC88 | 890D 92147D02 | mov dword ptr ds:[14391C120],ecx | pCurrentResolutionWidth->iInternalGameWidth
+ 000000014114AC8E | 0F57C9 | xorps xmm1,xmm1 |
+ 000000014114AC91 | 8915 8D147D02 | mov dword ptr ds:[14391C124],edx | pCurrentResolutionHeight->iInternalGameHeight
+ */
+ internal const string PATTERN_RESOLUTION_POINTER = "0F 57 D2 89 0D 00 00 00 00 0F 57 C9"; // 0F 57 D2 89 0D ?? ?? ?? ?? 0F 57 C9
+ internal const string PATTERN_RESOLUTION_POINTER_MASK = "xxxxx????xxx";
+ internal const int PATTERN_RESOLUTION_POINTER_OFFSET = 3;
+ internal const int PATTERN_RESOLUTION_POINTER_INSTRUCTION_LENGTH = 6;
+
+
+ /**
+ DATA SECTION. All resolutions are listed in memory as width1 height1 width2 height2 ...
+ Overwrite an unused one with desired new one. Some glitches, 1920x1080 and 1280x720 works best
+ */
+ internal const string PATTERN_RESOLUTION_DEFAULT = "80 07 00 00 38 04 00 00"; // 1920x1080
+ internal const string PATTERN_RESOLUTION_DEFAULT_720 = "40 06 00 00 84 03 00 00"; // 1280x720
+ internal const string PATTERN_RESOLUTION_DEFAULT_MASK = "xxxxxxxx";
+ internal static byte[] PATCH_RESOLUTION_DEFAULT_DISABLE = new byte[8] { 0x80, 0x07, 0x00, 0x00, 0x38, 0x04, 0x00, 0x00 };
+ internal static byte[] PATCH_RESOLUTION_DEFAULT_DISABLE_720 = new byte[8] { 0x40, 0x06, 0x00, 0x00, 0x84, 0x03, 0x00, 0x00 };
+
+
+ /**
+ Conditional jump instruction that determines if 16/9 scaling for game is enforced or not, overwrite with non conditional JMP so widescreen won't get clinched
+ 000000014012967A | 74 47 | je sekiro.1401296C3 | conditional jump
+ 000000014012967C | 47:8B94C7 1C020000 | mov r10d,dword ptr ds:[r15+r8*8+21C] | start of long resolution scaling calculation method within jump
+ */
+ internal const string PATTERN_RESOLUTION_SCALING_FIX = "47 47 8B 94 C7 1C 02 00 00"; // 47 47 8B 94 C7 1C 02 00 00
+ internal const string PATTERN_RESOLUTION_SCALING_FIX_MASK = "xxxxxxxxx";
+ internal const int PATTERN_RESOLUTION_SCALING_FIX_OFFSET = -1;
+ internal static byte[] PATCH_RESOLUTION_SCALING_FIX_ENABLE = new byte[1] { 0xEB }; // JMP
+ internal static byte[] PATCH_RESOLUTION_SCALING_FIX_DISABLE = new byte[1] { 0x74 }; // JE
+
+
+ /**
+ Reference pointer pFovTableEntry to FOV entry in game FOV table that gets used in FOV calculations. Overwrite pFovTableEntry address to use a higher or lower fFOV from table
+ 0000000140739548 | F3:0F1008 | movss xmm1,dword ptr ds:[rax] |
+ 000000014073954C | F3:0F590D 0CE79B02 | mulss xmm1,dword ptr ds:[1430F7C60] | pFovTableEntry->fFov
+ */
+ // credits to 'jackfuste' for original offset
+ internal const string PATTERN_FOVSETTING = "F3 0F 10 08 F3 0F 59 0D 00 00 9B 02"; // F3 0F 10 08 F3 0F 59 0D ?? ?? 9B 02
+ internal const string PATTERN_FOVSETTING_MASK = "xxxxxxxx??xx";
+ internal const int PATTERN_FOVSETTING_OFFSET = 8;
+ /**
+
+ */
+ internal static Dictionary PATCH_FOVSETTING_MATRIX = new Dictionary
+ {
+ { new byte[2] {0x00, 0xE7}, "- 50%" },
+ { new byte[2] {0x04, 0xE7}, "- 10%" },
+ { new byte[2] {0x10, 0xE7}, "+ 15%" },
+ { new byte[2] {0x14, 0xE7}, "+ 40%" },
+ { new byte[2] {0x18, 0xE7}, "+ 75%" },
+ { new byte[2] {0x1C, 0xE7}, "+ 90%" }
+ };
+ internal static byte[] PATCH_FOVSETTING_DISABLE = new byte[2] { 0x0C, 0xE7 }; // + 0%
+
+
+ /**
+ Reference pointer pTimeRelated to pTimescaleManager pointer, offset in struct to fTimescale which acts as a global speed scale for almost all ingame calculations
+ 0000000141149E87 | 48:8B05 3A24B402 | mov rax,qword ptr ds:[143C8C2C8] | pTimeRelated->[pTimescaleManager+0x360]->fTimescale
+ 0000000141149E8E | F3:0F1088 60030000 | movss xmm1,dword ptr ds:[rax+360] | offset from TimescaleManager->fTimescale
+ 0000000141149E96 | F3:0F5988 68020000 | mulss xmm1,dword ptr ds:[rax+268] |
+ */
+ // credits to 'Zullie the Witch' for original offset
+ internal const string PATTERN_TIMESCALE = "48 8B 05 00 00 00 00 F3 0F 10 88 00 00 00 00 F3 0F"; // 48 8B 05 ?? ?? ?? ?? F3 0F 10 88 ?? ?? ?? ?? F3 0F
+ internal const string PATTERN_TIMESCALE_MASK = "xxx????xxxx????xx";
+ internal const int PATTERN_TIMESCALE_INSTRUCTION_LENGTH = 7;
+ internal const int PATTERN_TIMESCALE_POINTER_OFFSET_OFFSET = 11;
+
+ /**
+ Reference pointer pPlayerStructRelated1 to 4 more pointers up to player data class, offset in struct to fTimescalePlayer which acts as a speed scale for the player character
+ 00000001406BF1D7 | 48:8B1D 128C4A03 | mov rbx,qword ptr ds:[143B67DF0] | pPlayerStructRelated1->[pPlayerStructRelated2+0x88]->[pPlayerStructRelated3+0x1FF8]->[pPlayerStructRelated4+0x28]->[pPlayerStructRelated5+0xD00]->fTimescalePlayer
+ 00000001406BF1DE | 48:85DB | test rbx,rbx |
+ 00000001406BF1E1 | 74 3C | je sekiro.1406BF21F |
+ 00000001406BF1E3 | 8B17 | mov edx,dword ptr ds:[rdi] |
+ */
+ // credits to 'Zullie the Witch' for original offset
+ internal const string PATTERN_TIMESCALE_PLAYER = "48 8B 1D 00 00 00 00 48 85 DB 74 3C 8B 17"; // 48 8B 1D ?? ?? ?? ?? 48 85 DB 74 3C 8B 17
+ internal const string PATTERN_TIMESCALE_PLAYER_MASK = "xxx????xxxxxxx";
+ internal const int PATTERN_TIMESCALE_PLAYER_INSTRUCTION_LENGTH = 7;
+ internal const int PATTERN_TIMESCALE_POINTER2_OFFSET = 0x88;
+ internal const int PATTERN_TIMESCALE_POINTER3_OFFSET = 0x1FF8;
+ internal const int PATTERN_TIMESCALE_POINTER4_OFFSET = 0x28;
+ internal const int PATTERN_TIMESCALE_POINTER5_OFFSET = 0xD00;
+ }
+}
diff --git a/SekiroFpsUnlockAndMore/MainWindow.xaml b/SekiroFpsUnlockAndMore/MainWindow.xaml
index fa0ab7b..6b6ed6a 100644
--- a/SekiroFpsUnlockAndMore/MainWindow.xaml
+++ b/SekiroFpsUnlockAndMore/MainWindow.xaml
@@ -5,25 +5,64 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SekiroFpsUnlockAndMore"
mc:Ignorable="d"
- Title="Sekiro FPS Unlocker and more v1.0.2" Height="Auto" SizeToContent="WidthAndHeight" Width="Auto" ResizeMode="CanMinimize" Loaded="Window_Loaded" Closing="Window_Closing">
+ Title="Sekiro FPS Unlocker and more v1.1.0" Width="Auto" Height="Auto" SizeToContent="WidthAndHeight" ResizeMode="CanMinimize" Loaded="Window_Loaded" Closing="Window_Closing">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This patcher does not modify game files, you have to start it every time.
If your monitor is 60 Hz you will have to use
fullscreen mode and force disable VSYNC
with Nvidia Control panel or AMD Radeon Settings to get frame rate unlock working.
@@ -31,18 +70,16 @@
If your monitor is >60 Hz you will have to use
borderless window mode or force the game to run on highest available refresh rate
using Nvidia Control panel or AMD Radeon Settings.
- Borderless window mode requires "Window mode" in game settings first.
- Custom resolution adds 21/9 support and overwrites default 1920x1080 resolution, hud will be limited to 16/9 though.
+ Borderless window mode requires "Windowed" in game settings first.
+ Custom resolution adds 21/9 support and overwrites a default resolution, hud will be limited to 16/9.
Borderless window mode with custom resolution requires you to patch and set resolution first, then add borderless patch afterwards.
- For FOV patch to work you need to be ingame.
- FOV patch resets after save game reload.
- See the link below for detailed information, GSYNC support and an AMD fix.
+ To enable Player Speed modification you have to be ingame first.
+ See the link below for detailed information, guides, GSYNC support and an AMD fix.
-
-
diff --git a/SekiroFpsUnlockAndMore/MainWindow.xaml.cs b/SekiroFpsUnlockAndMore/MainWindow.xaml.cs
index c819a9c..04e9a6d 100644
--- a/SekiroFpsUnlockAndMore/MainWindow.xaml.cs
+++ b/SekiroFpsUnlockAndMore/MainWindow.xaml.cs
@@ -1,10 +1,12 @@
using System;
using System.IO;
using System.Windows;
+using System.Threading;
using System.Diagnostics;
-using System.Windows.Input;
using System.Windows.Media;
+using System.Windows.Input;
using System.Windows.Interop;
+using System.Threading.Tasks;
using System.Windows.Threading;
using System.Collections.Generic;
using System.Runtime.InteropServices;
@@ -14,35 +16,21 @@ namespace SekiroFpsUnlockAndMore
{
public partial class MainWindow : Window
{
- internal const string PROCESS_NAME = "sekiro";
- internal const string PROCESS_TITLE = "Sekiro";
- internal const string PROCESS_DESCRIPTION = "Shadows Die Twice";
- internal byte[] PATCH_FRAMERATE_RUNNING_FIX_DISABLE = new byte[1] { 0x90 };
- internal byte[] PATCH_FRAMERATE_UNLIMITED = new byte[4] { 0x00, 0x00, 0x00, 0x00 };
- internal byte[] PATCH_WIDESCREEN_219_DISABLE = new byte[1] { 0x74 };
- internal byte[] PATCH_WIDESCREEN_219_ENABLE = new byte[1] { 0xEB };
- internal byte[] PATCH_FOV_DISABLE = new byte[2] { 0x0C, 0xE7};
- internal Dictionary PATCH_FOVMATRIX = new Dictionary
- {
- { new byte[2] {0x00, 0xE7}, "- 50%" },
- { new byte[2] {0x04, 0xE7}, "- 10%" },
- { new byte[2] {0x10, 0xE7}, "+ 15%" },
- { new byte[2] {0x14, 0xE7}, "+ 40%" },
- { new byte[2] {0x18, 0xE7}, "+ 75%" },
- { new byte[2] {0x1C, 0xE7}, "+ 90%" },
- };
-
- internal Process _game;
+ internal Process _gameProc;
internal IntPtr _gameHwnd = IntPtr.Zero;
- internal IntPtr _gameProc = IntPtr.Zero;
- internal static IntPtr _gameProcStatic;
+ internal IntPtr _gameAccessHwnd = IntPtr.Zero;
+ internal static IntPtr _gameAccessHwndStatic;
internal long _offset_framelock = 0x0;
- internal long _offset_framelock_running_fix = 0x0;
+ internal long _offset_framelock_speed_fix = 0x0;
internal long _offset_resolution = 0x0;
internal long _offset_resolution_default = 0x0;
- internal long _offset_widescreen_219 = 0x0;
+ internal long _offset_resolution_scaling_fix = 0x0;
internal long _offset_fovsetting = 0x0;
+ internal long _offset_timescale = 0x0;
+ internal long _offset_timescale_player = 0x0;
+ internal bool _use_resolution_720 = false;
+ internal SettingsService _settingsService;
internal readonly DispatcherTimer _dispatcherTimerCheck = new DispatcherTimer();
internal bool _running = false;
internal string _logPath;
@@ -59,19 +47,32 @@ namespace SekiroFpsUnlockAndMore
///
private void Window_Loaded(object sender, RoutedEventArgs e)
{
- _logPath = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location) + @"\SekiroFpsUnlockAndMore.log";
+ var mutex = new Mutex(true, "sekiroFpsUnlockAndMore", out bool isNewInstance);
+ if (!isNewInstance)
+ {
+ MessageBox.Show("Another instance is already running!", "Sekiro FPS Unlocker and more");
+ Environment.Exit(0);
+ }
+ GC.KeepAlive(mutex);
- this.cbSelectFov.ItemsSource = PATCH_FOVMATRIX;
+ _logPath = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location) + @"\SekiroFpsUnlockAndMore.log";
+ this.cbSelectFov.ItemsSource = GameData.PATCH_FOVSETTING_MATRIX;
this.cbSelectFov.SelectedIndex = 2;
- IntPtr hwnd = new WindowInteropHelper(this).Handle;
- if (!RegisterHotKey(hwnd, 9009, MOD_CONTROL, VK_P))
+ LoadConfiguration();
+
+ IntPtr hWnd = new WindowInteropHelper(this).Handle;
+ if (!RegisterHotKey(hWnd, 9009, MOD_CONTROL, VK_P))
MessageBox.Show("Hotkey is already in use, it may not work.", "Sekiro FPS Unlocker and more");
// add a hook for WindowsMessageQueue to recognize hotkey-press
ComponentDispatcher.ThreadFilterMessage += new ThreadMessageEventHandler(ComponentDispatcherThreadFilterMessage);
- _dispatcherTimerCheck.Tick += new EventHandler(CheckGame);
+ _dispatcherTimerCheck.Tick += new EventHandler(async (object s, EventArgs a) =>
+ {
+ bool result = await CheckGame();
+ if (result) PatchGame();
+ });
_dispatcherTimerCheck.Interval = new TimeSpan(0, 0, 0, 2);
_dispatcherTimerCheck.Start();
}
@@ -81,11 +82,12 @@ namespace SekiroFpsUnlockAndMore
///
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
+ SaveConfiguration();
ComponentDispatcher.ThreadFilterMessage -= ComponentDispatcherThreadFilterMessage;
- IntPtr hwnd = new WindowInteropHelper(this).Handle;
- UnregisterHotKey(hwnd, 9009);
- if (_gameProc != IntPtr.Zero)
- CloseHandle(_gameProc);
+ IntPtr hWnd = new WindowInteropHelper(this).Handle;
+ UnregisterHotKey(hWnd, 9009);
+ if (_gameAccessHwnd != IntPtr.Zero)
+ CloseHandle(_gameAccessHwnd);
}
///
@@ -93,39 +95,80 @@ namespace SekiroFpsUnlockAndMore
///
private void ComponentDispatcherThreadFilterMessage(ref MSG msg, ref bool handled)
{
- if (!handled)
+ if (handled) return;
+ if (msg.message != WM_HOTKEY_MSG_ID) return;
+
+ if (msg.wParam.ToInt32() == 9009) // patch game
{
- if (msg.message == WM_HOTKEY_MSG_ID) // hotkeyevent
- {
- if (msg.wParam.ToInt32() == 9009) // patch game
- {
- handled = true;
- PatchGame();
- }
- }
+ handled = true;
+ PatchGame();
}
}
+ ///
+ /// Load all saved settings from previous run.
+ ///
+ private void LoadConfiguration()
+ {
+ _settingsService = new SettingsService(Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location) + @"\SekiroFpsUnlockAndMore.xml");
+ if (!_settingsService.Load()) return;
+ this.cbFramelock.IsChecked = _settingsService.settings.cbFramelock;
+ this.tbFramelock.Text = _settingsService.settings.tbFramelock.ToString();
+ this.cbAddResolution.IsChecked = _settingsService.settings.cbAddResolution;
+ this.tbWidth.Text = _settingsService.settings.tbWidth.ToString();
+ this.tbHeight.Text = _settingsService.settings.tbHeight.ToString();
+ this.cbFov.IsChecked = _settingsService.settings.cbFov;
+ this.cbSelectFov.SelectedIndex = _settingsService.settings.cbSelectFov;
+ this.cbBorderless.IsChecked = _settingsService.settings.cbBorderless;
+ this.cbBorderlessStretch.IsChecked = _settingsService.settings.cbBorderlessStretch;
+ this.exGameMods.IsExpanded = _settingsService.settings.exGameMods;
+ this.cbGameSpeed.IsChecked = _settingsService.settings.cbGameSpeed;
+ this.tbGameSpeed.Text = _settingsService.settings.tbGameSpeed.ToString();
+ this.cbPlayerSpeed.IsChecked = _settingsService.settings.cbPlayerSpeed;
+ this.tbPlayerSpeed.Text = _settingsService.settings.tbPlayerSpeed.ToString();
+ }
+
+ ///
+ /// Save all settings to configuration file.
+ ///
+ private void SaveConfiguration()
+ {
+ _settingsService.settings.cbFramelock = this.cbFramelock.IsChecked == true;
+ _settingsService.settings.tbFramelock = Convert.ToInt32(this.tbFramelock.Text);
+ _settingsService.settings.cbAddResolution = this.cbAddResolution.IsChecked == true;
+ _settingsService.settings.tbWidth = Convert.ToInt32(this.tbWidth.Text);
+ _settingsService.settings.tbHeight = Convert.ToInt32(this.tbHeight.Text);
+ _settingsService.settings.cbFov = this.cbFov.IsChecked == true;
+ _settingsService.settings.cbSelectFov = this.cbSelectFov.SelectedIndex;
+ _settingsService.settings.cbBorderless = this.cbBorderless.IsChecked == true;
+ _settingsService.settings.cbBorderlessStretch = this.cbBorderlessStretch.IsChecked == true;
+ _settingsService.settings.exGameMods = this.exGameMods.IsExpanded;
+ _settingsService.settings.cbGameSpeed = this.cbGameSpeed.IsChecked == true;
+ _settingsService.settings.tbGameSpeed = Convert.ToInt32(this.tbGameSpeed.Text);
+ _settingsService.settings.cbPlayerSpeed = this.cbPlayerSpeed.IsChecked == true;
+ _settingsService.settings.tbPlayerSpeed = Convert.ToInt32(this.tbPlayerSpeed.Text);
+ _settingsService.Save();
+ }
+
///
/// Checks if game is running and initializes further functionality.
///
- private void CheckGame(object sender, EventArgs e)
+ private Task CheckGame()
{
- Process[] procList = Process.GetProcessesByName(PROCESS_NAME);
+ Process[] procList = Process.GetProcessesByName(GameData.PROCESS_NAME);
if (procList.Length < 1)
- return;
+ return Task.FromResult(false);
if (_running || _offset_framelock != 0x0)
- return;
+ return Task.FromResult(false);
int gameIndex = -1;
for (int i = 0; i < procList.Length; i++)
{
- if (procList[i].MainWindowTitle == PROCESS_TITLE && procList[i].MainModule.FileVersionInfo.FileDescription.Contains(PROCESS_DESCRIPTION))
- {
- gameIndex = i;
- break;
- }
+ if (procList[i].MainWindowTitle != GameData.PROCESS_TITLE || !procList[i].MainModule.FileVersionInfo.FileDescription.Contains(GameData.PROCESS_DESCRIPTION))
+ continue;
+ gameIndex = i;
+ break;
}
if (gameIndex < 0)
{
@@ -137,192 +180,276 @@ namespace SekiroFpsUnlockAndMore
LogToFile(string.Format("\tDescription #{0}: {1} | {2} | {3}", j, procList[j].MainWindowTitle, procList[j].MainModule.FileVersionInfo.CompanyName, procList[j].MainModule.FileVersionInfo.FileDescription));
LogToFile(string.Format("\tData #{0}: {1} | {2} | {3} | {4} | {5}", j, procList[j].MainModule.FileVersionInfo.FileVersion, procList[j].MainModule.ModuleMemorySize, procList[j].StartTime, procList[j].Responding, procList[j].HasExited));
}
- return;
+ Task.FromResult(false);
}
- _game = procList[gameIndex];
+ _gameProc = procList[gameIndex];
_gameHwnd = procList[gameIndex].MainWindowHandle;
- _gameProc = OpenProcess(PROCESS_ALL_ACCESS, false, (uint)procList[gameIndex].Id);
- _gameProcStatic = _gameProc;
- if (_gameHwnd == IntPtr.Zero || _gameProc == IntPtr.Zero || procList[gameIndex].MainModule.BaseAddress == IntPtr.Zero)
+ _gameAccessHwnd = OpenProcess(PROCESS_ALL_ACCESS, false, (uint)procList[gameIndex].Id);
+ _gameAccessHwndStatic = _gameAccessHwnd;
+ if (_gameHwnd == IntPtr.Zero || _gameAccessHwnd == IntPtr.Zero || _gameProc.MainModule.BaseAddress == IntPtr.Zero)
{
LogToFile("no access to game...");
- LogToFile("Hwnd: " + _gameHwnd.ToString("X"));
- LogToFile("Proc: " + _gameProc.ToString("X"));
- LogToFile("Base: " + procList[gameIndex].MainModule.BaseAddress.ToString("X"));
+ LogToFile("hWnd: " + _gameHwnd.ToString("X"));
+ LogToFile("Access hWnd: " + _gameAccessHwnd.ToString("X"));
+ LogToFile("BaseAddress: " + procList[gameIndex].MainModule.BaseAddress.ToString("X"));
if (!_retryAccess)
{
UpdateStatus("no access to game...", Brushes.Red);
_dispatcherTimerCheck.Stop();
- return;
+ Task.FromResult(false);
}
_gameHwnd = IntPtr.Zero;
- if (_gameProc != IntPtr.Zero)
+ if (_gameAccessHwnd != IntPtr.Zero)
{
- CloseHandle(_gameProc);
- _gameProc = IntPtr.Zero;
- _gameProcStatic = IntPtr.Zero;
+ CloseHandle(_gameAccessHwnd);
+ _gameAccessHwnd = IntPtr.Zero;
+ _gameAccessHwndStatic = IntPtr.Zero;
}
LogToFile("retrying...");
_retryAccess = false;
- return;
+ Task.FromResult(false);
}
//string gameFileVersion = FileVersionInfo.GetVersionInfo(procList[0].MainModule.FileName).FileVersion;
+ PatternScan patternScan = new PatternScan(_gameAccessHwnd, _gameProc.MainModule);
- _offset_framelock = PatternScan.FindPattern(_gameProc, procList[gameIndex].MainModule, Offsets.PATTERN_FRAMELOCK, Offsets.PATTERN_FRAMELOCK_MASK, ' ');
+ _offset_framelock = patternScan.FindPatternInternal(GameData.PATTERN_FRAMELOCK, GameData.PATTERN_FRAMELOCK_MASK, ' ') + GameData.PATTERN_FRAMELOCK_OFFSET;
Debug.WriteLine("1. Framelock found at: 0x" + _offset_framelock.ToString("X"));
- if (!IsValid(_offset_framelock))
+ if (!IsValidAddress(_offset_framelock))
{
- _offset_framelock = PatternScan.FindPattern(_gameProc, procList[gameIndex].MainModule, Offsets.PATTERN_FRAMELOCK_FUZZY, Offsets.PATTERN_FRAMELOCK_FUZZY_MASK, ' ') + Offsets.PATTERN_FRAMELOCK_FUZZY_OFFSET;
+ _offset_framelock = patternScan.FindPatternInternal(GameData.PATTERN_FRAMELOCK_FUZZY, GameData.PATTERN_FRAMELOCK_FUZZY_MASK, ' ') + GameData.PATTERN_FRAMELOCK_FUZZY_OFFSET;
Debug.WriteLine("2. Framelock found at: 0x" + _offset_framelock.ToString("X"));
}
- if (!IsValid(_offset_framelock))
+ if (!IsValidAddress(_offset_framelock))
{
UpdateStatus("framelock not found...", Brushes.Red);
LogToFile("framelock not found...");
- this.cbUnlockFps.IsEnabled = false;
- this.cbUnlockFps.IsChecked = false;
- }
- _offset_framelock_running_fix = PatternScan.FindPattern(_gameProc, procList[gameIndex].MainModule, Offsets.PATTERN_FRAMELOCK_RUNNING_FIX, Offsets.PATTERN_FRAMELOCK_RUNNING_FIX_MASK, ' ') + Offsets.PATTERN_FRAMELOCK_RUNNING_FIX_OFFSET;
- Debug.WriteLine("Running fix found at: 0x" + _offset_framelock_running_fix.ToString("X"));
- if (!IsValid(_offset_framelock_running_fix))
- {
- UpdateStatus("running fix not found...", Brushes.Red);
- LogToFile("running fix not found...");
- this.cbAddWidescreen.IsEnabled = false;
- this.cbAddWidescreen.IsChecked = false;
+ _offset_framelock = 0x0;
+ this.cbFramelock.IsEnabled = false;
}
- _offset_resolution_default = PatternScan.FindPattern(_gameProc, procList[gameIndex].MainModule, Offsets.PATTERN_RESOLUTION_DEFAULT, Offsets.PATTERN_RESOLUTION_DEFAULT_MASK, ' ');
+ _offset_framelock_speed_fix = patternScan.FindPatternInternal(GameData.PATTERN_FRAMELOCK_SPEED_FIX, GameData.PATTERN_FRAMELOCK_SPEED_FIX_MASK, ' ') + GameData.PATTERN_FRAMELOCK_SPEED_FIX_OFFSET;
+ Debug.WriteLine("Speed fix found at: 0x" + _offset_framelock_speed_fix.ToString("X"));
+ if (!IsValidAddress(_offset_framelock_speed_fix))
+ {
+ UpdateStatus("speed fix not found...", Brushes.Red);
+ LogToFile("speed fix not found...");
+ _offset_framelock_speed_fix = 0x0;
+ this.cbFramelock.IsEnabled = false;
+ }
+
+ bool enableResolutionPatch = true;
+ if ((int) SystemParameters.PrimaryScreenWidth < 1281) _use_resolution_720 = true;
+ _offset_resolution_default = patternScan.FindPatternInternal(!_use_resolution_720 ? GameData.PATTERN_RESOLUTION_DEFAULT : GameData.PATTERN_RESOLUTION_DEFAULT_720, GameData.PATTERN_RESOLUTION_DEFAULT_MASK, ' ');
Debug.WriteLine("Default resolution found at: 0x" + _offset_resolution_default.ToString("X"));
- if (!IsValid(_offset_resolution_default))
+ if (!IsValidAddress(_offset_resolution_default))
{
UpdateStatus("default resolution not found...", Brushes.Red);
LogToFile("default resolution not found...");
- this.cbAddWidescreen.IsEnabled = false;
- this.cbAddWidescreen.IsChecked = false;
+ enableResolutionPatch = false;
+ _offset_resolution_default = 0x0;
}
- _offset_widescreen_219 = PatternScan.FindPattern(_gameProc, procList[gameIndex].MainModule, Offsets.PATTERN_WIDESCREEN_219, Offsets.PATTERN_WIDESCREEN_219_MASK, ' ');
- Debug.WriteLine("Widescreen 21/9 found at: 0x" + _offset_widescreen_219.ToString("X"));
- if (!IsValid(_offset_widescreen_219))
+ _offset_resolution_scaling_fix = patternScan.FindPatternInternal(GameData.PATTERN_RESOLUTION_SCALING_FIX, GameData.PATTERN_RESOLUTION_SCALING_FIX_MASK, ' ') + GameData.PATTERN_RESOLUTION_SCALING_FIX_OFFSET;
+ Debug.WriteLine("Scaling fix found at: 0x" + _offset_resolution_scaling_fix.ToString("X"));
+ if (!IsValidAddress(_offset_resolution_scaling_fix))
{
- UpdateStatus("widescreen 21/9 not found...", Brushes.Red);
- LogToFile("Widescreen 21/9 not found...");
- this.cbAddWidescreen.IsEnabled = false;
- this.cbAddWidescreen.IsChecked = false;
+ UpdateStatus("scaling fix not found...", Brushes.Red);
+ LogToFile("scaling fix not found...");
+ enableResolutionPatch = false;
+ _offset_resolution_scaling_fix = 0x0;
}
-
- long offset_resolution_pointer = PatternScan.FindPattern(_gameProc, procList[gameIndex].MainModule, Offsets.PATTERN_RESOLUTION_POINTER, Offsets.PATTERN_RESOLUTION_POINTER_MASK, ' ') + Offsets.PATTERN_RESOLUTION_POINTER_OFFSET;
+ long offset_resolution_pointer = patternScan.FindPatternInternal(GameData.PATTERN_RESOLUTION_POINTER, GameData.PATTERN_RESOLUTION_POINTER_MASK, ' ') + GameData.PATTERN_RESOLUTION_POINTER_OFFSET;
Debug.WriteLine("Resolution pointer found at: 0x" + offset_resolution_pointer.ToString("X"));
- if (!IsValid(offset_resolution_pointer))
+ if (!IsValidAddress(offset_resolution_pointer))
{
- UpdateStatus("Resolution pointer not found...", Brushes.Red);
- LogToFile("Resolution pointer not found...");
- this.cbBorderless.IsEnabled = false;
- this.cbBorderless.IsChecked = false;
+ enableResolutionPatch = false;
}
else
{
- _offset_resolution = FindOffsetToStaticPointer(_gameProc, offset_resolution_pointer, Offsets.PATTERN_RESOLUTION_POINTER_INSTRUCTION_LENGTH);
+ _offset_resolution = DereferenceStaticX64Pointer(_gameAccessHwnd, offset_resolution_pointer, GameData.PATTERN_RESOLUTION_POINTER_INSTRUCTION_LENGTH);
Debug.WriteLine("Resolution found at: 0x" + _offset_resolution.ToString("X"));
- if (!IsValid(_offset_resolution))
+ if (!IsValidAddress(_offset_resolution))
{
- UpdateStatus("Resolution not valid...", Brushes.Red);
- LogToFile("Resolution not valid...");
- this.cbBorderless.IsEnabled = false;
- this.cbBorderless.IsChecked = false;
+ UpdateStatus("resolution not valid...", Brushes.Red);
+ LogToFile("resolution not valid...");
+ _offset_resolution = 0x0;
}
}
+ this.cbAddResolution.IsEnabled = enableResolutionPatch;
- _offset_fovsetting = PatternScan.FindPattern(_gameProc, procList[gameIndex].MainModule, Offsets.PATTERN_FOVSETTING, Offsets.PATTERN_FOVSETTING_MASK, ' ') + Offsets.PATTERN_FOVSETTING_OFFSET;
+ _offset_fovsetting = patternScan.FindPatternInternal(GameData.PATTERN_FOVSETTING, GameData.PATTERN_FOVSETTING_MASK, ' ') + GameData.PATTERN_FOVSETTING_OFFSET;
Debug.WriteLine("FOV found at: 0x" + _offset_fovsetting.ToString("X"));
- if (!IsValid(_offset_fovsetting))
+ if (!IsValidAddress(_offset_fovsetting))
{
UpdateStatus("FOV not found...", Brushes.Red);
LogToFile("FOV not found...");
+ _offset_fovsetting = 0x0;
this.cbFov.IsEnabled = false;
- this.cbFov.IsChecked = false;
}
+ this.cbBorderless.IsEnabled = true;
+
+ long offset_pTimeRelated = patternScan.FindPatternInternal(GameData.PATTERN_TIMESCALE, GameData.PATTERN_TIMESCALE_MASK, ' ');
+ Debug.WriteLine("pTimeRelated found at: 0x" + offset_pTimeRelated.ToString("X"));
+ if (IsValidAddress(offset_pTimeRelated))
+ {
+ long pTimescaleManager = DereferenceStaticX64Pointer(_gameAccessHwndStatic, offset_pTimeRelated, GameData.PATTERN_TIMESCALE_INSTRUCTION_LENGTH);
+ Debug.WriteLine("pTimescaleManager found at: 0x" + pTimescaleManager.ToString("X"));
+ if (IsValidAddress(pTimescaleManager))
+ {
+ _offset_timescale = Read(_gameAccessHwndStatic, pTimescaleManager) + Read(_gameAccessHwndStatic, offset_pTimeRelated + GameData.PATTERN_TIMESCALE_POINTER_OFFSET_OFFSET);
+ Debug.WriteLine("timescale found at: 0x" + _offset_timescale.ToString("X"));
+ if (!IsValidAddress(_offset_timescale))
+ {
+ _offset_timescale = 0x0;
+ }
+ }
+ }
+ if (_offset_timescale == 0x0)
+ {
+ UpdateStatus("Timescale not found...", Brushes.Red);
+ LogToFile("Timescale not found...");
+ this.cbGameSpeed.IsEnabled = false;
+ }
+
+ long pPlayerStructRelated1 = patternScan.FindPatternInternal(GameData.PATTERN_TIMESCALE_PLAYER, GameData.PATTERN_TIMESCALE_PLAYER_MASK, ' ');
+ Debug.WriteLine("pPlayerStructRelated1 found at: 0x" + pPlayerStructRelated1.ToString("X"));
+ if (IsValidAddress(pPlayerStructRelated1))
+ {
+ long pPlayerStructRelated2 = DereferenceStaticX64Pointer(_gameAccessHwndStatic, pPlayerStructRelated1, GameData.PATTERN_TIMESCALE_PLAYER_INSTRUCTION_LENGTH);
+ Debug.WriteLine("pPlayerStructRelated2 found at: 0x" + pPlayerStructRelated2.ToString("X"));
+ if (IsValidAddress(pPlayerStructRelated2))
+ {
+ long pPlayerStructRelated3 = Read(_gameAccessHwndStatic, pPlayerStructRelated2) + GameData.PATTERN_TIMESCALE_POINTER2_OFFSET;
+ Debug.WriteLine("pPlayerStructRelated3 found at: 0x" + pPlayerStructRelated3.ToString("X"));
+ if (IsValidAddress(pPlayerStructRelated3))
+ {
+ long pPlayerStructRelated4 = Read(_gameAccessHwndStatic, pPlayerStructRelated3) + GameData.PATTERN_TIMESCALE_POINTER3_OFFSET;
+ Debug.WriteLine("pPlayerStructRelated4 found at: 0x" + pPlayerStructRelated4.ToString("X"));
+ if (IsValidAddress(pPlayerStructRelated4))
+ {
+ long pPlayerStructRelated5 = Read(_gameAccessHwndStatic, pPlayerStructRelated4) + GameData.PATTERN_TIMESCALE_POINTER4_OFFSET;
+ Debug.WriteLine("pPlayerStructRelated5 found at: 0x" + pPlayerStructRelated5.ToString("X"));
+ if (IsValidAddress(pPlayerStructRelated5))
+ {
+ _offset_timescale_player = Read(_gameAccessHwndStatic, pPlayerStructRelated5) + GameData.PATTERN_TIMESCALE_POINTER5_OFFSET;
+ Debug.WriteLine("timescale found at: 0x" + _offset_timescale_player.ToString("X"));
+ if (!IsValidAddress(_offset_timescale_player))
+ {
+ _offset_timescale_player = 0x0;
+ }
+ }
+ }
+ }
+ }
+ }
+ if (_offset_timescale_player == 0x0)
+ {
+ UpdateStatus("Playerscale not found...", Brushes.Red);
+ LogToFile("Playerscale not found...");
+ this.cbPlayerSpeed.IsEnabled = false;
+ }
+
+ this.bPatch.IsEnabled = true;
+
_running = true;
_dispatcherTimerCheck.Stop();
- PatchGame();
+ return Task.FromResult(true);
}
///
- /// Patch up this broken port
+ /// Determines whether everything is ready for patching.
///
- private void PatchGame()
+ /// True if we can patch game, false otherwise.
+ private bool CanPatchGame()
{
- if (!_running)
- return;
+ if (!_running) return false;
+ if (!_gameProc.HasExited) return true;
- if (_game.HasExited)
- {
- _running = false;
- if (_gameProc != IntPtr.Zero)
- CloseHandle(_gameProc);
- _game = null;
- _gameHwnd = IntPtr.Zero;
- _gameProc = IntPtr.Zero;
- _gameProcStatic = IntPtr.Zero;
- _offset_framelock = 0x0;
- _offset_framelock_running_fix = 0x0;
- _offset_resolution = 0x0;
- _offset_resolution_default = 0x0;
- _offset_widescreen_219 = 0x0;
- _offset_fovsetting = 0x0;
- UpdateStatus("waiting for game...", Brushes.White);
- _dispatcherTimerCheck.Start();
- return;
- }
+ _running = false;
+ if (_gameAccessHwnd != IntPtr.Zero)
+ CloseHandle(_gameAccessHwnd);
+ _gameProc = null;
+ _gameHwnd = IntPtr.Zero;
+ _gameAccessHwnd = IntPtr.Zero;
+ _gameAccessHwndStatic = IntPtr.Zero;
+ _offset_framelock = 0x0;
+ _offset_framelock_speed_fix = 0x0;
+ _offset_resolution = 0x0;
+ _offset_resolution_default = 0x0;
+ _offset_resolution_scaling_fix = 0x0;
+ _offset_fovsetting = 0x0;
+ _offset_timescale = 0x0;
+ _offset_timescale_player = 0x0;
+ this.cbFramelock.IsEnabled = true;
+ this.cbAddResolution.IsEnabled = true;
+ this.cbFov.IsEnabled = true;
+ this.cbBorderless.IsEnabled = false;
+ this.bPatch.IsEnabled = false;
+ this.cbGameSpeed.IsEnabled = true;
+ this.cbPlayerSpeed.IsEnabled = true;
+ UpdateStatus("waiting for game...", Brushes.White);
+ _dispatcherTimerCheck.Start();
- if (this.cbUnlockFps.IsChecked == true)
+ return false;
+ }
+
+ ///
+ /// Patch the game's frame rate lock.
+ ///
+ /// Determines if status should be updated from within method, default is true.
+ private bool PatchFramelock(bool showStatus = true)
+ {
+ if (!this.cbFramelock.IsEnabled || _offset_framelock == 0x0 || !CanPatchGame()) return false;
+ if (this.cbFramelock.IsChecked == true)
{
int fps = -1;
- bool isNumber = Int32.TryParse(this.tbFps.Text, out fps);
- if (fps < 0 || !isNumber)
+ bool isNumber = Int32.TryParse(this.tbFramelock.Text, out fps);
+ if (fps < 1 || !isNumber)
{
- this.tbFps.Text = "60";
+ this.tbFramelock.Text = "60";
fps = 60;
}
- else if (fps > 0 && fps < 30)
+ else if (fps > 1 && fps < 30)
{
- this.tbFps.Text = "30";
+ this.tbFramelock.Text = "30";
fps = 30;
}
else if (fps > 300)
{
- this.tbFps.Text = "300";
+ this.tbFramelock.Text = "300";
fps = 300;
}
- if (fps == 0)
- {
- WriteBytes(_gameProcStatic, _offset_framelock, PATCH_FRAMERATE_UNLIMITED);
- WriteBytes(_gameProcStatic, _offset_framelock_running_fix, new byte[1] { 0xF8 }); // F8 is maximum
- }
- else
- {
- int speed = 144 + (int)Math.Ceiling((fps - 60) / 16f) * 8; // calculation from game functions
- if (speed > 248)
- speed = 248;
- float deltaTime = (1000f / fps) / 1000f;
- Debug.WriteLine("Deltatime hex: 0x" + getHexRepresentationFromFloat(deltaTime));
- Debug.WriteLine("Speed hex: 0x" + speed.ToString("X"));
- WriteBytes(_gameProcStatic, _offset_framelock, BitConverter.GetBytes(deltaTime));
- WriteBytes(_gameProcStatic, _offset_framelock_running_fix, new byte[] { (byte)Convert.ToInt16(speed) });
- }
+ byte[] speedFix = GameData.FindSpeedFixForRefreshRate(fps);
+ float deltaTime = (1000f / fps) / 1000f;
+ Debug.WriteLine("Deltatime hex: 0x" + GetHexRepresentationFromFloat(deltaTime));
+ Debug.WriteLine("Speed hex: 0x" + speedFix[0].ToString("X"));
+ WriteBytes(_gameAccessHwndStatic, _offset_framelock, BitConverter.GetBytes(deltaTime));
+ WriteBytes(_gameAccessHwndStatic, _offset_framelock_speed_fix, speedFix);
}
- else if (this.cbUnlockFps.IsChecked == false)
+ else if (this.cbFramelock.IsChecked == false)
{
float deltaTime = (1000f / 60) / 1000f;
- WriteBytes(_gameProcStatic, _offset_framelock, BitConverter.GetBytes(deltaTime));
- WriteBytes(_gameProcStatic, _offset_framelock_running_fix, PATCH_FRAMERATE_RUNNING_FIX_DISABLE);
+ WriteBytes(_gameAccessHwndStatic, _offset_framelock, BitConverter.GetBytes(deltaTime));
+ WriteBytes(_gameAccessHwndStatic, _offset_framelock_speed_fix, GameData.PATCH_FRAMELOCK_SPEED_FIX_DISABLE);
+ if (showStatus) UpdateStatus(DateTime.Now.ToString("HH:mm:ss") + " Game unpatched!", Brushes.White);
+ return false;
}
- if (this.cbAddWidescreen.IsChecked == true)
+ if (showStatus) UpdateStatus(DateTime.Now.ToString("HH:mm:ss") + " Game patched!", Brushes.Green);
+ return true;
+ }
+
+ ///
+ /// Patches the game's default resolution.
+ ///
+ /// Determines if status should be updated from within method, default is true.
+ private bool PatchResolution(bool showStatus = true)
+ {
+ if (!this.cbAddResolution.IsEnabled || _offset_resolution == 0x0 || _offset_resolution_default == 0x0 || _offset_resolution_scaling_fix == 0x0 || !CanPatchGame()) return false;
+ if (this.cbAddResolution.IsChecked == true)
{
bool isNumber = Int32.TryParse(this.tbWidth.Text, out int width);
if (width < 800 || !isNumber)
@@ -346,40 +473,66 @@ namespace SekiroFpsUnlockAndMore
this.tbHeight.Text = "2160";
height = 2160;
}
- WriteBytes(_gameProcStatic, _offset_resolution_default, BitConverter.GetBytes(width));
- WriteBytes(_gameProcStatic, _offset_resolution_default + 4, BitConverter.GetBytes(height));
- WriteBytes(_gameProcStatic, _offset_widescreen_219, PATCH_WIDESCREEN_219_ENABLE);
+ WriteBytes(_gameAccessHwndStatic, _offset_resolution_default, BitConverter.GetBytes(width));
+ WriteBytes(_gameAccessHwndStatic, _offset_resolution_default + 4, BitConverter.GetBytes(height));
+ WriteBytes(_gameAccessHwndStatic, _offset_resolution_scaling_fix, GameData.PATCH_RESOLUTION_SCALING_FIX_ENABLE);
}
- else if (this.cbAddWidescreen.IsChecked == false)
+ else if (this.cbAddResolution.IsChecked == false)
{
- WriteBytes(_gameProcStatic, _offset_resolution_default, BitConverter.GetBytes(1920));
- WriteBytes(_gameProcStatic, _offset_resolution_default + 4, BitConverter.GetBytes(1080));
- WriteBytes(_gameProcStatic, _offset_widescreen_219, PATCH_WIDESCREEN_219_DISABLE);
+ WriteBytes(_gameAccessHwndStatic, _offset_resolution_default, !_use_resolution_720 ? GameData.PATCH_RESOLUTION_DEFAULT_DISABLE : GameData.PATCH_RESOLUTION_DEFAULT_DISABLE_720);
+ WriteBytes(_gameAccessHwndStatic, _offset_resolution_scaling_fix, GameData.PATCH_RESOLUTION_SCALING_FIX_DISABLE);
+ if (showStatus) UpdateStatus(DateTime.Now.ToString("HH:mm:ss") + " Game unpatched!", Brushes.White);
+ return false;
}
+ if (showStatus) UpdateStatus(DateTime.Now.ToString("HH:mm:ss") + " Game patched!", Brushes.Green);
+ return true;
+ }
+
+ ///
+ /// Patches the game's field of view.
+ ///
+ /// Determines if status should be updated from within method, default is true.
+ private bool PatchFov(bool showStatus = true)
+ {
+ if (!this.cbFov.IsEnabled || _offset_fovsetting == 0x0 || !CanPatchGame()) return false;
if (this.cbFov.IsChecked == true)
{
- byte[] fovByte = ((KeyValuePair) this.cbSelectFov.SelectedItem).Key;
- WriteBytes(_gameProcStatic, _offset_fovsetting, fovByte);
+ byte[] fovByte = ((KeyValuePair)this.cbSelectFov.SelectedItem).Key;
+ WriteBytes(_gameAccessHwndStatic, _offset_fovsetting, fovByte);
}
else if (this.cbFov.IsChecked == false)
{
- WriteBytes(_gameProcStatic, _offset_fovsetting, PATCH_FOV_DISABLE);
+ WriteBytes(_gameAccessHwndStatic, _offset_fovsetting, GameData.PATCH_FOVSETTING_DISABLE);
+ if (showStatus) UpdateStatus(DateTime.Now.ToString("HH:mm:ss") + " Game unpatched!", Brushes.White);
+ return false;
}
+ if (showStatus) UpdateStatus(DateTime.Now.ToString("HH:mm:ss") + " Game patched!", Brushes.Green);
+ return true;
+ }
+
+ ///
+ /// Patches the game's window.
+ ///
+ /// Determines if status should be updated from within method, default is true.
+ private bool PatchWindow(bool showStatus = true)
+ {
+ if (!this.cbBorderless.IsEnabled || !CanPatchGame()) return false;
if (this.cbBorderless.IsChecked == true)
{
- if (IsFullscreen(_gameHwnd))
+ if (IsFullscreen(_gameHwnd) || IsMinimized(_gameHwnd))
{
- MessageBox.Show("Please exit fullscreen first before activating borderless window mode.", "Sekiro FPS Unlocker and more");
+ MessageBox.Show("Please un-minimize window and exit fullscreen first before activating borderless window mode.", "Sekiro FPS Unlocker and more");
this.cbBorderless.IsChecked = false;
+ return false;
}
else
{
if (!IsBorderless(_gameHwnd))
GetWindowRect(_gameHwnd, out _windowRect);
- int width = Read(_gameProc, _offset_resolution);
- int height = Read(_gameProc, _offset_resolution + 4);
+ int width = Read(_gameAccessHwnd, _offset_resolution);
+ int height = Read(_gameAccessHwnd, _offset_resolution + 4);
Debug.WriteLine(string.Format("Client Resolution: {0}x{1}", width, height));
if (this.cbBorderlessStretch.IsChecked == true)
SetWindowBorderless(_gameHwnd, (int)SystemParameters.PrimaryScreenWidth, (int)SystemParameters.PrimaryScreenHeight, 0, 0);
@@ -395,10 +548,108 @@ namespace SekiroFpsUnlockAndMore
int height = _windowRect.Bottom - _windowRect.Top;
Debug.WriteLine(string.Format("Window Resolution: {0}x{1}", width, height));
SetWindowWindowed(_gameHwnd, width, height, _windowRect.Left, _windowRect.Top);
+ if (showStatus) UpdateStatus(DateTime.Now.ToString("HH:mm:ss") + " Game unpatched!", Brushes.White);
}
+ return false;
+ }
+ else
+ {
+ return false;
}
- if (this.cbUnlockFps.IsChecked == true || this.cbAddWidescreen.IsChecked == true || this.cbFov.IsChecked == true)
+ if (showStatus) UpdateStatus(DateTime.Now.ToString("HH:mm:ss") + " Game patched!", Brushes.Green);
+ return true;
+ }
+
+ ///
+ /// Patches game's global speed.
+ ///
+ /// Determines if status should be updated from within method, default is true.
+ private bool PatchGameSpeed(bool showStatus = true)
+ {
+ if (!this.cbGameSpeed.IsEnabled || _offset_timescale == 0x0 || !CanPatchGame()) return false;
+ if (this.cbGameSpeed.IsChecked == true)
+ {
+ bool isNumber = Int32.TryParse(this.tbGameSpeed.Text, out int gameSpeed);
+ if (gameSpeed < 0 || !isNumber)
+ {
+ this.tbGameSpeed.Text = "100";
+ gameSpeed = 100;
+ }
+ else if (gameSpeed >= 999)
+ {
+ this.tbGameSpeed.Text = "999";
+ gameSpeed = 1000;
+ }
+ float timeScale = gameSpeed / 100f;
+ if (timeScale < 0.01f)
+ timeScale = 0.00001f;
+ WriteBytes(_gameAccessHwndStatic, _offset_timescale, BitConverter.GetBytes(timeScale));
+ }
+ else if (this.cbGameSpeed.IsChecked == false)
+ {
+ WriteBytes(_gameAccessHwndStatic, _offset_timescale, BitConverter.GetBytes(1.0f));
+ if (showStatus) UpdateStatus(DateTime.Now.ToString("HH:mm:ss") + " Game unpatched!", Brushes.White);
+ return false;
+ }
+
+ if (showStatus) UpdateStatus(DateTime.Now.ToString("HH:mm:ss") + " Game patched!", Brushes.Green);
+ return true;
+ }
+
+ ///
+ /// Patches game's player speed.
+ ///
+ /// Determines if status should be updated from within method, default is true.
+ private bool PatchPlayerSpeed(bool showStatus = true)
+ {
+ if (!this.cbPlayerSpeed.IsEnabled || _offset_timescale_player == 0x0 || !CanPatchGame()) return false;
+ if (this.cbPlayerSpeed.IsChecked == true)
+ {
+ bool isNumber = Int32.TryParse(this.tbPlayerSpeed.Text, out int playerSpeed);
+ if (playerSpeed < 0 || !isNumber)
+ {
+ this.tbPlayerSpeed.Text = "100";
+ playerSpeed = 100;
+ }
+ else if (playerSpeed >= 999)
+ {
+ this.tbPlayerSpeed.Text = "999";
+ playerSpeed = 1000;
+ }
+ float timeScalePlayer = playerSpeed / 100f;
+ if (timeScalePlayer < 0.01f)
+ timeScalePlayer = 0.00001f;
+ WriteBytes(_gameAccessHwndStatic, _offset_timescale_player, BitConverter.GetBytes(timeScalePlayer));
+ }
+ else if (this.cbPlayerSpeed.IsChecked == false)
+ {
+ WriteBytes(_gameAccessHwndStatic, _offset_timescale_player, BitConverter.GetBytes(1.0f));
+ if (showStatus) UpdateStatus(DateTime.Now.ToString("HH:mm:ss") + " Game unpatched!", Brushes.White);
+ return false;
+ }
+
+ if (showStatus) UpdateStatus(DateTime.Now.ToString("HH:mm:ss") + " Game patched!", Brushes.Green);
+ return true;
+ }
+
+ ///
+ /// Patch up this broken port of a game
+ ///
+ private void PatchGame()
+ {
+ if (!CanPatchGame()) return;
+
+ List results = new List
+ {
+ PatchFramelock(false),
+ PatchResolution(false),
+ PatchFov(false),
+ PatchWindow(false),
+ PatchGameSpeed(false),
+ PatchPlayerSpeed(false)
+ };
+ if (results.IndexOf(true) > -1)
UpdateStatus(DateTime.Now.ToString("HH:mm:ss") + " Game patched!", Brushes.Green);
else
UpdateStatus(DateTime.Now.ToString("HH:mm:ss") + " Game unpatched!", Brushes.White);
@@ -409,24 +660,41 @@ namespace SekiroFpsUnlockAndMore
///
/// The floating point number.
/// The hexadecimal representation of the input.
- private string getHexRepresentationFromFloat(float input)
+ private static string GetHexRepresentationFromFloat(float input)
{
uint f = BitConverter.ToUInt32(BitConverter.GetBytes(input), 0);
return "0x" + f.ToString("X8");
}
+ ///
+ /// Checks if window is minimized.
+ ///
+ /// The main window handle of the window.
+ ///
+ /// Even minimized fullscreen windows have WS_MINIMIZED normal borders and caption set.
+ ///
+ /// True if window is minimized.
+ private static bool IsMinimized(IntPtr hWnd)
+ {
+ long wndStyle = GetWindowLongPtr(hWnd, GWL_STYLE).ToInt64();
+ if (wndStyle == 0)
+ return false;
+
+ return (wndStyle & WS_MINIMIZE) != 0;
+ }
+
///
/// Checks if window is in fullscreen mode.
///
- /// The main window handle of the window.
+ /// The main window handle of the window.
///
/// Fullscreen windows have WS_EX_TOPMOST flag set.
///
/// True if window is run in fullscreen mode.
- private bool IsFullscreen(IntPtr hwnd)
+ private static bool IsFullscreen(IntPtr hWnd)
{
- long wndStyle = GetWindowLongPtr(hwnd, GWL_STYLE).ToInt64();
- long wndExStyle = GetWindowLongPtr(hwnd, GWL_EXSTYLE).ToInt64();
+ long wndStyle = GetWindowLongPtr(hWnd, GWL_STYLE).ToInt64();
+ long wndExStyle = GetWindowLongPtr(hWnd, GWL_EXSTYLE).ToInt64();
if (wndStyle == 0 || wndExStyle == 0)
return false;
@@ -445,14 +713,14 @@ namespace SekiroFpsUnlockAndMore
///
/// Checks if window is in borderless window mode.
///
- /// The main window handle of the window.
+ /// The main window handle of the window.
///
/// Borderless windows have WS_POPUP flag set.
///
/// True if window is run in borderless window mode.
- private bool IsBorderless(IntPtr hwnd)
+ private static bool IsBorderless(IntPtr hWnd)
{
- long wndStyle = GetWindowLongPtr(hwnd, GWL_STYLE).ToInt64();
+ long wndStyle = GetWindowLongPtr(hWnd, GWL_STYLE).ToInt64();
if (wndStyle == 0)
return false;
@@ -469,37 +737,39 @@ namespace SekiroFpsUnlockAndMore
///
/// Sets a window to ordinary windowed mode
///
- /// The handle to the window.
+ /// The handle to the window.
/// The desired window width.
/// The desired window height.
/// The desired X position of the window.
/// The desired Y position of the window.
- private void SetWindowWindowed(IntPtr hwnd, int width, int height, int posX, int posY)
+ /// Execute functionality without stealing focus, does not retain client size scaling. FOR DEMONSTRATION ONLY.
+ private static void SetWindowWindowed(IntPtr hWnd, int width, int height, int posX, int posY, bool demoMode = false)
{
- SetWindowLongPtr(hwnd, GWL_STYLE, WS_VISIBLE | WS_CAPTION | WS_BORDER | WS_CLIPSIBLINGS | WS_DLGFRAME | WS_SYSMENU | WS_GROUP | WS_MINIMIZEBOX);
- SetWindowPos(hwnd, HWND_NOTOPMOST, posX, posY, width, height, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
+ SetWindowLongPtr(hWnd, GWL_STYLE, WS_VISIBLE | WS_CAPTION | WS_BORDER | WS_CLIPSIBLINGS | WS_DLGFRAME | WS_SYSMENU | WS_GROUP | WS_MINIMIZEBOX);
+ SetWindowPos(hWnd, HWND_NOTOPMOST, posX, posY, width, height, !demoMode ? SWP_FRAMECHANGED | SWP_SHOWWINDOW : SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOACTIVATE);
}
///
/// Sets a window to borderless windowed mode and moves it to position 0x0.
///
- /// The handle to the window.
+ /// The handle to the window.
/// The desired window width.
/// The desired window height.
/// The desired X position of the window.
/// The desired Y position of the window.
- private void SetWindowBorderless(IntPtr hwnd, int width, int height, int posX, int posY)
+ /// Execute functionality without stealing focus, does not retain client size scaling. FOR DEMONSTRATION ONLY.
+ private static void SetWindowBorderless(IntPtr hWnd, int width, int height, int posX, int posY, bool demoMode = false)
{
- SetWindowLongPtr(hwnd, GWL_STYLE, WS_VISIBLE | WS_POPUP);
- SetWindowPos(hwnd, HWND_TOP, posX, posY, width, height, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
+ SetWindowLongPtr(hWnd, GWL_STYLE, WS_VISIBLE | WS_POPUP);
+ SetWindowPos(hWnd, !demoMode ? HWND_TOPMOST : HWND_NOTOPMOST, posX, posY, width, height, !demoMode ? SWP_FRAMECHANGED | SWP_SHOWWINDOW : SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOACTIVATE);
}
///
- /// Checks if a pointer is valid.
+ /// Checks if an address is valid.
///
- /// The address the pointer points to.
- /// True if pointer points to a valid address.
- private static bool IsValid(Int64 address)
+ /// The address (the pointer points to).
+ /// True if (pointer points to a) valid address.
+ private static bool IsValidAddress(Int64 address)
{
return (address >= 0x10000 && address < 0x000F000000000000);
}
@@ -535,16 +805,16 @@ namespace SekiroFpsUnlockAndMore
}
///
- /// Gets the static offset to a desired object instead of an offset to a pointer.
+ /// Gets the static offset to the referenced object instead of the offset from the instruction.
///
- /// Handle to the process in whose memory the pattern has been found.
- /// The address where the pattern has been found.
- /// The length of the instruction including the 4 bytes pointer.
- /// Static pointers in x86-64 are relative offsets from the instruction address.
- /// The static offset from the process to desired object).
- internal static Int64 FindOffsetToStaticPointer(IntPtr hProcess, Int64 lpPatternAddress, int instructionLength)
+ /// Handle to the process.
+ /// The address of the instruction.
+ /// The length of the instruction including the 4 bytes offset.
+ /// Static pointers in x86-64 are relative offsets from their instruction address.
+ /// The static offset from the process to the referenced object.
+ private static Int64 DereferenceStaticX64Pointer(IntPtr hProcess, Int64 lpInstructionAddress, int instructionLength)
{
- return lpPatternAddress + Read(hProcess, lpPatternAddress + (instructionLength -0x04)) + instructionLength;
+ return lpInstructionAddress + Read(hProcess, lpInstructionAddress + (instructionLength -0x04)) + instructionLength;
}
///
@@ -552,7 +822,7 @@ namespace SekiroFpsUnlockAndMore
///
/// The text to check.
/// True if inout is numeric only.
- private bool IsNumericInput(string text)
+ private static bool IsNumericInput(string text)
{
return Regex.IsMatch(text, "[^0-9]+");
}
@@ -599,22 +869,120 @@ namespace SekiroFpsUnlockAndMore
else e.CancelCommand();
}
- private void CheckBoxChanged_Handler(object sender, RoutedEventArgs e)
+ private void CbSelectFov_DropDownClosed(object sender, EventArgs e)
{
- PatchGame();
+ if (this.cbFov.IsChecked == true) PatchFov();
+ }
+
+ private void CbFramelock_Check_Handler(object sender, RoutedEventArgs e)
+ {
+ PatchFramelock();
+ }
+
+ private void CbAddResolution_Check_Handler(object sender, RoutedEventArgs e)
+ {
+ PatchResolution();
+ }
+
+ private void CbFov_Check_Handler(object sender, RoutedEventArgs e)
+ {
+ PatchFov();
}
private void CbBorderless_Checked(object sender, RoutedEventArgs e)
{
this.cbBorderlessStretch.IsEnabled = true;
- PatchGame();
+ PatchWindow();
}
private void CbBorderless_Unchecked(object sender, RoutedEventArgs e)
{
this.cbBorderlessStretch.IsEnabled = false;
this.cbBorderlessStretch.IsChecked = false;
- PatchGame();
+ PatchWindow();
+ }
+
+ private void CbBorderlessStretch_Check_Handler(object sender, RoutedEventArgs e)
+ {
+ PatchWindow();
+ }
+
+ private void CbGameSpeed_Check_Handler(object sender, RoutedEventArgs e)
+ {
+ PatchGameSpeed();
+ }
+
+ private void BGs0_Click(object sender, RoutedEventArgs e)
+ {
+ this.tbGameSpeed.Text = "0";
+ if (cbGameSpeed.IsChecked == true) PatchGameSpeed();
+ }
+
+ private void BGsLower_Click(object sender, RoutedEventArgs e)
+ {
+ int gameSpeed = -1;
+ Int32.TryParse(this.tbGameSpeed.Text, out gameSpeed);
+ if (gameSpeed > -1 && gameSpeed > 4)
+ {
+ this.tbGameSpeed.Text = (gameSpeed - 5).ToString();
+ if (cbGameSpeed.IsChecked == true) PatchGameSpeed();
+ }
+ }
+
+ private void BGsHigher_Click(object sender, RoutedEventArgs e)
+ {
+ int gameSpeed = -1;
+ Int32.TryParse(this.tbGameSpeed.Text, out gameSpeed);
+ if (gameSpeed > -1 && gameSpeed < 995)
+ {
+ this.tbGameSpeed.Text = (gameSpeed + 5).ToString();
+ if (cbGameSpeed.IsChecked == true) PatchGameSpeed();
+ }
+ }
+
+ private void BGs100_Click(object sender, RoutedEventArgs e)
+ {
+ this.tbGameSpeed.Text = "100";
+ if (cbGameSpeed.IsChecked == true) PatchGameSpeed();
+ }
+
+ private void CbPlayerSpeed_Check_Handler(object sender, RoutedEventArgs e)
+ {
+ PatchPlayerSpeed();
+ }
+
+ private void BPs0_Click(object sender, RoutedEventArgs e)
+ {
+ this.tbPlayerSpeed.Text = "0";
+ if (this.cbPlayerSpeed.IsChecked == true) PatchPlayerSpeed();
+ }
+
+ private void BPsLower_Click(object sender, RoutedEventArgs e)
+ {
+ int playerSpeed = -1;
+ Int32.TryParse(this.tbPlayerSpeed.Text, out playerSpeed);
+ if (playerSpeed > -1 && playerSpeed > 4)
+ {
+ this.tbPlayerSpeed.Text = (playerSpeed - 5).ToString();
+ if (this.cbPlayerSpeed.IsChecked == true) PatchPlayerSpeed();
+ }
+ }
+
+ private void BPsHigher_Click(object sender, RoutedEventArgs e)
+ {
+ int playerSpeed = -1;
+ Int32.TryParse(this.tbPlayerSpeed.Text, out playerSpeed);
+ if (playerSpeed > -1 && playerSpeed < 995)
+ {
+ this.tbPlayerSpeed.Text = (playerSpeed + 5).ToString();
+ if (this.cbPlayerSpeed.IsChecked == true) PatchPlayerSpeed();
+ }
+ }
+
+ private void BPs100_Click(object sender, RoutedEventArgs e)
+ {
+ this.tbPlayerSpeed.Text = "100";
+ if (this.cbPlayerSpeed.IsChecked == true) PatchPlayerSpeed();
}
private void BPatch_Click(object sender, RoutedEventArgs e)
@@ -629,27 +997,31 @@ namespace SekiroFpsUnlockAndMore
}
#region WINAPI
+
private const int WM_HOTKEY_MSG_ID = 0x0312;
private const int MOD_CONTROL = 0x0002;
private const uint VK_P = 0x0050;
private const uint PROCESS_ALL_ACCESS = 0x001F0FFF;
- private const int GWL_EXSTYLE = -20;
- private const int GWL_STYLE = -16;
- private const uint WS_CLIPSIBLINGS = 0x04000000;
- private const uint WS_DLGFRAME = 0x00400000;
- private const uint WS_SYSMENU = 0x00080000;
- private const uint WS_GROUP = 0x00020000;
- private const uint WS_MINIMIZEBOX = 0x00020000;
- private const uint WS_POPUP = 0x80000000;
- private const uint WS_VISIBLE = 0x10000000;
- private const uint WS_CAPTION = 0x00C00000;
- private const uint WS_BORDER = 0x00800000;
- private const uint WS_EX_TOPMOST = 0x00000008;
- private const int HWND_TOP = 0;
- private const int HWND_TOPMOST = -1;
- private const int HWND_NOTOPMOST = -2;
+ private const int GWL_STYLE = -16;
+ private const int GWL_EXSTYLE = -20;
+ private const uint WS_GROUP = 0x00020000;
+ private const uint WS_MINIMIZEBOX = 0x00020000;
+ private const uint WS_SYSMENU = 0x00080000;
+ private const uint WS_DLGFRAME = 0x00400000;
+ private const uint WS_BORDER = 0x00800000;
+ private const uint WS_CAPTION = 0x00C00000;
+ private const uint WS_CLIPSIBLINGS = 0x04000000;
+ private const uint WS_VISIBLE = 0x10000000;
+ private const uint WS_MINIMIZE = 0x20000000;
+ private const uint WS_POPUP = 0x80000000;
+ private const uint WS_EX_TOPMOST = 0x00000008;
+ private const int HWND_TOP = 0;
+ private const int HWND_TOPMOST = -1;
+ private const int HWND_NOTOPMOST = -2;
+ private const uint SWP_NOSIZE = 0x0001;
+ private const uint SWP_NOACTIVATE = 0x0010;
private const uint SWP_FRAMECHANGED = 0x0020;
- private const uint SWP_SHOWWINDOW = 0x0040;
+ private const uint SWP_SHOWWINDOW = 0x0040;
[DllImport("user32.dll")]
public static extern Boolean RegisterHotKey(IntPtr hWnd, Int32 id, UInt32 fsModifiers, UInt32 vlc);
@@ -676,7 +1048,7 @@ namespace SekiroFpsUnlockAndMore
public static extern IntPtr SetWindowPos(IntPtr hWnd, Int32 hWndInsertAfter, Int32 X, Int32 Y, Int32 cx, Int32 cy, UInt32 uFlags);
[DllImport("user32.dll", SetLastError = true)]
- public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
+ public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
@@ -703,6 +1075,7 @@ namespace SekiroFpsUnlockAndMore
UInt64 dwSize,
out IntPtr lpNumberOfBytesWritten);
+
#endregion
}
}
diff --git a/SekiroFpsUnlockAndMore/Offsets.cs b/SekiroFpsUnlockAndMore/Offsets.cs
deleted file mode 100644
index 9a41cc4..0000000
--- a/SekiroFpsUnlockAndMore/Offsets.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-
-namespace SekiroFpsUnlockAndMore
-{
- internal class Offsets
- {
- internal const string PATTERN_FRAMELOCK = "00 88 88 3C 4C 89 AB 00"; // ?? 88 88 3C 4C 89 AB ?? // pattern/signature of frame rate limiter, first byte (last in mem) can can be 88/90 instead of 89 due to precision loss on floating point numbers
- internal const string PATTERN_FRAMELOCK_MASK = "?xxxxxx?"; // mask for frame rate limiter signature scanning
- internal const string PATTERN_FRAMELOCK_LONG = "44 88 6B 00 C7 43 00 89 88 88 3C 4C 89 AB 00 00 00 00"; // 44 88 6B ?? C7 43 ?? 89 88 88 3C 4C 89 AB ?? ?? ?? ??
- internal const string PATTERN_FRAMELOCK_LONG_MASK = "xxx?xx?xxxxxxx????";
- internal const int PATTERN_FRAMELOCK_LONG_OFFSET = 7;
- internal const string PATTERN_FRAMELOCK_FUZZY = "C7 43 00 00 00 00 00 4C 89 AB 00 00 00 00"; // C7 43 ?? ?? ?? ?? ?? 4C 89 AB ?? ?? ?? ??
- internal const string PATTERN_FRAMELOCK_FUZZY_MASK = "xx?????xxx????";
- internal const int PATTERN_FRAMELOCK_FUZZY_OFFSET = 3; // offset to byte array from found position
- internal const string PATTERN_FRAMELOCK_RUNNING_FIX = "F3 0F 59 05 00 30 92 02 0F 2F F8"; // F3 0F 59 05 ?? 30 92 02 0F 2F F8 | 0F 51 C2 F3 0F 59 05 ?? ?? ?? ?? 0F 2F F8
- internal const string PATTERN_FRAMELOCK_RUNNING_FIX_MASK = "xxxx?xxxxxx";
- internal const int PATTERN_FRAMELOCK_RUNNING_FIX_OFFSET = 4;
- internal const string PATTERN_RESOLUTION_POINTER = "0F 57 D2 89 0D 00 00 00 00 0F 57 C9 89 15 00 00 00 00"; // 0F 57 D2 89 0D ?? ?? ?? ?? 0F 57 C9 89 15 ?? ?? ?? ??
- internal const string PATTERN_RESOLUTION_POINTER_MASK = "xxxxx????xxxxx????";
- internal const int PATTERN_RESOLUTION_POINTER_OFFSET = 3;
- internal const int PATTERN_RESOLUTION_POINTER_INSTRUCTION_LENGTH = 6;
- internal const string PATTERN_RESOLUTION_DEFAULT = "80 07 00 00 38 04"; // 1920x1080
- internal const string PATTERN_RESOLUTION_DEFAULT_MASK = "xxxxxx";
- internal const string PATTERN_WIDESCREEN_219 = "00 47 47 8B 94 C7 1C 02 00 00"; // ?? 47 47 8B 94 C7 1C 02 00 00
- internal const string PATTERN_WIDESCREEN_219_MASK = "?xxxxxxxxx";
-
- // credits to jackfuste for FOV findings
- internal const string PATTERN_FOVSETTING = "F3 0F 10 08 F3 0F 59 0D 00 00 9B 02"; // F3 0F 10 08 F3 0F 59 0D ?? ?? 9B 02
- internal const string PATTERN_FOVSETTING_MASK = "xxxxxxxx??xx";
- internal const int PATTERN_FOVSETTING_OFFSET = 8;
- }
-}
diff --git a/SekiroFpsUnlockAndMore/PatternScan.cs b/SekiroFpsUnlockAndMore/PatternScan.cs
index e7572ad..e48fd4c 100644
--- a/SekiroFpsUnlockAndMore/PatternScan.cs
+++ b/SekiroFpsUnlockAndMore/PatternScan.cs
@@ -6,6 +6,81 @@ namespace SekiroFpsUnlockAndMore
{
class PatternScan
{
+ private long dwStart = 0;
+ private byte[] bData;
+
+ ///
+ /// Initialize PatternScanner and read all memory from process.
+ ///
+ /// Handle to the process in whose memory pattern will be searched for.
+ /// Module which will be searched for the pattern.
+ public PatternScan(IntPtr hProcess, ProcessModule pModule)
+ {
+ if (IntPtr.Size == 4)
+ dwStart = (uint)pModule.BaseAddress;
+ else if (IntPtr.Size == 8)
+ dwStart = (long)pModule.BaseAddress;
+ int nSize = pModule.ModuleMemorySize;
+ bData = new byte[nSize];
+
+ if (!ReadProcessMemory(hProcess, dwStart, bData, nSize, out IntPtr lpNumberOfBytesRead))
+ throw new Exception("ReadProcessMemory error!");
+ if (lpNumberOfBytesRead.ToInt64() != nSize)
+ throw new Exception("ReadProcessMemory error!");
+ if (bData == null || bData.Length == 0)
+ throw new Exception("Could not read memory in FindPattern.");
+ }
+
+ ~PatternScan()
+ {
+ bData = null;
+ GC.Collect();
+ }
+
+ ///
+ /// Finds a pattern or signature inside another process's memory.
+ ///
+
+ /// A character-delimited string representing the pattern to be found.
+ /// A string of 'x' (match), '!' (not-match), or '?' (wildcard).
+ /// Determines how the string will be split. If null, defaults to ' '.
+ /// The address of the beginning of the pattern if found, 0 if not found
+ internal Int64 FindPatternInternal(string szPattern, string szMask, char cDelimiter = ' ')
+ {
+ string[] saPattern = szPattern.Split(cDelimiter);
+ byte[] bPattern = new byte[saPattern.Length];
+ for (int i = 0; i < saPattern.Length; i++)
+ bPattern[i] = Convert.ToByte(saPattern[i], 0x10);
+
+ if (bPattern == null || bPattern.Length == 0)
+ throw new ArgumentNullException("Pattern's length is zero!");
+ if (bPattern.Length != szMask.Length)
+ throw new ArgumentException("Pattern's bytes and szMask must be of the same size!");
+
+ long ix;
+ int iy;
+ bool bFound = false;
+ int patternLength = bPattern.Length;
+ int dataLength = bData.Length - patternLength;
+
+ for (ix = 0; ix < dataLength; ix++)
+ {
+ bFound = true;
+ for (iy = 0; iy < patternLength; iy++)
+ {
+ if ((szMask[iy] == 'x' && bPattern[iy] != bData[ix + iy]) ||
+ (szMask[iy] == '!' && bPattern[iy] == bData[ix + iy]))
+ {
+ bFound = false;
+ break;
+ }
+ }
+ if (bFound)
+ return Convert.ToInt64((long)dwStart + ix);
+ }
+ return 0;
+ }
+
///
/// Finds a pattern or signature inside another process's memory.
///
diff --git a/SekiroFpsUnlockAndMore/Properties/AssemblyInfo.cs b/SekiroFpsUnlockAndMore/Properties/AssemblyInfo.cs
index 01e564b..e19dbd7 100644
--- a/SekiroFpsUnlockAndMore/Properties/AssemblyInfo.cs
+++ b/SekiroFpsUnlockAndMore/Properties/AssemblyInfo.cs
@@ -21,5 +21,5 @@ using System.Runtime.InteropServices;
ResourceDictionaryLocation.SourceAssembly
)]
-[assembly: AssemblyVersion("1.0.2.0")]
-[assembly: AssemblyFileVersion("1.0.2.0")]
+[assembly: AssemblyVersion("1.1.0.0")]
+[assembly: AssemblyFileVersion("1.1.0.0")]
diff --git a/SekiroFpsUnlockAndMore/SekiroFpsUnlockAndMore.csproj b/SekiroFpsUnlockAndMore/SekiroFpsUnlockAndMore.csproj
index 8a3e127..6d54c55 100644
--- a/SekiroFpsUnlockAndMore/SekiroFpsUnlockAndMore.csproj
+++ b/SekiroFpsUnlockAndMore/SekiroFpsUnlockAndMore.csproj
@@ -8,7 +8,7 @@
WinExe
SekiroFpsUnlockAndMore
SekiroFpsUnlockAndMore
- v4.0
+ v4.5
512
{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
4
@@ -62,6 +62,7 @@
MSBuild:Compile
Designer
+
MSBuild:Compile
Designer
@@ -76,7 +77,7 @@
-
+
Code
diff --git a/SekiroFpsUnlockAndMore/SettingsService.cs b/SekiroFpsUnlockAndMore/SettingsService.cs
new file mode 100644
index 0000000..a21937c
--- /dev/null
+++ b/SekiroFpsUnlockAndMore/SettingsService.cs
@@ -0,0 +1,110 @@
+using System;
+using System.IO;
+using System.Windows;
+using System.Xml.Serialization;
+
+namespace SekiroFpsUnlockAndMore
+{
+ [XmlRoot("SekiroFpsUnlockAndMore")]
+ [Serializable]
+ public class SettingsService
+ {
+ private readonly string sConfigurationPath = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location) + @"\config.xml";
+
+ ///
+ /// Read and store settings here.
+ ///
+ public SettingsService settings;
+
+
+ /**
+ * Settings definition
+ */
+ [XmlElement]
+ public bool cbFramelock { get; set; }
+ [XmlElement]
+ public int tbFramelock { get; set; }
+ [XmlElement]
+ public bool cbAddResolution { get; set; }
+ [XmlElement]
+ public int tbWidth { get; set; }
+ [XmlElement]
+ public int tbHeight { get; set; }
+ [XmlElement]
+ public bool cbFov { get; set; }
+ [XmlElement]
+ public int cbSelectFov { get; set; }
+ [XmlElement]
+ public bool cbBorderless { get; set; }
+ [XmlElement]
+ public bool cbBorderlessStretch { get; set; }
+ [XmlElement]
+ public bool exGameMods { get; set; }
+ [XmlElement]
+ public bool cbGameSpeed { get; set; }
+ [XmlElement]
+ public int tbGameSpeed { get; set; }
+ [XmlElement]
+ public bool cbPlayerSpeed { get; set; }
+ [XmlElement]
+ public int tbPlayerSpeed { get; set; }
+
+ public SettingsService() { }
+
+ ///
+ /// Create a settings provider to load and save settings.
+ ///
+ /// The file path to the settings file.
+ public SettingsService(string settingsFilePath = null)
+ {
+ if (settingsFilePath != null)
+ {
+ sConfigurationPath = settingsFilePath;
+ settings = new SettingsService();
+ }
+ }
+
+ ///
+ /// Load settings from file into settings property.
+ ///
+ ///
+ internal bool Load()
+ {
+ if (!File.Exists(sConfigurationPath)) return false;
+
+ XmlSerializer xmlSerializer = new XmlSerializer(typeof(SettingsService));
+ using (StreamReader streamReader = new StreamReader(sConfigurationPath))
+ {
+ try
+ {
+ settings = (SettingsService)xmlSerializer.Deserialize(streamReader);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show("Error while loading configuration file:\n" + ex.Message, "Sekiro FPS Unlocker and more");
+ }
+ }
+ return false;
+ }
+
+ ///
+ /// Save settings from settings property to file.
+ ///
+ internal void Save()
+ {
+ XmlSerializer xmlSerializer = new XmlSerializer(typeof(SettingsService));
+ using (StreamWriter streamReader = new StreamWriter(sConfigurationPath))
+ {
+ try
+ {
+ xmlSerializer.Serialize(streamReader, settings);
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show("Error while writing configuration file:\n" + ex.Message, "Sekiro FPS Unlocker and more");
+ }
+ }
+ }
+ }
+}