mirror of
https://github.com/uberhalit/SekiroFpsUnlockAndMore.git
synced 2026-06-13 09:57:55 +00:00
Added FreezeMem for fTimescalePlayer
ApplicationSettings are decoupled from base class now Cleanup Refactor
This commit is contained in:
parent
12b6c52149
commit
772b69c1fb
7 changed files with 1743 additions and 1641 deletions
|
|
@ -1,196 +1,218 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SekiroFpsUnlockAndMore
|
||||
{
|
||||
internal class GameData
|
||||
{
|
||||
internal const string PROCESS_NAME = "sekiro";
|
||||
internal const string PROCESS_TITLE = "Sekiro";
|
||||
internal const string PROCESS_DESCRIPTION = "Shadows Die Twice";
|
||||
|
||||
|
||||
/**
|
||||
<float>fFrameTick determines default frame rate limit in seconds
|
||||
000000014116168D | C743 18 8988883C | mov dword ptr ds:[rbx+18],3C888889 | fFrameTick
|
||||
0000000141161694 | 4C:89AB 70020000 | mov qword ptr ds:[rbx+270],r13 |
|
||||
*/
|
||||
internal const string PATTERN_FRAMELOCK = "88 88 3C 4C 89 AB"; // 88 88 3C 4C 89 AB // first byte (last in mem) can can be 88/90 instead of 89 due to precision loss on floating point numbers
|
||||
internal const string PATTERN_FRAMELOCK_MASK = "xxxxxx"; // mask for frame rate limiter signature scanning
|
||||
internal const int PATTERN_FRAMELOCK_OFFSET = -1; // offset to byte array from found position
|
||||
internal const string PATTERN_FRAMELOCK_FUZZY = "C7 43 00 00 00 00 00 4C 89 AB"; // C7 43 ?? ?? ?? ?? ?? 4C 89 AB
|
||||
internal const string PATTERN_FRAMELOCK_FUZZY_MASK = "xx?????xxx";
|
||||
internal const int PATTERN_FRAMELOCK_FUZZY_OFFSET = 3;
|
||||
|
||||
|
||||
/**
|
||||
Reference pointer pFrametimeRunningSpeed to speed table entry that gets used in calculations. Add or remove multiplications of 4bytes to pFrametimeRunningSpeed address to use a higher or lower <float>fFrametimeCriticalRunningSpeed from table
|
||||
fFrametimeCriticalRunningSpeed should be roughly half the frame rate: 30 @ 60FPS limit, 50 @ 100FPS limit...
|
||||
00000001407D4E08 | F3:0F5905 90309202 | mulss xmm0,dword ptr ds:[1430F7EA0] | pFrametimeRunningSpeed->fFrametimeCriticalRunningSpeed
|
||||
*/
|
||||
internal const string PATTERN_FRAMELOCK_SPEED_FIX = "F3 0F 59 05 00 30 92 02"; // F3 0F 59 05 ?? 30 92 02 | 0F 51 C2 F3 0F 59 05 ?? ?? ?? ?? 0F 2F F8
|
||||
internal const string PATTERN_FRAMELOCK_SPEED_FIX_MASK = "xxxx?xxx";
|
||||
internal const int PATTERN_FRAMELOCK_SPEED_FIX_OFFSET = 4;
|
||||
/**
|
||||
00000001430F7E10
|
||||
Key: Patch to pFrametimeRunningSpeed last byte
|
||||
Value: Value resolve in float table from pFrametimeRunningSpeed->fFrametimeCriticalRunningSpeed
|
||||
Hardcoded cause lazy -> if anyone knows how the table is calculated then tell me and I'll buy you a beer
|
||||
*/
|
||||
internal static Dictionary<byte[], float> PATCH_FRAMELOCK_SPEED_FIX_MATRIX = new Dictionary<byte[], float>
|
||||
{
|
||||
{ new byte[1] {0x68}, 15f },
|
||||
{ new byte[1] {0x6C}, 16f },
|
||||
{ new byte[1] {0x70}, 16.6667f },
|
||||
{ new byte[1] {0x74}, 18f },
|
||||
{ new byte[1] {0x78}, 18.6875f },
|
||||
{ new byte[1] {0x7C}, 18.8516f },
|
||||
{ new byte[1] {0x80}, 20f },
|
||||
{ new byte[1] {0x84}, 24f },
|
||||
{ new byte[1] {0x88}, 25f },
|
||||
{ new byte[1] {0x8C}, 27.5f },
|
||||
{ new byte[1] {0x90}, 30f },
|
||||
{ new byte[1] {0x94}, 32f },
|
||||
{ new byte[1] {0x98}, 38.5f },
|
||||
{ new byte[1] {0x9C}, 40f },
|
||||
{ new byte[1] {0xA0}, 48f },
|
||||
{ new byte[1] {0xA4}, 49.5f },
|
||||
{ new byte[1] {0xA8}, 50f },
|
||||
{ new byte[1] {0xAC}, 57.2958f },
|
||||
{ new byte[1] {0xB0}, 60f },
|
||||
{ new byte[1] {0xB4}, 64f },
|
||||
{ new byte[1] {0xB8}, 66.75f },
|
||||
{ new byte[1] {0xBC}, 67f },
|
||||
{ new byte[1] {0xC0}, 78.8438f },
|
||||
{ new byte[1] {0xC4}, 80f },
|
||||
{ new byte[1] {0xC8}, 84f },
|
||||
{ new byte[1] {0xCC}, 90f },
|
||||
{ new byte[1] {0xD0}, 93.8f },
|
||||
{ new byte[1] {0xD4}, 100f },
|
||||
{ new byte[1] {0xD8}, 120f },
|
||||
{ new byte[1] {0xDC}, 127f },
|
||||
{ new byte[1] {0xE0}, 128f },
|
||||
{ new byte[1] {0xE4}, 130f },
|
||||
{ new byte[1] {0xE8}, 140f },
|
||||
{ new byte[1] {0xEC}, 144f },
|
||||
{ new byte[1] {0xF0}, 150f }
|
||||
};
|
||||
internal static byte[] PATCH_FRAMELOCK_SPEED_FIX_DISABLE = new byte[1] { 0x90 }; // 30f
|
||||
/// <summary>
|
||||
/// Finds closest valid speed fix value for a frame rate limit.
|
||||
/// </summary>
|
||||
/// <param name="frameLimit">The set frame rate limit.</param>
|
||||
/// <returns>The byte patch of the closest speed fix.</returns>
|
||||
internal static byte[] FindSpeedFixForRefreshRate(int frameLimit)
|
||||
{
|
||||
float idealSpeedFix = frameLimit / 2f;
|
||||
KeyValuePair<byte[], float> closestSpeedFix = new KeyValuePair<byte[], float>(PATCH_FRAMELOCK_SPEED_FIX_DISABLE, 30f);
|
||||
foreach (var speedFix in PATCH_FRAMELOCK_SPEED_FIX_MATRIX.OrderBy(kvp => kvp.Value))
|
||||
{
|
||||
if (Math.Abs(idealSpeedFix - speedFix.Value) < Math.Abs(idealSpeedFix - closestSpeedFix.Value))
|
||||
closestSpeedFix = speedFix;
|
||||
}
|
||||
return closestSpeedFix.Key;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Reference pointer pCurrentResolutionWidth to <int>iInternalGameWidth (and <int>iInternalGameHeight which is +4 bytes)
|
||||
000000014114AC85 | 0F57D2 | xorps xmm2,xmm2 |
|
||||
000000014114AC88 | 890D 92147D02 | mov dword ptr ds:[14391C120],ecx | pCurrentResolutionWidth->iInternalGameWidth
|
||||
000000014114AC8E | 0F57C9 | xorps xmm1,xmm1 |
|
||||
000000014114AC91 | 8915 8D147D02 | mov dword ptr ds:[14391C124],edx | pCurrentResolutionHeight->iInternalGameHeight
|
||||
*/
|
||||
internal const string PATTERN_RESOLUTION_POINTER = "0F 57 D2 89 0D 00 00 00 00 0F 57 C9"; // 0F 57 D2 89 0D ?? ?? ?? ?? 0F 57 C9
|
||||
internal const string PATTERN_RESOLUTION_POINTER_MASK = "xxxxx????xxx";
|
||||
internal const int PATTERN_RESOLUTION_POINTER_OFFSET = 3;
|
||||
internal const int PATTERN_RESOLUTION_POINTER_INSTRUCTION_LENGTH = 6;
|
||||
|
||||
|
||||
/**
|
||||
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_MASK = "xxxxxxxx";
|
||||
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 };
|
||||
|
||||
|
||||
/**
|
||||
Conditional jump instruction that determines if 16/9 scaling for game is enforced or not, overwrite with non conditional JMP so widescreen won't get clinched
|
||||
000000014012967A | 74 47 | je sekiro.1401296C3 | conditional jump
|
||||
000000014012967C | 47:8B94C7 1C020000 | mov r10d,dword ptr ds:[r15+r8*8+21C] | start of long resolution scaling calculation method within jump
|
||||
*/
|
||||
internal const string PATTERN_RESOLUTION_SCALING_FIX = "47 47 8B 94 C7 1C 02 00 00"; // 47 47 8B 94 C7 1C 02 00 00
|
||||
internal const string PATTERN_RESOLUTION_SCALING_FIX_MASK = "xxxxxxxxx";
|
||||
internal const int PATTERN_RESOLUTION_SCALING_FIX_OFFSET = -1;
|
||||
internal static byte[] PATCH_RESOLUTION_SCALING_FIX_ENABLE = new byte[1] { 0xEB }; // JMP
|
||||
internal static byte[] PATCH_RESOLUTION_SCALING_FIX_DISABLE = new byte[1] { 0x74 }; // JE
|
||||
|
||||
|
||||
/**
|
||||
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
|
||||
0000000140739548 | F3:0F1008 | movss xmm1,dword ptr ds:[rax] |
|
||||
000000014073954C | F3:0F590D 0CE79B02 | mulss xmm1,dword ptr ds:[1430F7C60] | pFovTableEntry->fFov
|
||||
*/
|
||||
// credits to 'jackfuste' for original offset
|
||||
internal const string PATTERN_FOVSETTING = "F3 0F 10 08 F3 0F 59 0D 00 00 9B 02"; // F3 0F 10 08 F3 0F 59 0D ?? ?? 9B 02
|
||||
internal const string PATTERN_FOVSETTING_MASK = "xxxxxxxx??xx";
|
||||
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 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] {0x14, 0xE7}, "+ 40%" },
|
||||
{ new byte[2] {0x18, 0xE7}, "+ 75%" },
|
||||
{ new byte[2] {0x1C, 0xE7}, "+ 90%" }
|
||||
};
|
||||
internal static byte[] PATCH_FOVSETTING_DISABLE = new byte[2] { 0x0C, 0xE7 }; // + 0%
|
||||
|
||||
|
||||
/**
|
||||
Reference pointer pTimeRelated to pTimescaleManager pointer, offset in struct to <float>fTimescale which acts as a global speed scale for almost all ingame calculations
|
||||
0000000141149E87 | 48:8B05 3A24B402 | mov rax,qword ptr ds:[143C8C2C8] | pTimeRelated->[pTimescaleManager+0x360]->fTimescale
|
||||
0000000141149E8E | F3:0F1088 60030000 | movss xmm1,dword ptr ds:[rax+360] | offset from TimescaleManager->fTimescale
|
||||
0000000141149E96 | F3:0F5988 68020000 | mulss xmm1,dword ptr ds:[rax+268] |
|
||||
*/
|
||||
// credits to 'Zullie the Witch' for original offset
|
||||
internal const string PATTERN_TIMESCALE = "48 8B 05 00 00 00 00 F3 0F 10 88 00 00 00 00 F3 0F"; // 48 8B 05 ?? ?? ?? ?? F3 0F 10 88 ?? ?? ?? ?? F3 0F
|
||||
internal const string PATTERN_TIMESCALE_MASK = "xxx????xxxx????xx";
|
||||
internal const int PATTERN_TIMESCALE_INSTRUCTION_LENGTH = 7;
|
||||
internal const int PATTERN_TIMESCALE_POINTER_OFFSET_OFFSET = 11;
|
||||
|
||||
/**
|
||||
Reference pointer pPlayerStructRelated1 to 4 more pointers up to player data class, offset in struct to <float>fTimescalePlayer which acts as a speed scale for the player character
|
||||
00000001406BF1D7 | 48:8B1D 128C4A03 | mov rbx,qword ptr ds:[143B67DF0] | pPlayerStructRelated1->[pPlayerStructRelated2+0x88]->[pPlayerStructRelated3+0x1FF8]->[pPlayerStructRelated4+0x28]->[pPlayerStructRelated5+0xD00]->fTimescalePlayer
|
||||
00000001406BF1DE | 48:85DB | test rbx,rbx |
|
||||
00000001406BF1E1 | 74 3C | je sekiro.1406BF21F |
|
||||
00000001406BF1E3 | 8B17 | mov edx,dword ptr ds:[rdi] |
|
||||
*/
|
||||
// credits to 'Zullie the Witch' for original offset
|
||||
internal const string PATTERN_TIMESCALE_PLAYER = "48 8B 1D 00 00 00 00 48 85 DB 74 3C 8B 17"; // 48 8B 1D ?? ?? ?? ?? 48 85 DB 74 3C 8B 17
|
||||
internal const string PATTERN_TIMESCALE_PLAYER_MASK = "xxx????xxxxxxx";
|
||||
internal const int PATTERN_TIMESCALE_PLAYER_INSTRUCTION_LENGTH = 7;
|
||||
internal const int PATTERN_TIMESCALE_POINTER2_OFFSET = 0x88;
|
||||
internal const int PATTERN_TIMESCALE_POINTER3_OFFSET = 0x1FF8;
|
||||
internal const int PATTERN_TIMESCALE_POINTER4_OFFSET = 0x28;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SekiroFpsUnlockAndMore
|
||||
{
|
||||
internal class GameData
|
||||
{
|
||||
internal const string PROCESS_NAME = "sekiro";
|
||||
internal const string PROCESS_TITLE = "Sekiro";
|
||||
internal const string PROCESS_DESCRIPTION = "Shadows Die Twice";
|
||||
|
||||
|
||||
/**
|
||||
<float>fFrameTick determines default frame rate limit in seconds
|
||||
000000014116168D | C743 18 8988883C | mov dword ptr ds:[rbx+18],3C888889 | fFrameTick
|
||||
0000000141161694 | 4C:89AB 70020000 | mov qword ptr ds:[rbx+270],r13 |
|
||||
*/
|
||||
internal const string PATTERN_FRAMELOCK = "88 88 3C 4C 89 AB"; // 88 88 3C 4C 89 AB // first byte (last in mem) can can be 88/90 instead of 89 due to precision loss on floating point numbers
|
||||
internal const string PATTERN_FRAMELOCK_MASK = "xxxxxx"; // mask for frame rate limiter signature scanning
|
||||
internal const int PATTERN_FRAMELOCK_OFFSET = -1; // offset to byte array from found position
|
||||
internal const string PATTERN_FRAMELOCK_FUZZY = "C7 43 00 00 00 00 00 4C 89 AB"; // C7 43 ?? ?? ?? ?? ?? 4C 89 AB
|
||||
internal const string PATTERN_FRAMELOCK_FUZZY_MASK = "xx?????xxx";
|
||||
internal const int PATTERN_FRAMELOCK_FUZZY_OFFSET = 3;
|
||||
|
||||
|
||||
/**
|
||||
Reference pointer pFrametimeRunningSpeed to speed table entry that gets used in calculations. Add or remove multiplications of 4bytes to pFrametimeRunningSpeed address to use a higher or lower <float>fFrametimeCriticalRunningSpeed from table
|
||||
fFrametimeCriticalRunningSpeed should be roughly half the frame rate: 30 @ 60FPS limit, 50 @ 100FPS limit...
|
||||
00000001407D4E08 | F3:0F5905 90309202 | mulss xmm0,dword ptr ds:[1430F7EA0] | pFrametimeRunningSpeed->fFrametimeCriticalRunningSpeed
|
||||
*/
|
||||
internal const string PATTERN_FRAMELOCK_SPEED_FIX = "F3 0F 59 05 00 30 92 02"; // F3 0F 59 05 ?? 30 92 02 | 0F 51 C2 F3 0F 59 05 ?? ?? ?? ?? 0F 2F F8
|
||||
internal const string PATTERN_FRAMELOCK_SPEED_FIX_MASK = "xxxx?xxx";
|
||||
internal const int PATTERN_FRAMELOCK_SPEED_FIX_OFFSET = 4;
|
||||
/**
|
||||
00000001430F7E10
|
||||
Key: Patch to pFrametimeRunningSpeed last byte
|
||||
Value: Value resolve in float table from pFrametimeRunningSpeed->fFrametimeCriticalRunningSpeed
|
||||
Hardcoded cause lazy -> if anyone knows how the table is calculated then tell me and I'll buy you a beer
|
||||
*/
|
||||
internal static Dictionary<byte[], float> PATCH_FRAMELOCK_SPEED_FIX_MATRIX = new Dictionary<byte[], float>
|
||||
{
|
||||
{ new byte[1] {0x68}, 15f },
|
||||
{ new byte[1] {0x6C}, 16f },
|
||||
{ new byte[1] {0x70}, 16.6667f },
|
||||
{ new byte[1] {0x74}, 18f },
|
||||
{ new byte[1] {0x78}, 18.6875f },
|
||||
{ new byte[1] {0x7C}, 18.8516f },
|
||||
{ new byte[1] {0x80}, 20f },
|
||||
{ new byte[1] {0x84}, 24f },
|
||||
{ new byte[1] {0x88}, 25f },
|
||||
{ new byte[1] {0x8C}, 27.5f },
|
||||
{ new byte[1] {0x90}, 30f },
|
||||
{ new byte[1] {0x94}, 32f },
|
||||
{ new byte[1] {0x98}, 38.5f },
|
||||
{ new byte[1] {0x9C}, 40f },
|
||||
{ new byte[1] {0xA0}, 48f },
|
||||
{ new byte[1] {0xA4}, 49.5f },
|
||||
{ new byte[1] {0xA8}, 50f },
|
||||
{ new byte[1] {0xAC}, 57.2958f },
|
||||
{ new byte[1] {0xB0}, 60f },
|
||||
{ new byte[1] {0xB4}, 64f },
|
||||
{ new byte[1] {0xB8}, 66.75f },
|
||||
{ new byte[1] {0xBC}, 67f },
|
||||
{ new byte[1] {0xC0}, 78.8438f },
|
||||
{ new byte[1] {0xC4}, 80f },
|
||||
{ new byte[1] {0xC8}, 84f },
|
||||
{ new byte[1] {0xCC}, 90f },
|
||||
{ new byte[1] {0xD0}, 93.8f },
|
||||
{ new byte[1] {0xD4}, 100f },
|
||||
{ new byte[1] {0xD8}, 120f },
|
||||
{ new byte[1] {0xDC}, 127f },
|
||||
{ new byte[1] {0xE0}, 128f },
|
||||
{ new byte[1] {0xE4}, 130f },
|
||||
{ new byte[1] {0xE8}, 140f },
|
||||
{ new byte[1] {0xEC}, 144f },
|
||||
{ new byte[1] {0xF0}, 150f }
|
||||
};
|
||||
internal static byte[] PATCH_FRAMELOCK_SPEED_FIX_DISABLE = new byte[1] { 0x90 }; // 30f
|
||||
/// <summary>
|
||||
/// Finds closest valid speed fix value for a frame rate limit.
|
||||
/// </summary>
|
||||
/// <param name="frameLimit">The set frame rate limit.</param>
|
||||
/// <returns>The byte patch of the closest speed fix.</returns>
|
||||
internal static byte[] FindSpeedFixForRefreshRate(int frameLimit)
|
||||
{
|
||||
float idealSpeedFix = frameLimit / 2f;
|
||||
KeyValuePair<byte[], float> closestSpeedFix = new KeyValuePair<byte[], float>(PATCH_FRAMELOCK_SPEED_FIX_DISABLE, 30f);
|
||||
foreach (var speedFix in PATCH_FRAMELOCK_SPEED_FIX_MATRIX.OrderBy(kvp => kvp.Value))
|
||||
{
|
||||
if (Math.Abs(idealSpeedFix - speedFix.Value) < Math.Abs(idealSpeedFix - closestSpeedFix.Value))
|
||||
closestSpeedFix = speedFix;
|
||||
}
|
||||
return closestSpeedFix.Key;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Reference pointer pCurrentResolutionWidth to <int>iInternalGameWidth (and <int>iInternalGameHeight which is +4 bytes)
|
||||
000000014114AC85 | 0F57D2 | xorps xmm2,xmm2 |
|
||||
000000014114AC88 | 890D 92147D02 | mov dword ptr ds:[14391C120],ecx | pCurrentResolutionWidth->iInternalGameWidth
|
||||
000000014114AC8E | 0F57C9 | xorps xmm1,xmm1 |
|
||||
000000014114AC91 | 8915 8D147D02 | mov dword ptr ds:[14391C124],edx | pCurrentResolutionHeight->iInternalGameHeight
|
||||
*/
|
||||
internal const string PATTERN_RESOLUTION_POINTER = "0F 57 D2 89 0D 00 00 00 00 0F 57 C9"; // 0F 57 D2 89 0D ?? ?? ?? ?? 0F 57 C9
|
||||
internal const string PATTERN_RESOLUTION_POINTER_MASK = "xxxxx????xxx";
|
||||
internal const int PATTERN_RESOLUTION_POINTER_OFFSET = 3;
|
||||
internal const int PATTERN_RESOLUTION_POINTER_INSTRUCTION_LENGTH = 6;
|
||||
|
||||
|
||||
/**
|
||||
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_MASK = "xxxxxxxx";
|
||||
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 };
|
||||
|
||||
|
||||
/**
|
||||
Conditional jump instruction that determines if 16/9 scaling for game is enforced or not, overwrite with non conditional JMP so widescreen won't get clinched
|
||||
000000014012967A | 74 47 | je sekiro.1401296C3 | conditional jump
|
||||
000000014012967C | 47:8B94C7 1C020000 | mov r10d,dword ptr ds:[r15+r8*8+21C] | start of long resolution scaling calculation method within jump
|
||||
*/
|
||||
internal const string PATTERN_RESOLUTION_SCALING_FIX = "47 47 8B 94 C7 1C 02 00 00"; // 47 47 8B 94 C7 1C 02 00 00
|
||||
internal const string PATTERN_RESOLUTION_SCALING_FIX_MASK = "xxxxxxxxx";
|
||||
internal const int PATTERN_RESOLUTION_SCALING_FIX_OFFSET = -1;
|
||||
internal static byte[] PATCH_RESOLUTION_SCALING_FIX_ENABLE = new byte[1] { 0xEB }; // JMP
|
||||
internal static byte[] PATCH_RESOLUTION_SCALING_FIX_DISABLE = new byte[1] { 0x74 }; // JE
|
||||
|
||||
|
||||
/**
|
||||
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
|
||||
0000000140739548 | F3:0F1008 | movss xmm1,dword ptr ds:[rax] |
|
||||
000000014073954C | F3:0F590D 0CE79B02 | mulss xmm1,dword ptr ds:[1430F7C60] | pFovTableEntry->fFov
|
||||
*/
|
||||
// credits to 'jackfuste' for original offset
|
||||
internal const string PATTERN_FOVSETTING = "F3 0F 10 08 F3 0F 59 0D 00 00 9B 02"; // F3 0F 10 08 F3 0F 59 0D ?? ?? 9B 02
|
||||
internal const string PATTERN_FOVSETTING_MASK = "xxxxxxxx??xx";
|
||||
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 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] {0x14, 0xE7}, "+ 40%" },
|
||||
{ new byte[2] {0x18, 0xE7}, "+ 75%" },
|
||||
{ new byte[2] {0x1C, 0xE7}, "+ 90%" }
|
||||
};
|
||||
internal static byte[] PATCH_FOVSETTING_DISABLE = new byte[2] { 0x0C, 0xE7 }; // + 0%
|
||||
|
||||
|
||||
/**
|
||||
Reference pointer pPlayerStatsRelated to PlayerStats pointer, offset in struct to <int>iPlayerDeaths
|
||||
00000001407AAC92 | 0FB648 7A | movzx ecx,byte ptr ds:[rax+7A] |
|
||||
00000001407AAC96 | 888B F7000000 | mov byte ptr ds:[rbx+F7],cl |
|
||||
00000001407AAC9C | 48:8B05 4DD03903 | mov rax,qword ptr ds:[143B47CF0] |
|
||||
00000001407AACA3 | 8B88 8C000000 | mov ecx,dword ptr ds:[rax+8C] |
|
||||
00000001407AACA9 | 898B F8000000 | mov dword ptr ds:[rbx+F8],ecx |
|
||||
00000001407AACAF | 48:8B05 3AD03903 | mov rax,qword ptr ds:[143B47CF0] | pPlayerStatsRelated->[PlayerStats+0x90]->iPlayerDeaths
|
||||
00000001407AACB6 | 8B88 90000000 | mov ecx,dword ptr ds:[rax+90] | offset pPlayerStats->iPlayerDeaths
|
||||
*/
|
||||
// credits to 'Me_TheCat' for original offset
|
||||
internal const string PATTERN_PLAYER_DEATHS = "0F B6 48 00 88 8B 00 00 00 00 48 8B 05 00 00 00 00 8B 88 00 00 00 00 89 8B 00 00 00 00 48 8B 05 00 00 00 00 8B 88 00 00 00 00"; // 0F B6 48 ?? 88 8B ?? ?? 00 00 48 8B 05 ?? ?? ?? ?? 8B 88 ?? ?? 00 00 89 8B ?? ?? 00 00 48 8B 05 ?? ?? ?? ?? 8B 88 ?? ?? 00 00
|
||||
internal const string PATTERN_PLAYER_DEATHS_MASK = "xxx?xx??xxxxx????xx??xxxx??xxxxx????xx??xx";
|
||||
internal const int PATTERN_PLAYER_DEATHS_OFFSET = 29;
|
||||
internal const int PATTERN_PLAYER_DEATHS_INSTRUCTION_LENGTH = 7;
|
||||
internal const int PATTERN_PLAYER_DEATHS_POINTER_OFFSET_OFFSET = 9;
|
||||
|
||||
/**
|
||||
Reference pointer pTotalKills to <int>iTotalKills, does not get updated on every kill but mostly on every 2nd, includes own player deaths...
|
||||
0000000141151838 | 48:8D0D A9A5B302 | lea rcx,qword ptr ds:[143C8BDE8] | pTotalKills->iTotalKills
|
||||
000000014115183F | 891481 | mov dword ptr ds:[rcx+rax*4],edx |
|
||||
0000000141151842 | C3 | ret |
|
||||
*/
|
||||
// credits to 'Me_TheCat' for original offset
|
||||
internal const string PATTERN_TOTAL_KILLS = "48 8D 0D 00 00 00 00 89 14 81 C3"; // 48 8D 0D ?? ?? ?? ?? 89 14 81 C3
|
||||
internal const string PATTERN_TOTAL_KILLS_MASK = "xxx????xxxx";
|
||||
internal const int PATTERN_TOTAL_KILLS_INSTRUCTION_LENGTH = 7;
|
||||
|
||||
|
||||
/**
|
||||
Reference pointer pTimeRelated to TimescaleManager pointer, offset in struct to <float>fTimescale which acts as a global speed scale for almost all ingame calculations
|
||||
0000000141149E87 | 48:8B05 3A24B402 | mov rax,qword ptr ds:[143C8C2C8] | pTimeRelated->[TimescaleManager+0x360]->fTimescale
|
||||
0000000141149E8E | F3:0F1088 60030000 | movss xmm1,dword ptr ds:[rax+360] | offset TimescaleManager->fTimescale
|
||||
0000000141149E96 | F3:0F5988 68020000 | mulss xmm1,dword ptr ds:[rax+268] |
|
||||
*/
|
||||
// credits to 'Zullie the Witch' for original offset
|
||||
internal const string PATTERN_TIMESCALE = "48 8B 05 00 00 00 00 F3 0F 10 88 00 00 00 00 F3 0F"; // 48 8B 05 ?? ?? ?? ?? F3 0F 10 88 ?? ?? ?? ?? F3 0F
|
||||
internal const string PATTERN_TIMESCALE_MASK = "xxx????xxxx????xx";
|
||||
internal const int PATTERN_TIMESCALE_INSTRUCTION_LENGTH = 7;
|
||||
internal const int PATTERN_TIMESCALE_POINTER_OFFSET_OFFSET = 11;
|
||||
|
||||
|
||||
/**
|
||||
Reference pointer pPlayerStructRelated1 to 4 more pointers up to player data class, offset in struct to <float>fTimescalePlayer which acts as a speed scale for the player character
|
||||
00000001406BF1D7 | 48:8B1D 128C4A03 | mov rbx,qword ptr ds:[143B67DF0] | pPlayerStructRelated1->[pPlayerStructRelated2+0x88]->[pPlayerStructRelated3+0x1FF8]->[pPlayerStructRelated4+0x28]->[pPlayerStructRelated5+0xD00]->fTimescalePlayer
|
||||
00000001406BF1DE | 48:85DB | test rbx,rbx |
|
||||
00000001406BF1E1 | 74 3C | je sekiro.1406BF21F |
|
||||
00000001406BF1E3 | 8B17 | mov edx,dword ptr ds:[rdi] |
|
||||
*/
|
||||
// credits to 'Zullie the Witch' for original offset
|
||||
internal const string PATTERN_TIMESCALE_PLAYER = "48 8B 1D 00 00 00 00 48 85 DB 74 3C 8B 17"; // 48 8B 1D ?? ?? ?? ?? 48 85 DB 74 3C 8B 17
|
||||
internal const string PATTERN_TIMESCALE_PLAYER_MASK = "xxx????xxxxxxx";
|
||||
internal const int PATTERN_TIMESCALE_PLAYER_INSTRUCTION_LENGTH = 7;
|
||||
internal const int PATTERN_TIMESCALE_POINTER2_OFFSET = 0x88;
|
||||
internal const int PATTERN_TIMESCALE_POINTER3_OFFSET = 0x1FF8;
|
||||
internal const int PATTERN_TIMESCALE_POINTER4_OFFSET = 0x28;
|
||||
internal const int PATTERN_TIMESCALE_POINTER5_OFFSET = 0xD00;
|
||||
|
||||
// game stat values by Me_TheCat
|
||||
internal const string PATTERN_PLAYER_DEATHS = "8B 88 90 00 00 00 89 8B FC 00 00 00 48 8B";
|
||||
internal const string PATTERN_PLAYER_DEATHS_MASK = "xxxxxxxxx???xx";
|
||||
|
||||
internal const string PATTERN_TOTAL_KILLS = "48 8D 0D 00 00 00 00 89 14 81 C3";
|
||||
internal const string PATTERN_TOTAL_KILLS_MASK = "xxx????xxxx";
|
||||
internal const int PATTERN_TOTAL_KILLS_OFFSET = 7;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,117 +1,109 @@
|
|||
<Window x:Class="SekiroFpsUnlockAndMore.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:SekiroFpsUnlockAndMore"
|
||||
mc:Ignorable="d"
|
||||
Title="Sekiro FPS Unlocker and more v1.1.0" Width="Auto" Height="Auto" SizeToContent="WidthAndHeight" ResizeMode="CanMinimize" Loaded="Window_Loaded" Closing="Window_Closing">
|
||||
|
||||
<Grid Background="#FFF9F9F9">
|
||||
<StackPanel Margin="10,10,10,0" Width="290" Height="Auto">
|
||||
<DockPanel LastChildFill="False">
|
||||
<CheckBox x:Name="cbFramelock" DockPanel.Dock="Left" Margin="0,0,0,0" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Content="Frame rate lock:" IsChecked="True" Checked="CbFramelock_Check_Handler" Unchecked="CbFramelock_Check_Handler" />
|
||||
<TextBox x:Name="tbFramelock" DockPanel.Dock="Right" Margin="0,0,0,0" Width="116" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Text="144" MaxLength="3" PreviewTextInput="Numeric_PreviewTextInput" DataObject.Pasting="Numeric_PastingHandler" />
|
||||
</DockPanel>
|
||||
<DockPanel Margin="0,5,0,0" LastChildFill="False">
|
||||
<CheckBox x:Name="cbAddResolution" DockPanel.Dock="Left" Margin="0,0,0,0" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Content="Add custom resolution:" Checked="CbAddResolution_Check_Handler" Unchecked="CbAddResolution_Check_Handler" />
|
||||
<TextBox x:Name="tbHeight" DockPanel.Dock="Right" Margin="0,0,0,0" Width="50" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Text="1080" MaxLength="4" PreviewTextInput="Numeric_PreviewTextInput" DataObject.Pasting="Numeric_PastingHandler" />
|
||||
<Label DockPanel.Dock="Right" Margin="0,0,0,0" Height="25" FontSize="14 px" Content="x" />
|
||||
<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"/>
|
||||
</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" IsEnabled="False" Checked="CbBorderless_Checked" Unchecked="CbBorderless_Unchecked"/>
|
||||
<CheckBox x:Name="cbBorderlessStretch" DockPanel.Dock="Right" Margin="10,0,0,0" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Content="Fullscreen stretch" IsEnabled="False" Checked="CbBorderlessStretch_Check_Handler" Unchecked="CbBorderlessStretch_Check_Handler" />
|
||||
</StackPanel>
|
||||
<DockPanel Margin="0,5,0,0" LastChildFill="False">
|
||||
<CheckBox x:Name="cbLogStats" Content="Log stats (Deaths, Enemies killed)" HorizontalAlignment="Left" Margin="0,0,0,0" VerticalAlignment="Top" FontSize="14 px" Checked="CbStatChanged" Unchecked="CbStatChanged" />
|
||||
</DockPanel>
|
||||
<Expander x:Name="exGameMods" Margin="-3,4,0,0" Height="Auto" FontSize="14 px" Header="Game modifications" IsExpanded="False">
|
||||
<Grid Margin="3,0,0,0" Background="#FFF9F9F9">
|
||||
<StackPanel Width="Auto" Height="Auto">
|
||||
<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 (%):" 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=">" 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="<" 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="To enable this start the game and load a save, then start the patcher." 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=">" 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="<" 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>
|
||||
</Expander>
|
||||
<Button x:Name="bPatch" Margin="0,7,0,0" Width="290" Height="30" FontSize="14 px" IsEnabled="False" Content="Patch game (CTRL + P)" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"
|
||||
Focusable="False" Click="BPatch_Click" />
|
||||
<TextBox x:Name="tbStatus" Margin="0,5,0,0" Width="290" Height="25" FontSize="14 px" FontWeight="Bold" Text="waiting for game..." TextAlignment="Center" TextWrapping="NoWrap" IsEnabled="False" />
|
||||
<TextBlock HorizontalAlignment="Left" TextWrapping="WrapWithOverflow" Margin="2,5,2,0" VerticalAlignment="Top" FontSize="11 px" IsEnabled="False">
|
||||
<TextBlock.Inlines>
|
||||
<Run FontWeight="Bold" Foreground="#FF0046FF">This patcher does not modify game files, you have to start it every time.</Run>
|
||||
<Run FontWeight="Bold">If your monitor is 60 Hz you will have to use</Run>
|
||||
<Run FontWeight="Bold" Foreground="#FFF00000">fullscreen mode and force disable VSYNC</Run>
|
||||
<Run FontWeight="Bold">with Nvidia Control panel or AMD Radeon Settings to get frame rate unlock working.</Run>
|
||||
<Run>To avoid stuttering it's recommended to disable VSYNC even with a 144 Hz monitor.</Run>
|
||||
<Run FontWeight="Bold">If your monitor is >60 Hz you will have to use</Run>
|
||||
<Run FontWeight="Bold" Foreground="#FFF00000">borderless window mode or force the game to run on highest available refresh rate</Run>
|
||||
<Run FontWeight="Bold">using Nvidia Control panel or AMD Radeon Settings.</Run>
|
||||
<Run>Borderless window mode requires "Windowed" in game settings first.</Run>
|
||||
<Run FontWeight="Bold">Custom resolution adds 21/9 support and overwrites a default resolution, hud will be limited to 16/9.</Run>
|
||||
<Run>Borderless window mode with custom resolution requires you to patch and set resolution first, then add borderless patch afterwards.</Run>
|
||||
<Run FontWeight="Bold">To enable Player Speed modification you have to be ingame first.</Run>
|
||||
<Run FontWeight="Bold" Foreground="#FFF00000">See the link below for detailed information, guides, GSYNC support and an AMD fix.</Run>
|
||||
</TextBlock.Inlines>
|
||||
</TextBlock>
|
||||
<Label HorizontalAlignment="Right" FontSize="12 px">
|
||||
<Hyperlink NavigateUri="https://github.com/uberhalit/SekiroFpsUnlockAndMore" RequestNavigate="Hyperlink_RequestNavigate">
|
||||
v1.1.0 - by uberhalit
|
||||
</Hyperlink>
|
||||
</Label>
|
||||
<StatusBar DockPanel.Dock="Bottom" Margin="-10,0,-10,0" VerticalAlignment="Bottom">
|
||||
<StatusBar.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
</Grid>
|
||||
</ItemsPanelTemplate>
|
||||
</StatusBar.ItemsPanel>
|
||||
<Window x:Class="SekiroFpsUnlockAndMore.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:SekiroFpsUnlockAndMore"
|
||||
mc:Ignorable="d"
|
||||
Title="Sekiro FPS Unlocker and more v1.1.1" Width="Auto" Height="Auto" SizeToContent="WidthAndHeight" ResizeMode="CanMinimize" Loaded="Window_Loaded" Closing="Window_Closing">
|
||||
|
||||
<StatusBarItem Grid.Column="0">
|
||||
<TextBlock Name="sbDeathCount">
|
||||
<Run Text="Deaths:"/>
|
||||
<Run Text="{Binding Deaths}"/>
|
||||
</TextBlock>
|
||||
</StatusBarItem>
|
||||
<Separator Grid.Column="1" />
|
||||
<StatusBarItem Grid.Column="2">
|
||||
<TextBlock Name="sbKillCount">
|
||||
<Run Text="Kills:"/>
|
||||
<Run Text="{Binding Kills}"/>
|
||||
</TextBlock>
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
<Grid Background="#FFF9F9F9">
|
||||
<DockPanel>
|
||||
<StackPanel DockPanel.Dock="Top" Margin="10,10,10,0" Width="290" Height="Auto">
|
||||
<DockPanel LastChildFill="False">
|
||||
<CheckBox x:Name="cbFramelock" DockPanel.Dock="Left" Margin="0,0,0,0" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Content="Frame rate lock:" IsChecked="True" Checked="CbFramelock_Check_Handler" Unchecked="CbFramelock_Check_Handler" />
|
||||
<TextBox x:Name="tbFramelock" DockPanel.Dock="Right" Margin="0,0,0,0" Width="116" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Text="144" MaxLength="3" PreviewTextInput="Numeric_PreviewTextInput" DataObject.Pasting="Numeric_PastingHandler" />
|
||||
</DockPanel>
|
||||
<DockPanel Margin="0,5,0,0" LastChildFill="False">
|
||||
<CheckBox x:Name="cbAddResolution" DockPanel.Dock="Left" Margin="0,0,0,0" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Content="Add custom resolution:" Checked="CbAddResolution_Check_Handler" Unchecked="CbAddResolution_Check_Handler" />
|
||||
<TextBox x:Name="tbHeight" DockPanel.Dock="Right" Margin="0,0,0,0" Width="50" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Text="1080" MaxLength="4" PreviewTextInput="Numeric_PreviewTextInput" DataObject.Pasting="Numeric_PastingHandler" />
|
||||
<Label DockPanel.Dock="Right" Margin="0,0,0,0" Height="25" FontSize="14 px" Content="x" />
|
||||
<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"/>
|
||||
</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"/>
|
||||
<CheckBox x:Name="cbBorderlessStretch" DockPanel.Dock="Right" Margin="10,0,0,0" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Content="Fullscreen stretch" IsEnabled="False" Checked="CbBorderlessStretch_Check_Handler" Unchecked="CbBorderlessStretch_Check_Handler" />
|
||||
</StackPanel>
|
||||
<CheckBox x:Name="cbLogStats" Margin="0,5,0,0" Height="25" FontSize="14 px" VerticalContentAlignment="Center" Content="Log stats to file (Deaths, Kills)" ToolTip="Check the guide on how to display these with OBS." Checked="CbStatChanged" Unchecked="CbStatChanged" />
|
||||
<Expander x:Name="exGameMods" Margin="-3,5,0,0" Height="Auto" FontSize="14 px" Header="Game modifications" IsExpanded="False">
|
||||
<Grid Margin="3,0,0,0" Background="#FFF9F9F9">
|
||||
<StackPanel Width="Auto" Height="Auto">
|
||||
<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 (%):" 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=">" 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="<" 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="To enable this start the game and load a save, then tick the checkbox." 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=">" 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="<" 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>
|
||||
</Expander>
|
||||
<Button x:Name="bPatch" Margin="0,7,0,0" Width="290" Height="30" FontSize="14 px" IsEnabled="False" Content="Patch game (CTRL + P)" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"
|
||||
Focusable="False" Click="BPatch_Click" />
|
||||
<TextBox x:Name="tbStatus" Margin="0,5,0,0" Width="290" Height="25" FontSize="14 px" FontWeight="Bold" Text="waiting for game..." TextAlignment="Center" TextWrapping="NoWrap" IsEnabled="False" />
|
||||
<TextBlock Margin="2,5,2,0" FontSize="11 px" TextWrapping="WrapWithOverflow" IsEnabled="False">
|
||||
<TextBlock.Inlines>
|
||||
<Run FontWeight="Bold" Foreground="#FF0046FF">This patcher does not modify game files, you have to start it every time.</Run>
|
||||
<Run FontWeight="Bold">If your monitor is 60 Hz you will have to use</Run>
|
||||
<Run FontWeight="Bold" Foreground="#FFF00000">fullscreen mode and force disable VSYNC</Run>
|
||||
<Run FontWeight="Bold">with Nvidia Control panel or AMD Radeon Settings to get frame rate unlock working.</Run>
|
||||
<Run>To avoid stuttering it's recommended to disable VSYNC even with a 144 Hz monitor.</Run>
|
||||
<Run FontWeight="Bold">If your monitor is >60 Hz you will have to use</Run>
|
||||
<Run FontWeight="Bold" Foreground="#FFF00000">borderless window mode or force the game to run on highest available refresh rate</Run>
|
||||
<Run FontWeight="Bold">using Nvidia Control panel or AMD Radeon Settings.</Run>
|
||||
<Run>Borderless window mode requires "Windowed" in game settings first.</Run>
|
||||
<Run FontWeight="Bold">Custom resolution adds 21/9 support and overwrites a default resolution, hud will be limited to 16/9.</Run>
|
||||
<Run>Borderless window mode with custom resolution requires you to patch and set resolution first, then add borderless patch afterwards.</Run>
|
||||
<Run FontWeight="Bold">To enable Player Speed modification you have to be ingame first.</Run>
|
||||
<Run FontWeight="Bold" Foreground="#FFF00000">See the link below for detailed information, guides, GSYNC support and an AMD fix.</Run>
|
||||
</TextBlock.Inlines>
|
||||
</TextBlock>
|
||||
<Label HorizontalAlignment="Right" FontSize="12 px">
|
||||
<Hyperlink NavigateUri="https://github.com/uberhalit/SekiroFpsUnlockAndMore" RequestNavigate="Hyperlink_RequestNavigate">
|
||||
v1.1.1 - by uberhalit
|
||||
</Hyperlink>
|
||||
</Label>
|
||||
</StackPanel>
|
||||
<StatusBar DockPanel.Dock="Bottom" Height="25">
|
||||
<StatusBarItem DockPanel.Dock="Left">
|
||||
<TextBlock Name="sbDeathCount">
|
||||
<Run Text="Deaths:" />
|
||||
<Run Text="{Binding Deaths}" />
|
||||
</TextBlock>
|
||||
</StatusBarItem>
|
||||
<Separator Margin="5,4,5,4" Style="{StaticResource {x:Static ToolBar.SeparatorStyleKey}}"/>
|
||||
<StatusBarItem DockPanel.Dock="Left">
|
||||
<TextBlock Name="sbKillCount">
|
||||
<Run Text="Kills:" />
|
||||
<Run Text="{Binding Kills}" />
|
||||
</TextBlock>
|
||||
</StatusBarItem>
|
||||
<Separator Margin="5,4,5,4" Style="{StaticResource {x:Static ToolBar.SeparatorStyleKey}}"/>
|
||||
<StatusBarItem DockPanel.Dock="Right" HorizontalAlignment="Right">
|
||||
<TextBlock Name="sbStatus" />
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -21,5 +21,5 @@ using System.Runtime.InteropServices;
|
|||
ResourceDictionaryLocation.SourceAssembly
|
||||
)]
|
||||
|
||||
[assembly: AssemblyVersion("1.1.0.1")]
|
||||
[assembly: AssemblyFileVersion("1.1.0.1")]
|
||||
[assembly: AssemblyVersion("1.1.1.0")]
|
||||
[assembly: AssemblyFileVersion("1.1.1.0")]
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@
|
|||
<SubType>Designer</SubType>
|
||||
</ApplicationDefinition>
|
||||
<Compile Include="SettingsService.cs" />
|
||||
<Compile Include="StatViewModel.cs" />
|
||||
<Compile Include="StatusViewModel.cs" />
|
||||
<Page Include="MainWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
|
|
|
|||
|
|
@ -1,112 +1,109 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace SekiroFpsUnlockAndMore
|
||||
{
|
||||
[XmlRoot("SekiroFpsUnlockAndMore")]
|
||||
[Serializable]
|
||||
public class SettingsService
|
||||
{
|
||||
private readonly string sConfigurationPath = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location) + @"\config.xml";
|
||||
|
||||
/// <summary>
|
||||
/// Read and store settings here.
|
||||
/// </summary>
|
||||
public SettingsService settings;
|
||||
|
||||
|
||||
/**
|
||||
* Settings definition
|
||||
*/
|
||||
[XmlElement]
|
||||
public bool cbFramelock { get; set; }
|
||||
[XmlElement]
|
||||
public int tbFramelock { get; set; }
|
||||
[XmlElement]
|
||||
public bool cbAddResolution { get; set; }
|
||||
[XmlElement]
|
||||
public int tbWidth { get; set; }
|
||||
[XmlElement]
|
||||
public int tbHeight { get; set; }
|
||||
[XmlElement]
|
||||
public bool cbFov { get; set; }
|
||||
[XmlElement]
|
||||
public int cbSelectFov { get; set; }
|
||||
[XmlElement]
|
||||
public bool cbBorderless { get; set; }
|
||||
[XmlElement]
|
||||
public bool cbBorderlessStretch { get; set; }
|
||||
[XmlElement]
|
||||
public bool exGameMods { get; set; }
|
||||
[XmlElement]
|
||||
public bool cbGameSpeed { get; set; }
|
||||
[XmlElement]
|
||||
public int tbGameSpeed { get; set; }
|
||||
[XmlElement]
|
||||
public bool cbPlayerSpeed { get; set; }
|
||||
[XmlElement]
|
||||
public int tbPlayerSpeed { get; set; }
|
||||
[XmlElement]
|
||||
public bool cbLogStats { get; set; }
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
public SettingsService() { }
|
||||
|
||||
/// <summary>
|
||||
/// Create a settings provider to load and save settings.
|
||||
/// </summary>
|
||||
/// <param name="settingsFilePath">The file path to the settings file.</param>
|
||||
public SettingsService(string settingsFilePath = null)
|
||||
{
|
||||
if (settingsFilePath != null)
|
||||
{
|
||||
sConfigurationPath = settingsFilePath;
|
||||
settings = new SettingsService();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load settings from file into settings property.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal bool Load()
|
||||
{
|
||||
if (!File.Exists(sConfigurationPath)) return false;
|
||||
|
||||
XmlSerializer xmlSerializer = new XmlSerializer(typeof(SettingsService));
|
||||
using (StreamReader streamReader = new StreamReader(sConfigurationPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
settings = (SettingsService)xmlSerializer.Deserialize(streamReader);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show("Error while loading configuration file:\n" + ex.Message, "Sekiro FPS Unlocker and more");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save settings from settings property to file.
|
||||
/// </summary>
|
||||
internal void Save()
|
||||
{
|
||||
XmlSerializer xmlSerializer = new XmlSerializer(typeof(SettingsService));
|
||||
using (StreamWriter streamReader = new StreamWriter(sConfigurationPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
xmlSerializer.Serialize(streamReader, settings);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show("Error while writing configuration file:\n" + ex.Message, "Sekiro FPS Unlocker and more");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace SekiroFpsUnlockAndMore
|
||||
{
|
||||
[XmlRoot("SekiroFpsUnlockAndMore")]
|
||||
[Serializable]
|
||||
public class ApplicationSettings
|
||||
{
|
||||
/**
|
||||
* Settings definition
|
||||
*/
|
||||
[XmlElement]
|
||||
public bool cbFramelock { get; set; }
|
||||
[XmlElement]
|
||||
public int tbFramelock { get; set; }
|
||||
[XmlElement]
|
||||
public bool cbAddResolution { get; set; }
|
||||
[XmlElement]
|
||||
public int tbWidth { get; set; }
|
||||
[XmlElement]
|
||||
public int tbHeight { get; set; }
|
||||
[XmlElement]
|
||||
public bool cbFov { get; set; }
|
||||
[XmlElement]
|
||||
public int cbSelectFov { get; set; }
|
||||
[XmlElement]
|
||||
public bool cbBorderless { get; set; }
|
||||
[XmlElement]
|
||||
public bool cbBorderlessStretch { get; set; }
|
||||
[XmlElement]
|
||||
public bool cbLogStats { get; set; }
|
||||
[XmlElement]
|
||||
public bool exGameMods { get; set; }
|
||||
[XmlElement]
|
||||
public bool cbGameSpeed { get; set; }
|
||||
[XmlElement]
|
||||
public int tbGameSpeed { get; set; }
|
||||
[XmlElement]
|
||||
public bool cbPlayerSpeed { get; set; }
|
||||
[XmlElement]
|
||||
public int tbPlayerSpeed { get; set; }
|
||||
}
|
||||
|
||||
public class SettingsService
|
||||
{
|
||||
private readonly string _sConfigurationPath = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location) + @"\config.xml";
|
||||
|
||||
/// <summary>
|
||||
/// Read and store settings here.
|
||||
/// </summary>
|
||||
public ApplicationSettings ApplicationSettings;
|
||||
|
||||
/// <summary>
|
||||
/// Create a settings provider to load and save settings.
|
||||
/// </summary>
|
||||
/// <param name="settingsFilePath">The file path to the settings file.</param>
|
||||
public SettingsService(string settingsFilePath = null)
|
||||
{
|
||||
if (settingsFilePath != null) _sConfigurationPath = settingsFilePath;
|
||||
ApplicationSettings = new ApplicationSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load settings from file into settings property.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal bool Load()
|
||||
{
|
||||
if (!File.Exists(_sConfigurationPath)) return false;
|
||||
|
||||
XmlSerializer xmlSerializer = new XmlSerializer(typeof(ApplicationSettings));
|
||||
using (StreamReader streamReader = new StreamReader(_sConfigurationPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
ApplicationSettings = (ApplicationSettings)xmlSerializer.Deserialize(streamReader);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show("Error while loading configuration file:\n" + ex.Message, "Sekiro FPS Unlocker and more");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save settings from settings property to file.
|
||||
/// </summary>
|
||||
internal void Save()
|
||||
{
|
||||
XmlSerializer xmlSerializer = new XmlSerializer(typeof(ApplicationSettings));
|
||||
using (StreamWriter streamReader = new StreamWriter(_sConfigurationPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
xmlSerializer.Serialize(streamReader, ApplicationSettings);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show("Error while writing configuration file:\n" + ex.Message, "Sekiro FPS Unlocker and more");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,43 +1,39 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace SekiroFpsUnlockAndMore
|
||||
{
|
||||
/// <summary>
|
||||
/// For Status bar display
|
||||
/// </summary>
|
||||
class StatViewModel : INotifyPropertyChanged
|
||||
{
|
||||
private int _deaths = 0;
|
||||
public int Deaths
|
||||
{
|
||||
get { return _deaths; }
|
||||
set
|
||||
{
|
||||
_deaths = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs("Deaths"));
|
||||
}
|
||||
}
|
||||
|
||||
private int _kills = 0;
|
||||
public int Kills
|
||||
{
|
||||
get { return _kills; }
|
||||
set
|
||||
{
|
||||
_kills = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs("Kills"));
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public void OnPropertyChanged(PropertyChangedEventArgs e)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace SekiroFpsUnlockAndMore
|
||||
{
|
||||
/// <summary>
|
||||
/// For Status bar display
|
||||
/// </summary>
|
||||
class StatusViewModel : INotifyPropertyChanged
|
||||
{
|
||||
private int _deaths = 0;
|
||||
public int Deaths
|
||||
{
|
||||
get { return _deaths; }
|
||||
set
|
||||
{
|
||||
_deaths = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs("Deaths"));
|
||||
}
|
||||
}
|
||||
|
||||
private int _kills = 0;
|
||||
public int Kills
|
||||
{
|
||||
get { return _kills; }
|
||||
set
|
||||
{
|
||||
_kills = value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs("Kills"));
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public void OnPropertyChanged(PropertyChangedEventArgs e)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue