From 12b6c52149ae049adb3ece03681d7346a0bcd5f2 Mon Sep 17 00:00:00 2001
From: Me_TheCat <49048115+MeTheCat@users.noreply.github.com>
Date: Sun, 31 Mar 2019 15:02:00 +0300
Subject: [PATCH] Feature/logstats (#8)
Added an option to output player death counter and kill counter to statusbar and to files. Useful for display on twitch stream.
---
README.md | 1 +
SekiroFpsUnlockAndMore/GameData.cs | 12 +-
SekiroFpsUnlockAndMore/MainWindow.xaml | 166 +++++++++++-------
SekiroFpsUnlockAndMore/MainWindow.xaml.cs | 125 +++++++++++--
.../SekiroFpsUnlockAndMore.csproj | 1 +
SekiroFpsUnlockAndMore/SettingsService.cs | 8 +-
SekiroFpsUnlockAndMore/StatViewModel.cs | 43 +++++
7 files changed, 267 insertions(+), 89 deletions(-)
create mode 100644 SekiroFpsUnlockAndMore/StatViewModel.cs
diff --git a/README.md b/README.md
index ed02e88..f7f6320 100644
--- a/README.md
+++ b/README.md
@@ -25,6 +25,7 @@ Patches games memory while running, does not modify any game files. Wrks with ev
* automatically patch game on startup
* seamlessly switch between windowed, borderless and borderless fullscreen
* hotkey for patching while in (borderless) window mode
+* log and display hidden counters such as deaths/kill count
## Usage
diff --git a/SekiroFpsUnlockAndMore/GameData.cs b/SekiroFpsUnlockAndMore/GameData.cs
index d08115c..c15a383 100644
--- a/SekiroFpsUnlockAndMore/GameData.cs
+++ b/SekiroFpsUnlockAndMore/GameData.cs
@@ -183,6 +183,14 @@ namespace SekiroFpsUnlockAndMore
internal const int PATTERN_TIMESCALE_POINTER2_OFFSET = 0x88;
internal const int PATTERN_TIMESCALE_POINTER3_OFFSET = 0x1FF8;
internal const int PATTERN_TIMESCALE_POINTER4_OFFSET = 0x28;
- internal const int PATTERN_TIMESCALE_POINTER5_OFFSET = 0xD00;
- }
+ internal const int PATTERN_TIMESCALE_POINTER5_OFFSET = 0xD00;
+
+ // game stat values by Me_TheCat
+ internal const string PATTERN_PLAYER_DEATHS = "8B 88 90 00 00 00 89 8B FC 00 00 00 48 8B";
+ internal const string PATTERN_PLAYER_DEATHS_MASK = "xxxxxxxxx???xx";
+
+ internal const string PATTERN_TOTAL_KILLS = "48 8D 0D 00 00 00 00 89 14 81 C3";
+ internal const string PATTERN_TOTAL_KILLS_MASK = "xxx????xxxx";
+ internal const int PATTERN_TOTAL_KILLS_OFFSET = 7;
+ }
}
diff --git a/SekiroFpsUnlockAndMore/MainWindow.xaml b/SekiroFpsUnlockAndMore/MainWindow.xaml
index 6b6ed6a..8197759 100644
--- a/SekiroFpsUnlockAndMore/MainWindow.xaml
+++ b/SekiroFpsUnlockAndMore/MainWindow.xaml
@@ -7,81 +7,111 @@
mc:Ignorable="d"
Title="Sekiro FPS Unlocker and more v1.1.0" Width="Auto" Height="Auto" SizeToContent="WidthAndHeight" ResizeMode="CanMinimize" Loaded="Window_Loaded" Closing="Window_Closing">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
- This patcher does not modify game files, you have to start it every time.
- If your monitor is 60 Hz you will have to use
- fullscreen mode and force disable VSYNC
- with Nvidia Control panel or AMD Radeon Settings to get frame rate unlock working.
- To avoid stuttering it's recommended to disable VSYNC even with a 144 Hz monitor.
- If your monitor is >60 Hz you will have to use
- borderless window mode or force the game to run on highest available refresh rate
- using Nvidia Control panel or AMD Radeon Settings.
- Borderless window mode requires "Windowed" in game settings first.
- Custom resolution adds 21/9 support and overwrites a default resolution, hud will be limited to 16/9.
- Borderless window mode with custom resolution requires you to patch and set resolution first, then add borderless patch afterwards.
- To enable Player Speed modification you have to be ingame first.
- See the link below for detailed information, guides, GSYNC support and an AMD fix.
-
-
-
-
-
+
+
+
+ This patcher does not modify game files, you have to start it every time.
+ If your monitor is 60 Hz you will have to use
+ fullscreen mode and force disable VSYNC
+ with Nvidia Control panel or AMD Radeon Settings to get frame rate unlock working.
+ To avoid stuttering it's recommended to disable VSYNC even with a 144 Hz monitor.
+ If your monitor is >60 Hz you will have to use
+ borderless window mode or force the game to run on highest available refresh rate
+ using Nvidia Control panel or AMD Radeon Settings.
+ Borderless window mode requires "Windowed" in game settings first.
+ Custom resolution adds 21/9 support and overwrites a default resolution, hud will be limited to 16/9.
+ Borderless window mode with custom resolution requires you to patch and set resolution first, then add borderless patch afterwards.
+ To enable Player Speed modification you have to be ingame first.
+ See the link below for detailed information, guides, GSYNC support and an AMD fix.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SekiroFpsUnlockAndMore/MainWindow.xaml.cs b/SekiroFpsUnlockAndMore/MainWindow.xaml.cs
index 84ff670..b4c6145 100644
--- a/SekiroFpsUnlockAndMore/MainWindow.xaml.cs
+++ b/SekiroFpsUnlockAndMore/MainWindow.xaml.cs
@@ -11,6 +11,8 @@ using System.Windows.Threading;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
+using System.Timers;
+using Timer = System.Timers.Timer;
namespace SekiroFpsUnlockAndMore
{
@@ -26,21 +28,34 @@ namespace SekiroFpsUnlockAndMore
internal long _offset_resolution_default = 0x0;
internal long _offset_resolution_scaling_fix = 0x0;
internal long _offset_fovsetting = 0x0;
+ //game stat offsets
+ internal long _offset_player_deaths = 0x0;
+ internal long _pointer_player_deaths = 0x0;
internal long _offset_timescale = 0x0;
internal long _offset_timescale_player = 0x0;
internal bool _use_resolution_720 = false;
+ internal long _offset_total_kills = 0x0;
+ internal long _pointer_total_kills = 0x0;
+ internal const string deathCounterFilename = "DeathCouner.txt";
internal SettingsService _settingsService;
- internal readonly DispatcherTimer _dispatcherTimerCheck = new DispatcherTimer();
+ internal const string totalKillsFilename = "TotalKillsCounter.txt";
+
+ internal StatViewModel _statViewModel = new StatViewModel();
+ private bool _statLoggingEnabled = false;
+ internal readonly Timer _statRecordTimer = new Timer();
+ internal readonly DispatcherTimer _dispatcherTimerCheck = new DispatcherTimer();
internal bool _running = false;
internal string _logPath;
internal bool _retryAccess = true;
internal RECT _windowRect;
+
- public MainWindow()
+ public MainWindow()
{
- InitializeComponent();
- }
+ InitializeComponent();
+ DataContext = _statViewModel;
+ }
///
/// On window loaded.
@@ -75,7 +90,11 @@ namespace SekiroFpsUnlockAndMore
});
_dispatcherTimerCheck.Interval = new TimeSpan(0, 0, 0, 2);
_dispatcherTimerCheck.Start();
- }
+
+ _statRecordTimer.Elapsed += new ElapsedEventHandler(StatReadTimer);
+ _statRecordTimer.Interval = 1500;
+ _statRecordTimer.Start();
+ }
///
/// On window closing.
@@ -88,7 +107,9 @@ namespace SekiroFpsUnlockAndMore
UnregisterHotKey(hWnd, 9009);
if (_gameAccessHwnd != IntPtr.Zero)
CloseHandle(_gameAccessHwnd);
- }
+
+ _statRecordTimer.Stop();
+ }
///
/// Windows Message queue (Wndproc) to catch HotKeyPressed
@@ -126,7 +147,8 @@ namespace SekiroFpsUnlockAndMore
this.tbGameSpeed.Text = _settingsService.settings.tbGameSpeed.ToString();
this.cbPlayerSpeed.IsChecked = _settingsService.settings.cbPlayerSpeed;
this.tbPlayerSpeed.Text = _settingsService.settings.tbPlayerSpeed.ToString();
- }
+ this.cbLogStats.IsChecked = _settingsService.settings.cbLogStats;
+ }
///
/// Save all settings to configuration file.
@@ -147,7 +169,8 @@ namespace SekiroFpsUnlockAndMore
_settingsService.settings.tbGameSpeed = Convert.ToInt32(this.tbGameSpeed.Text);
_settingsService.settings.cbPlayerSpeed = this.cbPlayerSpeed.IsChecked == true;
_settingsService.settings.tbPlayerSpeed = Convert.ToInt32(this.tbPlayerSpeed.Text);
- _settingsService.Save();
+ _settingsService.settings.cbLogStats = this.cbLogStats.IsChecked == true;
+ _settingsService.Save();
}
///
@@ -288,6 +311,29 @@ namespace SekiroFpsUnlockAndMore
this.cbFov.IsEnabled = false;
}
+ //Game stats
+ _offset_player_deaths = patternScan.FindPatternInternal(GameData.PATTERN_PLAYER_DEATHS, GameData.PATTERN_PLAYER_DEATHS_MASK, ' ');
+ Debug.WriteLine("Player Deaths found at: 0x" + _offset_player_deaths.ToString("X"));
+ if (!IsValidAddress(_offset_player_deaths))
+ {
+ LogToFile("Player death counter not found...");
+ }
+ else
+ {
+ _pointer_player_deaths = Read(_gameAccessHwndStatic, DereferenceStaticX64Pointer(_gameAccessHwndStatic, _offset_player_deaths, 0)) + 0x90;
+ }
+
+ _offset_total_kills = patternScan.FindPatternInternal(GameData.PATTERN_TOTAL_KILLS, GameData.PATTERN_TOTAL_KILLS_MASK, ' ') + GameData.PATTERN_TOTAL_KILLS_OFFSET;
+ Debug.WriteLine("Total kills found at: 0x" + _offset_total_kills.ToString("X"));
+ if (!IsValidAddress(_offset_total_kills))
+ {
+ LogToFile("Total kills counter not found...");
+ }
+ else
+ {
+ _pointer_total_kills = DereferenceStaticX64Pointer(_gameAccessHwndStatic, _offset_total_kills, 0);
+ }
+
this.cbBorderless.IsEnabled = true;
long offset_pTimeRelated = patternScan.FindPatternInternal(GameData.PATTERN_TIMESCALE, GameData.PATTERN_TIMESCALE_MASK, ' ');
@@ -353,7 +399,7 @@ namespace SekiroFpsUnlockAndMore
this.bPatch.IsEnabled = true;
- _running = true;
+ _running = true;
_dispatcherTimerCheck.Stop();
return Task.FromResult(true);
}
@@ -655,11 +701,33 @@ namespace SekiroFpsUnlockAndMore
UpdateStatus(DateTime.Now.ToString("HH:mm:ss") + " Game unpatched!", Brushes.White);
}
- ///
- /// Returns the hexadecimal representation of an IEEE-754 floating point number
- ///
- /// The floating point number.
- /// The hexadecimal representation of the input.
+ ///
+ /// Reads some hidden stats and outputs them to text files. Use to display counters on Twitch stream or just look at them and get disspointed
+ /// (_gameAccessHwndStatic, _pointer_player_deaths);
+ _statViewModel.Deaths = playerDeaths;
+ if (_statLoggingEnabled) LogStatFile(deathCounterFilename, playerDeaths.ToString());
+
+ if (IsValidAddress(_pointer_total_kills))
+ {
+ int totalKills = Read(_gameAccessHwndStatic, _pointer_total_kills);
+ totalKills -= playerDeaths; //Since this value seems to track every death, including the player
+ _statViewModel.Kills = totalKills;
+ if (_statLoggingEnabled) LogStatFile(totalKillsFilename, totalKills.ToString());
+ }
+ }
+ }
+
+ ///
+ /// Returns the hexadecimal representation of an IEEE-754 floating point number
+ ///
+ /// The floating point number.
+ /// The hexadecimal representation of the input.
private static string GetHexRepresentationFromFloat(float input)
{
uint f = BitConverter.ToUInt32(BitConverter.GetBytes(input), 0);
@@ -848,7 +916,27 @@ namespace SekiroFpsUnlockAndMore
}
}
- private void UpdateStatus(string text, Brush color)
+ ///
+ /// Logs stat values to separate files for the use in OBS
+ ///
+ /// File name
+ /// Just a single stat value
+ private void LogStatFile(string filename, string value)
+ {
+ try
+ {
+ using (StreamWriter writer = new StreamWriter(filename, false))
+ {
+ writer.Write(value);
+ }
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show("Failed writing stat file: " + ex.Message, "Sekiro Fps Unlock And More");
+ }
+ }
+
+ private void UpdateStatus(string text, Brush color)
{
this.tbStatus.Background = color;
this.tbStatus.Text = text;
@@ -985,7 +1073,12 @@ namespace SekiroFpsUnlockAndMore
if (this.cbPlayerSpeed.IsChecked == true) PatchPlayerSpeed();
}
- private void BPatch_Click(object sender, RoutedEventArgs e)
+ private void CbStatChanged(object sender, RoutedEventArgs e)
+ {
+ _statLoggingEnabled = (bool)cbLogStats.IsChecked;
+ }
+
+ private void BPatch_Click(object sender, RoutedEventArgs e)
{
PatchGame();
}
diff --git a/SekiroFpsUnlockAndMore/SekiroFpsUnlockAndMore.csproj b/SekiroFpsUnlockAndMore/SekiroFpsUnlockAndMore.csproj
index 6d54c55..7cfbfb8 100644
--- a/SekiroFpsUnlockAndMore/SekiroFpsUnlockAndMore.csproj
+++ b/SekiroFpsUnlockAndMore/SekiroFpsUnlockAndMore.csproj
@@ -63,6 +63,7 @@
Designer
+
MSBuild:Compile
Designer
diff --git a/SekiroFpsUnlockAndMore/SettingsService.cs b/SekiroFpsUnlockAndMore/SettingsService.cs
index a21937c..3dbee9f 100644
--- a/SekiroFpsUnlockAndMore/SettingsService.cs
+++ b/SekiroFpsUnlockAndMore/SettingsService.cs
@@ -47,9 +47,11 @@ namespace SekiroFpsUnlockAndMore
[XmlElement]
public bool cbPlayerSpeed { get; set; }
[XmlElement]
- public int tbPlayerSpeed { get; set; }
-
- public SettingsService() { }
+ public int tbPlayerSpeed { get; set; }
+ [XmlElement]
+ public bool cbLogStats { get; set; }
+
+ public SettingsService() { }
///
/// Create a settings provider to load and save settings.
diff --git a/SekiroFpsUnlockAndMore/StatViewModel.cs b/SekiroFpsUnlockAndMore/StatViewModel.cs
new file mode 100644
index 0000000..6c1a35c
--- /dev/null
+++ b/SekiroFpsUnlockAndMore/StatViewModel.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+
+namespace SekiroFpsUnlockAndMore
+{
+ ///
+ /// For Status bar display
+ ///
+ class StatViewModel : INotifyPropertyChanged
+ {
+ private int _deaths = 0;
+ public int Deaths
+ {
+ get { return _deaths; }
+ set
+ {
+ _deaths = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("Deaths"));
+ }
+ }
+
+ private int _kills = 0;
+ public int Kills
+ {
+ get { return _kills; }
+ set
+ {
+ _kills = value;
+ OnPropertyChanged(new PropertyChangedEventArgs("Kills"));
+ }
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public void OnPropertyChanged(PropertyChangedEventArgs e)
+ {
+ PropertyChanged?.Invoke(this, e);
+ }
+ }
+}