mirror of
https://github.com/uberhalit/SekiroFpsUnlockAndMore.git
synced 2026-06-13 18:07:55 +00:00
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.
This commit is contained in:
parent
f0ad1e49fa
commit
12b6c52149
7 changed files with 267 additions and 89 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -184,5 +184,13 @@ namespace SekiroFpsUnlockAndMore
|
|||
internal const int PATTERN_TIMESCALE_POINTER3_OFFSET = 0x1FF8;
|
||||
internal const int PATTERN_TIMESCALE_POINTER4_OFFSET = 0x28;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@
|
|||
<CheckBox x:Name="cbBorderless" DockPanel.Dock="Left" Margin="0,0,0,0" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Content="Borderless window" 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" />
|
||||
</StackPanel>
|
||||
<DockPanel Margin="0,5,0,0" LastChildFill="False">
|
||||
<CheckBox x:Name="cbLogStats" Content="Log stats (Deaths, Enemies killed)" HorizontalAlignment="Left" Margin="0,0,0,0" VerticalAlignment="Top" FontSize="14 px" Checked="CbStatChanged" Unchecked="CbStatChanged" />
|
||||
</DockPanel>
|
||||
<Expander x:Name="exGameMods" Margin="-3,4,0,0" Height="Auto" FontSize="14 px" Header="Game modifications" IsExpanded="False">
|
||||
<Grid Margin="3,0,0,0" Background="#FFF9F9F9">
|
||||
<StackPanel Width="Auto" Height="Auto">
|
||||
|
|
@ -82,6 +85,33 @@
|
|||
v1.1.0 - by uberhalit
|
||||
</Hyperlink>
|
||||
</Label>
|
||||
<StatusBar DockPanel.Dock="Bottom" Margin="-10,0,-10,0" VerticalAlignment="Bottom">
|
||||
<StatusBar.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
</Grid>
|
||||
</ItemsPanelTemplate>
|
||||
</StatusBar.ItemsPanel>
|
||||
|
||||
<StatusBarItem Grid.Column="0">
|
||||
<TextBlock Name="sbDeathCount">
|
||||
<Run Text="Deaths:"/>
|
||||
<Run Text="{Binding Deaths}"/>
|
||||
</TextBlock>
|
||||
</StatusBarItem>
|
||||
<Separator Grid.Column="1" />
|
||||
<StatusBarItem Grid.Column="2">
|
||||
<TextBlock Name="sbKillCount">
|
||||
<Run Text="Kills:"/>
|
||||
<Run Text="{Binding Kills}"/>
|
||||
</TextBlock>
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
|
|
|||
|
|
@ -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,20 +28,33 @@ 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 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()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = _statViewModel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -75,6 +90,10 @@ namespace SekiroFpsUnlockAndMore
|
|||
});
|
||||
_dispatcherTimerCheck.Interval = new TimeSpan(0, 0, 0, 2);
|
||||
_dispatcherTimerCheck.Start();
|
||||
|
||||
_statRecordTimer.Elapsed += new ElapsedEventHandler(StatReadTimer);
|
||||
_statRecordTimer.Interval = 1500;
|
||||
_statRecordTimer.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -88,6 +107,8 @@ namespace SekiroFpsUnlockAndMore
|
|||
UnregisterHotKey(hWnd, 9009);
|
||||
if (_gameAccessHwnd != IntPtr.Zero)
|
||||
CloseHandle(_gameAccessHwnd);
|
||||
|
||||
_statRecordTimer.Stop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -126,6 +147,7 @@ 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -147,6 +169,7 @@ 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.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<Int64>(_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, ' ');
|
||||
|
|
@ -655,6 +701,28 @@ namespace SekiroFpsUnlockAndMore
|
|||
UpdateStatus(DateTime.Now.ToString("HH:mm:ss") + " Game unpatched!", Brushes.White);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary
|
||||
private void StatReadTimer(object sender, EventArgs e)
|
||||
{
|
||||
if (_gameAccessHwndStatic == IntPtr.Zero) return;
|
||||
if (IsValidAddress(_pointer_player_deaths))
|
||||
{
|
||||
int playerDeaths = Read<Int32>(_gameAccessHwndStatic, _pointer_player_deaths);
|
||||
_statViewModel.Deaths = playerDeaths;
|
||||
if (_statLoggingEnabled) LogStatFile(deathCounterFilename, playerDeaths.ToString());
|
||||
|
||||
if (IsValidAddress(_pointer_total_kills))
|
||||
{
|
||||
int totalKills = Read<Int32>(_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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the hexadecimal representation of an IEEE-754 floating point number
|
||||
/// </summary>
|
||||
|
|
@ -848,6 +916,26 @@ namespace SekiroFpsUnlockAndMore
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs stat values to separate files for the use in OBS
|
||||
/// </summary>
|
||||
/// <param name="filename">File name</param>
|
||||
/// <param name="msg">Just a single stat value</param>
|
||||
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;
|
||||
|
|
@ -985,6 +1073,11 @@ namespace SekiroFpsUnlockAndMore
|
|||
if (this.cbPlayerSpeed.IsChecked == true) PatchPlayerSpeed();
|
||||
}
|
||||
|
||||
private void CbStatChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_statLoggingEnabled = (bool)cbLogStats.IsChecked;
|
||||
}
|
||||
|
||||
private void BPatch_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
PatchGame();
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@
|
|||
<SubType>Designer</SubType>
|
||||
</ApplicationDefinition>
|
||||
<Compile Include="SettingsService.cs" />
|
||||
<Compile Include="StatViewModel.cs" />
|
||||
<Page Include="MainWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ namespace SekiroFpsUnlockAndMore
|
|||
public bool cbPlayerSpeed { get; set; }
|
||||
[XmlElement]
|
||||
public int tbPlayerSpeed { get; set; }
|
||||
[XmlElement]
|
||||
public bool cbLogStats { get; set; }
|
||||
|
||||
public SettingsService() { }
|
||||
|
||||
|
|
|
|||
43
SekiroFpsUnlockAndMore/StatViewModel.cs
Normal file
43
SekiroFpsUnlockAndMore/StatViewModel.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace SekiroFpsUnlockAndMore
|
||||
{
|
||||
/// <summary>
|
||||
/// For Status bar display
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue