From 221438ce2dee471a5293561ffd49285aa516789b Mon Sep 17 00:00:00 2001 From: Fabian Date: Sun, 21 May 2023 00:56:25 +0000 Subject: [PATCH] Added support for 10.1.5 (READ THE README): - Introduced new --dev command line option - README: Read the new "IMPORTANT NOTE FOR LOCAL DEVELOPMENT & SERVER CONNECTIONS" section. Properly kill the launcher and game client on pattern detection fails. --- README.md | 9 +++++ src/LaunchOptions.cs | 6 ++- src/Launcher.cs | 81 ++++++++++++++++++++++++++--------------- src/Patches/Windows.cs | 1 + src/Patterns/Windows.cs | 3 ++ src/Program.cs | 2 +- 6 files changed, 69 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index f837ba0..b3a5f32 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,14 @@ A game launcher for World of Warcraft that allows you to connect to custom serve Please see our Open Source project [Documentation Repo](https://github.com/Arctium/Documentation) +### IMPORTANT NOTE FOR LOCAL DEVELOPMENT & SERVER CONNECTIONS +* LOCAL HOSTNAME & IP: `USE` the `--dev` command line parameter to to avoid issues with invalid certificate chains. +* EXTERNAL HOSTNAME: + * `DO NOT` use the `--dev` command line parameter. + * `USE` a valid certificate matching your authentication/bnet server host name. + * That certificate needs to be loaded by the authentication/bnet server too. +* EXTERNAL IP: `NOT SUPPORTED` + ### Binary Releases You can find signed binary releases at [Releases](https://github.com/Arctium/WoW-Launcher/releases) @@ -63,6 +71,7 @@ Options: --binary --keepcache [default: True] --staticseed + --dev [default: False], Required for local development without valid certificates. -?, -h, --help Show help and usage information Additional Arguments: diff --git a/src/LaunchOptions.cs b/src/LaunchOptions.cs index 551de1e..e521b3f 100644 --- a/src/LaunchOptions.cs +++ b/src/LaunchOptions.cs @@ -4,7 +4,7 @@ using System.CommandLine.Builder; using System.CommandLine.Parsing; -namespace Arctium.WoW.Launcher.Misc; +namespace Arctium.WoW.Launcher; static class LaunchOptions { @@ -13,6 +13,7 @@ static class LaunchOptions public static Option GameBinary = new("--binary"); public static Option KeepCache = new("--keepcache", () => true); public static Option UseStaticAuthSeed = new("--staticseed"); + public static Option DevMode = new("--dev", () => false); public static Parser Instance => new CommandLineBuilder(ConfigureCommandLine(RootCommand)) .UseHelp() @@ -28,7 +29,8 @@ static class LaunchOptions GamePath, GameBinary, KeepCache, - UseStaticAuthSeed + UseStaticAuthSeed, + DevMode }; static Command ConfigureCommandLine(Command rootCommand) diff --git a/src/Launcher.cs b/src/Launcher.cs index 7249c43..aea11e2 100644 --- a/src/Launcher.cs +++ b/src/Launcher.cs @@ -2,15 +2,13 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.CommandLine.Parsing; -using System.Reflection.PortableExecutable; - using static Arctium.WoW.Launcher.Misc.Helpers; namespace Arctium.WoW.Launcher; -class Launcher +static class Launcher { - public static CancellationTokenSource CancellationTokenSource => new(); + public static readonly CancellationTokenSource CancellationTokenSource = new(); public static string PrepareGameLaunch(ParseResult commandLineResult) { @@ -91,7 +89,7 @@ class Launcher return gameBinaryPath; } - public static bool LaunchGame(string appPath, string gameCommandLine, bool useStaticAuthSeed) + public static bool LaunchGame(string appPath, string gameCommandLine, ParseResult commandLineResult) { var startupInfo = new StartupInfo(); var processInfo = new ProcessInformation(); @@ -101,7 +99,8 @@ class Launcher Console.ForegroundColor = ConsoleColor.White; Console.WriteLine("Starting WoW client..."); - var createSuccess = NativeWindows.CreateProcess(null, $"{appPath} {gameCommandLine}", 0, 0, false, 4, 0, new FileInfo(appPath)?.DirectoryName, ref startupInfo, out processInfo); + var createSuccess = NativeWindows.CreateProcess(null, $"{appPath} {gameCommandLine}", 0, 0, false, 4, 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) @@ -117,10 +116,11 @@ class Launcher // Resume the process to initialize it. NativeWindows.NtResumeProcess(processInfo.ProcessHandle); - var mbi = new MemoryBasicInformation(); + MemoryBasicInformation mbi; // Wait for the memory region to be initialized. - while (NativeWindows.VirtualQueryEx(processInfo.ProcessHandle, memory.BaseAddress, out mbi, MemoryBasicInformation.Size) == 0 || mbi.RegionSize <= 0x1000) + while (NativeWindows.VirtualQueryEx(processInfo.ProcessHandle, memory.BaseAddress, out mbi, MemoryBasicInformation.Size) == 0 || + mbi.RegionSize <= 0x1000) { } if (mbi.BaseAddress != 0) @@ -139,17 +139,24 @@ class Launcher // We need to cache this here since we are using our RSA modulus as auth seed. var modulusOffset = memory.Data.FindPattern(Patterns.Common.SignatureModulus); + if (clientVersion is (9, 2, 7, _) or (3, _, _, _) or (10, <= 1, _, _) and not (10, 1, 5, _)) + { + Task.WaitAll(new[] + { + memory.PatchMemory(Patterns.Common.CertBundle, certBundleData, "Certificate Bundle"), + memory.PatchMemory(Patterns.Common.SignatureModulus, Patches.Common.SignatureModulus, "Certificate Signature RsaModulus") + }, CancellationTokenSource.Token); + } + // Wait for all direct memory patch tasks to complete, Task.WaitAll(new[] { - memory.PatchMemory(Patterns.Common.CertBundle, certBundleData, "Certificate Bundle"), - memory.PatchMemory(Patterns.Common.SignatureModulus, Patches.Common.SignatureModulus, "Certificate Signature RsaModulus"), memory.PatchMemory(Patterns.Common.ConnectToModulus, Patches.Common.RsaModulus, "ConnectTo RsaModulus"), // Recent clients have a different signing algorithm in EnterEncryptedMode. - (clientVersion is (9, 2, 7, _) or (3, _, _, _) or (10, _, _, _)) - ? memory.PatchMemory(Patterns.Common.CryptoEdPublicKey, Patches.Common.CryptoEdPublicKey, "GameCrypto Ed25519 PublicKey") - : memory.PatchMemory(Patterns.Common.CryptoRsaModulus, Patches.Common.RsaModulus, "GameCrypto RsaModulus"), + clientVersion is (9, 2, 7, _) or (3, _, _, _) or (10, _, _, _) + ? memory.PatchMemory(Patterns.Common.CryptoEdPublicKey, Patches.Common.CryptoEdPublicKey, "GameCrypto Ed25519 PublicKey") + : memory.PatchMemory(Patterns.Common.CryptoRsaModulus, Patches.Common.RsaModulus, "GameCrypto RsaModulus"), memory.PatchMemory(Patterns.Common.Portal, Patches.Common.Portal, "Login Portal"), memory.PatchMemory(Patterns.Common.VersionUrl, versionPatch, "Version URL"), @@ -162,13 +169,24 @@ class Launcher WaitForUnpack(ref processInfo, memory, ref mbi, gameAppData); #if x64 - Task.WaitAll(new[] + if (clientVersion is (9, 2, 7, _) or (3, _, _, _) or (10, <= 1, _, _) and not (10, 1, 5, _)) { - memory.QueuePatch(Patterns.Windows.CertBundle, Patches.Windows.CertBundle, "CertBundle"), - memory.QueuePatch(Patterns.Windows.CertCommonName, Patches.Windows.CertCommonName, "CertCommonName", 5) - }, CancellationTokenSource.Token); + Task.WaitAll(new[] + { + memory.QueuePatch(Patterns.Windows.CertBundle, Patches.Windows.CertBundle, "CertBundle"), + memory.QueuePatch(Patterns.Windows.CertCommonName, Patches.Windows.CertCommonName, "CertCommonName", 5) + }, CancellationTokenSource.Token); + } + else if (commandLineResult.GetValueForOption(LaunchOptions.DevMode)) + { + Task.WaitAll(new[] + { + memory.QueuePatch(Patterns.Windows.CertChain, Patches.Windows.CertChain, "CertChain"), + memory.QueuePatch(Patterns.Windows.CertCommonName, Patches.Windows.CertCommonName, "CertCommonName", 5) + }, CancellationTokenSource.Token); + } - if (useStaticAuthSeed) + if (commandLineResult.HasOption(LaunchOptions.UseStaticAuthSeed)) { Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine("Static auth seed used. Be sure that the server you are connecting to supports it."); @@ -186,12 +204,12 @@ class Launcher Task.WaitAll(new[] { (clientVersion is (10, _, _, _)) - ? memory.QueuePatch(Patterns.Windows.LoadByFileIdAlternate, Patches.Windows.NoJump, "LoadByFileId", 3) - : memory.QueuePatch(Patterns.Windows.LoadByFileId, Patches.Windows.NoJump, "LoadByFileId", 6), + ? memory.QueuePatch(Patterns.Windows.LoadByFileIdAlternate, Patches.Windows.NoJump, "LoadByFileId", 3) + : memory.QueuePatch(Patterns.Windows.LoadByFileId, Patches.Windows.NoJump, "LoadByFileId", 6), (clientVersion is (10, _, _, _)) - ? memory.QueuePatch(Patterns.Windows.LoadByFilePathAlternate, Patches.Windows.NoJump, "LoadByFilePath", 3) - : memory.QueuePatch(Patterns.Windows.LoadByFilePath, Patches.Windows.NoJump, "LoadByFilePath", 3) + ? memory.QueuePatch(Patterns.Windows.LoadByFilePathAlternate, Patches.Windows.NoJump, "LoadByFilePath", 3) + : memory.QueuePatch(Patterns.Windows.LoadByFilePath, Patches.Windows.NoJump, "LoadByFilePath", 3) }, CancellationTokenSource.Token); var (idAlloc, stringAlloc) = ModLoader.LoadFileMappings(processInfo.ProcessHandle); @@ -234,9 +252,14 @@ class Launcher } } } + // Only exit and do not print any exception messages to the console. + catch (OperationCanceledException) + { + NativeWindows.TerminateProcess(processInfo.ProcessHandle, 0); + } + // Just print out the exception we have and kill the game process. catch (Exception ex) { - // Just print out the exception we have and kill the game process. Console.WriteLine(ex); Console.WriteLine(ex.StackTrace); @@ -279,19 +302,19 @@ class Launcher { #if x64 // Wait for client initialization. - var initOffset = memory?.Read(mbi.BaseAddress, (int)mbi.RegionSize)?.FindPattern(Patterns.Windows.Init) ?? 0; + 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; + 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) + 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); #else // Get PE header info for client initialization. @@ -343,7 +366,6 @@ class Launcher 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); @@ -352,7 +374,7 @@ class Launcher if (WinMemory.IsUnconditionalJump(instructions)) continue; - var operandValue = 0; + int operandValue; if (WinMemory.IsShortJump(instructions)) operandValue = instructions[1] + 2; @@ -392,7 +414,6 @@ class Launcher // 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)); diff --git a/src/Patches/Windows.cs b/src/Patches/Windows.cs index aa9c90a..f3b769e 100644 --- a/src/Patches/Windows.cs +++ b/src/Patches/Windows.cs @@ -9,6 +9,7 @@ static class Windows public static byte[] Integrity = { 0xC2, 0x00, 0x00 }; public static byte[] CertBundle = { 0x90, 0x90 }; public static byte[] CertCommonName = { 0xB0, 0x01 }; + public static byte[] CertChain = { 0xB3, 0x01 }; public static byte[] ShortJump = { 0xEB }; public static byte[] NoJump = { 0x00, 0x00, 0x00, 0x00 }; public static byte[] AuthSeed = { 0x0F, 0x28, 0x05, 0xEF, 0xBE, 0xAD, 0xDE, 0x0F, 0x11, 0x02, 0xC3 }; diff --git a/src/Patterns/Windows.cs b/src/Patterns/Windows.cs index 6592d45..55749eb 100644 --- a/src/Patterns/Windows.cs +++ b/src/Patterns/Windows.cs @@ -17,6 +17,9 @@ static class Windows public static short[] CertBundle = { 0x75, 0x06, 0x48, -1, -1, 0x60, 0x5F, 0xC3 }; public static short[] CertCommonName = { 0x80, -1, 0x2A, 0x75, -1, 0x32, 0xC0, 0x48 }; + // Developer mode. + public static short[] CertChain = { 0x32, 0xDB, 0xEB, 0x02, 0xB3, 0x01, 0x48, 0x83, -1, -1, 0x00, 0x00, 0x00, 0x00 }; + // Auth seed function. public static short[] AuthSeed = { 0x57, 0x6F, 0x57, 0x00, 0xE8, -1, -1, -1, -1, 0x48, 0x8D }; diff --git a/src/Program.cs b/src/Program.cs index a2d1cb0..7aa63e6 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -16,7 +16,7 @@ LaunchOptions.RootCommand.SetHandler(context => var appPath = Launcher.PrepareGameLaunch(context.ParseResult); var gameCommandLine = string.Join(" ", context.ParseResult.UnmatchedTokens); - if (string.IsNullOrEmpty(appPath) || !Launcher.LaunchGame(appPath, gameCommandLine, context.ParseResult.HasOption(LaunchOptions.UseStaticAuthSeed))) + if (string.IsNullOrEmpty(appPath) || !Launcher.LaunchGame(appPath, gameCommandLine, context.ParseResult)) WaitAndExit(5000); });