added disable cam adjust option

added code cave generator for cam adjust
patternscan generates its own masks from pattern now
some housekeeping
updated readme
This commit is contained in:
uberhalit 2019-04-07 18:06:06 +02:00
parent af5458ada6
commit e9b04e1f32
10 changed files with 653 additions and 184 deletions

View file

@ -1,7 +1,7 @@
# Sekiro FPS Unlocker and more # Sekiro FPS Unlocker and more
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#. A small utility to remove frame rate limit, add custom resolutions with 21/9 widescreen support, change field of view (FOV), borderless window mode, display and log stats (OBS), diSable camera auto adjust on movement 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. Works 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/). 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. Also available on [Nexus Mods](https://www.nexusmods.com/sekiro/mods/13/).
## Download ## Download
@ -19,6 +19,7 @@ Patches games memory while running, does not modify any game files. Works with e
* add a custom resolution, 21/9 widescreen supported (will overwrite the default 1920x1080 / 1280x720 resolution, HUD limited to 16/9) * 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) * increase and decrease field of view (FOV)
* set the game to borderless window mode * set the game to borderless window mode
* disable camera auto rotate adjustment on movement (annoying for mouse users)
* display hidden counters such as death/kill count and optionally log them to file to display in OBS * display hidden counters such as death/kill count and optionally log them to file to display in OBS
* game modifications * game modifications
* global game speed modifier * global game speed modifier
@ -29,12 +30,12 @@ Patches games memory while running, does not modify any game files. Works with e
## Usage ## 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 following graphical guide has to be done if you want to unlock the game's framerate or play on a higher refresh rate in fullscreen. If you do not wish to use that feature you can scroll 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. The game enforces VSYNC and forces 60 Hz in fullscreen even on 144 Hz monitors so we have to override these.
#### TL;DR Nvidia: Use Nvidia Control Panel to set 'Vsync' to 'Off' and 'Preferred Refreshrate' to 'Highest available' on a Sekiro Profile. Troubleshoot: delete the (premade) Sekiro profile, add a new profile by stating the full file path to sekiro.exe and try again. If Preferred Refreshrate is missing or game still locks to 60fps see the guide further down on Nvidia Profile Inspector. #### TL;DR Nvidia: Use Nvidia Control Panel to set 'Vsync' to 'Off' and 'Preferred Refreshrate' to 'Highest available' on a Sekiro Profile. Troubleshoot: delete the (premade) Sekiro profile, add a new profile by stating the full file path to sekiro.exe and try again. If Preferred Refreshrate is missing or game still locks to 60fps see the guide further down on Nvidia Profile Inspector an general troubleshooting.
#### TL;DR AMD: Use Radeon Settings to set 'Wait for Vertical Refresh' to 'Enhanced Sync' on a Sekiro profile. Start Sekiro in windowed mode and switch to fullscreen once ingame. #### TL;DR AMD: Use Radeon Settings to set 'Wait for Vertical Refresh' to 'Enhanced Sync' on a Sekiro profile. Start Sekiro in windowed mode and switch to fullscreen once ingame. Troubleshoot: see the guide further down below.
#### 60 Hz monitors: disable VSYNC via driver (use 'Enhanced Sync' on AMD) and use fullscreen, see guide below #### 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 #### high refresh rate monitors: use borderless or force monitor to always use highest available refresh rate and then use fullscreen, see guide below
@ -73,7 +74,7 @@ The game enforces VSYNC and forces 60 Hz in fullscreen even on 144 Hz monitors s
### Follow these steps on AMD: ### Follow these steps on AMD:
1. Right click on Desktop -> `Display settings` 1. Right click on Desktop -> `Display settings`
2. Scroll down an click `Advanced Display Settings -> Display Adapter Properties` 2. Scroll down anD click `Advanced Display Settings -> Display Adapter Properties`
3. **Switch to `Monitor` tab and make sure your monitor is set to the highest Refresh rate possible:** 3. **Switch to `Monitor` tab and make sure your monitor is set to the highest Refresh rate possible:**
4. [![Make sure your monitor is set to the highest Refresh rate possible](https://camo.githubusercontent.com/8ba71a0b512eb68509f7e7506a92a78f3cd47537/68747470733a2f2f692e696d6775722e636f6d2f61774b4862774d2e706e67)](#) 4. [![Make sure your monitor is set to the highest Refresh rate possible](https://camo.githubusercontent.com/8ba71a0b512eb68509f7e7506a92a78f3cd47537/68747470733a2f2f692e696d6775722e636f6d2f61774b4862774d2e706e67)](#)
5. Open Radeon Settings 5. Open Radeon Settings
@ -102,6 +103,24 @@ The game enforces VSYNC and forces 60 Hz in fullscreen even on 144 Hz monitors s
10. Start the game and set it to Fullscreen 10. Start the game and set it to Fullscreen
11. Enjoy perfectly tearing free variable high refresh rates without VSYNC 11. Enjoy perfectly tearing free variable high refresh rates without VSYNC
### Troubleshooting:
* Make sure you followed the appreciate steps and didn't skip any (especially not the deletion of the Sekiro profile!)
* Try disabling `Fullscreen optimization` for Sekiro: right mouse click on `sekiro.exe -> Compatibility-> tick 'Disable fullscreen optimizations'`
* Try adding the whole game folder and `Sekiro FPS Unlocker and more` to your antivirus's exclusion list
* Try disabling `Steam Broadcast` (streaming via overlay)
* Do a clean reinstall of your graphic driver:
1. Download latest graphic drivers for your GPU
2. Download [DDU](https://www.guru3d.com/files-get/display-driver-uninstaller-download,1.html)
3. Disconnect internet so windows update won't auto-install minimal driver as soon as you uninstall them
4. Boot into safe mode
5. Completely uninstall graphic drivers and all utilities using DDU
6. Reboot
7. Install the latest driver
8. Reconnect internet
* Close and disable all screen recording and streaming applications
* Close and disable all overlays
* Close and disable all performance "booster" programs and alike
### To add a custom resolution ### To add a custom resolution
1. Start the game 1. Start the game
2. Start `Sekiro FPS Unlocker and more`, set you desired resolution and enable it by ticking the check box 2. Start `Sekiro FPS Unlocker and more`, set you desired resolution and enable it by ticking the check box
@ -137,6 +156,7 @@ The game enforces VSYNC and forces 60 Hz in fullscreen even on 144 Hz monitors s
2. Load up your save game 2. Load up your save game
3. Start `Sekiro FPS Unlocker and more` and expand `Game modifications` 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 4. Set your desired values and then tick the checkbox you'd wish to enable
5. Be aware that player and game speed modifications can potentially crash the game in certain cutscenes and NPC interactions
## Preview ## Preview
@ -156,7 +176,7 @@ The game enforces VSYNC and forces 60 Hz in fullscreen even on 144 Hz monitors s
## Building ## Building
Use Visual Studio 2017 to build Use Visual Studio 2017 or newer to build
## Contributing ## Contributing
@ -169,6 +189,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
## Credits ## Credits
* Me_TheCat for his contribution to log stats and display them in OBS * Me_TheCat for his contribution to log stats and display them in OBS
* Cielos for some camera adjustment offsets
* Zullie the Witch#7202 for game speed and player speed modifier offsets * Zullie the Witch#7202 for game speed and player speed modifier offsets
* jackfuste for FOV offset and basic running speed fix * jackfuste for FOV offset and basic running speed fix
* TyChii93#2376 for AMD and widescreen testing * TyChii93#2376 for AMD and widescreen testing
@ -176,15 +197,20 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
## Limitations ## Limitations
* 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 * 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, see Usage
* in fullscreen the game forces the monitor to 60 Hz so you'll have to handle this with driver override too, see Usage * 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 * your monitor has to support your custom resolution otherwise it won't show up correctly
* due to how the game renders altering 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
* Player speed modification needs a loaded save before it can be activated * Player speed modification needs a loaded save before it can be activated
* Player and game speed modification can potentially crash the game in certain cutscenes and NPC interactions, use with caution
* the hotkey won't work if the game runs in exclusive, true fullscreen mode * the hotkey won't work if the game runs in exclusive, true fullscreen mode
## Version History ## Version History
* v1.2.1 (2019-04-07)
* Added an option to disable automatic camera rotation adjust on movement (thanks to Cielos for some offsets)
* Added +25% FOV option
* Improved initial load time and patching speed
* v1.2.0 (2019-04-02) * v1.2.0 (2019-04-02)
* Added stats (kills & deaths for now) with an option to log them to file for display in OBS (thanks to Me_TheCat for his contribution) * Added stats (kills & deaths for now) with an option to log them to file for display in OBS (thanks to Me_TheCat for his contribution)
* Player speed modifier will stick between quicktravel now * Player speed modifier will stick between quicktravel now

View file

@ -0,0 +1,6 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/AutoDetectedNamingRules/=Parameters/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/AutoDetectedNamingRules/=Locals/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/AutoDetectedNamingRules/=PrivateConstants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="PATTERN_" Suffix="" Style="AA_BB" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/AutoDetectedNamingRules/=Constants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="WS_" Suffix="" Style="AA_BB"&gt;&lt;ExtraRule Prefix="" Suffix="" Style="AA_BB" /&gt;&lt;/Policy&gt;</s:String>
<s:Int64 x:Key="/Default/CodeStyle/Naming/CSharpAutoNaming/AutoNamingCompletedVersion/@EntryValue">2</s:Int64></wpf:ResourceDictionary>

View file

@ -0,0 +1,342 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace SekiroFpsUnlockAndMore
{
class CodeCaveGenerator
{
private class CodeCave
{
internal readonly long InstructionAddress;
internal readonly int OverwriteLength;
internal readonly long CodeCaveAddress;
internal bool Active;
private byte[] _originalInstructionset;
internal byte[] OriginalInstructionset
{
get => _originalInstructionset;
set
{
if (_originalInstructionset == null)
_originalInstructionset = value;
}
}
internal CodeCave(long instructionAddress, int overwriteLength, long codeCaveAddress)
{
InstructionAddress = instructionAddress;
OverwriteLength = overwriteLength;
CodeCaveAddress = codeCaveAddress;
Active = false;
_originalInstructionset = null;
}
}
private Dictionary<string, CodeCave> _codeCaves;
private static IntPtr _hProcess;
private static long _lpBaseAddress;
/// <summary>
/// Initialize functionality to create and manage code caves in given process's memory.
/// </summary>
/// <param name="hProcess">The handle to the process, needs all access flag.</param>
/// <param name="lpBaseAddress">The base address of the process.</param>
internal CodeCaveGenerator(IntPtr hProcess, long lpBaseAddress)
{
_codeCaves = new Dictionary<string, CodeCave>();
_hProcess = hProcess;
_lpBaseAddress = lpBaseAddress;
}
/// <summary>
/// Creates a new code cave with a unique name.
/// </summary>
/// <param name="szCaveName">The unique name of the code cave. Used to enable and disable caves.</param>
/// <param name="lpInstructionAddress">The address of the instruction that should be overwritten with jump.</param>
/// <param name="dwOverwriteLength">The length of the opcodes from lpInstructionAddress that should be overwritten with NOP, must be at least 5 bytes.</param>
/// <param name="cbCodeInject">The shellcode to place inside the code cave.</param>
/// <param name="bCopyOverwrittenInstructions">If overwritten instructions should be copied to the end of the code cave.</param>
/// <returns>True if code cave has been successfully created and is ready to be activated, false otherwise.</returns>
internal bool CreateNewCodeCave(string szCaveName, long lpInstructionAddress, int dwOverwriteLength, byte[] cbCodeInject, bool bCopyOverwrittenInstructions = false)
{
if (_codeCaves.ContainsKey(szCaveName))
{
DeactivateCodeCaveByName(szCaveName);
_codeCaves.Remove(szCaveName);
}
long caveAddress = CreateCodeCaveForInstruction(_hProcess, _lpBaseAddress, lpInstructionAddress, dwOverwriteLength, cbCodeInject, bCopyOverwrittenInstructions);
if (caveAddress < 0)
return false;
_codeCaves.Add(szCaveName, new CodeCave(lpInstructionAddress, dwOverwriteLength, caveAddress));
return true;
}
/// <summary>
/// Activates a code cave by name.
/// </summary>
/// <param name="szCaveName">The unique name of the code cave.</param>
/// <returns>True if code cave could be activated.</returns>
internal bool ActivateCodeCaveByName(string szCaveName)
{
if (!_codeCaves.ContainsKey(szCaveName) || _codeCaves[szCaveName].Active)
return false;
byte[] originalInstructionset = ActivateCodeCaveForInstruction(_hProcess, _codeCaves[szCaveName].InstructionAddress, _codeCaves[szCaveName].OverwriteLength, _codeCaves[szCaveName].CodeCaveAddress);
if (originalInstructionset.Length != _codeCaves[szCaveName].OverwriteLength)
return false;
_codeCaves[szCaveName].OriginalInstructionset = originalInstructionset;
_codeCaves[szCaveName].Active = true;
return true;
}
/// <summary>
/// Deactivates a code cave by name.
/// </summary>
/// <param name="szCaveName">The unique name of the code cave.</param>
/// <returns>True if code cave could be deactivated.</returns>
internal bool DeactivateCodeCaveByName(string szCaveName)
{
if (!_codeCaves.ContainsKey(szCaveName) || !_codeCaves[szCaveName].Active || _codeCaves[szCaveName].InstructionAddress < 0 || _codeCaves[szCaveName].OriginalInstructionset == null)
return false;
if (!WriteProcessMemory(_hProcess, _codeCaves[szCaveName].InstructionAddress, _codeCaves[szCaveName].OriginalInstructionset, (ulong) _codeCaves[szCaveName].OriginalInstructionset.Length, out IntPtr lpNumberOfBytesWritten) || lpNumberOfBytesWritten.ToInt32() != _codeCaves[szCaveName].OriginalInstructionset.Length)
{
MainWindow.LogToFile("Could not disable code cave in CodeCaveGenerator()!");
return false;
}
_codeCaves[szCaveName].Active = false;
return true;
}
/// <summary>
/// Gets the code cave address of an already created cave by name.
/// </summary>
/// <param name="szCaveName">The unique name of the code cave.</param>
/// <returns>The address of the code cave, -1 if none found.</returns>
internal long GetCodeCaveAddressByName(string szCaveName)
{
if (!_codeCaves.ContainsKey(szCaveName) || _codeCaves[szCaveName].CodeCaveAddress == 0)
return -1;
return _codeCaves[szCaveName].CodeCaveAddress;
}
/// <summary>
/// Clears all saved code caves. Does not deactivate them.
/// </summary>
internal void ClearCodeCaves()
{
_codeCaves.Clear();
}
/// <summary>
/// Creates a code cave to inject code within given process's memory in reach of given instruction address.
/// <para>Does not activate the code cave yet.</para>
/// </summary>
/// <param name="hProcess">The handle to the process, needs all access flag.</param>
/// <param name="lpBaseAddress">The base address of the process.</param>
/// <param name="lpInstructionAddress">The address of the instruction that should be overwritten with jump.</param>
/// <param name="dwOverwriteLength">The length of the opcodes from lpInstructionAddress that should be overwritten with NOP, must be at least 5 bytes.</param>
/// <param name="cbCodeInject">The shellcode to place inside the code cave.</param>
/// <param name="bCopyOverwrittenInstructions">If overwritten instructions should be copied to the end of the code cave.</param>
/// <remarks>Uses a relative 5 bytes jump instruction, will fail if there is no free memory within signed integer range (4bytes) from lpInstructionAddress.</remarks>
/// <returns>The address of the beginning of the code cave, -1 if operation failed.</returns>
private static long CreateCodeCaveForInstruction(IntPtr hProcess, long lpBaseAddress, long lpInstructionAddress, int dwOverwriteLength, byte[] cbCodeInject, bool bCopyOverwrittenInstructions = false)
{
if (IntPtr.Size != 8)
throw new Exception("Only x64 is supported!");
if (dwOverwriteLength < 5)
throw new Exception("dwOverwriteLength must be at least 5 bytes!");
// read instructions to replace with jump
byte[] cbOriginalInstructionset = new byte[dwOverwriteLength];
if (!ReadProcessMemory(hProcess, lpInstructionAddress, cbOriginalInstructionset, (ulong)dwOverwriteLength, out IntPtr lpNumberOfBytesRead) || lpNumberOfBytesRead.ToInt32() != dwOverwriteLength)
{
MainWindow.LogToFile("Failed to read original instruction set in CodeCaveGenerator()!");
return -1;
}
SYSTEM_INFO si = new SYSTEM_INFO();
GetSystemInfo(out si);
// find lowest and highest possible address in jump range from lpInstructionAddress
int iMinimalCaveSize = 8 * (int)Math.Round((cbCodeInject.Length + dwOverwriteLength + 6) / 8.0); // nearest size rounded to 8 bytes
if (iMinimalCaveSize < 32) iMinimalCaveSize = 32;
long lpMinimalJmpAddress = lpInstructionAddress + 6 - 0x70000000; // Int32.MinValue + a little buffer overhead
long lpMaximumJmpAddress = lpInstructionAddress + 0x70000000 - iMinimalCaveSize; // Int32.MaxValue + a little buffer overhead
if (lpMinimalJmpAddress < si.lpMinimumApplicationAddress) lpMinimalJmpAddress = si.lpMinimumApplicationAddress;
if (lpMaximumJmpAddress > si.lpMaximumApplicationAddress - iMinimalCaveSize) lpMaximumJmpAddress = si.lpMaximumApplicationAddress - iMinimalCaveSize;
// determine lowest possible memory block we could allocate for cave assuming base address lies at the beginning of a memory block
long lpPreferredAddress = lpBaseAddress - (int)((lpBaseAddress - lpMinimalJmpAddress) / si.dwAllocationGranularity) * si.dwAllocationGranularity;
// find lowest useable memory block and allocate it
long lpAllocationAddress = 0;
MEMORY_BASIC_INFORMATION64 mbi = new MEMORY_BASIC_INFORMATION64();
while (lpPreferredAddress < lpMaximumJmpAddress)
{
if (VirtualQueryEx(hProcess, new IntPtr(lpPreferredAddress), out mbi, MEMORY_BASIC_INFORMATION64_LENGTH) != MEMORY_BASIC_INFORMATION64_LENGTH)
break;
if (mbi.State == MEM_FREE && mbi.RegionSize > (ulong)iMinimalCaveSize)
{
lpAllocationAddress = VirtualAllocEx(hProcess, new IntPtr((long)mbi.BaseAddress), (uint)iMinimalCaveSize, ALLOCATIONTYPE_RESERVE | ALLOCATIONTYPE_COMMIT, MEMORYPROTECTION_EXECUTEREADWRITE);
if (lpAllocationAddress > 0)
break;
}
lpPreferredAddress = (long)mbi.BaseAddress + si.dwAllocationGranularity;
}
if (lpAllocationAddress == 0)
{
MainWindow.LogToFile("No usable memory region found or failed to allocate memory for code cave in CodeCaveGenerator()!");
return -1;
}
// calculate jump from cave to back to where we came from
uint lpRelativePointerFromCave = (uint)((lpInstructionAddress + dwOverwriteLength) - (lpAllocationAddress + cbCodeInject.Length + (bCopyOverwrittenInstructions ? dwOverwriteLength : 0))) - 5;
// generate jump instruction
byte[] cbJumpInstruction = new byte[] { 0xE9 }; // JMP relative to RIP
byte[] cbJumpFromCaveInstruction = cbJumpInstruction.Concat(BitConverter.GetBytes(lpRelativePointerFromCave)).ToArray();
// generate instructions for cave
byte[] cbCaveInstructions = cbCodeInject;
if (bCopyOverwrittenInstructions)
cbCaveInstructions = cbCaveInstructions.Concat(cbOriginalInstructionset).ToArray();
cbCaveInstructions = cbCaveInstructions.Concat(cbJumpFromCaveInstruction).ToArray();
// fill code cave with instructions
if (!WriteProcessMemory(hProcess, lpAllocationAddress, cbCaveInstructions, (ulong)cbCaveInstructions.Length, out IntPtr lpNumberOfBytesWritten) || lpNumberOfBytesWritten.ToInt32() != cbCaveInstructions.Length)
{
MainWindow.LogToFile("Failed to fill code cave in CodeCaveGenerator()!");
return -1;
}
return lpAllocationAddress;
}
/// <summary>
/// Creates a minimal code cave to inject code within given process's memory at given instruction address.
/// Uses a relative 5 bytes jump instruction, will fail if there is no free memory within signed integer range (4bytes) from lpInstructionAddress.
/// </summary>
/// <param name="hProcess">The handle to the process, needs all access flag.</param>
/// <param name="lpInstructionAddress">The address of the instruction that will be overwritten with jump.</param>
/// <param name="dwOverwriteLength">The length of the opcodes from lpInstructionAddress that should be overwritten with NOP, must be at least 5 bytes.</param>
/// <param name="lpCaveAddress">The address of the previously created code cave to where the jump should lead to.</param>
/// <returns>The overwritten instructions.</returns>
private static byte[] ActivateCodeCaveForInstruction(IntPtr hProcess, long lpInstructionAddress, int dwOverwriteLength, long lpCaveAddress)
{
// read instructions to replace with jump
byte[] cbOriginalInstructionset = new byte[dwOverwriteLength];
if (!ReadProcessMemory(hProcess, lpInstructionAddress, cbOriginalInstructionset, (ulong)dwOverwriteLength, out IntPtr lpNumberOfBytesRead) || lpNumberOfBytesRead.ToInt32() != dwOverwriteLength)
{
MainWindow.LogToFile("Failed to read original instruction set in CodeCaveGenerator()!");
return null;
}
// calculate relative jump offset
uint lpRelativePointerToCave = (uint)(lpCaveAddress - lpInstructionAddress) - 5;
// generate jump instruction and fill rest with NOPs
byte[] cbJumpInstruction = new byte[] { 0xE9 }; // JMP relative to RIP
byte[] cbJumpToCaveInstruction = cbJumpInstruction.Concat(BitConverter.GetBytes(lpRelativePointerToCave)).ToArray();
if (dwOverwriteLength > 5)
{
byte[] cbOverwrite = new byte[dwOverwriteLength - 5];
for (int i = 0; i < cbOverwrite.Length; i++)
cbOverwrite[i] = 0x90; // NOP
cbJumpToCaveInstruction = cbJumpToCaveInstruction.Concat(cbOverwrite).ToArray();
}
// write jump instruction to target address
if (!WriteProcessMemory(hProcess, lpInstructionAddress, cbJumpToCaveInstruction, (ulong)cbJumpToCaveInstruction.Length, out IntPtr lpNumberOfBytesWritten) || lpNumberOfBytesWritten.ToInt32() != cbJumpToCaveInstruction.Length)
{
MainWindow.LogToFile("Failed to overwrite target instructions in CodeCaveGenerator()!");
return null;
}
return cbOriginalInstructionset;
}
#region WINAPI
private const uint ALLOCATIONTYPE_COMMIT = 0x1000;
private const uint ALLOCATIONTYPE_RESERVE = 0x2000;
private const uint MEMORYPROTECTION_EXECUTEREADWRITE = 0x40;
private const int MEMORY_BASIC_INFORMATION64_LENGTH = 48;
private const int MEM_FREE = 0x10000;
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr OpenProcess(
UInt32 dwDesiredAccess,
Boolean bInheritHandle,
UInt32 dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern Boolean CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = false)]
private static extern void GetSystemInfo(out SYSTEM_INFO lpSystemInfo);
[StructLayout(LayoutKind.Sequential)]
internal struct SYSTEM_INFO
{
public UInt16 wProcessorArchitecture;
public UInt16 wReserved;
public UInt32 dwPageSize;
public Int64 lpMinimumApplicationAddress;
public Int64 lpMaximumApplicationAddress;
public IntPtr dwActiveProcessorMask;
public UInt32 dwNumberOfProcessors;
public UInt32 dwProcessorType;
public UInt32 dwAllocationGranularity;
public UInt16 wProcessorLevel;
public UInt16 wProcessorRevision;
}
[DllImport("kernel32.dll")]
private static extern Int32 VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, out MEMORY_BASIC_INFORMATION64 lpBuffer, uint dwLength);
[StructLayout(LayoutKind.Sequential)]
internal struct MEMORY_BASIC_INFORMATION64
{
public UInt64 BaseAddress;
public UInt64 AllocationBase;
public Int32 AllocationProtect;
public Int32 __alignment1;
public UInt64 RegionSize;
public Int32 State;
public Int32 Protect;
public Int32 Type;
public Int32 __alignment2;
}
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern Int64 VirtualAllocEx(
IntPtr hProcess,
IntPtr lpAddress,
uint dwSize,
UInt32 flAllocationType,
UInt32 flProtect);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern Boolean ReadProcessMemory(
IntPtr hProcess,
Int64 lpBaseAddress,
[Out] Byte[] lpBuffer,
UInt64 dwSize,
out IntPtr lpNumberOfBytesRead);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool WriteProcessMemory(
IntPtr hProcess,
Int64 lpBaseAddress,
[In, Out] Byte[] lpBuffer,
UInt64 dwSize,
out IntPtr lpNumberOfBytesWritten);
#endregion
}
}

View file

@ -17,11 +17,9 @@ namespace SekiroFpsUnlockAndMore
000000014116168D | C743 18 8988883C | mov dword ptr ds:[rbx+18],3C888889 | fFrameTick 000000014116168D | C743 18 8988883C | mov dword ptr ds:[rbx+18],3C888889 | fFrameTick
0000000141161694 | 4C:89AB 70020000 | mov qword ptr ds:[rbx+270],r13 | 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 = "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 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 = "C7 43 ?? ?? ?? ?? ?? 4C 89 AB";
internal const string PATTERN_FRAMELOCK_FUZZY_MASK = "xx?????xxx";
internal const int PATTERN_FRAMELOCK_FUZZY_OFFSET = 3; internal const int PATTERN_FRAMELOCK_FUZZY_OFFSET = 3;
@ -30,8 +28,7 @@ namespace SekiroFpsUnlockAndMore
fFrametimeCriticalRunningSpeed should be roughly half the frame rate: 30 @ 60FPS limit, 50 @ 100FPS limit... 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 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 internal const string PATTERN_FRAMELOCK_SPEED_FIX = "F3 0F 59 05 ?? 30 92 02";
internal const string PATTERN_FRAMELOCK_SPEED_FIX_MASK = "xxxx?xxx";
internal const int PATTERN_FRAMELOCK_SPEED_FIX_OFFSET = 4; internal const int PATTERN_FRAMELOCK_SPEED_FIX_OFFSET = 4;
/** /**
00000001430F7E10 00000001430F7E10
@ -39,7 +36,7 @@ namespace SekiroFpsUnlockAndMore
Value: Value resolve in float table from pFrametimeRunningSpeed->fFrametimeCriticalRunningSpeed 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 Hardcoded cause lazy -> if anyone knows how the table is calculated then tell me and I'll buy you a beer
*/ */
internal static Dictionary<byte[], float> PATCH_FRAMELOCK_SPEED_FIX_MATRIX = new Dictionary<byte[], float> internal static readonly Dictionary<byte[], float> PATCH_FRAMELOCK_SPEED_FIX_MATRIX = new Dictionary<byte[], float>
{ {
{ new byte[1] {0x68}, 15f }, { new byte[1] {0x68}, 15f },
{ new byte[1] {0x6C}, 16f }, { new byte[1] {0x6C}, 16f },
@ -77,7 +74,7 @@ namespace SekiroFpsUnlockAndMore
{ new byte[1] {0xEC}, 144f }, { new byte[1] {0xEC}, 144f },
{ new byte[1] {0xF0}, 150f } { new byte[1] {0xF0}, 150f }
}; };
internal static byte[] PATCH_FRAMELOCK_SPEED_FIX_DISABLE = new byte[1] { 0x90 }; // 30f internal static readonly byte[] PATCH_FRAMELOCK_SPEED_FIX_DISABLE = new byte[1] { 0x90 }; // 30f
/// <summary> /// <summary>
/// Finds closest valid speed fix value for a frame rate limit. /// Finds closest valid speed fix value for a frame rate limit.
/// </summary> /// </summary>
@ -103,8 +100,7 @@ namespace SekiroFpsUnlockAndMore
000000014114AC8E | 0F57C9 | xorps xmm1,xmm1 | 000000014114AC8E | 0F57C9 | xorps xmm1,xmm1 |
000000014114AC91 | 8915 8D147D02 | mov dword ptr ds:[14391C124],edx | pCurrentResolutionHeight->iInternalGameHeight 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 = "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_OFFSET = 3;
internal const int PATTERN_RESOLUTION_POINTER_INSTRUCTION_LENGTH = 6; internal const int PATTERN_RESOLUTION_POINTER_INSTRUCTION_LENGTH = 6;
@ -115,7 +111,6 @@ namespace SekiroFpsUnlockAndMore
*/ */
internal const string PATTERN_RESOLUTION_DEFAULT = "80 07 00 00 38 04 00 00"; // 1920x1080 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_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 = 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 }; internal static byte[] PATCH_RESOLUTION_DEFAULT_DISABLE_720 = new byte[8] { 0x40, 0x06, 0x00, 0x00, 0x84, 0x03, 0x00, 0x00 };
@ -125,8 +120,7 @@ namespace SekiroFpsUnlockAndMore
000000014012967A | 74 47 | je sekiro.1401296C3 | conditional jump 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 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 = "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 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_ENABLE = new byte[1] { 0xEB }; // JMP
internal static byte[] PATCH_RESOLUTION_SCALING_FIX_DISABLE = new byte[1] { 0x74 }; // JE internal static byte[] PATCH_RESOLUTION_SCALING_FIX_DISABLE = new byte[1] { 0x74 }; // JE
@ -138,15 +132,14 @@ namespace SekiroFpsUnlockAndMore
000000014073954C | F3:0F590D 0CE79B02 | mulss xmm1,dword ptr ds:[1430F7C60] | pFovTableEntry->fFov 000000014073954C | F3:0F590D 0CE79B02 | mulss xmm1,dword ptr ds:[1430F7C60] | pFovTableEntry->fFov
*/ */
// credits to 'jackfuste' for original offset // 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 = "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 const int PATTERN_FOVSETTING_OFFSET = 8;
/** /**
00000001430F7C60 00000001430F7C60
Key: Patch to pFovTableEntry (last 2 bytes) Key: Patch to pFovTableEntry (last 2 bytes)
Value: Value resolve in float table from pFovTableEntry->fFov Value: Value resolve in float table from pFovTableEntry->fFov
*/ */
internal static Dictionary<byte[], string> PATCH_FOVSETTING_MATRIX = new Dictionary<byte[], string> internal static readonly Dictionary<byte[], string> PATCH_FOVSETTING_MATRIX = new Dictionary<byte[], string>
{ {
{ new byte[2] {0x00, 0xE7}, "- 50%" }, { new byte[2] {0x00, 0xE7}, "- 50%" },
{ new byte[2] {0x04, 0xE7}, "- 10%" }, { new byte[2] {0x04, 0xE7}, "- 10%" },
@ -156,7 +149,7 @@ namespace SekiroFpsUnlockAndMore
{ new byte[2] {0x18, 0xE7}, "+ 75%" }, { new byte[2] {0x18, 0xE7}, "+ 75%" },
{ new byte[2] {0x1C, 0xE7}, "+ 90%" } { new byte[2] {0x1C, 0xE7}, "+ 90%" }
}; };
internal static byte[] PATCH_FOVSETTING_DISABLE = new byte[2] { 0x0C, 0xE7 }; // + 0% internal static readonly byte[] PATCH_FOVSETTING_DISABLE = new byte[2] { 0x0C, 0xE7 }; // + 0%
/** /**
@ -170,12 +163,12 @@ namespace SekiroFpsUnlockAndMore
00000001407AACB6 | 8B88 90000000 | mov ecx,dword ptr ds:[rax+90] | offset pPlayerStats->iPlayerDeaths 00000001407AACB6 | 8B88 90000000 | mov ecx,dword ptr ds:[rax+90] | offset pPlayerStats->iPlayerDeaths
*/ */
// credits to 'Me_TheCat' for original offset // credits to 'Me_TheCat' for original offset
internal const string PATTERN_PLAYER_DEATHS = "0F B6 48 00 88 8B 00 00 00 00 48 8B 05 00 00 00 00 8B 88 00 00 00 00 89 8B 00 00 00 00 48 8B 05 00 00 00 00 8B 88 00 00 00 00"; // 0F B6 48 ?? 88 8B ?? ?? 00 00 48 8B 05 ?? ?? ?? ?? 8B 88 ?? ?? 00 00 89 8B ?? ?? 00 00 48 8B 05 ?? ?? ?? ?? 8B 88 ?? ?? 00 00 internal const string PATTERN_PLAYER_DEATHS = "0F B6 48 ?? 88 8B ?? ?? 00 00 48 8B 05 ?? ?? ?? ?? 8B 88 ?? ?? 00 00 89 8B ?? ?? 00 00 48 8B 05 ?? ?? ?? ?? 8B 88 ?? ?? 00 00";
internal const string PATTERN_PLAYER_DEATHS_MASK = "xxx?xx??xxxxx????xx??xxxx??xxxxx????xx??xx";
internal const int PATTERN_PLAYER_DEATHS_OFFSET = 29; internal const int PATTERN_PLAYER_DEATHS_OFFSET = 29;
internal const int PATTERN_PLAYER_DEATHS_INSTRUCTION_LENGTH = 7; internal const int PATTERN_PLAYER_DEATHS_INSTRUCTION_LENGTH = 7;
internal const int PATTERN_PLAYER_DEATHS_POINTER_OFFSET_OFFSET = 9; internal const int PATTERN_PLAYER_DEATHS_POINTER_OFFSET_OFFSET = 9;
/** /**
Reference pointer pTotalKills to <int>iTotalKills, does not get updated on every kill but mostly on every 2nd, includes own player deaths... Reference pointer pTotalKills to <int>iTotalKills, does not get updated on every kill but mostly on every 2nd, includes own player deaths...
0000000141151838 | 48:8D0D A9A5B302 | lea rcx,qword ptr ds:[143C8BDE8] | pTotalKills->iTotalKills 0000000141151838 | 48:8D0D A9A5B302 | lea rcx,qword ptr ds:[143C8BDE8] | pTotalKills->iTotalKills
@ -183,8 +176,7 @@ namespace SekiroFpsUnlockAndMore
0000000141151842 | C3 | ret | 0000000141151842 | C3 | ret |
*/ */
// credits to 'Me_TheCat' for original offset // credits to 'Me_TheCat' for original offset
internal const string PATTERN_TOTAL_KILLS = "48 8D 0D 00 00 00 00 89 14 81 C3"; // 48 8D 0D ?? ?? ?? ?? 89 14 81 C3 internal const string PATTERN_TOTAL_KILLS = "48 8D 0D ?? ?? ?? ?? 89 14 81 C3";
internal const string PATTERN_TOTAL_KILLS_MASK = "xxx????xxxx";
internal const int PATTERN_TOTAL_KILLS_INSTRUCTION_LENGTH = 7; internal const int PATTERN_TOTAL_KILLS_INSTRUCTION_LENGTH = 7;
@ -195,8 +187,7 @@ namespace SekiroFpsUnlockAndMore
0000000141149E96 | F3:0F5988 68020000 | mulss xmm1,dword ptr ds:[rax+268] | 0000000141149E96 | F3:0F5988 68020000 | mulss xmm1,dword ptr ds:[rax+268] |
*/ */
// credits to 'Zullie the Witch' for original offset // 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 = "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_INSTRUCTION_LENGTH = 7;
internal const int PATTERN_TIMESCALE_POINTER_OFFSET_OFFSET = 11; internal const int PATTERN_TIMESCALE_POINTER_OFFSET_OFFSET = 11;
@ -209,12 +200,76 @@ namespace SekiroFpsUnlockAndMore
00000001406BF1E3 | 8B17 | mov edx,dword ptr ds:[rdi] | 00000001406BF1E3 | 8B17 | mov edx,dword ptr ds:[rdi] |
*/ */
// credits to 'Zullie the Witch' for original offset // 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 = "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_PLAYER_INSTRUCTION_LENGTH = 7;
internal const int PATTERN_TIMESCALE_POINTER2_OFFSET = 0x88; internal const int PATTERN_TIMESCALE_POINTER2_OFFSET = 0x88;
internal const int PATTERN_TIMESCALE_POINTER3_OFFSET = 0x1FF8; internal const int PATTERN_TIMESCALE_POINTER3_OFFSET = 0x1FF8;
internal const int PATTERN_TIMESCALE_POINTER4_OFFSET = 0x28; internal const int PATTERN_TIMESCALE_POINTER4_OFFSET = 0x28;
internal const int PATTERN_TIMESCALE_POINTER5_OFFSET = 0xD00; internal const int PATTERN_TIMESCALE_POINTER5_OFFSET = 0xD00;
/**
Controls camera pitch. xmm4 holds new pitch from a calculation while rps+170 holds current one from mouse so we overwrite xmm4 with the old pitch value
000000014073AF26 | 0F29A5 70080000 | movaps xmmword ptr ss:[rbp+870],xmm4 | code inject overwrite from here
000000014073AF2D | 0F29A5 80080000 | movaps xmmword ptr ss:[rbp+880],xmm4 | jump back here from code inject
000000014073AF34 | 0F29A6 70010000 | movaps xmmword ptr ds:[rsi+170],xmm4 | camPitch, newCamPitch
000000014073AF3B | EB 1C | jmp sekiro.14073AF59 |
000000014073AF3D | F3:0F108E 74010000 | movss xmm1,dword ptr ds:[rsi+174] |
*/
internal const string PATTERN_CAMADJUST_PITCH = "0F 29 ?? ?? ?? 00 00 0F 29 ?? ?? ?? 00 00 0F 29 ?? ?? ?? 00 00 EB ?? F3";
internal const int INJECT_CAMADJUST_PITCH_OVERWRITE_LENGTH = 7;
internal static readonly byte[] INJECT_CAMADJUST_PITCH_SHELLCODE = new byte[]
{
0x0F, 0x28, 0xA6, 0x70, 0x01, 0x00, 0x00, // movaps xmm4,xmmword ptr ds:[rsi+170]
0x0F, 0x29, 0xA5, 0x70, 0x08, 0x00, 0x00 // movaps xmmword ptr ss:[rbp+870],xmm4
};
/**
Controls automatic camera yaw adjust on move on Z-axis. xmm0 holds new yaw while rsi+174 holds current one prior movement so we overwrite xmm0 with the old yaw value
000000014073AF4C | E8 6F60FFFF | call sekiro.140730FC0 |
000000014073AF51 | F3:0F1186 74010000 | movss dword ptr ds:[rsi+174],xmm0 | camYaw, newCamYaw | code inject overwrite from here
000000014073AF59 | 80BE A3020000 00 | cmp byte ptr ds:[rsi+2A3],0 | jump back here from code inject
000000014073AF60 | 0F84 2F020000 | je sekiro.14073B195 |
*/
internal const string PATTERN_CAMADJUST_YAW_Z = "E8 ?? ?? ?? ?? F3 ?? ?? ?? ?? ?? 00 00 80 ?? ?? ?? 00 00 00 0F 84";
internal const int PATTERN_CAMADJUST_YAW_Z_OFFSET = 5;
internal const int INJECT_CAMADJUST_YAW_Z_OVERWRITE_LENGTH = 8;
internal static readonly byte[] INJECT_CAMADJUST_YAW_Z_SHELLCODE = new byte[]
{
0xF3, 0x0F, 0x10, 0x86, 0x74, 0x01, 0x00, 0x00, // movss xmm0,dword ptr ds:[rsi+174]
0xF3, 0x0F, 0x11, 0x86, 0x74, 0x01, 0x00, 0x00 // movss dword ptr ds:[rsi+174],xmm0
};
/**
Controls automatic camera pitch adjust on move on XY-axis. Pointer in rax holds new pitch while rsi+170 holds current one prior movement so we overwrite xmm0 with the old pitch value and then overwrite [rax] with xmm0
000000014073B476 | F3:0F1000 | movss xmm0,dword ptr ds:[rax] | newCamPitch | code inject overwrite from here
000000014073B47A | F3:0F1186 70010000 | movss dword ptr ds:[rsi+170],xmm0 | camPitch
000000014073B482 | F3:0F1085 E4120000 | movss xmm0,dword ptr ss:[rbp+12E4] | jump back here from code inject
000000014073B48A | E8 91BDFFFF | call sekiro.140737220 |
000000014073B48F | 0F28D0 | movaps xmm2,xmm0 |
*/
// thanks to 'Cielos' for original offset
internal const string PATTERN_CAMADJUST_PITCH_XY = "F3 ?? ?? ?? F3 ?? ?? ?? ?? ?? 00 00 F3 ?? ?? ?? ?? ?? ?? ?? E8 ?? ?? ?? ?? 0F";
internal const int INJECT_CAMADJUST_PITCH_XY_OVERWRITE_LENGTH = 12;
internal static readonly byte[] INJECT_CAMADJUST_PITCH_XY_SHELLCODE = new byte[]
{
0xF3, 0x0F, 0x10, 0x86, 0x70, 0x01, 0x00, 0x00, // movss xmm0,dword ptr ds:[rsi+170]
0xF3, 0x0F, 0x11, 0x00, // movss dword ptr ds:[rax],xmm0
0xF3, 0x0F, 0x10, 0x00, // movss xmm0,dword ptr ds:[rax]
0xF3, 0x0F, 0x11, 0x86, 0x70, 0x01, 0x00, 0x00 // movss dword ptr ds:[rsi+170],xmm0
};
/**
Controls automatic camera yaw adjust on move on XY-axis. xmm0 new yaw while rsi+174 holds current one prior movement so we overwrite xmm0 with the old yaw value
000000014073B564 | E8 B7BCFFFF | call sekiro.140737220 |
000000014073B569 | F3:0F1186 74010000 | movss dword ptr ds:[rsi+174],xmm0 | camYaw, newCamYaw | code inject overwrite from here
000000014073B571 | E9 9A020000 | jmp sekiro.14073B810 | jump back here from code inject
*/
// thanks to 'Cielos' for original offset
internal const string PATTERN_CAMADJUST_YAW_XY = "E8 ?? ?? ?? ?? F3 0F 11 86 74 01 00 00 E9";
internal const int PATTERN_CAMADJUST_YAW_XY_OFFSET = 5;
internal const int INJECT_CAMADJUST_YAW_XY_OVERWRITE_LENGTH = 8;
internal static readonly byte[] INJECT_CAMADJUST_YAW_XY_SHELLCODE = new byte[]
{
0xF3, 0x0F, 0x10, 0x86, 0x74, 0x01, 0x00, 0x00, // movss xmm0,dword ptr ds:[rsi+174]
0xF3, 0x0F, 0x11, 0x86, 0x74, 0x01, 0x00, 0x00 // movss dword ptr ds:[rsi+174],xmm0
};
} }
} }

