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 ## Version History
* v1.2.1.1 (2019-04-09) * 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 * Added prompt to let user decide between mouse or controller input
* This selection will fix locked up-down controls (pitch) on controllers * This selection will fix locked up-down controls (pitch) on controllers if 'disable camera auto rotation" is used
* v1.2.1 (2019-04-07) * v1.2.1 (2019-04-07)
* Added an option to disable automatic camera rotation adjust on movement (thanks to Cielos for some offsets) * Added an option to disable automatic camera rotation adjust on movement (thanks to Cielos for some offsets)
* Added +25% FOV option * 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 ... 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 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 = "80 07 00 00 38 04 00 00 00 08 00 00 80 04 00 00"; // 1920x1080
internal const string PATTERN_RESOLUTION_DEFAULT_720 = "40 06 00 00 84 03 00 00"; // 1280x720 internal const string PATTERN_RESOLUTION_DEFAULT_720 = "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 = 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 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] | 0000000140739548 | F3:0F1008 | movss xmm1,dword ptr ds:[rax] |
000000014073954C | F3:0F590D 0CE79B02 | mulss xmm1,dword ptr ds:[1430F7C60] | pFovTableEntry->fFov 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 // 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; internal const int PATTERN_FOVSETTING_OFFSET = 8;
/** internal const float PATCH_FOVSETTING_DISABLE = 0.0174533f; // Rad2Deg -> 1°
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%
/** /**

View file

@ -5,7 +5,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SekiroFpsUnlockAndMore" xmlns:local="clr-namespace:SekiroFpsUnlockAndMore"
mc:Ignorable="d" mc:Ignorable="d"
Title="Sekiro FPS Unlocker and more v1.2.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"> <Grid Background="#FFF9F9F9">
<DockPanel> <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" /> <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>
<DockPanel Margin="0,5,0,0" LastChildFill="False"> <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" /> <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" />
<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"/> <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> </DockPanel>
<StackPanel Margin="0,5,0,0" Orientation="Horizontal"> <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"/> <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"> <StackPanel Width="Auto" Height="Auto">
<DockPanel Margin="0,3,0,0" LastChildFill="False"> <DockPanel Margin="0,3,0,0" LastChildFill="False">
<CheckBox x:Name="cbGameSpeed" DockPanel.Dock="Left" Margin="0,0,0,0" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Content="Game speed (%):" ToolTip="Increase or decrease. Can potentially crash the game in cutscenes, use with caution." Checked="CbGameSpeed_Check_Handler" Unchecked="CbGameSpeed_Check_Handler" /> <CheckBox x:Name="cbGameSpeed" DockPanel.Dock="Left" Margin="0,0,0,0" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Content="Game speed (%):" ToolTip="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}}" <Button x:Name="bGs100" DockPanel.Dock="Right" Content="100" Margin="0,0,0,0" Width="30" Height="25" FontSize="14 px" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Focusable="False" Click="BGs100_Click" />
Focusable="False" Click="BGs100_Click" /> <Button x:Name="bGsHigher" DockPanel.Dock="Right" Content="&gt;" Margin="0,0,3,0" Width="25" Height="25" FontSize="14 px" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Focusable="False" Click="BGsHigher_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" /> <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}}" <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" />
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="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>
<DockPanel Margin="0,5,0,0" LastChildFill="False"> <DockPanel Margin="0,5,0,0" LastChildFill="False">
<CheckBox x:Name="cbPlayerSpeed" DockPanel.Dock="Left" Margin="0,0,0,0" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Content="Player speed (%):" ToolTip="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" /> <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}}" <Button x:Name="bPs100" DockPanel.Dock="Right" Content="100" Margin="0,0,0,0" Width="30" Height="25" FontSize="14 px" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Focusable="False" Click="BPs100_Click" />
Focusable="False" Click="BPs100_Click" /> <Button x:Name="bPsHigher" DockPanel.Dock="Right" Content="&gt;" Margin="0,0,3,0" Width="25" Height="25" FontSize="14 px" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Focusable="False" Click="BPsHigher_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" /> <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}}" <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" />
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="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> </DockPanel>
</StackPanel> </StackPanel>
</Grid> </Grid>
@ -83,7 +78,7 @@
</TextBlock> </TextBlock>
<Label HorizontalAlignment="Right" FontSize="12 px"> <Label HorizontalAlignment="Right" FontSize="12 px">
<Hyperlink NavigateUri="https://github.com/uberhalit/SekiroFpsUnlockAndMore" RequestNavigate="Hyperlink_RequestNavigate"> <Hyperlink NavigateUri="https://github.com/uberhalit/SekiroFpsUnlockAndMore" RequestNavigate="Hyperlink_RequestNavigate">
v1.2.1 - by uberhalit v1.2.2 - by uberhalit
</Hyperlink> </Hyperlink>
</Label> </Label>
</StackPanel> </StackPanel>

View file

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

View file

@ -5,51 +5,188 @@ using System.Runtime.InteropServices;
namespace SekiroFpsUnlockAndMore 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 long InstructionAddress;
internal readonly int OverwriteLength; internal readonly long CaveAddress;
internal readonly long CodeCaveAddress;
internal bool Active; internal bool Active;
private byte[] _originalInstructionset; private byte[] _originalInstruction;
internal byte[] OriginalInstructionset internal byte[] OriginalInstruction
{ {
get => _originalInstructionset; get => _originalInstruction;
set set
{ {
if (_originalInstructionset == null) if (_originalInstruction == null)
_originalInstructionset = value; _originalInstruction = value;
} }
} }
internal CodeCave(long instructionAddress, int overwriteLength, long codeCaveAddress) internal MemoryCave(long instructionAddress, long caveAddress)
{ {
InstructionAddress = instructionAddress; InstructionAddress = instructionAddress;
OverwriteLength = overwriteLength; CaveAddress = caveAddress;
CodeCaveAddress = codeCaveAddress; _originalInstruction = null;
Active = false; 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 Dictionary<string, CodeCave> _codeCaves;
private static IntPtr _hProcess; private static IntPtr _hProcess;
private static long _lpBaseAddress; private static long _lpBaseAddress;
/// <summary> /// <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> /// </summary>
/// <param name="hProcess">The handle to the process, needs all access flag.</param> /// <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="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>(); _codeCaves = new Dictionary<string, CodeCave>();
_hProcess = hProcess; _hProcess = hProcess;
_lpBaseAddress = lpBaseAddress; _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> /// <summary>
/// Creates a new code cave with a unique name. /// Creates a new code cave with a unique name.
/// </summary> /// </summary>
@ -69,7 +206,7 @@ namespace SekiroFpsUnlockAndMore
long caveAddress = CreateCodeCaveForInstruction(_hProcess, _lpBaseAddress, lpInstructionAddress, dwOverwriteLength, cbCodeInject, bCopyOverwrittenInstructions); long caveAddress = CreateCodeCaveForInstruction(_hProcess, _lpBaseAddress, lpInstructionAddress, dwOverwriteLength, cbCodeInject, bCopyOverwrittenInstructions);
if (caveAddress < 0) if (caveAddress < 0)
return false; return false;
_codeCaves.Add(szCaveName, new CodeCave(lpInstructionAddress, dwOverwriteLength, caveAddress)); _codeCaves.Add(szCaveName, new CodeCave(lpInstructionAddress, caveAddress, dwOverwriteLength));
return true; return true;
} }
@ -83,10 +220,10 @@ namespace SekiroFpsUnlockAndMore
if (!_codeCaves.ContainsKey(szCaveName) || _codeCaves[szCaveName].Active) if (!_codeCaves.ContainsKey(szCaveName) || _codeCaves[szCaveName].Active)
return false; 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) if (originalInstructionset.Length != _codeCaves[szCaveName].OverwriteLength)
return false; return false;
_codeCaves[szCaveName].OriginalInstructionset = originalInstructionset; _codeCaves[szCaveName].OriginalInstruction = originalInstructionset;
_codeCaves[szCaveName].Active = true; _codeCaves[szCaveName].Active = true;
return true; return true;
} }
@ -98,18 +235,42 @@ namespace SekiroFpsUnlockAndMore
/// <returns>True if code cave could be deactivated.</returns> /// <returns>True if code cave could be deactivated.</returns>
internal bool DeactivateCodeCaveByName(string szCaveName) 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; 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; return false;
} }
_codeCaves[szCaveName].Active = false; _codeCaves[szCaveName].Active = false;
return true; 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> /// <summary>
/// Gets the code cave address of an already created cave by name. /// Gets the code cave address of an already created cave by name.
/// </summary> /// </summary>
@ -117,19 +278,50 @@ namespace SekiroFpsUnlockAndMore
/// <returns>The address of the code cave, -1 if none found.</returns> /// <returns>The address of the code cave, -1 if none found.</returns>
internal long GetCodeCaveAddressByName(string szCaveName) internal long GetCodeCaveAddressByName(string szCaveName)
{ {
if (!_codeCaves.ContainsKey(szCaveName) || _codeCaves[szCaveName].CodeCaveAddress == 0) if (!_codeCaves.ContainsKey(szCaveName) || _codeCaves[szCaveName].CaveAddress == 0)
return -1; return -1;
return _codeCaves[szCaveName].CodeCaveAddress; return _codeCaves[szCaveName].CaveAddress;
} }
/// <summary> /// <summary>
/// Clears all saved code caves. Does not deactivate them. /// Clears all saved caves internally. Does not deactivate nor remove them from memory.
/// </summary> /// </summary>
internal void ClearCodeCaves() internal void ClearCaves()
{ {
_dataCaves.Clear();
_codeCaves.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> /// <summary>
/// Creates a code cave to inject code within given process's memory in reach of given instruction address. /// 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> /// <para>Does not activate the code cave yet.</para>
@ -154,45 +346,13 @@ namespace SekiroFpsUnlockAndMore
byte[] cbOriginalInstructionset = new byte[dwOverwriteLength]; byte[] cbOriginalInstructionset = new byte[dwOverwriteLength];
if (!ReadProcessMemory(hProcess, lpInstructionAddress, cbOriginalInstructionset, (ulong)dwOverwriteLength, out IntPtr lpNumberOfBytesRead) || lpNumberOfBytesRead.ToInt32() != 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; return -1;
} }
SYSTEM_INFO si = new SYSTEM_INFO(); long lpAllocationAddress = AllocateMemoryNearAddress(hProcess, lpBaseAddress, lpInstructionAddress, cbCodeInject.Length + dwOverwriteLength, true, 6);
GetSystemInfo(out si); if (lpAllocationAddress < 1)
// find lowest and highest possible address in jump range from lpInstructionAddress
int iMinimalCaveSize = 8 * (int)Math.Round((cbCodeInject.Length + dwOverwriteLength + 6) / 8.0); // nearest size rounded to 8 bytes
if (iMinimalCaveSize < 32) iMinimalCaveSize = 32;
long lpMinimalJmpAddress = lpInstructionAddress + 6 - 0x70000000; // Int32.MinValue + a little buffer overhead
long lpMaximumJmpAddress = lpInstructionAddress + 0x70000000 - iMinimalCaveSize; // Int32.MaxValue + a little buffer overhead
if (lpMinimalJmpAddress < si.lpMinimumApplicationAddress) lpMinimalJmpAddress = si.lpMinimumApplicationAddress;
if (lpMaximumJmpAddress > si.lpMaximumApplicationAddress - iMinimalCaveSize) lpMaximumJmpAddress = si.lpMaximumApplicationAddress - iMinimalCaveSize;
// determine lowest possible memory block we could allocate for cave assuming base address lies at the beginning of a memory block
long lpPreferredAddress = lpBaseAddress - (int)((lpBaseAddress - lpMinimalJmpAddress) / si.dwAllocationGranularity) * si.dwAllocationGranularity;
// find lowest useable memory block and allocate it
long lpAllocationAddress = 0;
MEMORY_BASIC_INFORMATION64 mbi = new MEMORY_BASIC_INFORMATION64();
while (lpPreferredAddress < lpMaximumJmpAddress)
{
if (VirtualQueryEx(hProcess, new IntPtr(lpPreferredAddress), out mbi, MEMORY_BASIC_INFORMATION64_LENGTH) != MEMORY_BASIC_INFORMATION64_LENGTH)
break;
if (mbi.State == MEM_FREE && mbi.RegionSize > (ulong)iMinimalCaveSize)
{
lpAllocationAddress = VirtualAllocEx(hProcess, new IntPtr((long)mbi.BaseAddress), (uint)iMinimalCaveSize, ALLOCATIONTYPE_RESERVE | ALLOCATIONTYPE_COMMIT, MEMORYPROTECTION_EXECUTEREADWRITE);
if (lpAllocationAddress > 0)
break;
}
lpPreferredAddress = (long)mbi.BaseAddress + si.dwAllocationGranularity;
}
if (lpAllocationAddress == 0)
{
MainWindow.LogToFile("No usable memory region found or failed to allocate memory for code cave in CodeCaveGenerator()!");
return -1; return -1;
}
// calculate jump from cave to back to where we came from // calculate jump from cave to back to where we came from
uint lpRelativePointerFromCave = (uint)((lpInstructionAddress + dwOverwriteLength) - (lpAllocationAddress + cbCodeInject.Length + (bCopyOverwrittenInstructions ? dwOverwriteLength : 0))) - 5; uint lpRelativePointerFromCave = (uint)((lpInstructionAddress + dwOverwriteLength) - (lpAllocationAddress + cbCodeInject.Length + (bCopyOverwrittenInstructions ? dwOverwriteLength : 0))) - 5;
@ -210,7 +370,7 @@ namespace SekiroFpsUnlockAndMore
// fill code cave with instructions // fill code cave with instructions
if (!WriteProcessMemory(hProcess, lpAllocationAddress, cbCaveInstructions, (ulong)cbCaveInstructions.Length, out IntPtr lpNumberOfBytesWritten) || lpNumberOfBytesWritten.ToInt32() != cbCaveInstructions.Length) 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; return -1;
} }
@ -232,7 +392,7 @@ namespace SekiroFpsUnlockAndMore
byte[] cbOriginalInstructionset = new byte[dwOverwriteLength]; byte[] cbOriginalInstructionset = new byte[dwOverwriteLength];
if (!ReadProcessMemory(hProcess, lpInstructionAddress, cbOriginalInstructionset, (ulong)dwOverwriteLength, out IntPtr lpNumberOfBytesRead) || lpNumberOfBytesRead.ToInt32() != 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; return null;
} }
@ -253,17 +413,69 @@ namespace SekiroFpsUnlockAndMore
// write jump instruction to target address // write jump instruction to target address
if (!WriteProcessMemory(hProcess, lpInstructionAddress, cbJumpToCaveInstruction, (ulong)cbJumpToCaveInstruction.Length, out IntPtr lpNumberOfBytesWritten) || lpNumberOfBytesWritten.ToInt32() != cbJumpToCaveInstruction.Length) 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 null;
} }
return cbOriginalInstructionset; 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 #region WINAPI
private const uint ALLOCATIONTYPE_COMMIT = 0x1000; private const uint ALLOCATIONTYPE_COMMIT = 0x1000;
private const uint ALLOCATIONTYPE_RESERVE = 0x2000; private const uint ALLOCATIONTYPE_RESERVE = 0x2000;
private const uint MEMORYPROTECTION_READWRITE = 0x04;
private const uint MEMORYPROTECTION_EXECUTEREADWRITE = 0x40; private const uint MEMORYPROTECTION_EXECUTEREADWRITE = 0x40;
private const int MEMORY_BASIC_INFORMATION64_LENGTH = 48; private const int MEMORY_BASIC_INFORMATION64_LENGTH = 48;
private const int MEM_FREE = 0x10000; private const int MEM_FREE = 0x10000;

View file

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

View file

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

View file

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