FOV can be changed contiously now

added data caves
fixed widescreen issue in combination with borderless
This commit is contained in:
uberhalit 2019-04-10 23:39:16 +02:00
parent b5f1d8b73e
commit fc06f8530e
8 changed files with 431 additions and 183 deletions

View file

@ -213,8 +213,8 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
## Version History
* v1.2.1.1 (2019-04-09)
* Added prompt to let user decide between mouse or controller input if 'disable camera auto rotation" is used
* This selection will fix locked up-down controls (pitch) on controllers
* Added prompt to let user decide between mouse or controller input
* This selection will fix locked up-down controls (pitch) on controllers if 'disable camera auto rotation" is used
* 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

View file

@ -109,10 +109,10 @@ namespace SekiroFpsUnlockAndMore
DATA SECTION. All resolutions are listed in memory as <int>width1 <int>height1 <int>width2 <int>height2 ...
Overwrite an unused one with desired new one. Some glitches, 1920x1080 and 1280x720 works best
*/
internal const string PATTERN_RESOLUTION_DEFAULT = "80 07 00 00 38 04 00 00"; // 1920x1080
internal const string PATTERN_RESOLUTION_DEFAULT_720 = "40 06 00 00 84 03 00 00"; // 1280x720
internal const string PATTERN_RESOLUTION_DEFAULT = "80 07 00 00 38 04 00 00 00 08 00 00 80 04 00 00"; // 1920x1080
internal const string PATTERN_RESOLUTION_DEFAULT_720 = "00 05 00 00 D0 02 00 00 A0 05 00 00 2A 03 00 00"; // 1280x720
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] { 0x00, 0x05, 0x00, 0x00, 0xD0, 0x02, 0x00, 0x00 };
/**
@ -128,28 +128,15 @@ namespace SekiroFpsUnlockAndMore
/**
Reference pointer pFovTableEntry to FOV entry in game FOV table that gets used in FOV calculations. Overwrite pFovTableEntry address to use a higher or lower <float>fFOV from table
FOV is in radians while default is 1.0deg (0.0174533rad), to increase by 25% you'd write 1.25deg (0.0218166rad) as fFov
0000000140739548 | F3:0F1008 | movss xmm1,dword ptr ds:[rax] |
000000014073954C | F3:0F590D 0CE79B02 | mulss xmm1,dword ptr ds:[1430F7C60] | pFovTableEntry->fFov
0000000140739554 | F3:0F5C4E 50 | subss xmm1,dword ptr ds:[rsi+50] |
*/
// credits to 'jackfuste' for original offset
internal const string PATTERN_FOVSETTING = "F3 0F 10 08 F3 0F 59 0D ?? ?? 9B 02";
internal const string PATTERN_FOVSETTING = "F3 0F 10 08 F3 0F 59 0D ?? ?? ?? ?? F3 0F 5C 4E";
internal const int PATTERN_FOVSETTING_OFFSET = 8;
/**
00000001430F7C60
Key: Patch to pFovTableEntry (last 2 bytes)
Value: Value resolve in float table from pFovTableEntry->fFov
*/
internal static readonly Dictionary<byte[], string> PATCH_FOVSETTING_MATRIX = new Dictionary<byte[], string>
{
{ 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%" }
};
internal static readonly byte[] PATCH_FOVSETTING_DISABLE = new byte[2] { 0x0C, 0xE7 }; // + 0%
internal const float PATCH_FOVSETTING_DISABLE = 0.0174533f; // Rad2Deg -> 1°
/**

View file

@ -5,7 +5,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SekiroFpsUnlockAndMore"
mc:Ignorable="d"
Title="Sekiro FPS Unlocker and more v1.2.1" Width="Auto" Height="Auto" SizeToContent="WidthAndHeight" ResizeMode="CanMinimize" Loaded="Window_Loaded" Closing="Window_Closing">
Title="Sekiro FPS Unlocker and more v1.2.2" Width="Auto" Height="Auto" SizeToContent="WidthAndHeight" ResizeMode="CanMinimize" Loaded="Window_Loaded" Closing="Window_Closing">
<Grid Background="#FFF9F9F9">
<DockPanel>
@ -21,8 +21,11 @@
<TextBox x:Name="tbWidth" DockPanel.Dock="Right" Margin="0,0,0,0" Width="50" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Text="2560" MaxLength="4" PreviewTextInput="Numeric_PreviewTextInput" DataObject.Pasting="Numeric_PastingHandler" />
</DockPanel>
<DockPanel Margin="0,5,0,0" LastChildFill="False">
<CheckBox x:Name="cbFov" DockPanel.Dock="Left" Margin="0,0,0,0" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Content="Increase FOV by:" Checked="CbFov_Check_Handler" Unchecked="CbFov_Check_Handler" />
<ComboBox x:Name="cbSelectFov" DockPanel.Dock="Right" Margin="0,0,0,0" Width="116" Height="25" FontSize="14 px" SelectedValuePath="Key" DisplayMemberPath="Value" DropDownClosed="CbSelectFov_DropDownClosed"/>
<CheckBox x:Name="cbFov" DockPanel.Dock="Left" Margin="0,0,0,0" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Content="Change FOV by (%):" Checked="CbFov_Check_Handler" Unchecked="CbFov_Check_Handler" />
<Button x:Name="bFovHigher" DockPanel.Dock="Right" Content="&gt;" Margin="0,0,0,0" Width="25" Height="25" FontSize="14 px" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Focusable="False" Click="BFovHigher_Click" />
<TextBox x:Name="tbFov" DockPanel.Dock="Right" Margin="0,0,3,0" Width="30" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Text="25" TextAlignment="Center" MaxLength="3" PreviewTextInput="SignedNumeric_PreviewTextInput" DataObject.Pasting="SignedNumeric_PastingHandler" />
<Button x:Name="bFovLower" DockPanel.Dock="Right" Content="&lt;" Margin="0,0,3,0" Width="25" Height="25" FontSize="14 px" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Focusable="False" Click="BFovLower_Click" />
<Button x:Name="bFov0" DockPanel.Dock="Right" Content="0" Margin="0,0,3,0" Width="27" Height="25" FontSize="14 px" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Focusable="False" Click="BFov0_Click" />
</DockPanel>
<StackPanel Margin="0,5,0,0" Orientation="Horizontal">
<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"/>
@ -35,27 +38,19 @@
<StackPanel Width="Auto" Height="Auto">
<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 (%):" ToolTip="Increase or decrease. 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}}"
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}}"
Focusable="False" Click="BGsHigher_Click" />
<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" />
<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}}" Focusable="False" Click="BGsHigher_Click" />
<TextBox x:Name="tbGameSpeed" DockPanel.Dock="Right" Margin="0,0,3,0" Width="30" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Text="100" TextAlignment="Center" MaxLength="3" PreviewTextInput="Numeric_PreviewTextInput" DataObject.Pasting="Numeric_PastingHandler" />
<Button x:Name="bGsLower" DockPanel.Dock="Right" Content="&lt;" Margin="0,0,3,0" Width="25" Height="25" FontSize="14 px" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"
Focusable="False" Click="BGsLower_Click" />
<Button x:Name="bGs0" DockPanel.Dock="Right" Content="0" Margin="0,0,3,0" Width="30" Height="25" FontSize="14 px" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"
Focusable="False" Click="BGs0_Click" />
<Button x:Name="bGsLower" DockPanel.Dock="Right" Content="&lt;" Margin="0,0,3,0" Width="25" Height="25" FontSize="14 px" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Focusable="False" Click="BGsLower_Click" />
<Button x:Name="bGs0" DockPanel.Dock="Right" Content="0" Margin="0,0,3,0" Width="30" Height="25" FontSize="14 px" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Focusable="False" Click="BGs0_Click" />
</DockPanel>
<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="Increase or decrease. 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}}"
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}}"
Focusable="False" Click="BPsHigher_Click" />
<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" />
<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}}" Focusable="False" Click="BPsHigher_Click" />
<TextBox x:Name="tbPlayerSpeed" DockPanel.Dock="Right" Margin="0,0,3,0" Width="30" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Text="100" TextAlignment="Center" MaxLength="3" PreviewTextInput="Numeric_PreviewTextInput" DataObject.Pasting="Numeric_PastingHandler" />
<Button x:Name="bPsLower" DockPanel.Dock="Right" Content="&lt;" Margin="0,0,3,0" Width="25" Height="25" FontSize="14 px" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"
Focusable="False" Click="BPsLower_Click" />
<Button x:Name="bPs0" DockPanel.Dock="Right" Content="0" Margin="0,0,3,0" Width="30" Height="25" FontSize="14 px" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"
Focusable="False" Click="BPs0_Click" />
<Button x:Name="bPsLower" DockPanel.Dock="Right" Content="&lt;" Margin="0,0,3,0" Width="25" Height="25" FontSize="14 px" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Focusable="False" Click="BPsLower_Click" />
<Button x:Name="bPs0" DockPanel.Dock="Right" Content="0" Margin="0,0,3,0" Width="30" Height="25" FontSize="14 px" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Focusable="False" Click="BPs0_Click" />
</DockPanel>
</StackPanel>
</Grid>
@ -83,7 +78,7 @@
</TextBlock>
<Label HorizontalAlignment="Right" FontSize="12 px">
<Hyperlink NavigateUri="https://github.com/uberhalit/SekiroFpsUnlockAndMore" RequestNavigate="Hyperlink_RequestNavigate">
v1.2.1 - by uberhalit
v1.2.2 - by uberhalit
</Hyperlink>
</Label>
</StackPanel>

View file

@ -27,14 +27,13 @@ namespace SekiroFpsUnlockAndMore
internal long _offset_resolution = 0x0;
internal long _offset_resolution_default = 0x0;
internal long _offset_resolution_scaling_fix = 0x0;
internal long _offset_fovsetting = 0x0;
internal long _offset_total_kills = 0x0;
internal long _offset_player_deaths = 0x0;
internal long _offset_timescale = 0x0;
internal long _offset_timescale_player = 0x0;
internal long _offset_timescale_player_pointer_start = 0x0;
internal CodeCaveGenerator _codeCaveGenerator;
internal MemoryCaveGenerator _memoryCaveGenerator;
internal SettingsService _settingsService;
internal StatusViewModel _statusViewModel = new StatusViewModel();
@ -49,10 +48,12 @@ namespace SekiroFpsUnlockAndMore
internal string _killCounterPath;
internal bool _retryAccess = true;
internal bool _use_resolution_720 = false;
internal bool _codeCavesGenerated = false;
internal bool _codeCave_camadjust = false;
internal bool _dataCave_fovsetting = false;
internal bool _statLoggingEnabled = false;
internal RECT _windowRect;
internal const string _DATACAVE_FOV_POINTER = "fovPointer";
internal const string _CODECAVE_CAMADJUST_PITCH = "camAdjustPitch";
internal const string _CODECAVE_CAMADJUST_YAW_Z = "camAdjustYawZ";
internal const string _CODECAVE_CAMADJUST_PITCH_XY = "camAdjustPitchXY";
@ -81,9 +82,6 @@ namespace SekiroFpsUnlockAndMore
_deathCounterPath = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location) + @"\DeathCounter.txt";
_killCounterPath = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location) + @"\TotalKillsCounter.txt";
this.cbSelectFov.ItemsSource = GameData.PATCH_FOVSETTING_MATRIX;
this.cbSelectFov.SelectedIndex = 2;
LoadConfiguration();
if (_settingsService.ApplicationSettings.cameraAdjustNotify)
@ -164,7 +162,7 @@ namespace SekiroFpsUnlockAndMore
this.tbWidth.Text = _settingsService.ApplicationSettings.tbWidth.ToString();
this.tbHeight.Text = _settingsService.ApplicationSettings.tbHeight.ToString();
this.cbFov.IsChecked = _settingsService.ApplicationSettings.cbFov;
this.cbSelectFov.SelectedIndex = _settingsService.ApplicationSettings.cbSelectFov;
this.tbFov.Text = _settingsService.ApplicationSettings.tbFov.ToString();
this.cbBorderless.IsChecked = _settingsService.ApplicationSettings.cbBorderless;
this.cbBorderlessStretch.IsChecked = _settingsService.ApplicationSettings.cbBorderlessStretch;
this.cbCamAdjust.IsChecked = _settingsService.ApplicationSettings.cbCamAdjust;
@ -187,7 +185,7 @@ namespace SekiroFpsUnlockAndMore
_settingsService.ApplicationSettings.tbWidth = Convert.ToInt32(this.tbWidth.Text);
_settingsService.ApplicationSettings.tbHeight = Convert.ToInt32(this.tbHeight.Text);
_settingsService.ApplicationSettings.cbFov = this.cbFov.IsChecked == true;
_settingsService.ApplicationSettings.cbSelectFov = this.cbSelectFov.SelectedIndex;
_settingsService.ApplicationSettings.tbFov = Convert.ToInt32(this.tbFov.Text);
_settingsService.ApplicationSettings.cbBorderless = this.cbBorderless.IsChecked == true;
_settingsService.ApplicationSettings.cbBorderlessStretch = this.cbBorderlessStretch.IsChecked == true;
_settingsService.ApplicationSettings.cbCamAdjust = this.cbCamAdjust.IsChecked == true;
@ -287,7 +285,7 @@ namespace SekiroFpsUnlockAndMore
private void ReadGame(object sender, DoWorkEventArgs doWorkEventArgs)
{
PatternScan patternScan = new PatternScan(_gameAccessHwnd, _gameProc.MainModule);
_codeCaveGenerator = new CodeCaveGenerator(_gameAccessHwnd, _gameProc.MainModule.BaseAddress.ToInt64());
_memoryCaveGenerator = new MemoryCaveGenerator(_gameAccessHwnd, _gameProc.MainModule.BaseAddress.ToInt64());
_offset_framelock = patternScan.FindPattern(GameData.PATTERN_FRAMELOCK) + GameData.PATTERN_FRAMELOCK_OFFSET;
Debug.WriteLine("fFrameTick found at: 0x" + _offset_framelock.ToString("X"));
@ -324,10 +322,14 @@ namespace SekiroFpsUnlockAndMore
_offset_resolution = 0x0;
}
_offset_fovsetting = patternScan.FindPattern(GameData.PATTERN_FOVSETTING) + GameData.PATTERN_FOVSETTING_OFFSET;
Debug.WriteLine("pFovTableEntry found at: 0x" + _offset_fovsetting.ToString("X"));
if (!IsValidAddress(_offset_fovsetting))
_offset_fovsetting = 0x0;
long lpFovPointer = patternScan.FindPattern(GameData.PATTERN_FOVSETTING) + GameData.PATTERN_FOVSETTING_OFFSET;
Debug.WriteLine("lpFovPointer found at: 0x" + lpFovPointer.ToString("X"));
if (IsValidAddress(lpFovPointer))
{
if (_memoryCaveGenerator.CreateNewDataCave(_DATACAVE_FOV_POINTER, lpFovPointer, BitConverter.GetBytes(GameData.PATCH_FOVSETTING_DISABLE), PointerStyle.dwRelative))
_dataCave_fovsetting = true;
Debug.WriteLine("lpFovPointer data cave at: 0x" + _memoryCaveGenerator.GetDataCaveAddressByName(_DATACAVE_FOV_POINTER).ToString("X"));
}
long ref_lpPlayerStatsRelated = patternScan.FindPattern(GameData.PATTERN_PLAYER_DEATHS) + GameData.PATTERN_PLAYER_DEATHS_OFFSET;
Debug.WriteLine("ref_lpPlayerStatsRelated found at: 0x" + ref_lpPlayerStatsRelated.ToString("X"));
@ -415,17 +417,17 @@ namespace SekiroFpsUnlockAndMore
{
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)
_memoryCaveGenerator.CreateNewCodeCave(_CODECAVE_CAMADJUST_PITCH, lpCamAdjustPitch, GameData.INJECT_CAMADJUST_PITCH_OVERWRITE_LENGTH, GameData.INJECT_CAMADJUST_PITCH_SHELLCODE),
_memoryCaveGenerator.CreateNewCodeCave(_CODECAVE_CAMADJUST_YAW_Z, lpCamAdjustYawZ, GameData.INJECT_CAMADJUST_YAW_Z_OVERWRITE_LENGTH, GameData.INJECT_CAMADJUST_YAW_Z_SHELLCODE),
_memoryCaveGenerator.CreateNewCodeCave(_CODECAVE_CAMADJUST_PITCH_XY, lpCamAdjustPitchXY, GameData.INJECT_CAMADJUST_PITCH_XY_OVERWRITE_LENGTH, GameData.INJECT_CAMADJUST_PITCH_XY_SHELLCODE),
_memoryCaveGenerator.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"));
Debug.WriteLine("lpCamAdjustPitch code cave at: 0x" + _memoryCaveGenerator.GetCodeCaveAddressByName(_CODECAVE_CAMADJUST_PITCH).ToString("X"));
Debug.WriteLine("lpCamAdjustYawZ code cave at: 0x" + _memoryCaveGenerator.GetCodeCaveAddressByName(_CODECAVE_CAMADJUST_YAW_Z).ToString("X"));
Debug.WriteLine("lpCamAdjustPitchXY code cave at: 0x" + _memoryCaveGenerator.GetCodeCaveAddressByName(_CODECAVE_CAMADJUST_PITCH_XY).ToString("X"));
Debug.WriteLine("lpCamAdjustYawXY code cave at: 0x" + _memoryCaveGenerator.GetCodeCaveAddressByName(_CODECAVE_CAMADJUST_YAW_XY).ToString("X"));
if (results.IndexOf(false) < 0)
_codeCavesGenerated = true;
_codeCave_camadjust = true;
}
}
@ -448,7 +450,7 @@ namespace SekiroFpsUnlockAndMore
this.cbFramelock.IsEnabled = false;
}
if ((int)SystemParameters.PrimaryScreenWidth < 1281) _use_resolution_720 = true;
if ((int)SystemParameters.PrimaryScreenWidth < 1920) _use_resolution_720 = true;
if (_offset_resolution_default == 0x0)
{
UpdateStatus("default resolution not found...", Brushes.Red);
@ -468,10 +470,10 @@ namespace SekiroFpsUnlockAndMore
this.cbAddResolution.IsEnabled = false;
}
if (_offset_fovsetting == 0x0)
if (!_dataCave_fovsetting)
{
UpdateStatus("fov table not found...", Brushes.Red);
LogToFile("fov table not found...");
UpdateStatus("could not create FOV table...", Brushes.Red);
LogToFile("could not create FOV table...");
this.cbFov.IsEnabled = false;
}
@ -492,12 +494,12 @@ namespace SekiroFpsUnlockAndMore
this.cbBorderless.IsEnabled = true;
if (!_codeCavesGenerated)
if (!_codeCave_camadjust)
{
UpdateStatus("cam adjust not found...", Brushes.Red);
LogToFile("cam adjust not found...");
}
this.cbCamAdjust.IsEnabled = _codeCavesGenerated;
this.cbCamAdjust.IsEnabled = _codeCave_camadjust;
if (_offset_timescale == 0x0)
{
@ -569,14 +571,15 @@ namespace SekiroFpsUnlockAndMore
_offset_resolution = 0x0;
_offset_resolution_default = 0x0;
_offset_resolution_scaling_fix = 0x0;
_offset_fovsetting = 0x0;
_offset_player_deaths = 0x0;
_offset_total_kills = 0x0;
_offset_timescale = 0x0;
_offset_timescale_player = 0x0;
_offset_timescale_player_pointer_start = 0x0;
_codeCaveGenerator.ClearCodeCaves();
_codeCaveGenerator = null;
_dataCave_fovsetting = false;
_codeCave_camadjust = false;
_memoryCaveGenerator.ClearCaves();
_memoryCaveGenerator = null;
this.cbFramelock.IsEnabled = true;
this.cbAddResolution.IsEnabled = true;
this.cbFov.IsEnabled = true;
@ -647,11 +650,12 @@ namespace SekiroFpsUnlockAndMore
if (!this.cbAddResolution.IsEnabled || _offset_resolution == 0x0 || _offset_resolution_default == 0x0 || _offset_resolution_scaling_fix == 0x0 || !CanPatchGame()) return false;
if (this.cbAddResolution.IsChecked == true)
{
this.cbBorderless.IsChecked = false;
bool isNumber = Int32.TryParse(this.tbWidth.Text, out int width);
if (width < 800 || !isNumber)
{
this.tbWidth.Text = "2560";
width = 2560;
this.tbWidth.Text = "800";
width = 800;
}
else if (width > 5760)
{
@ -661,8 +665,8 @@ namespace SekiroFpsUnlockAndMore
isNumber = Int32.TryParse(this.tbHeight.Text, out int height);
if (height < 450 || !isNumber)
{
this.tbHeight.Text = "1080";
height = 1080;
this.tbHeight.Text = "450";
height = 450;
}
else if (height > 2160)
{
@ -675,6 +679,7 @@ namespace SekiroFpsUnlockAndMore
}
else if (this.cbAddResolution.IsChecked == false)
{
this.cbBorderless.IsChecked = false;
WriteBytes(_gameAccessHwndStatic, _offset_resolution_default, !_use_resolution_720 ? GameData.PATCH_RESOLUTION_DEFAULT_DISABLE : GameData.PATCH_RESOLUTION_DEFAULT_DISABLE_720);
WriteBytes(_gameAccessHwndStatic, _offset_resolution_scaling_fix, GameData.PATCH_RESOLUTION_SCALING_FIX_DISABLE);
if (showStatus) UpdateStatus(DateTime.Now.ToString("HH:mm:ss") + " Game unpatched!", Brushes.White);
@ -691,15 +696,28 @@ namespace SekiroFpsUnlockAndMore
/// <param name="showStatus">Determines if status should be updated from within method, default is true.</param>
private bool PatchFov(bool showStatus = true)
{
if (!this.cbFov.IsEnabled || _offset_fovsetting == 0x0 || !CanPatchGame()) return false;
if (!this.cbFov.IsEnabled || !_dataCave_fovsetting|| !CanPatchGame()) return false;
if (this.cbFov.IsChecked == true)
{
byte[] fovByte = ((KeyValuePair<byte[], string>)this.cbSelectFov.SelectedItem).Key;
WriteBytes(_gameAccessHwndStatic, _offset_fovsetting, fovByte);
bool isNumber = Int32.TryParse(this.tbFov.Text, out int fovIncrease);
if (fovIncrease < -95 || !isNumber)
{
this.tbFov.Text = "-95";
fovIncrease = -95;
}
else if (fovIncrease > 95)
{
this.tbFov.Text = "95";
fovIncrease = 95;
}
float fovValue = (float)(Math.PI / 180) * ((fovIncrease / 100.0f) + 1); // convert change in %degree to radians
_memoryCaveGenerator.UpdateDataCaveValueByName(_DATACAVE_FOV_POINTER, BitConverter.GetBytes(fovValue));
_memoryCaveGenerator.ActivateDataCaveByName(_DATACAVE_FOV_POINTER);
}
else if (this.cbFov.IsChecked == false)
{
WriteBytes(_gameAccessHwndStatic, _offset_fovsetting, GameData.PATCH_FOVSETTING_DISABLE);
_memoryCaveGenerator.DeactivateDataCaveByName(_DATACAVE_FOV_POINTER);
if (showStatus) UpdateStatus(DateTime.Now.ToString("HH:mm:ss") + " Game unpatched!", Brushes.White);
return false;
}
@ -723,18 +741,15 @@ namespace SekiroFpsUnlockAndMore
this.cbBorderless.IsChecked = false;
return false;
}
if (!IsBorderless(_gameHwnd))
GetWindowRect(_gameHwnd, out _windowRect);
int width = Read<Int32>(_gameAccessHwnd, _offset_resolution);
int height = Read<Int32>(_gameAccessHwnd, _offset_resolution + 4);
Debug.WriteLine(string.Format("Client Resolution: {0}x{1}", width, height));
if (this.cbBorderlessStretch.IsChecked == true)
SetWindowBorderless(_gameHwnd, (int)SystemParameters.PrimaryScreenWidth, (int)SystemParameters.PrimaryScreenHeight, 0, 0);
else
{
if (!IsBorderless(_gameHwnd))
GetWindowRect(_gameHwnd, out _windowRect);
int width = Read<Int32>(_gameAccessHwnd, _offset_resolution);
int height = Read<Int32>(_gameAccessHwnd, _offset_resolution + 4);
Debug.WriteLine(string.Format("Client Resolution: {0}x{1}", width, height));
if (this.cbBorderlessStretch.IsChecked == true)
SetWindowBorderless(_gameHwnd, (int)SystemParameters.PrimaryScreenWidth, (int)SystemParameters.PrimaryScreenHeight, 0, 0);
else
SetWindowBorderless(_gameHwnd, width, height, _windowRect.Left, _windowRect.Top);
}
SetWindowBorderless(_gameHwnd, width, height, _windowRect.Left, _windowRect.Top);
}
else if (this.cbBorderless.IsChecked == false && IsBorderless(_gameHwnd))
{
@ -868,7 +883,7 @@ namespace SekiroFpsUnlockAndMore
/// </summary>
private void InjectToGame()
{
if (!CanPatchGame() || !_codeCavesGenerated) return;
if (!CanPatchGame() || !_codeCave_camadjust) return;
if (this.cbCamAdjust.IsChecked == true)
{
@ -896,20 +911,20 @@ namespace SekiroFpsUnlockAndMore
}
this.cbCamAdjust.IsEnabled = false;
_codeCaveGenerator.ActivateCodeCaveByName(_CODECAVE_CAMADJUST_PITCH);
_codeCaveGenerator.ActivateCodeCaveByName(_CODECAVE_CAMADJUST_YAW_Z);
_memoryCaveGenerator.ActivateCodeCaveByName(_CODECAVE_CAMADJUST_PITCH);
_memoryCaveGenerator.ActivateCodeCaveByName(_CODECAVE_CAMADJUST_YAW_Z);
if (!_settingsService.ApplicationSettings.peasantInput)
_codeCaveGenerator.ActivateCodeCaveByName(_CODECAVE_CAMADJUST_PITCH_XY); // BREAKS PITCH AND OTHER CONTROLS ON CONTROLLERS
_codeCaveGenerator.ActivateCodeCaveByName(_CODECAVE_CAMADJUST_YAW_XY);
_memoryCaveGenerator.ActivateCodeCaveByName(_CODECAVE_CAMADJUST_PITCH_XY); // BREAKS PITCH AND OTHER CONTROLS ON CONTROLLERS
_memoryCaveGenerator.ActivateCodeCaveByName(_CODECAVE_CAMADJUST_YAW_XY);
this.cbCamAdjust.IsEnabled = true;
}
else
{
this.cbCamAdjust.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);
_memoryCaveGenerator.DeactivateCodeCaveByName(_CODECAVE_CAMADJUST_PITCH);
_memoryCaveGenerator.DeactivateCodeCaveByName(_CODECAVE_CAMADJUST_YAW_Z);
_memoryCaveGenerator.DeactivateCodeCaveByName(_CODECAVE_CAMADJUST_PITCH_XY);
_memoryCaveGenerator.DeactivateCodeCaveByName(_CODECAVE_CAMADJUST_YAW_XY);
this.cbCamAdjust.IsEnabled = true;
}
}
@ -1131,6 +1146,16 @@ namespace SekiroFpsUnlockAndMore
return Regex.IsMatch(text, "[^0-9]+");
}
/// <summary>
/// Check whether input is (signed) numeric only.
/// </summary>
/// <param name="text">The text to check.</param>
/// <returns>True if input is (signed) numeric only.</returns>
private static bool IsSignedNumericInput(string text)
{
return Regex.IsMatch(text, "[-+]?[0-9]+");
}
/// <summary>
/// Logs messages to log file.
/// </summary>
@ -1193,9 +1218,19 @@ namespace SekiroFpsUnlockAndMore
else e.CancelCommand();
}
private void CbSelectFov_DropDownClosed(object sender, EventArgs e)
private void SignedNumeric_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
if (this.cbFov.IsChecked == true) PatchFov();
e.Handled = IsSignedNumericInput(e.Text);
}
private void SignedNumeric_PastingHandler(object sender, DataObjectPastingEventArgs e)
{
if (e.DataObject.GetDataPresent(typeof(string)))
{
string text = (string)e.DataObject.GetData(typeof(string));
if (IsSignedNumericInput(text)) e.CancelCommand();
}
else e.CancelCommand();
}
private void CbFramelock_Check_Handler(object sender, RoutedEventArgs e)
@ -1213,14 +1248,40 @@ namespace SekiroFpsUnlockAndMore
PatchFov();
}
private void BFov0_Click(object sender, RoutedEventArgs e)
{
this.tbFov.Text = "0";
if (this.cbFov.IsChecked == true) PatchFov();
}
private void BFovLower_Click(object sender, RoutedEventArgs e)
{
if (Int32.TryParse(this.tbFov.Text, out int fov) && fov > -91)
{
this.tbFov.Text = (fov - 5).ToString();
if (this.cbFov.IsChecked == true) PatchFov();
}
}
private void BFovHigher_Click(object sender, RoutedEventArgs e)
{
if (Int32.TryParse(this.tbFov.Text, out int fov) && fov < 91)
{
this.tbFov.Text = (fov + 5).ToString();
if (this.cbFov.IsChecked == true) PatchFov();
}
}
private void CbBorderless_Checked(object sender, RoutedEventArgs e)
{
if (!this.cbBorderless.IsEnabled) return;
this.cbBorderlessStretch.IsEnabled = true;
PatchWindow();
}
private void CbBorderless_Unchecked(object sender, RoutedEventArgs e)
{
if (!this.cbBorderless.IsEnabled) return;
this.cbBorderlessStretch.IsEnabled = false;
this.cbBorderlessStretch.IsChecked = false;
PatchWindow();
@ -1228,6 +1289,7 @@ namespace SekiroFpsUnlockAndMore
private void CbBorderlessStretch_Check_Handler(object sender, RoutedEventArgs e)
{
if (!this.cbBorderlessStretch.IsEnabled) return;
PatchWindow();
}
@ -1255,9 +1317,7 @@ namespace SekiroFpsUnlockAndMore
private void BGsLower_Click(object sender, RoutedEventArgs e)
{
int gameSpeed = -1;
Int32.TryParse(this.tbGameSpeed.Text, out gameSpeed);
if (gameSpeed > -1 && gameSpeed > 4)
if (Int32.TryParse(this.tbGameSpeed.Text, out int gameSpeed) && gameSpeed > 4)
{
this.tbGameSpeed.Text = (gameSpeed - 5).ToString();
if (cbGameSpeed.IsChecked == true) PatchGameSpeed();
@ -1266,9 +1326,7 @@ namespace SekiroFpsUnlockAndMore
private void BGsHigher_Click(object sender, RoutedEventArgs e)
{
int gameSpeed = -1;
Int32.TryParse(this.tbGameSpeed.Text, out gameSpeed);
if (gameSpeed > -1 && gameSpeed < 995)
if (Int32.TryParse(this.tbGameSpeed.Text, out int gameSpeed) && gameSpeed < 995)
{
this.tbGameSpeed.Text = (gameSpeed + 5).ToString();
if (cbGameSpeed.IsChecked == true) PatchGameSpeed();
@ -1294,9 +1352,7 @@ namespace SekiroFpsUnlockAndMore
private void BPsLower_Click(object sender, RoutedEventArgs e)
{
int playerSpeed = -1;
Int32.TryParse(this.tbPlayerSpeed.Text, out playerSpeed);
if (playerSpeed > -1 && playerSpeed > 4)
if (Int32.TryParse(this.tbPlayerSpeed.Text, out int playerSpeed) && playerSpeed > 4)
{
this.tbPlayerSpeed.Text = (playerSpeed - 5).ToString();
if (this.cbPlayerSpeed.IsChecked == true) PatchPlayerSpeed();
@ -1305,9 +1361,7 @@ namespace SekiroFpsUnlockAndMore
private void BPsHigher_Click(object sender, RoutedEventArgs e)
{
int playerSpeed = -1;
Int32.TryParse(this.tbPlayerSpeed.Text, out playerSpeed);
if (playerSpeed > -1 && playerSpeed < 995)
if (Int32.TryParse(this.tbPlayerSpeed.Text, out int playerSpeed) && playerSpeed < 995)
{
this.tbPlayerSpeed.Text = (playerSpeed + 5).ToString();
if (this.cbPlayerSpeed.IsChecked == true) PatchPlayerSpeed();

View file

@ -5,51 +5,188 @@ using System.Runtime.InteropServices;
namespace SekiroFpsUnlockAndMore
{
class CodeCaveGenerator
internal enum PointerStyle
{
private class CodeCave
dwRelative,
dwAbsolute,
qwRelative,
qwAbsolute
}
internal static class PointerStyleMethods
{
/// <summary>
/// Returns pointer size of pointer style.
/// </summary>
public static int PointerSize(this PointerStyle pointerStyle)
{
if (pointerStyle == PointerStyle.qwAbsolute || pointerStyle == PointerStyle.qwRelative) return 8;
return 4;
}
}
class MemoryCaveGenerator
{
private class MemoryCave
{
internal readonly long InstructionAddress;
internal readonly int OverwriteLength;
internal readonly long CodeCaveAddress;
internal readonly long CaveAddress;
internal bool Active;
private byte[] _originalInstructionset;
internal byte[] OriginalInstructionset
private byte[] _originalInstruction;
internal byte[] OriginalInstruction
{
get => _originalInstructionset;
get => _originalInstruction;
set
{
if (_originalInstructionset == null)
_originalInstructionset = value;
}
if (_originalInstruction == null)
_originalInstruction = value;
}
}
internal CodeCave(long instructionAddress, int overwriteLength, long codeCaveAddress)
internal MemoryCave(long instructionAddress, long caveAddress)
{
InstructionAddress = instructionAddress;
OverwriteLength = overwriteLength;
CodeCaveAddress = codeCaveAddress;
CaveAddress = caveAddress;
_originalInstruction = null;
Active = false;
_originalInstructionset = null;
}
}
private class DataCave : MemoryCave
{
internal readonly PointerStyle PointerStyle;
internal DataCave(long instructionAddress, long codeCaveAddress, PointerStyle pointerStyle) : base(instructionAddress, codeCaveAddress)
{
PointerStyle = pointerStyle;
}
}
private class CodeCave : MemoryCave
{
internal readonly int OverwriteLength;
internal CodeCave(long instructionAddress, long codeCaveAddress, int overwriteLength) : base(instructionAddress, codeCaveAddress)
{
OverwriteLength = overwriteLength;
}
}
private Dictionary<string, DataCave> _dataCaves;
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.
/// Initialize functionality to create and manage memory 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)
internal MemoryCaveGenerator(IntPtr hProcess, long lpBaseAddress)
{
_dataCaves = new Dictionary<string, DataCave>();
_codeCaves = new Dictionary<string, CodeCave>();
_hProcess = hProcess;
_lpBaseAddress = lpBaseAddress;
}
/// <summary>
/// Creates a new data cave with a unique name.
/// </summary>
/// <param name="szCaveName">The unique name of the data cave. Used to enable and disable caves.</param>
/// <param name="lpPointerAddress">The address of the pointer that should later access the data in the cave.</param>
/// <param name="cbDataInsert">The data to place inside the data cave.</param>
/// <param name="pointerStyle">The type of the pointer to later replace with data cave address, default assumes a 4 byte relative pointer.</param>
/// <returns>True if code cave has been successfully created and is ready to be activated, false otherwise.</returns>
internal bool CreateNewDataCave(string szCaveName, long lpPointerAddress, byte[] cbDataInsert, PointerStyle pointerStyle = PointerStyle.dwRelative)
{
if (_dataCaves.ContainsKey(szCaveName))
_dataCaves.Remove(szCaveName);
long caveAddress = CreateDataCaveForPointer(_hProcess, _lpBaseAddress, lpPointerAddress, pointerStyle.PointerSize(), cbDataInsert);
if (caveAddress < 0)
return false;
// read original pointer
byte[] cbOriginalPointer = new byte[pointerStyle.PointerSize()];
if (!ReadProcessMemory(_hProcess, lpPointerAddress, cbOriginalPointer, (ulong)pointerStyle.PointerSize(), out IntPtr lpNumberOfBytesRead) || lpNumberOfBytesRead.ToInt32() != pointerStyle.PointerSize())
{
MainWindow.LogToFile("Failed to read original pointer in MemoryCaveGenerator()!");
return false;
}
_dataCaves.Add(szCaveName, new DataCave(lpPointerAddress, caveAddress, pointerStyle));
_dataCaves[szCaveName].OriginalInstruction = cbOriginalPointer;
return true;
}
/// <summary>
/// Activates a data cave by name.
/// </summary>
/// <param name="szCaveName">The unique name of the data cave.</param>
/// <returns>True if data cave could be activated.</returns>
internal bool ActivateDataCaveByName(string szCaveName)
{
if (!_dataCaves.ContainsKey(szCaveName) || _dataCaves[szCaveName].Active)
return false;
// get pointer value
byte[] cbPointer = new byte[_dataCaves[szCaveName].PointerStyle.PointerSize()];
if (_dataCaves[szCaveName].PointerStyle == PointerStyle.dwAbsolute || _dataCaves[szCaveName].PointerStyle == PointerStyle.qwAbsolute)
cbPointer = _dataCaves[szCaveName].PointerStyle.PointerSize() == 4 ? BitConverter.GetBytes((int)_dataCaves[szCaveName].CaveAddress) : BitConverter.GetBytes(_dataCaves[szCaveName].CaveAddress);
else
{
long lpRip = _dataCaves[szCaveName].InstructionAddress + _dataCaves[szCaveName].PointerStyle.PointerSize();
cbPointer = _dataCaves[szCaveName].PointerStyle.PointerSize() == 4 ? BitConverter.GetBytes((int)(_dataCaves[szCaveName].CaveAddress - lpRip)) : BitConverter.GetBytes(_dataCaves[szCaveName].CaveAddress - lpRip);
}
// overwrite pointer with new data cave address
if (!WriteProcessMemory(_hProcess, _dataCaves[szCaveName].InstructionAddress, cbPointer, (ulong)cbPointer.Length, out IntPtr lpNumberOfBytesWritten) || lpNumberOfBytesWritten.ToInt32() != _dataCaves[szCaveName].PointerStyle.PointerSize())
{
MainWindow.LogToFile("Failed to overwrite target pointer in MemoryCaveGenerator()!");
return false;
}
_dataCaves[szCaveName].Active = true;
return true;
}
/// <summary>
/// Deactivates a data cave by name.
/// </summary>
/// <param name="szCaveName">The unique name of the data cave.</param>
/// <returns>True if data cave could be deactivated.</returns>
internal bool DeactivateDataCaveByName(string szCaveName)
{
if (!_dataCaves.ContainsKey(szCaveName) || !_dataCaves[szCaveName].Active || _dataCaves[szCaveName].InstructionAddress < 0 || _dataCaves[szCaveName].OriginalInstruction == null)
return false;
if (!WriteProcessMemory(_hProcess, _dataCaves[szCaveName].InstructionAddress, _dataCaves[szCaveName].OriginalInstruction, (ulong)_dataCaves[szCaveName].OriginalInstruction.Length, out IntPtr lpNumberOfBytesWritten) || lpNumberOfBytesWritten.ToInt32() != _dataCaves[szCaveName].PointerStyle.PointerSize())
{
MainWindow.LogToFile("Could not disable data cave in MemoryCaveGenerator()!");
return false;
}
_dataCaves[szCaveName].Active = false;
return true;
}
/// <summary>
/// Updates the data inside the data cave by name.
/// </summary>
/// <param name="szCaveName">The unique name of the data cave.</param>
/// <param name="cbNewData">The new data to write.</param>
/// <returns>True if data could be written.</returns>
internal bool UpdateDataCaveValueByName(string szCaveName, byte[] cbNewData)
{
if (!_dataCaves.ContainsKey(szCaveName) || _dataCaves[szCaveName].CaveAddress < 0)
return false;
if (!WriteProcessMemory(_hProcess, _dataCaves[szCaveName].CaveAddress, cbNewData, (ulong)cbNewData.Length, out IntPtr lpNumberOfBytesWritten) || lpNumberOfBytesWritten.ToInt32() != cbNewData.Length)
{
MainWindow.LogToFile("Could not write data to cave in MemoryCaveGenerator()!");
return false;
}
return true;
}
/// <summary>
/// Creates a new code cave with a unique name.
/// </summary>
@ -69,7 +206,7 @@ namespace SekiroFpsUnlockAndMore
long caveAddress = CreateCodeCaveForInstruction(_hProcess, _lpBaseAddress, lpInstructionAddress, dwOverwriteLength, cbCodeInject, bCopyOverwrittenInstructions);
if (caveAddress < 0)
return false;
_codeCaves.Add(szCaveName, new CodeCave(lpInstructionAddress, dwOverwriteLength, caveAddress));
_codeCaves.Add(szCaveName, new CodeCave(lpInstructionAddress, caveAddress, dwOverwriteLength));
return true;
}
@ -83,10 +220,10 @@ namespace SekiroFpsUnlockAndMore
if (!_codeCaves.ContainsKey(szCaveName) || _codeCaves[szCaveName].Active)
return false;
byte[] originalInstructionset = ActivateCodeCaveForInstruction(_hProcess, _codeCaves[szCaveName].InstructionAddress, _codeCaves[szCaveName].OverwriteLength, _codeCaves[szCaveName].CodeCaveAddress);
byte[] originalInstructionset = ActivateCodeCaveForInstruction(_hProcess, _codeCaves[szCaveName].InstructionAddress, _codeCaves[szCaveName].OverwriteLength, _codeCaves[szCaveName].CaveAddress);
if (originalInstructionset.Length != _codeCaves[szCaveName].OverwriteLength)
return false;
_codeCaves[szCaveName].OriginalInstructionset = originalInstructionset;
_codeCaves[szCaveName].OriginalInstruction = originalInstructionset;
_codeCaves[szCaveName].Active = true;
return true;
}
@ -98,18 +235,42 @@ namespace SekiroFpsUnlockAndMore
/// <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)
if (!_codeCaves.ContainsKey(szCaveName) || !_codeCaves[szCaveName].Active || _codeCaves[szCaveName].InstructionAddress < 0 || _codeCaves[szCaveName].OriginalInstruction == 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)
if (!WriteProcessMemory(_hProcess, _codeCaves[szCaveName].InstructionAddress, _codeCaves[szCaveName].OriginalInstruction, (ulong)_codeCaves[szCaveName].OriginalInstruction.Length, out IntPtr lpNumberOfBytesWritten) || lpNumberOfBytesWritten.ToInt32() != _codeCaves[szCaveName].OriginalInstruction.Length)
{
MainWindow.LogToFile("Could not disable code cave in CodeCaveGenerator()!");
MainWindow.LogToFile("Could not disable code cave in MemoryCaveGenerator()!");
return false;
}
_codeCaves[szCaveName].Active = false;
return true;
}
/// <summary>
/// Gets the data cave address of an already created cave by name.
/// </summary>
/// <param name="szCaveName">The unique name of the data cave.</param>
/// <returns>The address of the code cave, -1 if none found.</returns>
internal long GetDataCaveAddressByName(string szCaveName)
{
if (!_dataCaves.ContainsKey(szCaveName) || _dataCaves[szCaveName].CaveAddress == 0)
return -1;
return _dataCaves[szCaveName].CaveAddress;
}
/// <summary>
/// Gets the data cave original pointer value by name.
/// </summary>
/// <param name="szCaveName">The unique name of the data cave.</param>
/// <returns>The original pointer value of the data cave, null if none found.</returns>
internal byte[] GetDataCaveOriginalPointerByName(string szCaveName)
{
if (!_dataCaves.ContainsKey(szCaveName) || _dataCaves[szCaveName].OriginalInstruction == null)
return null;
return _dataCaves[szCaveName].OriginalInstruction;
}
/// <summary>
/// Gets the code cave address of an already created cave by name.
/// </summary>
@ -117,19 +278,50 @@ namespace SekiroFpsUnlockAndMore
/// <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)
if (!_codeCaves.ContainsKey(szCaveName) || _codeCaves[szCaveName].CaveAddress == 0)
return -1;
return _codeCaves[szCaveName].CodeCaveAddress;
return _codeCaves[szCaveName].CaveAddress;
}
/// <summary>
/// Clears all saved code caves. Does not deactivate them.
/// Clears all saved caves internally. Does not deactivate nor remove them from memory.
/// </summary>
internal void ClearCodeCaves()
internal void ClearCaves()
{
_dataCaves.Clear();
_codeCaves.Clear();
}
/// <summary>
/// Creates a data cave to link a pointer to within given process's memory in reach of given instruction address.
/// <para>Does not activate the data 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="lpPointerAddress">The address of the pointer that should later access the data in the cave.</param>
/// <param name="dwPointerLength">The lengths of the pointer to later replace with data cave address.</param>
/// <param name="cbDataInsert">The data to place inside the data cave.</param>
/// <remarks>Assumes a relative 4 bytes pointer, will fail if there is no free memory within signed integer range (4bytes) from lpInstructionAddress.</remarks>
/// <returns>The address of the beginning of the data cave, -1 if operation failed.</returns>
private static long CreateDataCaveForPointer(IntPtr hProcess, long lpBaseAddress, long lpPointerAddress, int dwPointerLength, byte[] cbDataInsert)
{
if (IntPtr.Size != 8)
throw new Exception("Only x64 is supported!");
long lpAllocationAddress = AllocateMemoryNearAddress(hProcess, lpBaseAddress, lpPointerAddress, cbDataInsert.Length, false, dwPointerLength);
if (lpAllocationAddress < 1)
return -1;
// fill data cave
if (!WriteProcessMemory(hProcess, lpAllocationAddress, cbDataInsert, (ulong)cbDataInsert.Length, out IntPtr lpNumberOfBytesWritten) || lpNumberOfBytesWritten.ToInt32() != cbDataInsert.Length)
{
MainWindow.LogToFile("Failed to fill data cave in MemoryCaveGenerator()!");
return -1;
}
return lpAllocationAddress;
}
/// <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>
@ -154,45 +346,13 @@ namespace SekiroFpsUnlockAndMore
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()!");
MainWindow.LogToFile("Failed to read original instruction set in MemoryCaveGenerator()!");
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()!");
long lpAllocationAddress = AllocateMemoryNearAddress(hProcess, lpBaseAddress, lpInstructionAddress, cbCodeInject.Length + dwOverwriteLength, true, 6);
if (lpAllocationAddress < 1)
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;
@ -210,7 +370,7 @@ namespace SekiroFpsUnlockAndMore
// 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()!");
MainWindow.LogToFile("Failed to fill code cave in MemoryCaveGenerator()!");
return -1;
}
@ -232,7 +392,7 @@ namespace SekiroFpsUnlockAndMore
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()!");
MainWindow.LogToFile("Failed to read original instruction set in MemoryCaveGenerator()!");
return null;
}
@ -253,17 +413,69 @@ namespace SekiroFpsUnlockAndMore
// 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()!");
MainWindow.LogToFile("Failed to overwrite target instructions in MemoryCaveGenerator()!");
return null;
}
return cbOriginalInstructionset;
}
/// <summary>
/// Allocates memory at least the size of given sizes in DWORD range of given instruction address.
/// </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 later access the allocated memory.</param>
/// <param name="dwInsertLength">The length of the insert that should be placed in allocated memory.</param>
/// <param name="bExecuteAccess">A boolean to determine if allocated memory should be executable, default is true.</param>
/// <param name="dwAccessInstructionLength">The length of the instruction that will jump or read from the allocated memory. Default assumes a 5 bytes JMP.</param>
/// <returns>The address of the beginning of the memory allocated, -1 if operation failed.</returns>
private static long AllocateMemoryNearAddress(IntPtr hProcess, long lpBaseAddress, long lpInstructionAddress, int dwInsertLength, bool bExecuteAccess = true, int dwAccessInstructionLength = 6)
{
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((dwInsertLength + dwAccessInstructionLength) / 8.0); // nearest size rounded to 8 bytes
if (iMinimalCaveSize < 32) iMinimalCaveSize = 32;
long lpMinimalJmpAddress = lpInstructionAddress + dwAccessInstructionLength - 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, bExecuteAccess ? MEMORYPROTECTION_EXECUTEREADWRITE : MEMORYPROTECTION_READWRITE);
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 MemoryCaveGenerator()!");
return -1;
}
return lpAllocationAddress;
}
#region WINAPI
private const uint ALLOCATIONTYPE_COMMIT = 0x1000;
private const uint ALLOCATIONTYPE_RESERVE = 0x2000;
private const uint MEMORYPROTECTION_READWRITE = 0x04;
private const uint MEMORYPROTECTION_EXECUTEREADWRITE = 0x40;
private const int MEMORY_BASIC_INFORMATION64_LENGTH = 48;
private const int MEM_FREE = 0x10000;

View file

@ -21,5 +21,5 @@ using System.Runtime.InteropServices;
ResourceDictionaryLocation.SourceAssembly
)]
[assembly: AssemblyVersion("1.2.1.1")]
[assembly: AssemblyFileVersion("1.2.1.1")]
[assembly: AssemblyVersion("1.2.2.0")]
[assembly: AssemblyFileVersion("1.2.2.0")]

View file

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

View file

@ -31,7 +31,7 @@ namespace SekiroFpsUnlockAndMore
[XmlElement]
public bool cbFov { get; set; }
[XmlElement]
public int cbSelectFov { get; set; }
public int tbFov { get; set; }
[XmlElement]
public bool cbBorderless { get; set; }
[XmlElement]