View file

@ -5,7 +5,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SekiroFpsUnlockAndMore" xmlns:local="clr-namespace:SekiroFpsUnlockAndMore"
mc:Ignorable="d" mc:Ignorable="d"
Title="Sekiro FPS Unlocker and more v1.2.0" Width="Auto" Height="Auto" SizeToContent="WidthAndHeight" ResizeMode="CanMinimize" Loaded="Window_Loaded" Closing="Window_Closing"> Title="Sekiro FPS Unlocker and more v1.2.1" Width="Auto" Height="Auto" SizeToContent="WidthAndHeight" ResizeMode="CanMinimize" Loaded="Window_Loaded" Closing="Window_Closing">
<Grid Background="#FFF9F9F9"> <Grid Background="#FFF9F9F9">
<DockPanel> <DockPanel>
@ -28,12 +28,13 @@
<CheckBox x:Name="cbBorderless" DockPanel.Dock="Left" Margin="0,0,0,0" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Content="Borderless window" ToolTip="To enable this set 'Windowed' mode in game options first." IsEnabled="False" Checked="CbBorderless_Checked" Unchecked="CbBorderless_Unchecked"/> <CheckBox x:Name="cbBorderless" DockPanel.Dock="Left" Margin="0,0,0,0" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Content="Borderless window" ToolTip="To enable this set 'Windowed' mode in game options first." IsEnabled="False" Checked="CbBorderless_Checked" Unchecked="CbBorderless_Unchecked"/>
<CheckBox x:Name="cbBorderlessStretch" DockPanel.Dock="Right" Margin="10,0,0,0" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Content="Fullscreen stretch" IsEnabled="False" Checked="CbBorderlessStretch_Check_Handler" Unchecked="CbBorderlessStretch_Check_Handler" /> <CheckBox x:Name="cbBorderlessStretch" DockPanel.Dock="Right" Margin="10,0,0,0" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Content="Fullscreen stretch" IsEnabled="False" Checked="CbBorderlessStretch_Check_Handler" Unchecked="CbBorderlessStretch_Check_Handler" />
</StackPanel> </StackPanel>
<CheckBox x:Name="cbCamAdjust" Margin="0,5,0,0" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Content="Disable camera auto rotate on movement" ToolTip="Disables the automatic camera adjustment on movement. Annoying when mouse is used." Checked="CbCamAdjust_Check_Handler" Unchecked="CbCamAdjust_Check_Handler" />
<CheckBox x:Name="cbLogStats" Margin="0,5,0,0" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Content="Log stats (Deaths, Kills) to file for OBS" ToolTip="Check the guide on how to display these on stream with OBS." Checked="CbStatChanged" Unchecked="CbStatChanged" /> <CheckBox x:Name="cbLogStats" Margin="0,5,0,0" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Content="Log stats (Deaths, Kills) to file for OBS" ToolTip="Check the guide on how to display these on stream with OBS." Checked="CbStatChanged" Unchecked="CbStatChanged" />
<Expander x:Name="exGameMods" Margin="-3,5,0,0" Height="Auto" FontSize="14 px" Header="Game modifications" IsExpanded="False"> <Expander x:Name="exGameMods" Margin="-3,5,0,0" Height="Auto" FontSize="14 px" Header="Game modifications" IsExpanded="False">
<Grid Margin="3,0,0,0" Background="#FFF9F9F9"> <Grid Margin="3,0,0,0" Background="#FFF9F9F9">
<StackPanel Width="Auto" Height="Auto"> <StackPanel Width="Auto" Height="Auto">
<DockPanel Margin="0,3,0,0" LastChildFill="False"> <DockPanel Margin="0,3,0,0" LastChildFill="False">
<CheckBox x:Name="cbGameSpeed" DockPanel.Dock="Left" Margin="0,0,0,0" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Content="Game speed (%):" Checked="CbGameSpeed_Check_Handler" Unchecked="CbGameSpeed_Check_Handler" /> <CheckBox x:Name="cbGameSpeed" DockPanel.Dock="Left" Margin="0,0,0,0" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Content="Game speed (%):" ToolTip="Can potentially crash the game in cutscenes, use with caution." Checked="CbGameSpeed_Check_Handler" Unchecked="CbGameSpeed_Check_Handler" />
<Button x:Name="bGs100" DockPanel.Dock="Right" Content="100" Margin="0,0,0,0" Width="30" Height="25" FontSize="14 px" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" <Button x:Name="bGs100" DockPanel.Dock="Right" Content="100" Margin="0,0,0,0" Width="30" Height="25" FontSize="14 px" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"
Focusable="False" Click="BGs100_Click" /> Focusable="False" Click="BGs100_Click" />
<Button x:Name="bGsHigher" DockPanel.Dock="Right" Content="&gt;" Margin="0,0,3,0" Width="25" Height="25" FontSize="14 px" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" <Button x:Name="bGsHigher" DockPanel.Dock="Right" Content="&gt;" Margin="0,0,3,0" Width="25" Height="25" FontSize="14 px" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"
@ -45,7 +46,7 @@
Focusable="False" Click="BGs0_Click" /> Focusable="False" Click="BGs0_Click" />
</DockPanel> </DockPanel>
<DockPanel Margin="0,5,0,0" LastChildFill="False"> <DockPanel Margin="0,5,0,0" LastChildFill="False">
<CheckBox x:Name="cbPlayerSpeed" DockPanel.Dock="Left" Margin="0,0,0,0" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Content="Player speed (%):" ToolTip="To enable this start the game and load a save, then tick the checkbox." Checked="CbPlayerSpeed_Check_Handler" Unchecked="CbPlayerSpeed_Check_Handler" /> <CheckBox x:Name="cbPlayerSpeed" DockPanel.Dock="Left" Margin="0,0,0,0" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Content="Player speed (%):" ToolTip="To enable this start the game and load a save, then tick the checkbox. Can potentially crash the game in cutscenes, use with caution." Checked="CbPlayerSpeed_Check_Handler" Unchecked="CbPlayerSpeed_Check_Handler" />
<Button x:Name="bPs100" DockPanel.Dock="Right" Content="100" Margin="0,0,0,0" Width="30" Height="25" FontSize="14 px" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" <Button x:Name="bPs100" DockPanel.Dock="Right" Content="100" Margin="0,0,0,0" Width="30" Height="25" FontSize="14 px" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"
Focusable="False" Click="BPs100_Click" /> Focusable="False" Click="BPs100_Click" />
<Button x:Name="bPsHigher" DockPanel.Dock="Right" Content="&gt;" Margin="0,0,3,0" Width="25" Height="25" FontSize="14 px" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" <Button x:Name="bPsHigher" DockPanel.Dock="Right" Content="&gt;" Margin="0,0,3,0" Width="25" Height="25" FontSize="14 px" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"
@ -74,14 +75,14 @@
<Run FontWeight="Bold">using Nvidia Control panel or AMD Radeon Settings.</Run> <Run FontWeight="Bold">using Nvidia Control panel or AMD Radeon Settings.</Run>
<Run>Borderless window mode requires "Windowed" in game settings first.</Run> <Run>Borderless window mode requires "Windowed" in game settings first.</Run>
<Run FontWeight="Bold">Custom resolution adds 21/9 support and overwrites a default resolution, hud will be limited to 16/9.</Run> <Run FontWeight="Bold">Custom resolution adds 21/9 support and overwrites a default resolution, hud will be limited to 16/9.</Run>
<Run>Borderless window mode with custom resolution requires you to patch and set resolution first, then add borderless patch afterwards.</Run> <Run>Borderless window mode with custom resolution requires you to patch and set resolution first, then activate borderless afterwards.</Run>
<Run FontWeight="Bold">To enable Player Speed modification you have to be ingame first.</Run> <Run FontWeight="Bold">To enable Player Speed modification you have to be ingame first.</Run>
<Run FontWeight="Bold" Foreground="#FFF00000">See the link below for detailed information, guides, GSYNC support and an AMD fix.</Run> <Run FontWeight="Bold" Foreground="#FFF00000">See the link below for detailed information, guides, GSYNC support and an AMD fix.</Run>
</TextBlock.Inlines> </TextBlock.Inlines>
</TextBlock> </TextBlock>
<Label HorizontalAlignment="Right" FontSize="12 px"> <Label HorizontalAlignment="Right" FontSize="12 px">
<Hyperlink NavigateUri="https://github.com/uberhalit/SekiroFpsUnlockAndMore" RequestNavigate="Hyperlink_RequestNavigate"> <Hyperlink NavigateUri="https://github.com/uberhalit/SekiroFpsUnlockAndMore" RequestNavigate="Hyperlink_RequestNavigate">
v1.2.0 - by uberhalit v1.2.1 - by uberhalit
</Hyperlink> </Hyperlink>
</Label> </Label>
</StackPanel> </StackPanel>

