From ef49c34d18f6c3ce331e9c32b92171bd7cd2364c Mon Sep 17 00:00:00 2001 From: Fabian Date: Wed, 13 Oct 2021 22:13:45 +0200 Subject: [PATCH] New command line system. Prepare support for multiple game versions. --- README.md | 11 +- src/Arctium.WoW.Launcher.csproj | 1 + src/Constants/GameVersion.cs | 11 ++ src/LaunchOptions.cs | 39 ++++ src/Launcher.cs | 290 ++++++++++++++++++++++++++++ src/Patches/Common.cs | 5 +- src/Patterns/Common.cs | 8 +- src/Program.cs | 328 ++------------------------------ 8 files changed, 364 insertions(+), 329 deletions(-) create mode 100644 src/Constants/GameVersion.cs create mode 100644 src/LaunchOptions.cs create mode 100644 src/Launcher.cs diff --git a/README.md b/README.md index 1f034a0..5d85315 100644 --- a/README.md +++ b/README.md @@ -11,14 +11,13 @@ The WoW Launcher source is licensed under the [MIT](https://github.com/Arctium/W You can find signed binary releases at [Arctium](https://arctium.io/wow) ### Supported Game Versions -* 9.0.5 or later (master) -* 2.5.1 or later (2.5.x) +* 9.0.5 or later (windows) ## Building ### Build Prerequisites -* [.NET Core SDK 6.0.0-rc.1 or later](https://dotnet.microsoft.com/download/dotnet/6.0) -* Optional for native builds: C++ workload through Visual Studio 2019/2022 or latest C++ build tools +* [.NET Core SDK 6.0.0-rc.2 or later](https://dotnet.microsoft.com/download/dotnet/6.0) +* Optional for native builds: C++ workload through Visual Studio 2022 or latest C++ build tools ### Build Instructions Windows (native) * Execute `dotnet publish -r win-x64 /p:Configuration=Release /p:platform="x64" --self-contained` @@ -30,9 +29,7 @@ You can find signed binary releases at [Arctium](https://arctium.io/wow) 3. Run the `Actium WoW Launcher.exe` ### Parameters -* `--path "Your WoW Path"` to specify your custom wow path. -* `--binary "Your WoW Binary"` to specify your custom wow binary name. -* `-keepcache` to skip deleting the Cache folder on launch.. +* Use --help ## WARNING diff --git a/src/Arctium.WoW.Launcher.csproj b/src/Arctium.WoW.Launcher.csproj index 38b57c7..b37a75a 100644 --- a/src/Arctium.WoW.Launcher.csproj +++ b/src/Arctium.WoW.Launcher.csproj @@ -31,6 +31,7 @@ + diff --git a/src/Constants/GameVersion.cs b/src/Constants/GameVersion.cs new file mode 100644 index 0000000..e20464b --- /dev/null +++ b/src/Constants/GameVersion.cs @@ -0,0 +1,11 @@ +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Arctium.WoW.Launcher.Constants; + +enum GameVersion +{ + Retail, + Classic, + ClassicEra, +} + diff --git a/src/LaunchOptions.cs b/src/LaunchOptions.cs new file mode 100644 index 0000000..3217271 --- /dev/null +++ b/src/LaunchOptions.cs @@ -0,0 +1,39 @@ +// Copyright (c) Arctium. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.CommandLine; +using System.CommandLine.Builder; +using System.CommandLine.Parsing; + +using Command = System.CommandLine.Command; + +namespace Arctium.WoW.Launcher.Misc +{ + static class LaunchOptions + { + public static Option Version = new("--version", () => GameVersion.Retail); + public static Option GamePath = new("--path", () => AppDomain.CurrentDomain.BaseDirectory); + public static Option KeepCache = new("--keepcache", () => true); + + public static Parser Instance => new CommandLineBuilder(ConfigureCommandLine(RootCommand)) + .UseHelp() + .UseParseDirective() + .UseSuggestDirective() + .Build(); + + public static RootCommand RootCommand = new("Arctium\0WoW\0Launcher") + { + Version, + GamePath, + KeepCache + }; + + static Command ConfigureCommandLine(Command rootCommand) + { + // Do not show errors for unknown command line parameters. + rootCommand.TreatUnmatchedTokensAsErrors = true; + + return rootCommand; + } + } +} diff --git a/src/Launcher.cs b/src/Launcher.cs new file mode 100644 index 0000000..c06b22c --- /dev/null +++ b/src/Launcher.cs @@ -0,0 +1,290 @@ +// Copyright (c) Arctium. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using static Arctium.WoW.Launcher.Misc.Helpers; +using System.CommandLine.Parsing; + +namespace Arctium.WoW.Launcher; + +class Launcher +{ + public static string PrepareGameLaunch(ParseResult commandLineResult) + { + var gameVersion = commandLineResult.ValueForOption(LaunchOptions.Version); + var (SubFolder, BinaryName, MajorGameVersion, MinGameBuild) = gameVersion switch + { + GameVersion.Retail => ("_retail_", "Wow.exe", 9, 37862), + //GameVersion.Classic => ("_classic_", "WowClassic.exe", 2, 39926), + //GameVersion.ClassicEra => ("_classic_era_", "WowClassic.exe", 1, 40347), + _ => throw new NotImplementedException("Invalid game version specified."), + }; + + var currentFolder = commandLineResult.ValueForOption(LaunchOptions.GamePath); + var gameFolder = $"{currentFolder}/{SubFolder}"; + var gameBinaryPath = $"{gameFolder}/{BinaryName}"; + + // Also support game installations without branch sub folders. + if (commandLineResult.HasOption(LaunchOptions.GamePath) || !File.Exists(gameBinaryPath)) + { + gameFolder = currentFolder; + gameBinaryPath = $"{gameFolder}/{BinaryName}"; + } + + if (!File.Exists(gameBinaryPath) || GetVersionValueFromClient(gameBinaryPath, 3) != MajorGameVersion) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"[Error] No {gameVersion} client found."); + + return String.Empty; + } + + var gameClientBuild = GetVersionValueFromClient(gameBinaryPath, 0); + + if (gameClientBuild < MinGameBuild && gameClientBuild != 0) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"Your found client version {gameClientBuild} is not supported."); + Console.WriteLine($"The minimum required build is {MinGameBuild}"); + + return String.Empty; + } + + // Delete the cache folder by default. + if (!commandLineResult.ValueForOption(LaunchOptions.KeepCache)) + { + try + { + // Trying to delete the cache folder. + Directory.Delete($"{gameFolder}/Cache", true); + } + catch (Exception) + { + // We don't care if it worked. Swallow it! + } + } + + return gameBinaryPath; + } + + public static bool LaunchGame(string appPath, string gameCommandLine) + { + var startupInfo = new StartupInfo(); + var processInfo = new ProcessInformation(); + + try + { + Console.ForegroundColor = ConsoleColor.White; + Console.WriteLine("Starting WoW client..."); + + var createSuccess = NativeWindows.CreateProcess(null, $"{appPath} {gameCommandLine}", 0, 0, false, 4U, 0, new FileInfo(appPath)?.DirectoryName, ref startupInfo, out processInfo); + + // On some systems we have to launch the game with the application name used. + if (!createSuccess) + createSuccess = NativeWindows.CreateProcess(appPath, $" {gameCommandLine}", 0, 0, false, 4U, 0, null, ref startupInfo, out processInfo); + + // Start process with suspend flags. + if (createSuccess) + { + var appFileInfo = new FileInfo(appPath); + var memory = new WinMemory(processInfo, appFileInfo); + + byte[] certBundleData = Convert.FromBase64String(Patches.Common.CertBundleData); + + // Build the version URL from the game binary build. + int wowBuild = GetVersionValueFromClient(appPath, 0); + byte[] versionPatch = Patches.Common.GetVersionUrl(wowBuild); + + // Wait for all direct memory patch tasks to complete, + Task.WaitAll(memory.PatchMemory(Patterns.Common.CertBundle, certBundleData, "Certificate Bundle"), + memory.PatchMemory(Patterns.Common.SignatureModulus, Patches.Common.SignatureModulus, "Certificate Signature Modulus"), + memory.PatchMemory(Patterns.Common.ConnectToModulus, Patches.Common.Modulus, "ConnectTo Modulus"), + memory.PatchMemory(Patterns.Common.ChangeProtocolModulus, Patches.Common.Modulus, "ChangeProtocol (GameCrypt) Modulus"), + memory.PatchMemory(Patterns.Common.Portal, Patches.Common.Portal, "Login Portal"), + memory.PatchMemory(Patterns.Common.VersionUrl, versionPatch, "Version URL"), + memory.PatchMemory(Patterns.Windows.LauncherLogin, Patches.Windows.LauncherLogin, "Launcher Login Registry")); + + // Resume the process to initialize it. + NativeWindows.NtResumeProcess(processInfo.ProcessHandle); + + var mbi = new MemoryBasicInformation(); + + // Wait for the memory region to be initialized. + while (NativeWindows.VirtualQueryEx(processInfo.ProcessHandle, memory.BaseAddress, out mbi, MemoryBasicInformation.Size) == 0 || mbi.RegionSize <= 0x1000) + { } + + if (mbi.BaseAddress != 0) + { + var patches = new Dictionary(); + + PrepareAntiCrash(memory, patches, ref mbi, ref processInfo); + + memory.RefreshMemoryData((int)mbi.RegionSize); + + // Get patch locations. + var certBundleOffset = memory.Data.FindPattern(Patterns.Windows.CertBundle); + var certCommonNameOffset = memory.Data.FindPattern(Patterns.Windows.CertCommonName); + + if (certBundleOffset == 0 || certCommonNameOffset == 0) + { + NativeWindows.TerminateProcess(processInfo.ProcessHandle, 0); + + Console.ForegroundColor = ConsoleColor.Red; + + Console.WriteLine("Not all patterns could be found:"); + Console.WriteLine($"CertBundle: {certBundleOffset != 0}"); + Console.WriteLine($"CertCommonName: {certCommonNameOffset != 0}"); + Console.WriteLine(); + + Console.ForegroundColor = ConsoleColor.Yellow; + + Console.WriteLine("Please contact the developer."); + + return false; + } + + patches["CertBundle"] = (certBundleOffset, Patches.Windows.CertBundle); + patches["CertCommonName"] = (certCommonNameOffset + 5, Patches.Windows.CertCommonName); + + NativeWindows.NtResumeProcess(processInfo.ProcessHandle); + + if (memory.RemapAndPatch(patches)) + { + Console.WriteLine("Done :) "); + + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("You can login now."); + + return true; + } + else + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("Error while launching the client."); + + NativeWindows.TerminateProcess(processInfo.ProcessHandle, 0); + } + } + } + } + catch (Exception ex) + { + // Just print out the exception we have and kill the game process. + Console.WriteLine(ex); + Console.WriteLine(ex.StackTrace); + + NativeWindows.TerminateProcess(processInfo.ProcessHandle, 0); + } + + return false; + } + + static void PrepareAntiCrash(WinMemory memory, Dictionary patches, ref MemoryBasicInformation mbi, ref ProcessInformation processInfo) + { + // Wait for client initialization. + var initOffset = memory?.Read(mbi.BaseAddress, (int)mbi.RegionSize)?.FindPattern(Patterns.Windows.Init) ?? 0; + + while (initOffset == 0) + { + initOffset = memory?.Read(mbi.BaseAddress, (int)mbi.RegionSize)?.FindPattern(Patterns.Windows.Init) ?? 0; + + Console.WriteLine("Waiting for client initialization..."); + } + + initOffset += BitConverter.ToUInt32(memory.Read(initOffset + memory.BaseAddress + 2, 4), 0) + 10; + + while (memory?.Read(initOffset + memory.BaseAddress, 1)?[0] == null || + memory?.Read(initOffset + memory.BaseAddress, 1)?[0] == 0) + memory.Data = memory.Read(mbi.BaseAddress, (int)mbi.RegionSize); + + memory.RefreshMemoryData((int)mbi.RegionSize); + + // Suspend the process and handle the patches. + NativeWindows.NtSuspendProcess(processInfo.ProcessHandle); + + // Get Integrity check locations + var integrityOffsets = memory.Data.FindPattern(Patterns.Windows.Integrity, int.MaxValue, (int)mbi.RegionSize).ToArray(); + + // Encrypt integrity offsets and patches and add them to the patch list. + for (var i = 0; i < integrityOffsets.Length; i++) + patches[$"Integrity{i}"] = (integrityOffsets[i], Patches.Windows.Integrity); + + // Get Integrity check locations + var integrityOffsets2 = memory.Data.FindPattern(Patterns.Windows.Integrity2, int.MaxValue, (int)mbi.RegionSize).ToArray(); + + // Encrypt integrity offsets and patches and add them to the patch list. + for (var i = 0; i < integrityOffsets2.Length; i++) + patches[$"Integrity{integrityOffsets.Length + i}"] = (integrityOffsets2[i], Patches.Windows.Integrity); + + // Get Remap check locations. + var remapOffsets = memory.Data.FindPattern(Patterns.Windows.Remap, int.MaxValue, (int)mbi.RegionSize); + var lastAddress = 0; + + foreach (var a in remapOffsets) + { + var instructionStart = (int)a + 4; + var instructionEnd = (int)a + 4 + 6; + var instructions = new byte[6]; + + Buffer.BlockCopy(memory.Data, instructionStart, instructions, 0, 6); + + // Skip unconditional jumps. + if (memory.IsUnconditionalJump(instructions)) + continue; + + var operandValue = 0; + + if (memory.IsShortJump(instructions)) + operandValue = instructions[1] + 2; + else if (memory.IsJump(instructions)) + operandValue = BitConverter.ToInt32(instructions, 2) + 6; + else + throw new InvalidDataException("Invalid operand value."); + + var jumpToValue = a + operandValue + 4; + var tempPatches = new ConcurrentDictionary(); + + // Find all references of real code parts inside the remap check functions. + Parallel.For(lastAddress, memory.Data.Length, i => + { + if (memory.IsJump(memory.Data, i)) + { + var jumpOperand = BitConverter.ToInt32(memory.Data, i + 2); + var jumpSize = (int)jumpToValue - i - 6; + + if (jumpOperand == jumpSize) + { + // Add 1 because we patch the instruction start. + // This results in a shorter overall instruction length. + var jumpBytes = new byte[] { 0xE9 }.Concat(BitConverter.GetBytes(jumpSize + 1)).ToArray(); + + tempPatches.TryAdd($"Jump{i}", (i, jumpBytes)); + } + } + else if (memory.IsShortJump(memory.Data, i)) + { + var jumpOperand = memory.Data[i + 1]; + var jumpSize = (int)jumpToValue - i - 2; + + if (jumpOperand == jumpSize) + { + // Check for 0x48 here. This is an indicator for the test instructions. + // Might need some better checks or future updates. + if (memory.Data[i - 3] == 0x48) + { + var iBytes = BitConverter.GetBytes(i); + var jumpBytes = new byte[] { 0xEB }; + + tempPatches.TryAdd($"ShortJump{i}", (i, jumpBytes)); + } + } + } + }); + + // Add the remap crash patches to the patch list. + foreach (var p in tempPatches) + patches[p.Key] = (p.Value.Item1, p.Value.Item2); + + lastAddress = (int)a; + } + } +} diff --git a/src/Patches/Common.cs b/src/Patches/Common.cs index 1803b3c..e55e136 100644 --- a/src/Patches/Common.cs +++ b/src/Patches/Common.cs @@ -40,9 +40,8 @@ static class Common 0xDA, 0x2B, 0x4B, 0x22, 0x40, 0xAB, 0x4F, 0xD3, 0xDD, 0x91, 0x5C, 0x54, 0x4C, 0x4B, 0xB8, 0xAB, 0x68, 0x84, 0x47, 0xCA, 0xE6, 0x8D, 0xFF, 0x12, 0xEC, 0x03, 0x5C, 0xAF, 0xC7, 0xC6, 0x90, 0xB7 }; - public static byte[] GetVersionUrl(int build) => Encoding.UTF8.GetBytes($"trinity6.github.io/%s/%s/{build}/versi"); - - public static byte[] Portal = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + public static byte[] GetVersionUrl(int build) => Encoding.UTF8.GetBytes($"ngdp.arctium.io/%s/%s/{build}/versions"); + public static byte[] Portal = new byte[Patterns.Common.Portal.Length]; // Our own ca_bundle.txt.signed file. // Supported certificates: diff --git a/src/Patterns/Common.cs b/src/Patterns/Common.cs index 8429145..fc2663f 100644 --- a/src/Patterns/Common.cs +++ b/src/Patterns/Common.cs @@ -5,10 +5,12 @@ namespace Arctium.WoW.Launcher.Patterns; static class Common { - public static short[] CertBundle = "{\"Created\":".ToPattern(); public static short[] ConnectToModulus = { 0x91, 0xD5, 0x9B, 0xB7, 0xD4, 0xE1, 0x83, 0xA5 }; public static short[] SignatureModulus = { 0x35, 0xFF, 0x17, 0xE7, 0x33, 0xC4, 0xD3, 0xD4 }; public static short[] ChangeProtocolModulus = { 0x71, 0xFD, 0xFA, 0x60, 0x14, 0x0D, 0xF2, 0x05 }; - public static short[] VersionUrl = { 0x25, 0x73, 0x2E, 0x70, 0x61, 0x74, 0x63, 0x68, 0x2E, 0x62, 0x61, 0x74, 0x74, 0x6C, 0x65, 0x2E, 0x6E, 0x65, 0x74, 0x3A, 0x31, 0x31, 0x31, 0x39, 0x2F, 0x25, 0x73, 0x2F, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73 }; - public static short[] Portal = { 0x2E, 0x61, 0x63, 0x74, 0x75, 0x61, 0x6C, 0x2E, 0x62, 0x61, 0x74, 0x74, 0x6C, 0x65, 0x2E, 0x6E, 0x65, 0x74, 0x00 }; + + public static short[] CertBundle = "{\"Created\":".ToPattern(); + public static short[] VersionUrl = "%s.patch.battle.net:1119/%s/versions".ToPattern(); + public static short[] Portal = ".actual.battle.net\0".ToPattern(); + public static short[] CommandLineHelp = "World of Warcraft usage".ToPattern(); } diff --git a/src/Program.cs b/src/Program.cs index e16a712..400c7a0 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -2,336 +2,32 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using static Arctium.WoW.Launcher.Misc.Helpers; +using System.CommandLine; +using System.CommandLine.Parsing; +using System.CommandLine.Invocation; namespace Arctium.WoW.Launcher; class Program { - static string wowBinary = "Wow.exe"; - static string wowPath = string.Empty; - static bool keepCache = false; - static string consoleArgs = string.Empty; - - static void Main(string[] args) + static async Task Main(string[] args) { PrintHeader("WoW Client Launcher"); - // Handle console launch args. - for (var i = 0; i < args.Length; i++) + LaunchOptions.RootCommand.Handler = CommandHandler.Create((ParseResult parseResult) => { - if (args[i] == "--path") - { - wowPath = args[i + 1]; - i++; + var appPath = Launcher.PrepareGameLaunch(parseResult); - continue; - } - else if (args[i] == "--binary") - { - wowBinary = args[i + 1]; - i++; + var gameCommandLine = string.Join(" ", parseResult.UnmatchedTokens); - continue; - } - else if (args[i] == "-keepcache") - { - keepCache = true; + if (string.IsNullOrEmpty(appPath) || !Launcher.LaunchGame(appPath, gameCommandLine)) + WaitAndExit(5000); + }); - continue; - } - - consoleArgs += " " + args[i]; - } - - var appPath = PrepareGameLaunch(); - - LaunchGame(appPath); + await LaunchOptions.Instance.InvokeAsync(args); } - static string PrepareGameLaunch() - { - // App info - var curDir = AppDomain.CurrentDomain.BaseDirectory; - var dataDir = $"{curDir}/_retail_"; - var appPath = $"{curDir}/_retail_/{wowBinary}"; - - if (!string.IsNullOrEmpty(wowPath)) - { - dataDir = wowPath; - appPath = $"{dataDir}/{wowBinary}"; - } - - // Also support game installations without branch sub folders. - if (!File.Exists(appPath)) - { - dataDir = $"{curDir}"; - appPath = $"{curDir}/{wowBinary}"; - } - - if (!File.Exists(appPath) || GetVersionValueFromClient(appPath, 3) != 9) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"Copy this launcher to your game folder!"); - Console.WriteLine(); - Console.WriteLine($"OR"); - Console.WriteLine(); - - Console.WriteLine("Use the '--path \"Your WoW Path\"' option to specify your custom wow sub path."); - Console.WriteLine("Use the '--binary \"YourWoWBinaryname\"' option to specify your custom wow binary name."); - Console.WriteLine(); - - WaitAndExit(300000); - } - - if (GetVersionValueFromClient(appPath, 0) < 37862 && GetVersionValueFromClient(appPath, 0) != 0) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"Your client version {GetVersionValueFromClient(appPath, 0)} is not supported."); - Console.WriteLine($"The minimum required build is 9.0.5.37862"); - - WaitAndExit(10000); - } - - // Delete the cache folder by default. - if (!keepCache) - { - try - { - // Trying to delete the cache folder. - Directory.Delete($"{dataDir}/Cache", true); - } - catch (Exception) - { - // We don't care if it worked. Swallow it! - } - } - - return appPath; - } - - static void LaunchGame(string appPath) - { - var startupInfo = new StartupInfo(); - var processInfo = new ProcessInformation(); - - try - { - Console.ForegroundColor = ConsoleColor.White; - Console.WriteLine("Starting WoW client..."); - - var createSuccess = NativeWindows.CreateProcess(null, $"{appPath} {consoleArgs}", 0, 0, false, 4U, 0, new FileInfo(appPath)?.DirectoryName, ref startupInfo, out processInfo); - - // On some systems we have to launch the game with the application name used. - if (!createSuccess) - createSuccess = NativeWindows.CreateProcess(appPath, $" {consoleArgs}", 0, 0, false, 4U, 0, null, ref startupInfo, out processInfo); - - // Start process with suspend flags. - if (createSuccess) - { - var appFileInfo = new FileInfo(appPath); - var memory = new WinMemory(processInfo, appFileInfo); - - byte[] certBundleData = Convert.FromBase64String(Patches.Common.CertBundleData); - - // Build the version URL from the game binary build. - int wowBuild = GetVersionValueFromClient(appPath, 0); - byte[] versionPatch = Patches.Common.GetVersionUrl(wowBuild); - - // Wait for all direct memory patch tasks to complete, - Task.WaitAll(memory.PatchMemory(Patterns.Common.CertBundle, certBundleData, "Certificate Bundle"), - memory.PatchMemory(Patterns.Common.SignatureModulus, Patches.Common.SignatureModulus, "Certificate Signature Modulus"), - memory.PatchMemory(Patterns.Common.ConnectToModulus, Patches.Common.Modulus, "ConnectTo Modulus"), - memory.PatchMemory(Patterns.Common.ChangeProtocolModulus, Patches.Common.Modulus, "ChangeProtocol (GameCrypt) Modulus"), - memory.PatchMemory(Patterns.Common.Portal, Patches.Common.Portal, "Login Portal"), - memory.PatchMemory(Patterns.Common.VersionUrl, versionPatch, "Version URL"), - memory.PatchMemory(Patterns.Windows.LauncherLogin, Patches.Windows.LauncherLogin, "Launcher Login Registry")); - - // Resume the process to initialize it. - NativeWindows.NtResumeProcess(processInfo.ProcessHandle); - - var mbi = new MemoryBasicInformation(); - - // Wait for the memory region to be initialized. - while (NativeWindows.VirtualQueryEx(processInfo.ProcessHandle, memory.BaseAddress, out mbi, MemoryBasicInformation.Size) == 0 || mbi.RegionSize <= 0x1000) - { } - - if (mbi.BaseAddress != 0) - { - var patches = new Dictionary(); - - PrepareAntiCrash(memory, patches, ref mbi, ref processInfo); - - memory.RefreshMemoryData((int)mbi.RegionSize); - - // Get patch locations. - var certBundleOffset = memory.Data.FindPattern(Patterns.Windows.CertBundle); - var certCommonNameOffset = memory.Data.FindPattern(Patterns.Windows.CertCommonName); - - if (certBundleOffset == 0 || certCommonNameOffset == 0) - { - NativeWindows.TerminateProcess(processInfo.ProcessHandle, 0); - - Console.ForegroundColor = ConsoleColor.Red; - - Console.WriteLine("Not all patterns could be found:"); - Console.WriteLine($"CertBundle: {certBundleOffset != 0}"); - Console.WriteLine($"CertCommonName: {certCommonNameOffset != 0}"); - Console.WriteLine(); - - Console.ForegroundColor = ConsoleColor.Yellow; - - Console.WriteLine("Please contact the developer."); - - WaitAndExit(5000); - } - - patches["CertBundle"] = (certBundleOffset, Patches.Windows.CertBundle); - patches["CertCommonName"] = (certCommonNameOffset + 5, Patches.Windows.CertCommonName); - - NativeWindows.NtResumeProcess(processInfo.ProcessHandle); - - if (memory.RemapAndPatch(patches)) - { - Console.WriteLine("Done :) "); - - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("You can login now."); - - WaitAndExit(0); - } - else - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine("Error while launching the client."); - - NativeWindows.TerminateProcess(processInfo.ProcessHandle, 0); - - WaitAndExit(5000); - } - } - } - } - catch (Exception ex) - { - // Just print out the exception we have and kill the game process. - Console.WriteLine(ex); - Console.WriteLine(ex.StackTrace); - - NativeWindows.TerminateProcess(processInfo.ProcessHandle, 0); - } - } - - static void PrepareAntiCrash(WinMemory memory, Dictionary patches, ref MemoryBasicInformation mbi, ref ProcessInformation processInfo) - { - // Wait for client initialization. - var initOffset = memory?.Read(mbi.BaseAddress, (int)mbi.RegionSize)?.FindPattern(Patterns.Windows.Init) ?? 0; - - while (initOffset == 0) - { - initOffset = memory?.Read(mbi.BaseAddress, (int)mbi.RegionSize)?.FindPattern(Patterns.Windows.Init) ?? 0; - - Console.WriteLine("Waiting for client initialization..."); - } - - initOffset += BitConverter.ToUInt32(memory.Read(initOffset + memory.BaseAddress + 2, 4), 0) + 10; - - while (memory?.Read(initOffset + memory.BaseAddress, 1)?[0] == null || - memory?.Read(initOffset + memory.BaseAddress, 1)?[0] == 0) - memory.Data = memory.Read(mbi.BaseAddress, (int)mbi.RegionSize); - - memory.RefreshMemoryData((int)mbi.RegionSize); - - // Suspend the process and handle the patches. - NativeWindows.NtSuspendProcess(processInfo.ProcessHandle); - - // Get Integrity check locations - var integrityOffsets = memory.Data.FindPattern(Patterns.Windows.Integrity, int.MaxValue, (int)mbi.RegionSize).ToArray(); - - // Encrypt integrity offsets and patches and add them to the patch list. - for (var i = 0; i < integrityOffsets.Length; i++) - patches[$"Integrity{i}"] = (integrityOffsets[i], Patches.Windows.Integrity); - - // Get Integrity check locations - var integrityOffsets2 = memory.Data.FindPattern(Patterns.Windows.Integrity2, int.MaxValue, (int)mbi.RegionSize).ToArray(); - - // Encrypt integrity offsets and patches and add them to the patch list. - for (var i = 0; i < integrityOffsets2.Length; i++) - patches[$"Integrity{integrityOffsets.Length + i}"] = (integrityOffsets2[i], Patches.Windows.Integrity); - - // Get Remap check locations. - var remapOffsets = memory.Data.FindPattern(Patterns.Windows.Remap, int.MaxValue, (int)mbi.RegionSize); - var lastAddress = 0; - - foreach (var a in remapOffsets) - { - var instructionStart = (int)a + 4; - var instructionEnd = (int)a + 4 + 6; - var instructions = new byte[6]; - - Buffer.BlockCopy(memory.Data, instructionStart, instructions, 0, 6); - - // Skip unconditional jumps. - if (memory.IsUnconditionalJump(instructions)) - continue; - - var operandValue = 0; - - if (memory.IsShortJump(instructions)) - operandValue = instructions[1] + 2; - else if (memory.IsJump(instructions)) - operandValue = BitConverter.ToInt32(instructions, 2) + 6; - else - throw new InvalidDataException("Invalid operand value."); - - var jumpToValue = a + operandValue + 4; - var tempPatches = new ConcurrentDictionary(); - - // Find all references of real code parts inside the remap check functions. - Parallel.For(lastAddress, memory.Data.Length, i => - { - if (memory.IsJump(memory.Data, i)) - { - var jumpOperand = BitConverter.ToInt32(memory.Data, i + 2); - var jumpSize = (int)jumpToValue - i - 6; - - if (jumpOperand == jumpSize) - { - // Add 1 because we patch the instruction start. - // This results in a shorter overall instruction length. - var jumpBytes = new byte[] { 0xE9 }.Concat(BitConverter.GetBytes(jumpSize + 1)).ToArray(); - - tempPatches.TryAdd($"Jump{i}", (i, jumpBytes)); - } - } - else if (memory.IsShortJump(memory.Data, i)) - { - var jumpOperand = memory.Data[i + 1]; - var jumpSize = (int)jumpToValue - i - 2; - - if (jumpOperand == jumpSize) - { - // Check for 0x48 here. This is an indicator for the test instructions. - // Might need some better checks or future updates. - if (memory.Data[i - 3] == 0x48) - { - var iBytes = BitConverter.GetBytes(i); - var jumpBytes = new byte[] { 0xEB }; - - tempPatches.TryAdd($"ShortJump{i}", (i, jumpBytes)); - } - } - } - }); - - // Add the remap crash patches to the patch list. - foreach (var p in tempPatches) - patches[p.Key] = (p.Value.Item1, p.Value.Item2); - - lastAddress = (int)a; - } - } - - static void WaitAndExit(int ms = 2000) + public static void WaitAndExit(int ms = 2000) { Console.ForegroundColor = ConsoleColor.Gray; Console.WriteLine($"Closing in {ms / 1000} seconds...");