added +25% FOV option

moved costy patterscans to external thread
added check to game version
fixed whitespaces/tabstops
This commit is contained in:
uberhalit 2019-04-02 21:37:37 +02:00
parent af0e5a25da
commit af5458ada6
6 changed files with 243 additions and 222 deletions

View file

@ -1,7 +1,7 @@
# 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#.
Patches games memory while running, does not modify any game files. Wrks with every game version (legit steam & oh-not-so-legit), should work with all future updates. Also available [Nexus Mods](https://www.nexusmods.com/sekiro/mods/13/).
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/).
## Download
@ -130,7 +130,7 @@ The game enforces VSYNC and forces 60 Hz in fullscreen even on 144 Hz monitors s
7. Select either `DeathCounter.txt` (only tracks true deaths, excluding revives) or `TotalKillsCounter.txt`
8. Customize font style, color and position
9. To add additional counters repeat steps 4-7
10. [[!On Stream Display with OBS](https://camo.githubusercontent.com/007910d42ace53ee0db0ea8b61d525751b9d48a6/68747470733a2f2f692e696d6775722e636f6d2f4c39546e6f34462e706e67)](#)
10. [![On Stream Display with OBS](https://camo.githubusercontent.com/007910d42ace53ee0db0ea8b61d525751b9d48a6/68747470733a2f2f692e696d6775722e636f6d2f4c39546e6f34462e706e67)](#)
### To use any of the game modifications
1. Start the game

View file

@ -9,6 +9,7 @@ namespace SekiroFpsUnlockAndMore
internal const string PROCESS_NAME = "sekiro";
internal const string PROCESS_TITLE = "Sekiro";
internal const string PROCESS_DESCRIPTION = "Shadows Die Twice";
internal const string PROCESS_EXE_VERSION = "1.2.0.0";
/**
@ -142,7 +143,7 @@ namespace SekiroFpsUnlockAndMore
internal const int PATTERN_FOVSETTING_OFFSET = 8;
/**
00000001430F7C60
Key: Patch to pFovTableEntry last 2 bytes
Key: Patch to pFovTableEntry (last 2 bytes)
Value: Value resolve in float table from pFovTableEntry->fFov
*/
internal static Dictionary<byte[], string> PATCH_FOVSETTING_MATRIX = new Dictionary<byte[], string>
@ -150,6 +151,7 @@ namespace SekiroFpsUnlockAndMore
{ new byte[2] {0x00, 0xE7}, "- 50%" },
{ new byte[2] {0x04, 0xE7}, "- 10%" },
{ new byte[2] {0x10, 0xE7}, "+ 15%" },
{ new byte[2] {0x42, 0x9B}, "+ 25%" },
{ new byte[2] {0x14, 0xE7}, "+ 40%" },
{ new byte[2] {0x18, 0xE7}, "+ 75%" },
{ new byte[2] {0x1C, 0xE7}, "+ 90%" }

View file

@ -6,6 +6,7 @@ using System.Threading;
using System.Diagnostics;
using System.Windows.Media;
using System.Windows.Input;
using System.ComponentModel;
using System.Windows.Interop;
using System.Threading.Tasks;
using System.Windows.Threading;
@ -36,9 +37,10 @@ namespace SekiroFpsUnlockAndMore
internal SettingsService _settingsService;
internal StatusViewModel _statusViewModel = new StatusViewModel();
internal readonly System.Timers.Timer _timerStatsCheck = new System.Timers.Timer();
internal readonly DispatcherTimer _dispatcherTimerGameCheck = new DispatcherTimer();
internal readonly DispatcherTimer _dispatcherTimerFreezeMem = new DispatcherTimer();
internal readonly DispatcherTimer _dispatcherTimerCheck = new DispatcherTimer();
internal readonly BackgroundWorker _bgwScanGame = new BackgroundWorker();
internal readonly System.Timers.Timer _timerStatsCheck = new System.Timers.Timer();
internal bool _running = false;
internal bool _gameInitializing = false;
internal string _logPath;
@ -63,7 +65,7 @@ namespace SekiroFpsUnlockAndMore
var mutex = new Mutex(true, "sekiroFpsUnlockAndMore", out bool isNewInstance);
if (!isNewInstance)
{
MessageBox.Show("Another instance is already running!", "Sekiro FPS Unlocker and more");
MessageBox.Show("Another instance is already running!", "Sekiro FPS Unlocker and more", MessageBoxButton.OK, MessageBoxImage.Exclamation);
Environment.Exit(0);
}
GC.KeepAlive(mutex);
@ -79,17 +81,24 @@ namespace SekiroFpsUnlockAndMore
IntPtr hWnd = new WindowInteropHelper(this).Handle;
if (!RegisterHotKey(hWnd, 9009, MOD_CONTROL, VK_P))
MessageBox.Show("Hotkey is already in use, it may not work.", "Sekiro FPS Unlocker and more");
MessageBox.Show("Hotkey is already in use, it may not work.", "Sekiro FPS Unlocker and more", MessageBoxButton.OK, MessageBoxImage.Warning);
ComponentDispatcher.ThreadFilterMessage += new ThreadMessageEventHandler(ComponentDispatcherThreadFilterMessage);
_dispatcherTimerCheck.Tick += new EventHandler(async (object s, EventArgs a) =>
_bgwScanGame.DoWork += new DoWorkEventHandler(ReadGame);
_bgwScanGame.RunWorkerCompleted += new RunWorkerCompletedEventHandler(OnReadGameFinish);
_dispatcherTimerGameCheck.Tick += new EventHandler(async (object s, EventArgs a) =>
{
bool result = await CheckGame();
if (result) PatchGame();
if (result)
{
_bgwScanGame.RunWorkerAsync();
_dispatcherTimerGameCheck.Stop();
}
});
_dispatcherTimerCheck.Interval = new TimeSpan(0, 0, 0, 0, 2000);
_dispatcherTimerCheck.Start();
_dispatcherTimerGameCheck.Interval = new TimeSpan(0, 0, 0, 0, 2000);
_dispatcherTimerGameCheck.Start();
_dispatcherTimerFreezeMem.Tick += new EventHandler(FreezeMemory);
_dispatcherTimerFreezeMem.Interval = new TimeSpan(0, 0, 0, 0, 2000);
@ -179,6 +188,9 @@ namespace SekiroFpsUnlockAndMore
/// </summary>
private Task<bool> CheckGame()
{
// game process have been found last check and can be read now, aborting
if (_gameInitializing) return Task.FromResult(true);
Process[] procList = Process.GetProcessesByName(GameData.PROCESS_NAME);
if (procList.Length < 1)
return Task.FromResult(false);
@ -220,7 +232,7 @@ namespace SekiroFpsUnlockAndMore
if (!_retryAccess)
{
UpdateStatus("no access to game...", Brushes.Red);
_dispatcherTimerCheck.Stop();
_dispatcherTimerGameCheck.Stop();
return Task.FromResult(false);
}
_gameHwnd = IntPtr.Zero;
@ -235,14 +247,23 @@ namespace SekiroFpsUnlockAndMore
return Task.FromResult(false);
}
// give the game some time to initialize
if (!_gameInitializing)
string gameFileVersion = FileVersionInfo.GetVersionInfo(procList[0].MainModule.FileName).FileVersion;
if (gameFileVersion != GameData.PROCESS_EXE_VERSION && !_settingsService.ApplicationSettings.gameVersionNotify)
{
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;
}
// give the game some time to initialize
_gameInitializing = true;
return Task.FromResult(false);
}
//string gameFileVersion = FileVersionInfo.GetVersionInfo(procList[0].MainModule.FileName).FileVersion;
/// <summary>
/// Read all game offsets and pointer (external).
/// </summary>
private void ReadGame(object sender, DoWorkEventArgs doWorkEventArgs)
{
PatternScan patternScan = new PatternScan(_gameAccessHwnd, _gameProc.MainModule);
_offset_framelock = patternScan.FindPatternInternal(GameData.PATTERN_FRAMELOCK, GameData.PATTERN_FRAMELOCK_MASK, ' ') + GameData.PATTERN_FRAMELOCK_OFFSET;
@ -253,83 +274,41 @@ namespace SekiroFpsUnlockAndMore
Debug.WriteLine("2. fFrameTick found at: 0x" + _offset_framelock.ToString("X"));
}
if (!IsValidAddress(_offset_framelock))
{
UpdateStatus("fFrameTick not found...", Brushes.Red);
LogToFile("fFrameTick not found...");
_offset_framelock = 0x0;
this.cbFramelock.IsEnabled = false;
}
_offset_framelock_speed_fix = patternScan.FindPatternInternal(GameData.PATTERN_FRAMELOCK_SPEED_FIX, GameData.PATTERN_FRAMELOCK_SPEED_FIX_MASK, ' ') + GameData.PATTERN_FRAMELOCK_SPEED_FIX_OFFSET;
Debug.WriteLine("pFrametimeRunningSpeed at: 0x" + _offset_framelock_speed_fix.ToString("X"));
if (!IsValidAddress(_offset_framelock_speed_fix))
{
UpdateStatus("pFrametimeRunningSpeed no found...", Brushes.Red);
LogToFile("pFrametimeRunningSpeed not found...");
_offset_framelock_speed_fix = 0x0;
this.cbFramelock.IsEnabled = false;
}
bool enableResolutionPatch = true;
if ((int) SystemParameters.PrimaryScreenWidth < 1281) _use_resolution_720 = true;
_offset_resolution_default = patternScan.FindPatternInternal(!_use_resolution_720 ? GameData.PATTERN_RESOLUTION_DEFAULT : GameData.PATTERN_RESOLUTION_DEFAULT_720, GameData.PATTERN_RESOLUTION_DEFAULT_MASK, ' ');
_offset_resolution_default = patternScan.FindPatternInternal((int)SystemParameters.PrimaryScreenWidth > 1280 ? GameData.PATTERN_RESOLUTION_DEFAULT : GameData.PATTERN_RESOLUTION_DEFAULT_720, GameData.PATTERN_RESOLUTION_DEFAULT_MASK, ' ');
Debug.WriteLine("default resolution found at: 0x" + _offset_resolution_default.ToString("X"));
if (!IsValidAddress(_offset_resolution_default))
{
UpdateStatus("default resolution not found...", Brushes.Red);
LogToFile("default resolution not found...");
enableResolutionPatch = false;
_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;
Debug.WriteLine("scaling fix found at: 0x" + _offset_resolution_scaling_fix.ToString("X"));
if (!IsValidAddress(_offset_resolution_scaling_fix))
{
UpdateStatus("scaling fix not found...", Brushes.Red);
LogToFile("scaling fix not found...");
enableResolutionPatch = false;
_offset_resolution_scaling_fix = 0x0;
}
long ref_pCurrentResolutionWidth = patternScan.FindPatternInternal(GameData.PATTERN_RESOLUTION_POINTER, GameData.PATTERN_RESOLUTION_POINTER_MASK, ' ') + GameData.PATTERN_RESOLUTION_POINTER_OFFSET;
Debug.WriteLine("ref_pCurrentResolutionWidth found at: 0x" + ref_pCurrentResolutionWidth.ToString("X"));
if (!IsValidAddress(ref_pCurrentResolutionWidth))
{
UpdateStatus("re_pCurrentResolutionWidth not found...", Brushes.Red);
LogToFile("ref_pCurrentResolutionWidth not found...");
enableResolutionPatch = false;
}
else
if (IsValidAddress(ref_pCurrentResolutionWidth))
{
_offset_resolution = DereferenceStaticX64Pointer(_gameAccessHwnd, ref_pCurrentResolutionWidth, GameData.PATTERN_RESOLUTION_POINTER_INSTRUCTION_LENGTH);
Debug.WriteLine("pCurrentResolutionWidth at: 0x" + _offset_resolution.ToString("X"));
if (!IsValidAddress(_offset_resolution))
{
UpdateStatus("pCurrentResolutionWidth not valid...", Brushes.Red);
LogToFile("pCurrentResolutionWidth not valid...");
_offset_resolution = 0x0;
}
}
this.cbAddResolution.IsEnabled = enableResolutionPatch;
_offset_fovsetting = patternScan.FindPatternInternal(GameData.PATTERN_FOVSETTING, GameData.PATTERN_FOVSETTING_MASK, ' ') + GameData.PATTERN_FOVSETTING_OFFSET;
Debug.WriteLine("pFovTableEntry found at: 0x" + _offset_fovsetting.ToString("X"));
if (!IsValidAddress(_offset_fovsetting))
{
UpdateStatus("pFovTableEntry not found...", Brushes.Red);
LogToFile("pFovTableEntry not found...");
_offset_fovsetting = 0x0;
this.cbFov.IsEnabled = false;
}
long ref_pPlayerStatsRelated = patternScan.FindPatternInternal(GameData.PATTERN_PLAYER_DEATHS, GameData.PATTERN_PLAYER_DEATHS_MASK, ' ') + GameData.PATTERN_PLAYER_DEATHS_OFFSET;
Debug.WriteLine("ref_pPlayerStatsRelated found at: 0x" + ref_pPlayerStatsRelated.ToString("X"));
if (!IsValidAddress(ref_pPlayerStatsRelated))
{
UpdateStatus("ref_pPlayerStatsRelated not found...", Brushes.Red);
LogToFile("ref_pPlayerStatsRelated not found...");
this.cbLogStats.IsEnabled = false;
}
else
if (IsValidAddress(ref_pPlayerStatsRelated))
{
long pPlayerStatsRelated = DereferenceStaticX64Pointer(_gameAccessHwndStatic, ref_pPlayerStatsRelated, GameData.PATTERN_PLAYER_DEATHS_INSTRUCTION_LENGTH);
Debug.WriteLine("pPlayerStatsRelated found at: 0x" + pPlayerStatsRelated.ToString("X"));
@ -338,40 +317,22 @@ namespace SekiroFpsUnlockAndMore
int playerStatsToDeathsOffset = Read<Int32>(_gameAccessHwndStatic, ref_pPlayerStatsRelated + GameData.PATTERN_PLAYER_DEATHS_POINTER_OFFSET_OFFSET);
Debug.WriteLine("offset pPlayerStats->iPlayerDeaths found : 0x" + playerStatsToDeathsOffset.ToString("X"));
if (playerStatsToDeathsOffset > 0) _offset_player_deaths = Read<Int64>(_gameAccessHwndStatic, pPlayerStatsRelated) + playerStatsToDeathsOffset;
if (playerStatsToDeathsOffset > 0)
_offset_player_deaths = Read<Int64>(_gameAccessHwndStatic, pPlayerStatsRelated) + playerStatsToDeathsOffset;
Debug.WriteLine("iPlayerDeaths found at: 0x" + _offset_player_deaths.ToString("X"));
}
}
if (!IsValidAddress(_offset_player_deaths))
{
UpdateStatus("Player Deaths not found...", Brushes.Red);
LogToFile("Player Deaths not found...");
_offset_player_deaths = 0x0;
this.cbLogStats.IsEnabled = false;
}
long ref_pTotalKills = patternScan.FindPatternInternal(GameData.PATTERN_TOTAL_KILLS, GameData.PATTERN_TOTAL_KILLS_MASK, ' ');
Debug.WriteLine("ref_pTotalKills found at: 0x" + ref_pTotalKills.ToString("X"));
if (!IsValidAddress(ref_pTotalKills))
{
UpdateStatus("ref_pTotalKills not found...", Brushes.Red);
LogToFile("ref_pTotalKills not found...");
this.cbLogStats.IsEnabled = false;
}
else
if (IsValidAddress(ref_pTotalKills))
{
_offset_total_kills = DereferenceStaticX64Pointer(_gameAccessHwndStatic, ref_pTotalKills, GameData.PATTERN_TOTAL_KILLS_INSTRUCTION_LENGTH);
if (!IsValidAddress(_offset_total_kills))
{
UpdateStatus("pTotalKills not valid...", Brushes.Red);
LogToFile("pTotalKills not valid...");
_offset_total_kills = 0x0;
this.cbLogStats.IsEnabled = false;
}
}
if (_offset_player_deaths > 0x0 && _offset_total_kills > 0x0) _timerStatsCheck.Start();
this.cbBorderless.IsEnabled = true;
long ref_pTimeRelated = patternScan.FindPatternInternal(GameData.PATTERN_TIMESCALE, GameData.PATTERN_TIMESCALE_MASK, ' ');
Debug.WriteLine("ref_pTimeRelated found at: 0x" + ref_pTimeRelated.ToString("X"));
@ -384,17 +345,9 @@ namespace SekiroFpsUnlockAndMore
_offset_timescale = Read<Int64>(_gameAccessHwndStatic, pTimescaleManager) + Read<Int32>(_gameAccessHwndStatic, ref_pTimeRelated + GameData.PATTERN_TIMESCALE_POINTER_OFFSET_OFFSET);
Debug.WriteLine("fTimescale found at: 0x" + _offset_timescale.ToString("X"));
if (!IsValidAddress(_offset_timescale))
{
_offset_timescale = 0x0;
}
}
}
if (_offset_timescale == 0x0)
{
UpdateStatus("fTimescale not found...", Brushes.Red);
LogToFile("fTimescale not found...");
this.cbGameSpeed.IsEnabled = false;
}
long pPlayerStructRelated1 = patternScan.FindPatternInternal(GameData.PATTERN_TIMESCALE_PLAYER, GameData.PATTERN_TIMESCALE_PLAYER_MASK, ' ');
Debug.WriteLine("pPlayerStructRelated1 found at: 0x" + pPlayerStructRelated1.ToString("X"));
@ -420,7 +373,6 @@ namespace SekiroFpsUnlockAndMore
_offset_timescale_player = Read<Int64>(_gameAccessHwndStatic, pPlayerStructRelated5) + GameData.PATTERN_TIMESCALE_POINTER5_OFFSET;
Debug.WriteLine("fTimescalePlayer found at: 0x" + _offset_timescale_player.ToString("X"));
if (!IsValidAddress(_offset_timescale_player))
{
_offset_timescale_player = 0x0;
}
}
@ -428,18 +380,86 @@ namespace SekiroFpsUnlockAndMore
}
}
}
/// <summary>
/// All game data has been read.
/// </summary>
private void OnReadGameFinish(object sender, RunWorkerCompletedEventArgs runWorkerCompletedEventArgs)
{
if (_offset_framelock == 0x0)
{
UpdateStatus("frame tick not found...", Brushes.Red);
LogToFile("frame tick not found...");
this.cbFramelock.IsEnabled = false;
}
if (_offset_framelock_speed_fix == 0x0)
{
UpdateStatus("running speed fix no found...", Brushes.Red);
LogToFile("running speed fix not found...");
this.cbFramelock.IsEnabled = false;
}
if ((int)SystemParameters.PrimaryScreenWidth < 1281) _use_resolution_720 = true;
if (_offset_resolution_default == 0x0)
{
UpdateStatus("default resolution not found...", Brushes.Red);
LogToFile("default resolution not found...");
this.cbAddResolution.IsEnabled = false;
}
if (_offset_resolution_scaling_fix == 0x0)
{
UpdateStatus("scaling fix not found...", Brushes.Red);
LogToFile("scaling fix not found...");
this.cbAddResolution.IsEnabled = false;
}
if (_offset_resolution == 0x0)
{
UpdateStatus("current resolution not found...", Brushes.Red);
LogToFile("current resolution not found...");
this.cbAddResolution.IsEnabled = false;
}
if (_offset_fovsetting == 0x0)
{
UpdateStatus("fov table not found...", Brushes.Red);
LogToFile("fov table not found...");
this.cbFov.IsEnabled = false;
}
if (_offset_player_deaths == 0x0)
{
UpdateStatus("player deaths not found...", Brushes.Red);
LogToFile("player deaths not found...");
this.cbLogStats.IsEnabled = false;
}
if (_offset_total_kills == 0x0)
{
UpdateStatus("player kills not found...", Brushes.Red);
LogToFile("player kills not found...");
this.cbLogStats.IsEnabled = false;
}
if (_offset_player_deaths > 0x0 && _offset_total_kills > 0x0)
_timerStatsCheck.Start();
this.cbBorderless.IsEnabled = true;
if (_offset_timescale == 0x0)
{
UpdateStatus("timescale not found...", Brushes.Red);
LogToFile("timescale not found...");
this.cbGameSpeed.IsEnabled = false;
}
if (_offset_timescale_player_pointer_start == 0x0)
{
UpdateStatus("Playerscale not found...", Brushes.Red);
LogToFile("Playerscale not found...");
UpdateStatus("player timescale not found...", Brushes.Red);
//LogToFile("player timescale not found...");
this.cbPlayerSpeed.IsEnabled = false;
}
this.bPatch.IsEnabled = true;
_running = true;
_dispatcherTimerCheck.Stop();
return Task.FromResult(true);
PatchGame();
}
/// <summary>
@ -461,13 +481,11 @@ namespace SekiroFpsUnlockAndMore
{
_offset_timescale_player = Read<Int64>(_gameAccessHwndStatic, pPlayerStructRelated5) + GameData.PATTERN_TIMESCALE_POINTER5_OFFSET;
if (IsValidAddress(_offset_timescale_player))
{
valid = true;
}
}
}
}
}
if (!valid) _offset_timescale_player = 0x0;
}
@ -509,7 +527,7 @@ namespace SekiroFpsUnlockAndMore
this.cbGameSpeed.IsEnabled = true;
this.cbPlayerSpeed.IsEnabled = true;
UpdateStatus("waiting for game...", Brushes.White);
_dispatcherTimerCheck.Start();
_dispatcherTimerGameCheck.Start();
return false;
}
@ -821,7 +839,7 @@ namespace SekiroFpsUnlockAndMore
/// </summary>
private void StatsReadTimer(object sender, EventArgs e)
{
if (_gameAccessHwndStatic == IntPtr.Zero || _offset_player_deaths == 0x0 || _offset_total_kills == 0x0) return;
if (!_running || _gameAccessHwndStatic == IntPtr.Zero || _offset_player_deaths == 0x0 || _offset_total_kills == 0x0) return;
int playerDeaths = Read<Int32>(_gameAccessHwndStatic, _offset_player_deaths);
_statusViewModel.Deaths = playerDeaths;
if (_statLoggingEnabled) LogStatsFile(_deathCounterPath, playerDeaths.ToString());
@ -990,7 +1008,7 @@ namespace SekiroFpsUnlockAndMore
/// <returns>The static offset from the process to the referenced object.</returns>
private static Int64 DereferenceStaticX64Pointer(IntPtr hProcess, Int64 lpInstructionAddress, int instructionLength)
{
return lpInstructionAddress + Read<Int32>(hProcess, lpInstructionAddress + (instructionLength -0x04)) + instructionLength;
return lpInstructionAddress + Read<Int32>(hProcess, lpInstructionAddress + (instructionLength - 0x04)) + instructionLength;
}
/// <summary>

View file

@ -13,6 +13,7 @@ namespace SekiroFpsUnlockAndMore
* Settings definition
*/
[XmlElement]
public bool gameVersionNotify { get; set; }
public bool cbFramelock { get; set; }
[XmlElement]
public int tbFramelock { get; set; }