diff --git a/src/LaunchOptions.cs b/src/LaunchOptions.cs index 4ab6977..4c0fc9b 100644 --- a/src/LaunchOptions.cs +++ b/src/LaunchOptions.cs @@ -16,6 +16,10 @@ static class LaunchOptions public static Option KeepCache = new("--keepcache", () => true); public static Option UseStaticAuthSeed = new("--staticseed"); public static Option DevMode = new("--dev", () => true); + public static Option VersionUrl = new("--versionurl"); + public static Option CdnsUrl = new("--cdnsurl"); + public static Option ProductName = new("--product", () => "wow"); + public static Option CdnRegion = new("--region", () => "EU"); // Game command line options. public static Option GameConfig = new("-config", () => "Config.wtf"); @@ -36,6 +40,10 @@ static class LaunchOptions KeepCache, UseStaticAuthSeed, DevMode, + VersionUrl, + CdnsUrl, + ProductName, + CdnRegion, GameConfig }; diff --git a/src/Launcher.cs b/src/Launcher.cs index bc669e6..b7ecfed 100644 --- a/src/Launcher.cs +++ b/src/Launcher.cs @@ -98,7 +98,7 @@ static class Launcher else { var config = File.ReadAllText(configPath); - + portal = ParsePortal(config); LaunchOptions.IsDevModeAllowed = IsDevModeAllowed(ipFilter, portal.IPAddress); @@ -113,7 +113,7 @@ static class Launcher Console.WriteLine($"Developer mode: {(devModeEnabled ? "Enabled" : "Disabled")}"); Console.WriteLine(); Console.ForegroundColor = ConsoleColor.Gray; - + // Check for valid certificate when dev mode is disabled. if (!devModeEnabled) { @@ -138,7 +138,7 @@ static class Launcher }, null ); - + sslStream.AuthenticateAsClient(portal.HostName); } catch (SocketException) @@ -166,6 +166,38 @@ static class Launcher public static bool LaunchGame(string appPath, string gameCommandLine, ParseResult commandLineResult) { + // Build the version URL from the game binary build. + var clientVersion = GetVersionValueFromClient(appPath); + + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine($"Client Build {clientVersion}"); + Console.WriteLine($"Client Path '{appPath}'"); + Console.WriteLine(); + Console.ResetColor(); + + // Assign the region and product dependent version url to check it's online status. + var versionUrl = commandLineResult.GetValueForOption(LaunchOptions.VersionUrl) + ?? Patches.Common.GetVersionUrl(clientVersion.Build, commandLineResult.GetValueForOption(LaunchOptions.CdnRegion), + commandLineResult.GetValueForOption(LaunchOptions.ProductName)); + + if (!CheckUrl(versionUrl, fallbackUrl: Patterns.Common.VersionUrl).GetAwaiter().GetResult()) + versionUrl = Patterns.Common.VersionUrl; + else + // Assign the region and product independent version url. + versionUrl = commandLineResult.GetValueForOption(LaunchOptions.VersionUrl) ?? Patches.Common.GetVersionUrl(clientVersion.Build); + + var cdnsUrl = commandLineResult.GetValueForOption(LaunchOptions.CdnsUrl) ?? Patches.Common.CdnsUrl; + + if (!CheckUrl(cdnsUrl, fallbackUrl: Patterns.Common.CdnsUrl).GetAwaiter().GetResult()) + cdnsUrl = Patterns.Common.CdnsUrl; + + Console.ForegroundColor = ConsoleColor.Cyan; + Console.WriteLine("Game CDN connection info:"); + Console.WriteLine($"Version file: {versionUrl}"); + Console.WriteLine($"CDNs file: {cdnsUrl}"); + Console.WriteLine(); + Console.ResetColor(); + var startupInfo = new StartupInfo(); var processInfo = new ProcessInformation(); @@ -204,17 +236,6 @@ static class Launcher byte[] certBundleData = Convert.FromBase64String(Patches.Common.CertBundleData); - // Build the version URL from the game binary build. - var clientVersion = GetVersionValueFromClient(appPath); - - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine(); - Console.WriteLine($"Client Build {clientVersion}"); - Console.WriteLine($"Client Path '{appPath}'"); - Console.WriteLine(); - Console.ResetColor(); - - byte[] versionPatch = Patches.Common.GetVersionUrl(clientVersion.Build); // Refresh the client data before patching. memory.RefreshMemoryData((int)gameAppData.Length); @@ -232,6 +253,7 @@ static class Launcher }, CancellationTokenSource.Token); } + // Wait for all direct memory patch tasks to complete, Task.WaitAll(new[] { @@ -243,8 +265,8 @@ static class Launcher : 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"), - memory.PatchMemory(Patterns.Common.CdnsUrl, Patches.Common.CdnsUrl, "CDNs URL"), + memory.PatchMemory(Patterns.Common.VersionUrl.ToPattern(), Encoding.UTF8.GetBytes(versionUrl), "Version URL"), + memory.PatchMemory(Patterns.Common.CdnsUrl.ToPattern(), Encoding.UTF8.GetBytes(cdnsUrl), "CDNs URL"), memory.PatchMemory(Patterns.Windows.LauncherLogin, Patches.Windows.LauncherLogin, "Launcher Login Registry") }, CancellationTokenSource.Token); diff --git a/src/Misc/Helpers.cs b/src/Misc/Helpers.cs index 4dd5de2..54d31ed 100644 --- a/src/Misc/Helpers.cs +++ b/src/Misc/Helpers.cs @@ -8,6 +8,15 @@ namespace Arctium.WoW.Launcher.Misc; static class Helpers { + public static bool IsDebugBuild() + { +#if DEBUG + return true; +#else + return false; +#endif + } + public static (int Major, int Minor, int Revision, int Build) GetVersionValueFromClient(string fileName) { var fileVersionInfo = FileVersionInfo.GetVersionInfo(fileName); @@ -90,4 +99,25 @@ static class Helpers return (string.Empty, string.Empty, port); } } + + public static async Task CheckUrl(string url, string fallbackUrl) + { + using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) }; + + try + { + var result = await httpClient.GetAsync(url); + + if (!result.IsSuccessStatusCode) + Console.WriteLine($"{url} not reachable. Falling back to {fallbackUrl}"); + + return result.IsSuccessStatusCode; + } + catch (Exception) + { + Console.WriteLine($"{url} not reachable. Falling back to {fallbackUrl}"); + + return false; + } + } } diff --git a/src/ModLoader.cs b/src/ModLoader.cs index 7f2240a..5715d16 100644 --- a/src/ModLoader.cs +++ b/src/ModLoader.cs @@ -29,7 +29,7 @@ class ModLoader var hookAddress = memory.Data.FindPattern(Patterns.Windows.CustomFileIdHook); if (hookAddress == 0) - return false; + throw new InvalidDataException("CustomFileIdHook"); // Read original data from the hook function. var originalBytes = memory.Read(memory.BaseAddress + hookAddress, 13); diff --git a/src/Patches/Common.cs b/src/Patches/Common.cs index 20e7718..20fef6d 100644 --- a/src/Patches/Common.cs +++ b/src/Patches/Common.cs @@ -43,8 +43,8 @@ static class Common public static byte[] CryptoEdPublicKey = { 0x02, 0x59, 0x6F, 0x0D, 0x0C, 0x06, 0x1A, 0x8B, 0x30, 0x74, 0x59, 0x88, 0xFD, 0x72, 0xC5, 0x9E, 0x29, 0xEC, 0x36, 0x7F, 0xB0, 0xF3, 0x41, 0xF2, 0x8E, 0x0F, 0x08, 0xD0, 0x37, 0xBA, 0xFC, 0x69 }; - public static byte[] GetVersionUrl(int build) => Encoding.UTF8.GetBytes($"ngdp.arctium.io/%s/%s/{build}/versions"); - public static byte[] CdnsUrl => Encoding.UTF8.GetBytes("http://ngdp.arctium.io/customs/wow/cdns"); + public static string GetVersionUrl(int build, string region = "%s", string product = "%s") => $"http://ngdp.arctium.io/{region}/{product}/{build}/versions"; + public static string CdnsUrl => "http://ngdp.arctium.io/customs/wow/cdns"; public static byte[] Portal = new byte[Patterns.Common.Portal.Length]; // Our own ca_bundle.txt.signed file. diff --git a/src/Patterns/Common.cs b/src/Patterns/Common.cs index 0626649..e3d94c1 100644 --- a/src/Patterns/Common.cs +++ b/src/Patterns/Common.cs @@ -11,8 +11,7 @@ static class Common public static short[] CryptoEdPublicKey = { 0x15, 0xD6, 0x18, 0xBD, 0x7D, 0xB5, 0x77, 0xBD }; public static short[] CertBundle = "{\"Created\":".ToPattern(); - public static short[] VersionUrl = "%s.patch.battle.net:1119/%s/versions".ToPattern(); - public static short[] CdnsUrl = "http://%s.patch.battle.net:1119/%s/cdns".ToPattern(); + public static string VersionUrl = "http://%s.patch.battle.net:1119/%s/versions"; + public static string CdnsUrl = "http://%s.patch.battle.net:1119/%s/cdns"; 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 fa0853c..b51e36a 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -3,6 +3,7 @@ using System.CommandLine.Parsing; using Arctium.WoW.Launcher; + using static Arctium.WoW.Launcher.Misc.Helpers; // "Arctium" should not be removed from the final binary name.