View file

@ -34,6 +34,7 @@ namespace SekiroFpsUnlockAndMore
internal long _offset_timescale_player = 0x0; internal long _offset_timescale_player = 0x0;
internal long _offset_timescale_player_pointer_start = 0x0; internal long _offset_timescale_player_pointer_start = 0x0;
internal CodeCaveGenerator _codeCaveGenerator;
internal SettingsService _settingsService; internal SettingsService _settingsService;
internal StatusViewModel _statusViewModel = new StatusViewModel(); internal StatusViewModel _statusViewModel = new StatusViewModel();
@ -43,14 +44,20 @@ namespace SekiroFpsUnlockAndMore
internal readonly System.Timers.Timer _timerStatsCheck = new System.Timers.Timer(); internal readonly System.Timers.Timer _timerStatsCheck = new System.Timers.Timer();
internal bool _running = false; internal bool _running = false;
internal bool _gameInitializing = false; internal bool _gameInitializing = false;
internal string _logPath; internal static string _logPath;
internal string _deathCounterPath; internal string _deathCounterPath;
internal string _killCounterPath; internal string _killCounterPath;
internal bool _retryAccess = true; internal bool _retryAccess = true;
internal bool _use_resolution_720 = false; internal bool _use_resolution_720 = false;
internal bool _codeCavesGenerated = false;
internal bool _statLoggingEnabled = false; internal bool _statLoggingEnabled = false;
internal RECT _windowRect; internal RECT _windowRect;
internal const string _CODECAVE_CAMADJUST_PITCH = "camAdjustPitch";
internal const string _CODECAVE_CAMADJUST_YAW_Z = "camAdjustYawZ";
internal const string _CODECAVE_CAMADJUST_PITCH_XY = "camAdjustPitchXY";
internal const string _CODECAVE_CAMADJUST_YAW_XY = "camAdjustYawXY";
public MainWindow() public MainWindow()
{ {
InitializeComponent(); InitializeComponent();
@ -152,6 +159,7 @@ namespace SekiroFpsUnlockAndMore
this.cbSelectFov.SelectedIndex = _settingsService.ApplicationSettings.cbSelectFov; this.cbSelectFov.SelectedIndex = _settingsService.ApplicationSettings.cbSelectFov;
this.cbBorderless.IsChecked = _settingsService.ApplicationSettings.cbBorderless; this.cbBorderless.IsChecked = _settingsService.ApplicationSettings.cbBorderless;
this.cbBorderlessStretch.IsChecked = _settingsService.ApplicationSettings.cbBorderlessStretch; this.cbBorderlessStretch.IsChecked = _settingsService.ApplicationSettings.cbBorderlessStretch;
this.cbCamAdjust.IsChecked = _settingsService.ApplicationSettings.cbCamAdjust;
this.cbLogStats.IsChecked = _settingsService.ApplicationSettings.cbLogStats; this.cbLogStats.IsChecked = _settingsService.ApplicationSettings.cbLogStats;
this.exGameMods.IsExpanded = _settingsService.ApplicationSettings.exGameMods; this.exGameMods.IsExpanded = _settingsService.ApplicationSettings.exGameMods;
this.cbGameSpeed.IsChecked = _settingsService.ApplicationSettings.cbGameSpeed; this.cbGameSpeed.IsChecked = _settingsService.ApplicationSettings.cbGameSpeed;
@ -174,6 +182,7 @@ namespace SekiroFpsUnlockAndMore
_settingsService.ApplicationSettings.cbSelectFov = this.cbSelectFov.SelectedIndex; _settingsService.ApplicationSettings.cbSelectFov = this.cbSelectFov.SelectedIndex;
_settingsService.ApplicationSettings.cbBorderless = this.cbBorderless.IsChecked == true; _settingsService.ApplicationSettings.cbBorderless = this.cbBorderless.IsChecked == true;
_settingsService.ApplicationSettings.cbBorderlessStretch = this.cbBorderlessStretch.IsChecked == true; _settingsService.ApplicationSettings.cbBorderlessStretch = this.cbBorderlessStretch.IsChecked == true;
_settingsService.ApplicationSettings.cbCamAdjust = this.cbCamAdjust.IsChecked == true;
_settingsService.ApplicationSettings.cbLogStats = this.cbLogStats.IsChecked == true; _settingsService.ApplicationSettings.cbLogStats = this.cbLogStats.IsChecked == true;
_settingsService.ApplicationSettings.exGameMods = this.exGameMods.IsExpanded; _settingsService.ApplicationSettings.exGameMods = this.exGameMods.IsExpanded;
_settingsService.ApplicationSettings.cbGameSpeed = this.cbGameSpeed.IsChecked == true; _settingsService.ApplicationSettings.cbGameSpeed = this.cbGameSpeed.IsChecked == true;
@ -253,6 +262,10 @@ namespace SekiroFpsUnlockAndMore
MessageBox.Show("Unknown game version.\nSome functions might not work properly or even crash the game. Check for updates on this utility regularly following the link at the bottom.", "Sekiro FPS Unlocker and more", MessageBoxButton.OK, MessageBoxImage.Warning); MessageBox.Show("Unknown game version.\nSome functions might not work properly or even crash the game. Check for updates on this utility regularly following the link at the bottom.", "Sekiro FPS Unlocker and more", MessageBoxButton.OK, MessageBoxImage.Warning);
_settingsService.ApplicationSettings.gameVersionNotify = true; _settingsService.ApplicationSettings.gameVersionNotify = true;
} }
else
{
_settingsService.ApplicationSettings.gameVersionNotify = false;
}
// give the game some time to initialize // give the game some time to initialize
_gameInitializing = true; _gameInitializing = true;
@ -265,112 +278,113 @@ namespace SekiroFpsUnlockAndMore
private void ReadGame(object sender, DoWorkEventArgs doWorkEventArgs) private void ReadGame(object sender, DoWorkEventArgs doWorkEventArgs)
{ {
PatternScan patternScan = new PatternScan(_gameAccessHwnd, _gameProc.MainModule); PatternScan patternScan = new PatternScan(_gameAccessHwnd, _gameProc.MainModule);
_codeCaveGenerator = new CodeCaveGenerator(_gameAccessHwnd, _gameProc.MainModule.BaseAddress.ToInt64());
_offset_framelock = patternScan.FindPatternInternal(GameData.PATTERN_FRAMELOCK, GameData.PATTERN_FRAMELOCK_MASK, ' ') + GameData.PATTERN_FRAMELOCK_OFFSET; _offset_framelock = patternScan.FindPattern(GameData.PATTERN_FRAMELOCK) + GameData.PATTERN_FRAMELOCK_OFFSET;
Debug.WriteLine("fFrameTick found at: 0x" + _offset_framelock.ToString("X")); Debug.WriteLine("fFrameTick found at: 0x" + _offset_framelock.ToString("X"));
if (!IsValidAddress(_offset_framelock)) if (!IsValidAddress(_offset_framelock))
{ {
_offset_framelock = patternScan.FindPatternInternal(GameData.PATTERN_FRAMELOCK_FUZZY, GameData.PATTERN_FRAMELOCK_FUZZY_MASK, ' ') + GameData.PATTERN_FRAMELOCK_FUZZY_OFFSET; _offset_framelock = patternScan.FindPattern(GameData.PATTERN_FRAMELOCK_FUZZY) + GameData.PATTERN_FRAMELOCK_FUZZY_OFFSET;
Debug.WriteLine("2. fFrameTick found at: 0x" + _offset_framelock.ToString("X")); Debug.WriteLine("2. fFrameTick found at: 0x" + _offset_framelock.ToString("X"));
} }
if (!IsValidAddress(_offset_framelock)) if (!IsValidAddress(_offset_framelock))
_offset_framelock = 0x0; _offset_framelock = 0x0;
_offset_framelock_speed_fix = patternScan.FindPatternInternal(GameData.PATTERN_FRAMELOCK_SPEED_FIX, GameData.PATTERN_FRAMELOCK_SPEED_FIX_MASK, ' ') + GameData.PATTERN_FRAMELOCK_SPEED_FIX_OFFSET; _offset_framelock_speed_fix = patternScan.FindPattern(GameData.PATTERN_FRAMELOCK_SPEED_FIX) + GameData.PATTERN_FRAMELOCK_SPEED_FIX_OFFSET;
Debug.WriteLine("pFrametimeRunningSpeed at: 0x" + _offset_framelock_speed_fix.ToString("X")); Debug.WriteLine("pFrametimeRunningSpeed at: 0x" + _offset_framelock_speed_fix.ToString("X"));
if (!IsValidAddress(_offset_framelock_speed_fix)) if (!IsValidAddress(_offset_framelock_speed_fix))
_offset_framelock_speed_fix = 0x0; _offset_framelock_speed_fix = 0x0;
_offset_resolution_default = patternScan.FindPatternInternal((int)SystemParameters.PrimaryScreenWidth > 1280 ? GameData.PATTERN_RESOLUTION_DEFAULT : GameData.PATTERN_RESOLUTION_DEFAULT_720, GameData.PATTERN_RESOLUTION_DEFAULT_MASK, ' '); _offset_resolution_default = patternScan.FindPattern((int)SystemParameters.PrimaryScreenWidth > 1280 ? GameData.PATTERN_RESOLUTION_DEFAULT : GameData.PATTERN_RESOLUTION_DEFAULT_720);
Debug.WriteLine("default resolution found at: 0x" + _offset_resolution_default.ToString("X")); Debug.WriteLine("default resolution found at: 0x" + _offset_resolution_default.ToString("X"));
if (!IsValidAddress(_offset_resolution_default)) if (!IsValidAddress(_offset_resolution_default))
_offset_resolution_default = 0x0; _offset_resolution_default = 0x0;
_offset_resolution_scaling_fix = patternScan.FindPatternInternal(GameData.PATTERN_RESOLUTION_SCALING_FIX, GameData.PATTERN_RESOLUTION_SCALING_FIX_MASK, ' ') + GameData.PATTERN_RESOLUTION_SCALING_FIX_OFFSET; _offset_resolution_scaling_fix = patternScan.FindPattern(GameData.PATTERN_RESOLUTION_SCALING_FIX) + GameData.PATTERN_RESOLUTION_SCALING_FIX_OFFSET;
Debug.WriteLine("scaling fix found at: 0x" + _offset_resolution_scaling_fix.ToString("X")); Debug.WriteLine("scaling fix found at: 0x" + _offset_resolution_scaling_fix.ToString("X"));
if (!IsValidAddress(_offset_resolution_scaling_fix)) if (!IsValidAddress(_offset_resolution_scaling_fix))
_offset_resolution_scaling_fix = 0x0; _offset_resolution_scaling_fix = 0x0;
long ref_pCurrentResolutionWidth = patternScan.FindPatternInternal(GameData.PATTERN_RESOLUTION_POINTER, GameData.PATTERN_RESOLUTION_POINTER_MASK, ' ') + GameData.PATTERN_RESOLUTION_POINTER_OFFSET; long ref_lpCurrentResolutionWidth = patternScan.FindPattern(GameData.PATTERN_RESOLUTION_POINTER) + GameData.PATTERN_RESOLUTION_POINTER_OFFSET;
Debug.WriteLine("ref_pCurrentResolutionWidth found at: 0x" + ref_pCurrentResolutionWidth.ToString("X")); Debug.WriteLine("ref_lpCurrentResolutionWidth found at: 0x" + ref_lpCurrentResolutionWidth.ToString("X"));
if (IsValidAddress(ref_pCurrentResolutionWidth)) if (IsValidAddress(ref_lpCurrentResolutionWidth))
{ {
_offset_resolution = DereferenceStaticX64Pointer(_gameAccessHwnd, ref_pCurrentResolutionWidth, GameData.PATTERN_RESOLUTION_POINTER_INSTRUCTION_LENGTH); _offset_resolution = DereferenceStaticX64Pointer(_gameAccessHwnd, ref_lpCurrentResolutionWidth, GameData.PATTERN_RESOLUTION_POINTER_INSTRUCTION_LENGTH);
Debug.WriteLine("pCurrentResolutionWidth at: 0x" + _offset_resolution.ToString("X")); Debug.WriteLine("lpCurrentResolutionWidth at: 0x" + _offset_resolution.ToString("X"));
if (!IsValidAddress(_offset_resolution)) if (!IsValidAddress(_offset_resolution))
_offset_resolution = 0x0; _offset_resolution = 0x0;
} }
_offset_fovsetting = patternScan.FindPatternInternal(GameData.PATTERN_FOVSETTING, GameData.PATTERN_FOVSETTING_MASK, ' ') + GameData.PATTERN_FOVSETTING_OFFSET; _offset_fovsetting = patternScan.FindPattern(GameData.PATTERN_FOVSETTING) + GameData.PATTERN_FOVSETTING_OFFSET;
Debug.WriteLine("pFovTableEntry found at: 0x" + _offset_fovsetting.ToString("X")); Debug.WriteLine("pFovTableEntry found at: 0x" + _offset_fovsetting.ToString("X"));
if (!IsValidAddress(_offset_fovsetting)) if (!IsValidAddress(_offset_fovsetting))
_offset_fovsetting = 0x0; _offset_fovsetting = 0x0;
long ref_pPlayerStatsRelated = patternScan.FindPatternInternal(GameData.PATTERN_PLAYER_DEATHS, GameData.PATTERN_PLAYER_DEATHS_MASK, ' ') + GameData.PATTERN_PLAYER_DEATHS_OFFSET; long ref_lpPlayerStatsRelated = patternScan.FindPattern(GameData.PATTERN_PLAYER_DEATHS) + GameData.PATTERN_PLAYER_DEATHS_OFFSET;
Debug.WriteLine("ref_pPlayerStatsRelated found at: 0x" + ref_pPlayerStatsRelated.ToString("X")); Debug.WriteLine("ref_lpPlayerStatsRelated found at: 0x" + ref_lpPlayerStatsRelated.ToString("X"));
if (IsValidAddress(ref_pPlayerStatsRelated)) if (IsValidAddress(ref_lpPlayerStatsRelated))
{ {
long pPlayerStatsRelated = DereferenceStaticX64Pointer(_gameAccessHwndStatic, ref_pPlayerStatsRelated, GameData.PATTERN_PLAYER_DEATHS_INSTRUCTION_LENGTH); long lpPlayerStatsRelated = DereferenceStaticX64Pointer(_gameAccessHwndStatic, ref_lpPlayerStatsRelated, GameData.PATTERN_PLAYER_DEATHS_INSTRUCTION_LENGTH);
Debug.WriteLine("pPlayerStatsRelated found at: 0x" + pPlayerStatsRelated.ToString("X")); Debug.WriteLine("lpPlayerStatsRelated found at: 0x" + lpPlayerStatsRelated.ToString("X"));
if (IsValidAddress(pPlayerStatsRelated)) if (IsValidAddress(lpPlayerStatsRelated))
{ {
int playerStatsToDeathsOffset = Read<Int32>(_gameAccessHwndStatic, ref_pPlayerStatsRelated + GameData.PATTERN_PLAYER_DEATHS_POINTER_OFFSET_OFFSET); int dwPlayerStatsToDeathsOffset = Read<Int32>(_gameAccessHwndStatic, ref_lpPlayerStatsRelated + GameData.PATTERN_PLAYER_DEATHS_POINTER_OFFSET_OFFSET);
Debug.WriteLine("offset pPlayerStats->iPlayerDeaths found : 0x" + playerStatsToDeathsOffset.ToString("X")); Debug.WriteLine("offset pPlayerStats->iPlayerDeaths found : 0x" + dwPlayerStatsToDeathsOffset.ToString("X"));
if (playerStatsToDeathsOffset > 0) if (dwPlayerStatsToDeathsOffset > 0)
_offset_player_deaths = Read<Int64>(_gameAccessHwndStatic, pPlayerStatsRelated) + playerStatsToDeathsOffset; _offset_player_deaths = Read<Int64>(_gameAccessHwndStatic, lpPlayerStatsRelated) + dwPlayerStatsToDeathsOffset;
Debug.WriteLine("iPlayerDeaths found at: 0x" + _offset_player_deaths.ToString("X")); Debug.WriteLine("iPlayerDeaths found at: 0x" + _offset_player_deaths.ToString("X"));
} }
} }
if (!IsValidAddress(_offset_player_deaths)) if (!IsValidAddress(_offset_player_deaths))
_offset_player_deaths = 0x0; _offset_player_deaths = 0x0;
long ref_pTotalKills = patternScan.FindPatternInternal(GameData.PATTERN_TOTAL_KILLS, GameData.PATTERN_TOTAL_KILLS_MASK, ' '); long ref_lpTotalKills = patternScan.FindPattern(GameData.PATTERN_TOTAL_KILLS);
Debug.WriteLine("ref_pTotalKills found at: 0x" + ref_pTotalKills.ToString("X")); Debug.WriteLine("ref_lpTotalKills found at: 0x" + ref_lpTotalKills.ToString("X"));
if (IsValidAddress(ref_pTotalKills)) if (IsValidAddress(ref_lpTotalKills))
{ {
_offset_total_kills = DereferenceStaticX64Pointer(_gameAccessHwndStatic, ref_pTotalKills, GameData.PATTERN_TOTAL_KILLS_INSTRUCTION_LENGTH); _offset_total_kills = DereferenceStaticX64Pointer(_gameAccessHwndStatic, ref_lpTotalKills, GameData.PATTERN_TOTAL_KILLS_INSTRUCTION_LENGTH);
if (!IsValidAddress(_offset_total_kills)) if (!IsValidAddress(_offset_total_kills))
_offset_total_kills = 0x0; _offset_total_kills = 0x0;
} }
long ref_pTimeRelated = patternScan.FindPatternInternal(GameData.PATTERN_TIMESCALE, GameData.PATTERN_TIMESCALE_MASK, ' '); long ref_lpTimeRelated = patternScan.FindPattern(GameData.PATTERN_TIMESCALE);
Debug.WriteLine("ref_pTimeRelated found at: 0x" + ref_pTimeRelated.ToString("X")); Debug.WriteLine("ref_lpTimeRelated found at: 0x" + ref_lpTimeRelated.ToString("X"));
if (IsValidAddress(ref_pTimeRelated)) if (IsValidAddress(ref_lpTimeRelated))
{ {
long pTimescaleManager = DereferenceStaticX64Pointer(_gameAccessHwndStatic, ref_pTimeRelated, GameData.PATTERN_TIMESCALE_INSTRUCTION_LENGTH); long lpTimescaleManager = DereferenceStaticX64Pointer(_gameAccessHwndStatic, ref_lpTimeRelated, GameData.PATTERN_TIMESCALE_INSTRUCTION_LENGTH);
Debug.WriteLine("pTimescaleManager found at: 0x" + pTimescaleManager.ToString("X")); Debug.WriteLine("lpTimescaleManager found at: 0x" + lpTimescaleManager.ToString("X"));
if (IsValidAddress(pTimescaleManager)) if (IsValidAddress(lpTimescaleManager))
{ {
_offset_timescale = Read<Int64>(_gameAccessHwndStatic, pTimescaleManager) + Read<Int32>(_gameAccessHwndStatic, ref_pTimeRelated + GameData.PATTERN_TIMESCALE_POINTER_OFFSET_OFFSET); _offset_timescale = Read<Int64>(_gameAccessHwndStatic, lpTimescaleManager) + Read<Int32>(_gameAccessHwndStatic, ref_lpTimeRelated + GameData.PATTERN_TIMESCALE_POINTER_OFFSET_OFFSET);
Debug.WriteLine("fTimescale found at: 0x" + _offset_timescale.ToString("X")); Debug.WriteLine("fTimescale found at: 0x" + _offset_timescale.ToString("X"));
if (!IsValidAddress(_offset_timescale)) if (!IsValidAddress(_offset_timescale))
_offset_timescale = 0x0; _offset_timescale = 0x0;
} }
} }
long pPlayerStructRelated1 = patternScan.FindPatternInternal(GameData.PATTERN_TIMESCALE_PLAYER, GameData.PATTERN_TIMESCALE_PLAYER_MASK, ' '); long lpPlayerStructRelated1 = patternScan.FindPattern(GameData.PATTERN_TIMESCALE_PLAYER);
Debug.WriteLine("pPlayerStructRelated1 found at: 0x" + pPlayerStructRelated1.ToString("X")); Debug.WriteLine("lpPlayerStructRelated1 found at: 0x" + lpPlayerStructRelated1.ToString("X"));
if (IsValidAddress(pPlayerStructRelated1)) if (IsValidAddress(lpPlayerStructRelated1))
{ {
long pPlayerStructRelated2 = DereferenceStaticX64Pointer(_gameAccessHwndStatic, pPlayerStructRelated1, GameData.PATTERN_TIMESCALE_PLAYER_INSTRUCTION_LENGTH); long lpPlayerStructRelated2 = DereferenceStaticX64Pointer(_gameAccessHwndStatic, lpPlayerStructRelated1, GameData.PATTERN_TIMESCALE_PLAYER_INSTRUCTION_LENGTH);
Debug.WriteLine("pPlayerStructRelated2 found at: 0x" + pPlayerStructRelated2.ToString("X")); Debug.WriteLine("lpPlayerStructRelated2 found at: 0x" + lpPlayerStructRelated2.ToString("X"));
if (IsValidAddress(pPlayerStructRelated2)) if (IsValidAddress(lpPlayerStructRelated2))
{ {
_offset_timescale_player_pointer_start = pPlayerStructRelated2; _offset_timescale_player_pointer_start = lpPlayerStructRelated2;
long pPlayerStructRelated3 = Read<Int64>(_gameAccessHwndStatic, pPlayerStructRelated2) + GameData.PATTERN_TIMESCALE_POINTER2_OFFSET; long lpPlayerStructRelated3 = Read<Int64>(_gameAccessHwndStatic, lpPlayerStructRelated2) + GameData.PATTERN_TIMESCALE_POINTER2_OFFSET;
Debug.WriteLine("pPlayerStructRelated3 found at: 0x" + pPlayerStructRelated3.ToString("X")); Debug.WriteLine("lpPlayerStructRelated3 found at: 0x" + lpPlayerStructRelated3.ToString("X"));
if (IsValidAddress(pPlayerStructRelated3)) if (IsValidAddress(lpPlayerStructRelated3))
{ {
long pPlayerStructRelated4 = Read<Int64>(_gameAccessHwndStatic, pPlayerStructRelated3) + GameData.PATTERN_TIMESCALE_POINTER3_OFFSET; long lpPlayerStructRelated4 = Read<Int64>(_gameAccessHwndStatic, lpPlayerStructRelated3) + GameData.PATTERN_TIMESCALE_POINTER3_OFFSET;
Debug.WriteLine("pPlayerStructRelated4 found at: 0x" + pPlayerStructRelated4.ToString("X")); Debug.WriteLine("lpPlayerStructRelated4 found at: 0x" + lpPlayerStructRelated4.ToString("X"));
if (IsValidAddress(pPlayerStructRelated4)) if (IsValidAddress(lpPlayerStructRelated4))
{ {
long pPlayerStructRelated5 = Read<Int64>(_gameAccessHwndStatic, pPlayerStructRelated4) + GameData.PATTERN_TIMESCALE_POINTER4_OFFSET; long lpPlayerStructRelated5 = Read<Int64>(_gameAccessHwndStatic, lpPlayerStructRelated4) + GameData.PATTERN_TIMESCALE_POINTER4_OFFSET;
Debug.WriteLine("pPlayerStructRelated5 found at: 0x" + pPlayerStructRelated5.ToString("X")); Debug.WriteLine("lpPlayerStructRelated5 found at: 0x" + lpPlayerStructRelated5.ToString("X"));
if (IsValidAddress(pPlayerStructRelated5)) if (IsValidAddress(lpPlayerStructRelated5))
{ {
_offset_timescale_player = Read<Int64>(_gameAccessHwndStatic, pPlayerStructRelated5) + GameData.PATTERN_TIMESCALE_POINTER5_OFFSET; _offset_timescale_player = Read<Int64>(_gameAccessHwndStatic, lpPlayerStructRelated5) + GameData.PATTERN_TIMESCALE_POINTER5_OFFSET;
Debug.WriteLine("fTimescalePlayer found at: 0x" + _offset_timescale_player.ToString("X")); Debug.WriteLine("fTimescalePlayer found at: 0x" + _offset_timescale_player.ToString("X"));
if (!IsValidAddress(_offset_timescale_player)) if (!IsValidAddress(_offset_timescale_player))
_offset_timescale_player = 0x0; _offset_timescale_player = 0x0;
@ -379,6 +393,31 @@ namespace SekiroFpsUnlockAndMore
} }
} }
} }
long lpCamAdjustPitch = patternScan.FindPattern(GameData.PATTERN_CAMADJUST_PITCH);
long lpCamAdjustYawZ = patternScan.FindPattern(GameData.PATTERN_CAMADJUST_YAW_Z) + GameData.PATTERN_CAMADJUST_YAW_Z_OFFSET;
long lpCamAdjustPitchXY = patternScan.FindPattern(GameData.PATTERN_CAMADJUST_PITCH_XY);
long lpCamAdjustYawXY = patternScan.FindPattern(GameData.PATTERN_CAMADJUST_YAW_XY) + GameData.PATTERN_CAMADJUST_YAW_XY_OFFSET;
Debug.WriteLine("lpCamAdjustPitch found at: 0x" + lpCamAdjustPitch.ToString("X"));
Debug.WriteLine("lpCamAdjustYawZ found at: 0x" + lpCamAdjustYawZ.ToString("X"));
Debug.WriteLine("lpCamAdjustPitchXY found at: 0x" + lpCamAdjustPitchXY.ToString("X"));
Debug.WriteLine("lpCamAdjustYawXY found at: 0x" + lpCamAdjustYawXY.ToString("X"));
if (IsValidAddress(lpCamAdjustPitch) && IsValidAddress(lpCamAdjustYawZ) && IsValidAddress(lpCamAdjustPitchXY) && IsValidAddress(lpCamAdjustYawXY))
{
List<bool> results = new List<bool>
{
_codeCaveGenerator.CreateNewCodeCave(_CODECAVE_CAMADJUST_PITCH, lpCamAdjustPitch, GameData.INJECT_CAMADJUST_PITCH_OVERWRITE_LENGTH, GameData.INJECT_CAMADJUST_PITCH_SHELLCODE),
_codeCaveGenerator.CreateNewCodeCave(_CODECAVE_CAMADJUST_YAW_Z, lpCamAdjustYawZ, GameData.INJECT_CAMADJUST_YAW_Z_OVERWRITE_LENGTH, GameData.INJECT_CAMADJUST_YAW_Z_SHELLCODE),
_codeCaveGenerator.CreateNewCodeCave(_CODECAVE_CAMADJUST_PITCH_XY, lpCamAdjustPitchXY, GameData.INJECT_CAMADJUST_PITCH_XY_OVERWRITE_LENGTH, GameData.INJECT_CAMADJUST_PITCH_XY_SHELLCODE),
_codeCaveGenerator.CreateNewCodeCave(_CODECAVE_CAMADJUST_YAW_XY, lpCamAdjustYawXY, GameData.INJECT_CAMADJUST_YAW_XY_OVERWRITE_LENGTH, GameData.INJECT_CAMADJUST_YAW_XY_SHELLCODE)
};
Debug.WriteLine("lpCamAdjustPitch code cave at: 0x" + _codeCaveGenerator.GetCodeCaveAddressByName(_CODECAVE_CAMADJUST_PITCH).ToString("X"));
Debug.WriteLine("lpCamAdjustYawZ code cave at: 0x" + _codeCaveGenerator.GetCodeCaveAddressByName(_CODECAVE_CAMADJUST_YAW_Z).ToString("X"));
Debug.WriteLine("lpCamAdjustPitchXY code cave at: 0x" + _codeCaveGenerator.GetCodeCaveAddressByName(_CODECAVE_CAMADJUST_PITCH_XY).ToString("X"));
Debug.WriteLine("lpCamAdjustYawXY code cave at: 0x" + _codeCaveGenerator.GetCodeCaveAddressByName(_CODECAVE_CAMADJUST_YAW_XY).ToString("X"));
if (results.IndexOf(false) < 0)
_codeCavesGenerated = true;
}
} }
/// <summary> /// <summary>
@ -444,6 +483,13 @@ namespace SekiroFpsUnlockAndMore
this.cbBorderless.IsEnabled = true; this.cbBorderless.IsEnabled = true;
if (!_codeCavesGenerated)
{
UpdateStatus("cam adjust not found...", Brushes.Red);
LogToFile("cam adjust not found...");
}
this.cbCamAdjust.IsEnabled = _codeCavesGenerated;
if (_offset_timescale == 0x0) if (_offset_timescale == 0x0)
{ {
UpdateStatus("timescale not found...", Brushes.Red); UpdateStatus("timescale not found...", Brushes.Red);
@ -460,6 +506,7 @@ namespace SekiroFpsUnlockAndMore
this.bPatch.IsEnabled = true; this.bPatch.IsEnabled = true;
_running = true; _running = true;
PatchGame(); PatchGame();
InjectToGame();
} }
/// <summary> /// <summary>
@ -470,16 +517,16 @@ namespace SekiroFpsUnlockAndMore
bool valid = false; bool valid = false;
if (_offset_timescale_player_pointer_start > 0) if (_offset_timescale_player_pointer_start > 0)
{ {
long pPlayerStructRelated3 = Read<Int64>(_gameAccessHwndStatic, _offset_timescale_player_pointer_start) + GameData.PATTERN_TIMESCALE_POINTER2_OFFSET; long lpPlayerStructRelated3 = Read<Int64>(_gameAccessHwndStatic, _offset_timescale_player_pointer_start) + GameData.PATTERN_TIMESCALE_POINTER2_OFFSET;
if (IsValidAddress(pPlayerStructRelated3)) if (IsValidAddress(lpPlayerStructRelated3))
{ {
long pPlayerStructRelated4 = Read<Int64>(_gameAccessHwndStatic, pPlayerStructRelated3) + GameData.PATTERN_TIMESCALE_POINTER3_OFFSET; long lpPlayerStructRelated4 = Read<Int64>(_gameAccessHwndStatic, lpPlayerStructRelated3) + GameData.PATTERN_TIMESCALE_POINTER3_OFFSET;
if (IsValidAddress(pPlayerStructRelated4)) if (IsValidAddress(lpPlayerStructRelated4))
{ {
long pPlayerStructRelated5 = Read<Int64>(_gameAccessHwndStatic, pPlayerStructRelated4) + GameData.PATTERN_TIMESCALE_POINTER4_OFFSET; long lpPlayerStructRelated5 = Read<Int64>(_gameAccessHwndStatic, lpPlayerStructRelated4) + GameData.PATTERN_TIMESCALE_POINTER4_OFFSET;
if (IsValidAddress(pPlayerStructRelated5)) if (IsValidAddress(lpPlayerStructRelated5))
{ {
_offset_timescale_player = Read<Int64>(_gameAccessHwndStatic, pPlayerStructRelated5) + GameData.PATTERN_TIMESCALE_POINTER5_OFFSET; _offset_timescale_player = Read<Int64>(_gameAccessHwndStatic, lpPlayerStructRelated5) + GameData.PATTERN_TIMESCALE_POINTER5_OFFSET;
if (IsValidAddress(_offset_timescale_player)) if (IsValidAddress(_offset_timescale_player))
valid = true; valid = true;
} }
@ -519,10 +566,13 @@ namespace SekiroFpsUnlockAndMore
_offset_timescale = 0x0; _offset_timescale = 0x0;
_offset_timescale_player = 0x0; _offset_timescale_player = 0x0;
_offset_timescale_player_pointer_start = 0x0; _offset_timescale_player_pointer_start = 0x0;
_codeCaveGenerator.ClearCodeCaves();
_codeCaveGenerator = null;
this.cbFramelock.IsEnabled = true; this.cbFramelock.IsEnabled = true;
this.cbAddResolution.IsEnabled = true; this.cbAddResolution.IsEnabled = true;
this.cbFov.IsEnabled = true; this.cbFov.IsEnabled = true;
this.cbBorderless.IsEnabled = false; this.cbBorderless.IsEnabled = false;
this.cbCamAdjust.IsEnabled = true;
this.bPatch.IsEnabled = false; this.bPatch.IsEnabled = false;
this.cbGameSpeed.IsEnabled = true; this.cbGameSpeed.IsEnabled = true;
this.cbPlayerSpeed.IsEnabled = true; this.cbPlayerSpeed.IsEnabled = true;
@ -783,7 +833,7 @@ namespace SekiroFpsUnlockAndMore
} }
/// <summary> /// <summary>
/// Patch up this broken port of a game /// Patch up this broken port of a game.
/// </summary> /// </summary>
private void PatchGame() private void PatchGame()
{ {
@ -804,6 +854,33 @@ namespace SekiroFpsUnlockAndMore
UpdateStatus(DateTime.Now.ToString("HH:mm:ss") + " Game unpatched!", Brushes.White); UpdateStatus(DateTime.Now.ToString("HH:mm:ss") + " Game unpatched!", Brushes.White);
} }
/// <summary>
/// Inject or eject code to game using code caves.
/// </summary>
private void InjectToGame()
{
if (!CanPatchGame() || !_codeCavesGenerated) return;
if (this.cbCamAdjust.IsChecked == true)
{
this.IsEnabled = false;
_codeCaveGenerator.ActivateCodeCaveByName(_CODECAVE_CAMADJUST_PITCH);
_codeCaveGenerator.ActivateCodeCaveByName(_CODECAVE_CAMADJUST_YAW_Z);
_codeCaveGenerator.ActivateCodeCaveByName(_CODECAVE_CAMADJUST_PITCH_XY);
_codeCaveGenerator.ActivateCodeCaveByName(_CODECAVE_CAMADJUST_YAW_XY);
this.IsEnabled = true;
}
else
{
this.IsEnabled = false;
_codeCaveGenerator.DeactivateCodeCaveByName(_CODECAVE_CAMADJUST_PITCH);
_codeCaveGenerator.DeactivateCodeCaveByName(_CODECAVE_CAMADJUST_YAW_Z);
_codeCaveGenerator.DeactivateCodeCaveByName(_CODECAVE_CAMADJUST_PITCH_XY);
_codeCaveGenerator.DeactivateCodeCaveByName(_CODECAVE_CAMADJUST_YAW_XY);
this.IsEnabled = true;
}
}
/// <summary> /// <summary>
/// Freeze values in memory that can't be patched to require no freezing easily. /// Freeze values in memory that can't be patched to require no freezing easily.
/// </summary> /// </summary>
@ -972,14 +1049,14 @@ namespace SekiroFpsUnlockAndMore
/// Reads a given type from processes memory using a generic method. /// Reads a given type from processes memory using a generic method.
/// </summary> /// </summary>
/// <typeparam name="T">The base type to read.</typeparam> /// <typeparam name="T">The base type to read.</typeparam>
/// <param name="gameProc">The process handle to read from.</param> /// <param name="hProcess">The process handle to read from.</param>
/// <param name="lpBaseAddress">The address to read from.</param> /// <param name="lpBaseAddress">The address to read from.</param>
/// <returns>The given base type read from memory.</returns> /// <returns>The given base type read from memory.</returns>
/// <remarks>GCHandle and Marshal are costy.</remarks> /// <remarks>GCHandle and Marshal are costy.</remarks>
private static T Read<T>(IntPtr gameProc, Int64 lpBaseAddress) private static T Read<T>(IntPtr hProcess, Int64 lpBaseAddress)
{ {
byte[] lpBuffer = new byte[Marshal.SizeOf(typeof(T))]; byte[] lpBuffer = new byte[Marshal.SizeOf(typeof(T))];
ReadProcessMemory(gameProc, lpBaseAddress, lpBuffer, (ulong)lpBuffer.Length, out _); ReadProcessMemory(hProcess, lpBaseAddress, lpBuffer, (ulong)lpBuffer.Length, out _);
GCHandle gcHandle = GCHandle.Alloc(lpBuffer, GCHandleType.Pinned); GCHandle gcHandle = GCHandle.Alloc(lpBuffer, GCHandleType.Pinned);
T structure = (T)Marshal.PtrToStructure(gcHandle.AddrOfPinnedObject(), typeof(T)); T structure = (T)Marshal.PtrToStructure(gcHandle.AddrOfPinnedObject(), typeof(T));
gcHandle.Free(); gcHandle.Free();
@ -989,13 +1066,13 @@ namespace SekiroFpsUnlockAndMore
/// <summary> /// <summary>
/// Writes a given type and value to processes memory using a generic method. /// Writes a given type and value to processes memory using a generic method.
/// </summary> /// </summary>
/// <param name="gameProc">The process handle to read from.</param> /// <param name="hProcess">The process handle to read from.</param>
/// <param name="lpBaseAddress">The address to write from.</param> /// <param name="lpBaseAddress">The address to write from.</param>
/// <param name="bytes">The byte array to write.</param> /// <param name="bytes">The byte array to write.</param>
/// <returns>True if successful, false otherwise.</returns> /// <returns>True if successful, false otherwise.</returns>
private static bool WriteBytes(IntPtr gameProc, Int64 lpBaseAddress, byte[] bytes) private static bool WriteBytes(IntPtr hProcess, Int64 lpBaseAddress, byte[] bytes)
{ {
return WriteProcessMemory(gameProc, lpBaseAddress, bytes, (ulong)bytes.Length, out _); return WriteProcessMemory(hProcess, lpBaseAddress, bytes, (ulong)bytes.Length, out _);
} }
/// <summary> /// <summary>
@ -1025,7 +1102,7 @@ namespace SekiroFpsUnlockAndMore
/// Logs messages to log file /// Logs messages to log file
/// </summary> /// </summary>
/// <param name="msg">The message to write to file.</param> /// <param name="msg">The message to write to file.</param>
private void LogToFile(string msg) internal static void LogToFile(string msg)
{ {
string timedMsg = "[" + DateTime.Now + "] " + msg; string timedMsg = "[" + DateTime.Now + "] " + msg;
Debug.WriteLine(timedMsg); Debug.WriteLine(timedMsg);
@ -1121,6 +1198,11 @@ namespace SekiroFpsUnlockAndMore
PatchWindow(); PatchWindow();
} }
private void CbCamAdjust_Check_Handler(object sender, RoutedEventArgs e)
{
InjectToGame();
}
private void CbStatChanged(object sender, RoutedEventArgs e) private void CbStatChanged(object sender, RoutedEventArgs e)
{ {
_statLoggingEnabled = cbLogStats.IsChecked == true; _statLoggingEnabled = cbLogStats.IsChecked == true;

View file

@ -14,7 +14,7 @@ namespace SekiroFpsUnlockAndMore
/// </summary> /// </summary>
/// <param name="hProcess">Handle to the process in whose memory pattern will be searched for.</param> /// <param name="hProcess">Handle to the process in whose memory pattern will be searched for.</param>
/// <param name="pModule">Module which will be searched for the pattern.</param> /// <param name="pModule">Module which will be searched for the pattern.</param>
public PatternScan(IntPtr hProcess, ProcessModule pModule) internal PatternScan(IntPtr hProcess, ProcessModule pModule)
{ {
if (IntPtr.Size == 4) if (IntPtr.Size == 4)
dwStart = (uint)pModule.BaseAddress; dwStart = (uint)pModule.BaseAddress;
@ -24,11 +24,15 @@ namespace SekiroFpsUnlockAndMore
bData = new byte[nSize]; bData = new byte[nSize];
if (!ReadProcessMemory(hProcess, dwStart, bData, nSize, out IntPtr lpNumberOfBytesRead)) if (!ReadProcessMemory(hProcess, dwStart, bData, nSize, out IntPtr lpNumberOfBytesRead))
throw new Exception("ReadProcessMemory error!"); {
if (lpNumberOfBytesRead.ToInt64() != nSize) MainWindow.LogToFile("Could not read memory in PatternScan()!");
throw new Exception("ReadProcessMemory error!"); return;
if (bData == null || bData.Length == 0) }
throw new Exception("Could not read memory in PatternScan."); if (lpNumberOfBytesRead.ToInt64() != nSize || bData == null || bData.Length == 0)
{
MainWindow.LogToFile("ReadProcessMemory error in PatternScan()!");
return;
}
} }
~PatternScan() ~PatternScan()
@ -40,19 +44,28 @@ namespace SekiroFpsUnlockAndMore
/// <summary> /// <summary>
/// Finds a pattern or signature inside initialized process. /// Finds a pattern or signature inside initialized process.
/// </summary> /// </summary>
/// <param name="szPattern">A character-delimited string representing the pattern to be found.</param> /// <param name="szPattern">A character-delimited string representing the pattern to be found, '??' act as a wildcard.</param>
/// <param name="szMask">A string of 'x' (match), '!' (not-match), or '?' (wildcard).</param> /// <param name="cDelimiter">Determines how the string will be split. Defaults is ' '.</param>
/// <param name="cDelimiter">Determines how the string will be split. If null, defaults to ' '.</param>
/// <returns>The address of the beginning of the pattern if found, 0 if not found.</returns> /// <returns>The address of the beginning of the pattern if found, 0 if not found.</returns>
internal Int64 FindPatternInternal(string szPattern, string szMask, char cDelimiter = ' ') internal Int64 FindPattern(string szPattern, char cDelimiter = ' ')
{ {
string[] saPattern = szPattern.Split(cDelimiter); string[] saPattern = szPattern.Split(cDelimiter);
string szMask = "";
for (int i = 0; i < saPattern.Length; i++)
{
if (saPattern[i] == "??")
{
szMask += "?";
saPattern[i] = "0";
}
else szMask += "x";
}
byte[] bPattern = new byte[saPattern.Length]; byte[] bPattern = new byte[saPattern.Length];
for (int i = 0; i < saPattern.Length; i++) for (int i = 0; i < saPattern.Length; i++)
bPattern[i] = Convert.ToByte(saPattern[i], 0x10); bPattern[i] = Convert.ToByte(saPattern[i], 0x10);
if (bPattern == null || bPattern.Length == 0) if (bPattern == null || bPattern.Length == 0)
throw new ArgumentNullException("Pattern's length is zero!"); throw new ArgumentException("Pattern's length is zero!");
if (bPattern.Length != szMask.Length) if (bPattern.Length != szMask.Length)
throw new ArgumentException("Pattern's bytes and szMask must be of the same size!"); throw new ArgumentException("Pattern's bytes and szMask must be of the same size!");
@ -67,78 +80,18 @@ namespace SekiroFpsUnlockAndMore
bFound = true; bFound = true;
for (iy = 0; iy < patternLength; iy++) for (iy = 0; iy < patternLength; iy++)
{ {
if ((szMask[iy] == 'x' && bPattern[iy] != bData[ix + iy]) || if (szMask[iy] != 'x' || bPattern[iy] == bData[ix + iy]) continue;
(szMask[iy] == '!' && bPattern[iy] == bData[ix + iy]))
{
bFound = false; bFound = false;
break; break;
} }
}
if (bFound) if (bFound)
return Convert.ToInt64((long)dwStart + ix); return Convert.ToInt64((long) dwStart + ix);
} }
return 0; return 0;
} }
/// <summary>
/// Finds a pattern or signature inside another process's memory.
/// </summary>
/// <param name="hProcess">Handle to the process in whose memory pattern will be searched for.</param>
/// <param name="pModule">Module which will be searched for the pattern.</param>
/// <param name="szPattern">A character-delimited string representing the pattern to be found.</param>
/// <param name="szMask">A string of 'x' (match), '!' (not-match), or '?' (wildcard).</param>
/// <param name="cDelimiter">Determines how the string will be split. If null, defaults to ' '.</param>
/// <returns>The address of the beginning of the pattern if found, 0 if not found.</returns>
internal static Int64 FindPattern(IntPtr hProcess, ProcessModule pModule, 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 dwStart = 0;
if (IntPtr.Size == 4)
dwStart = (uint)pModule.BaseAddress;
else if (IntPtr.Size == 8)
dwStart = (long)pModule.BaseAddress;
int nSize = pModule.ModuleMemorySize;
byte[] 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.");
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;
}
[DllImport("kernel32.dll", SetLastError = true)] [DllImport("kernel32.dll", SetLastError = true)]
private static extern bool ReadProcessMemory( private static extern bool ReadProcessMemory(

View file

@ -5,7 +5,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyTitle("SekiroFpsUnlockAndMore")] [assembly: AssemblyTitle("SekiroFpsUnlockAndMore")]
[assembly: AssemblyDescription("")] [assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")] [assembly: AssemblyCompany("uberhalit")]
[assembly: AssemblyProduct("SekiroFpsUnlockAndMore")] [assembly: AssemblyProduct("SekiroFpsUnlockAndMore")]
[assembly: AssemblyCopyright("Copyright © uberhalit 2019")] [assembly: AssemblyCopyright("Copyright © uberhalit 2019")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
@ -21,5 +21,5 @@ using System.Runtime.InteropServices;
ResourceDictionaryLocation.SourceAssembly ResourceDictionaryLocation.SourceAssembly
)] )]
[assembly: AssemblyVersion("1.2.0.0")] [assembly: AssemblyVersion("1.2.1.0")]
[assembly: AssemblyFileVersion("1.2.0.0")] [assembly: AssemblyFileVersion("1.2.1.0")]

View file

@ -62,6 +62,7 @@
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
</ApplicationDefinition> </ApplicationDefinition>
<Compile Include="CodeCaveGenerator.cs" />
<Compile Include="SettingsService.cs" /> <Compile Include="SettingsService.cs" />
<Compile Include="StatusViewModel.cs" /> <Compile Include="StatusViewModel.cs" />
<Page Include="MainWindow.xaml"> <Page Include="MainWindow.xaml">

View file

@ -14,6 +14,7 @@ namespace SekiroFpsUnlockAndMore
*/ */
[XmlElement] [XmlElement]
public bool gameVersionNotify { get; set; } public bool gameVersionNotify { get; set; }
[XmlElement]
public bool cbFramelock { get; set; } public bool cbFramelock { get; set; }
[XmlElement] [XmlElement]
public int tbFramelock { get; set; } public int tbFramelock { get; set; }
@ -32,6 +33,8 @@ namespace SekiroFpsUnlockAndMore
[XmlElement] [XmlElement]
public bool cbBorderlessStretch { get; set; } public bool cbBorderlessStretch { get; set; }
[XmlElement] [XmlElement]
public bool cbCamAdjust { get; set; }
[XmlElement]
public bool cbLogStats { get; set; } public bool cbLogStats { get; set; }
[XmlElement] [XmlElement]
public bool exGameMods { get; set; } public bool exGameMods { get; set; }