diff --git a/PeacockPatcher.exe b/PeacockPatcher.exe index a080af7ac..6cc4f908b 100644 Binary files a/PeacockPatcher.exe and b/PeacockPatcher.exe differ diff --git a/components/index.ts b/components/index.ts index 72e26484c..cd68268c8 100644 --- a/components/index.ts +++ b/components/index.ts @@ -212,6 +212,13 @@ app.get( "scpc-prod" } + if (req.params.audience === "pcgdk-prod") { + // PC Xbox version is a special audience + config.Versions[0].Name = "pcgdk-prod" + config.Versions[0].SERVER_VER.GlobalAuthentication.RequestedAudience = + "pcgdk-prod_8" + } + config.Versions[0].ISSUER_ID = req.query.issuer || "*" config.Versions[0].SERVER_VER.Metrics.MetricsServerHost = `${proto}://${serverhost}` @@ -298,6 +305,7 @@ app.use( break case "fghi4567xQOCheZIin0pazB47qGUvZw4": case STEAM_NAMESPACE_2021: + case "RETAIL": req.serverVersion = "8-14" break default: @@ -560,6 +568,7 @@ function startServer(options: { hmr: boolean; pluginDevHost: boolean }): void { "contracts", join("userdata", "epicids"), join("userdata", "steamids"), + join("userdata", "xboxids"), join("userdata", "users"), join("userdata", "h1", "steamids"), join("userdata", "h1", "epicids"), diff --git a/components/oauthToken.ts b/components/oauthToken.ts index fa7d782f3..17a73bed5 100644 --- a/components/oauthToken.ts +++ b/components/oauthToken.ts @@ -65,9 +65,9 @@ export async function handleOauthToken( noTimestamp: true, } - let external_platform: "steam" | "epic", + let external_platform: "steam" | "epic" | "live", external_userid: string, - external_users_folder: "steamids" | "epicids", + external_users_folder: "steamids" | "epicids" | "xboxids", external_appid: string if (req.body.grant_type === "external_steam") { @@ -102,6 +102,11 @@ export async function handleOauthToken( external_platform = "epic" external_userid = req.body.epic_userid external_users_folder = "epicids" + } else if (req.body.grant_type === "external_xbox") { + external_appid = "RETAIL" + external_platform = "live" + external_userid = req.body.xbox_userid + external_users_folder = "xboxids" } else if (req.body.grant_type === "refresh_token") { // send back the token from the request (re-signed so the timestamps update) extractToken(req) // init req.jwt @@ -148,7 +153,8 @@ export async function handleOauthToken( const isHitman3 = external_appid === "fghi4567xQOCheZIin0pazB47qGUvZw4" || - external_appid === STEAM_NAMESPACE_2021 + external_appid === STEAM_NAMESPACE_2021 || + external_appid === "RETAIL" const gameVersion: GameVersion = isFrankenstein ? "scpc" @@ -199,13 +205,20 @@ export async function handleOauthToken( } /* - Store user auth for all games except scpc + Store user auth for all games except scpc & h3 xbox */ - if (!isFrankenstein) { - const authContainer = new OfficialServerAuth( - gameVersion, - req.body.access_token, - ) + if (!isFrankenstein && external_platform !== "live") { + let gameAuthToken: string + + if (external_platform === "epic") { + gameAuthToken = req.body.access_token + } else if (external_platform === "steam") { + gameAuthToken = req.body.steam_clienttoken + } else { + gameAuthToken = undefined + } + + const authContainer = new OfficialServerAuth(gameVersion, gameAuthToken) log(LogLevel.DEBUG, `Setting up container with ID ${req.body.pId}.`) @@ -226,12 +239,20 @@ export async function handleOauthToken( true, ) as UserProfile userData.Id = req.body.pId - userData.LinkedAccounts[external_platform] = external_userid + + // IOI uses "xboxone" here + if (external_platform === "live") { + userData.LinkedAccounts["xboxone"] = external_userid + } else { + userData.LinkedAccounts[external_platform] = external_userid + } if (external_platform === "steam") { userData.SteamId = req.body.steam_userid } else if (external_platform === "epic") { userData.EpicId = req.body.epic_userid + } else if (external_platform === "live") { + userData.XboxLiveId = req.body.xbox_userid } if ( @@ -276,6 +297,9 @@ export async function handleOauthToken( gameVersion, STEAM_NAMESPACE_2021, ).get(req.body.pId) + } else if (external_platform === "live") { + // TODO: fetch proper entitlements + return ["9P2JC6R9S37C"] } else { log(LogLevel.ERROR, "Unsupported platform.") return [] diff --git a/components/types/types.ts b/components/types/types.ts index 912935729..5ff84c202 100644 --- a/components/types/types.ts +++ b/components/types/types.ts @@ -55,6 +55,7 @@ export type GameAudience = | "xboxone-prod" | "scpc-prod" | "playtest01-prod_8" + | "pcgdk-prod_8" /** * Data from the JSON Web Token (JWT) authentication scheme. @@ -78,9 +79,9 @@ export interface JwtData { */ userid: string /** - * Either "steam" or "epic" on PC. + * Either "steam", "epic" or "live" on PC. */ - platform: "steam" | "epic" + platform: "steam" | "epic" | "live" /** * Client/account locale. */ diff --git a/components/utils.ts b/components/utils.ts index 7ecabadd9..dd7cac493 100644 --- a/components/utils.ts +++ b/components/utils.ts @@ -114,7 +114,9 @@ export function extractToken( res?: Response, next?: NextFunction, ): void { - const header = req.header("Authorization") + // Xbox version uses X-GPS-Authorization + const header = + req.header("X-GPS-Authorization") ?? req.header("Authorization") const auth = header ? header.split(" ") : [] if (auth.length === 2 && auth[0].toLowerCase() === "bearer") { diff --git a/patcher/AOBScanner.cs b/patcher/AOBScanner.cs index 89b9ee49c..78c59091a 100644 --- a/patcher/AOBScanner.cs +++ b/patcher/AOBScanner.cs @@ -6,471 +6,439 @@ namespace HitmanPatcher { - internal static class AOBScanner - { - public static bool TryGetHitmanVersionByScanning(Process process, - IntPtr hProcess, out HitmanVersion result) + public static class AOBScanner + { + public static bool TryGetHitmanVersionByScanning(Process process, IntPtr hProcess, out HitmanVersion result) + { + Stopwatch bench = Stopwatch.StartNew(); + + IntPtr baseAddress = process.MainModule.BaseAddress; + byte[] exeData = new byte[process.MainModule.ModuleMemorySize]; + UIntPtr bytesread; + Pinvoke.ReadProcessMemory(hProcess, baseAddress, exeData, (UIntPtr)exeData.Length, out bytesread); // fuck it, just read the whole thing + + Task> getCertpinPatches = Task.Factory.ContinueWhenAll(new Task[] + { + findCertpin_nearjump(exeData), + findCertpin_nearjump_new(exeData), + findCertpin_shortjump(exeData), + }, tasks => tasks.Select(task => task.Result).Where(x => x != null)); + + Task> getAuthheadPatches = Task.Factory.ContinueWhenAll(new Task[] + { + findAuthhead3_30(exeData), + findAuthhead2_72(exeData), + findAuthhead1_15(exeData), + }, tasks => tasks.Select(task => task.Result).Where(x => x != null)); + + Task> getConfigdomainPatches = Task.Factory.ContinueWhenAll(new Task[] + { + findConfigdomain(exeData), + }, tasks => tasks.Select(task => task.Result).Where(x => x != null)); + + Task> getProtocolPatches = Task.Factory.ContinueWhenAll(new Task[] + { + findProtocolCombined(exeData), + }, tasks => tasks.Select(task => task.Result).Where(x => x != null)); + + Task> getDynresForceofflinePatches = Task.Factory.ContinueWhenAll(new Task[] + { + findDynresForceoffline(exeData), + }, tasks => tasks.Select(task => task.Result).Where(x => x != null)); + + + Task>[] alltasks = + { + getCertpinPatches, getAuthheadPatches, getConfigdomainPatches, getProtocolPatches, getDynresForceofflinePatches + }; + Task.WaitAll(alltasks); + + bench.Stop(); + Console.WriteLine(bench.Elapsed.ToString()); + + // error out if any task does not have exactly 1 result + if (alltasks.Any(task => task.Result.Count() != 1)) + { + result = null; + return false; + } + + result = new HitmanVersion() + { + certpin = getCertpinPatches.Result.First(), + authheader = getAuthheadPatches.Result.First(), + configdomain = getConfigdomainPatches.Result.First(), + protocol = getProtocolPatches.Result.First(), + dynres_noforceoffline = getDynresForceofflinePatches.Result.First() + }; + + return true; + } + + #region certpin + + private static Task findCertpin_nearjump(byte[] data) + { + return Task.Factory.ContinueWhenAll(new[] + { + Task.Factory.StartNew(() => findPattern(data, 0xe, "? ? 9afdffffc747302f000000c7471803000000")), // 2.x + Task.Factory.StartNew(() => findPattern(data, 0xc, "? ? 9afdffffc747302f000000c7471803000000")), // 1.15 + }, tasks => + { + IEnumerable offsets = tasks.SelectMany(task => task.Result); + if (offsets.Count() != 1) + return null; + return new[] { new Patch(offsets.First(), "0F85", "90E9", MemProtection.PAGE_EXECUTE_READ) }; + }); + } + + private static Task findCertpin_nearjump_new(byte[] data) { - Stopwatch bench = Stopwatch.StartNew(); - - IntPtr baseAddress = process.MainModule.BaseAddress; - byte[] exeData = new byte[process.MainModule.ModuleMemorySize]; - Pinvoke.ReadProcessMemory(hProcess, baseAddress, exeData, - (UIntPtr) exeData.Length, - out _); // fuck it, just read the whole thing - - Task> getCertpinPatches = - Task.Factory.ContinueWhenAll(new Task[] - { - findCertpin_nearjump(exeData), - findCertpin_shortjump(exeData), - }, - tasks => - tasks.Select(task => task.Result) - .Where(x => x != null)); - - Task> getAuthheadPatches = - Task.Factory.ContinueWhenAll(new Task[] - { - findAuthhead3_30(exeData), - findAuthhead2_72(exeData), - findAuthhead1_15(exeData), - }, - tasks => - tasks.Select(task => task.Result) - .Where(x => x != null)); - - Task> getConfigdomainPatches = - Task.Factory.ContinueWhenAll(new Task[] - { - findConfigdomain(exeData), - }, - tasks => - tasks.Select(task => task.Result) - .Where(x => x != null)); - - Task> getProtocolPatches = - Task.Factory.ContinueWhenAll(new Task[] - { - findProtocol3_30(exeData), - }, - tasks => - tasks.Select(task => task.Result) - .Where(x => x != null)); - - Task> getDynresForceofflinePatches = - Task.Factory.ContinueWhenAll(new Task[] - { - findDynresForceoffline(exeData), - }, - tasks => - tasks.Select(task => task.Result) - .Where(x => x != null)); - - - Task>[] alltasks = - { - getCertpinPatches, getAuthheadPatches, getConfigdomainPatches, - getProtocolPatches, getDynresForceofflinePatches - }; - // ReSharper disable once CoVariantArrayConversion - Task.WaitAll(alltasks); - - bench.Stop(); - Console.WriteLine(bench.Elapsed.ToString()); - - // error out if any task does not have exactly 1 result - if (alltasks.Any(task => task.Result.Count() != 1)) - { - result = null; - return false; - } - -#if DEBUG - Note("CertPin", getCertpinPatches.Result.First()[0]); - Note("AuthHeader1", getAuthheadPatches.Result.First()[0]); - Note("AuthHeader2", getAuthheadPatches.Result.First()[1]); - Note("ConfigDomain", getConfigdomainPatches.Result.First()[0]); - Note("Protocol", getProtocolPatches.Result.First()[0]); - Note("DynamicResources", getDynresForceofflinePatches.Result.First()[0]); -#endif - - result = new HitmanVersion() - { - certpin = getCertpinPatches.Result.First(), - authheader = getAuthheadPatches.Result.First(), - configdomain = getConfigdomainPatches.Result.First(), - protocol = getProtocolPatches.Result.First(), - dynres_noforceoffline = - getDynresForceofflinePatches.Result.First() - }; - - return true; - } - - #region Utilities - -#if DEBUG - private static void Note(string name, Patch patch) - { - MainForm.GetInstance().log($"{name}: {patch.offset:X} {BitConverter.ToString(patch.original).Replace("-", string.Empty)} {BitConverter.ToString(patch.patch).Replace("-", string.Empty)}"); - } -#endif - - #endregion - - #region certpin - - private static Task findCertpin_nearjump(byte[] data) - { - return Task.Factory.ContinueWhenAll(new[] - { - Task.Factory.StartNew(() => findPattern(data, 0xe, - "? ? 9afdffffc747302f000000c7471803000000")), - Task.Factory.StartNew(() => findPattern(data, 0xc, - "? ? 9afdffffc747302f000000c7471803000000")), // 1.15 - Task.Factory.StartNew(() => findPattern(data, 0xd, - "? ? 6ffdffffc747302f000000c7471804000000")) - }, tasks => - { - IEnumerable offsets = - tasks.SelectMany(task => task.Result); - if (offsets.Count() != 1) - return null; - return new[] - { - new Patch(offsets.First(), "0F85", "90E9", - MemProtection.PAGE_EXECUTE_READ) - }; - }); - } - - private static Task findCertpin_shortjump(byte[] data) - { - return Task.Factory.ContinueWhenAll(new[] - { - Task.Factory.StartNew(() => - findPattern(data, 0x3, "? 0ec746302f000000c7461803000000")), - Task.Factory.StartNew(() => - findPattern(data, 0x2, - "? 0ec746302f000000c7461803000000")), // v2.13 - }, tasks => - { - IEnumerable offsets = - tasks.SelectMany(task => task.Result); - if (offsets.Count() != 1) - return null; - return new[] - { - new Patch(offsets.First(), "75", "EB", - MemProtection.PAGE_EXECUTE_READ) - }; - }); - } - - #endregion - - #region authheader - - private static Task findAuthhead3_30(byte[] data) - { - return Task.Factory.ContinueWhenAll(new[] - { - Task.Factory.StartNew(() => - findPattern(data, 0xd, "0f85b50000004883f90675e8")), - Task.Factory.StartNew(() => - findPattern(data, 0xd, "9090909090904883f90675e8")), - Task.Factory.StartNew(() => - findPattern(data, 0x3, "0f84b800000084db0f85b0000000")), - Task.Factory.StartNew(() => - findPattern(data, 0x3, "90909090909084db0f85b0000000")) - }, tasks => - { - // concat results for non-patched and patched - IEnumerable offsetspart1 = - tasks.Take(2).SelectMany(task => task.Result); - IEnumerable offsetspart2 = tasks.Skip(2).Take(2) - .SelectMany(task => task.Result); - if (offsetspart1.Count() != 1 || offsetspart2.Count() != 1) - return null; - return new[] - { - new Patch(offsetspart1.First(), "0F85B5000000", - "909090909090", MemProtection.PAGE_EXECUTE_READ), - new Patch(offsetspart2.First(), "0F84B8000000", - "909090909090", MemProtection.PAGE_EXECUTE_READ) - }; - }); - } - - private static Task findAuthhead2_72(byte[] data) - { - return Task.Factory.ContinueWhenAll(new[] - { - Task.Factory.StartNew(() => - findPattern(data, 0x8, "? 18488d15ff02cd00")), - Task.Factory.StartNew(() => - findPattern(data, 0x8, "? 18488d155fd6cc00")), - Task.Factory.StartNew(() => - findPattern(data, 0x7, "? 18488d152018cc00")), // v2.13 - Task.Factory.StartNew(() => - findPattern(data, 0xc, "0f84860000004584e4")), - Task.Factory.StartNew(() => - findPattern(data, 0xc, "9090909090904584e4")), - Task.Factory.StartNew(() => - findPattern(data, 0xb, "0f84830000004584e4")), // v2.13 - Task.Factory.StartNew(() => - findPattern(data, 0xb, "9090909090904584e4")), // v2.13 - }, tasks => - { - // concat results for non-patched and patched - IEnumerable offsetspart1 = - tasks.Take(3).SelectMany(task => task.Result); - IEnumerable offsetspart2 = tasks.Skip(3).Take(4) - .SelectMany(task => task.Result); - if (offsetspart1.Count() != 1 || offsetspart2.Count() != 1) - return null; - return new[] - { - new Patch(offsetspart1.First(), "75", "EB", - MemProtection.PAGE_EXECUTE_READ), - new Patch(offsetspart2.First(), "0F8486000000", - "909090909090", MemProtection.PAGE_EXECUTE_READ) - }; - }); - } - - private static Task findAuthhead1_15(byte[] data) - { - return Task.Factory.ContinueWhenAll(new[] - { - Task.Factory.StartNew(() => - findPattern(data, 0x5, "0f84b3000000498bcf")), - Task.Factory.StartNew(() => - findPattern(data, 0x5, "909090909090498bcf")), - Task.Factory.StartNew(() => - findPattern(data, 0x5, "0f84a30000004584ed")), - Task.Factory.StartNew(() => - findPattern(data, 0x5, "9090909090904584ed")) - }, tasks => - { - // concat results for non-patched and patched - IEnumerable offsetspart1 = - tasks.Take(2).SelectMany(task => task.Result); - IEnumerable offsetspart2 = tasks.Skip(2).Take(2) - .SelectMany(task => task.Result); - if (offsetspart1.Count() != 1 || offsetspart2.Count() != 1) - return null; - return new[] - { - new Patch(offsetspart1.First(), "0F84B3000000", - "909090909090", MemProtection.PAGE_EXECUTE_READ), - new Patch(offsetspart2.First(), "0F84A3000000", - "909090909090", MemProtection.PAGE_EXECUTE_READ) - }; - }); - } - - #endregion - - #region configdomain - - private static Task findConfigdomain(byte[] data) - { - return Task.Factory.ContinueWhenAll(new[] - { - Task.Factory.StartNew(() => findPattern(data, 0xe, - "488905 ? ? ? 03488d0d ? ? ? 034883c4205b48ff25 ? ? ? 01")) - .ContinueWith(task => - task.Result.Select(addr => - addr + 14 + - BitConverter.ToInt32(data, addr + 10)) - .ToArray()), - Task.Factory.StartNew(() => findPattern(data, 0xd, - "83f06e69d093010001e8 ? ? 0400488d05 ? ? ? 01ba000100004c8d05 ? ? ? 01488905 ? ? ? 02488d0d ? ? ? 02")) - .ContinueWith(task => - task.Result.Select(addr => - addr + 47 + - BitConverter.ToInt32(data, addr + 43)) - .ToArray()), - Task.Factory.StartNew(() => findPattern(data, 0xd, - "83f16e69d193010001488d0d ? ? ? 02e8 ? ? 0400488d05 ? ? ? 01ba000100004c8d05 ? ? ? 01488905 ? ? ? 02488d0d ? ? ? 02")) - .ContinueWith(task => - task.Result.Select(addr => - addr + 54 + - BitConverter.ToInt32(data, addr + 50)) - .ToArray()), // 2.13: big boy - Task.Factory.StartNew(() => findPattern(data, 0x0, - "4883ec28baa71ace87488d0d ? ? ? 02e8 ? ? 0200488d05 ? ? ? 01ba000100004c8d05 ? ? ? 01488905 ? ? ? 02488d0d ? ? ? 02")) - .ContinueWith(task => - task.Result.Select(addr => - addr + 54 + - BitConverter.ToInt32(data, addr + 50)) - .ToArray()) // 1.15: big boy as well - }, tasks => - { - IEnumerable offsets = - tasks.SelectMany(task => task.Result); - if (offsets.Count() != 1) - return null; - return new[] - { - new Patch(offsets.First(), "", "", - MemProtection.PAGE_READWRITE, "configdomain") - }; - }); - } - - #endregion - - #region protocol - - private static Task findProtocol3_30(byte[] data) - { - return Task.Factory.ContinueWhenAll(new[] - { - Task.Factory.StartNew(() => - findPattern(data, 0x0, "? 747470733a2f2f7b307d00")), - Task.Factory.StartNew(() => - findPattern(data, 0x8, "? 747470733a2f2f7b307d00")) - }, tasks => - { - IEnumerable offsets = - tasks.SelectMany(task => task.Result); - if (offsets.Count() != 1) - return null; - return new[] - { - new Patch(offsets.First(), "68", "61", - MemProtection.PAGE_READONLY) - }; - }); - } - - #endregion - - #region dynres_forceoffline - - private static Task findDynresForceoffline(byte[] data) - { - return Task.Factory.ContinueWhenAll(new[] - { - Task.Factory.StartNew(() => findPattern(data, 0x1, - "83f17269c193010001488d0d ? ? ? 0383f06569d093010001e8 ? ? 0400488d05 ? ? ? 01c705 ? ? ? 0301000000")) - .ContinueWith(task => - task.Result.Select(addr => - addr + 47 + - BitConverter.ToInt32(data, addr + 39)) - .ToArray()), - Task.Factory.StartNew(() => findPattern(data, 0xb, - "83f17269c193010001488d0d ? ? ? 0283f06569d093010001e8 ? ? 0400488d05 ? ? ? 01c705 ? ? ? 0201000000")) // 2.72 - .ContinueWith(task => - task.Result.Select(addr => - addr + 47 + - BitConverter.ToInt32(data, addr + 39)) - .ToArray()), - Task.Factory.StartNew(() => findPattern(data, 0x7, - "83f17269c193010001488d0d ? ? ? 0283f06569d093010001e8 ? ? 0400488d05 ? ? ? 01c705 ? ? ? 0201000000")) // 2.13 - .ContinueWith(task => - task.Result.Select(addr => - addr + 47 + - BitConverter.ToInt32(data, addr + 39)) - .ToArray()), - Task.Factory.StartNew(() => findPattern(data, 0x4, - "ba2734ff12488d0d ? ? ? 02e8 ? ? 0200488d05 ? ? ? 01c705 ? ? ? 0201000000")) // 1.15 - .ContinueWith(task => - task.Result.Select(addr => - addr + 34 + - BitConverter.ToInt32(data, addr + 26)) - .ToArray()) - }, tasks => - { - IEnumerable offsets = - tasks.SelectMany(task => task.Result); - if (offsets.Count() != 1) - return null; - return new[] - { - new Patch(offsets.First(), "01", "00", - MemProtection.PAGE_EXECUTE_READWRITE) - }; - }); - } - - #endregion - - private static int[] findPattern(byte[] data, byte alignment, - string pattern) - { - List results = new List(); - int offset = 0; - - // convert string pattern to byte array - List bytepatternlist = new List(pattern.Length); - List wildcardslist = new List(pattern.Length); - pattern = pattern.Replace(" ", ""); // remove spaces - for (int i = 0; i < pattern.Length; i += 2) - { - string twochars = pattern.Substring(i, 2); - if (twochars.StartsWith("?")) - { - if (wildcardslist.Count == - 0) // pattern starts with wildcard(s) - { - alignment += 1; - offset -= 1; - i -= 1; // step forward 1 less because it's one '?' instead of two hex chars - } - else - { - wildcardslist.Add(1); - for (int j = wildcardslist.Count - 2; - wildcardslist[j] > 0; - j--) - { - wildcardslist[j] += 1; - } - - bytepatternlist.Add(0x00); // dummy value - i -= 1; // step forward 1 less because it's one '?' instead of two hex chars - } - } - else - { - wildcardslist.Add(0); - bytepatternlist.Add(Convert.ToByte(twochars, 16)); - } - } - - byte[] bytepattern = bytepatternlist.ToArray(); - int[] wildcards = wildcardslist.ToArray(); - int patternLen = bytepattern.Length; - int searchEnd = data.Length - patternLen; - // int[] matched = new int[patternLen]; - byte firstbyte = bytepattern[0]; - - // search data for byte array - for (int i = alignment; i < searchEnd; i += 16) - { - if (data[i] == firstbyte) - { - var k = 1; - k += wildcards[k]; - while (data[i + k] == bytepattern[k]) - { - k += 1; - if (k == patternLen) - { - results.Add(i + offset); - break; - } - - k += wildcards[k]; - } - } - } - - return results.ToArray(); - } - } -} + return Task.Factory.StartNew(() => findPattern(data, 0x4, "0f8478fdffff4584f6 ? ? 6ffdffff")).ContinueWith(task => + { + if (task.Result.Length != 1) + return null; // pattern should occur only once + return new[] + { + new Patch(task.Result[0] + 9, "0F85", "90E9", MemProtection.PAGE_EXECUTE_READ), + }; + }); + } + + private static Task findCertpin_shortjump(byte[] data) + { + return Task.Factory.ContinueWhenAll(new[] + { + Task.Factory.StartNew(() => findPattern(data, 0x3, "? 0ec746302f000000c7461803000000")), + Task.Factory.StartNew(() => findPattern(data, 0x2, "? 0ec746302f000000c7461803000000")), // v2.13 + }, tasks => + { + IEnumerable offsets = tasks.SelectMany(task => task.Result); + if (offsets.Count() != 1) + return null; + return new[] { new Patch(offsets.First(), "75", "EB", MemProtection.PAGE_EXECUTE_READ) }; + }); + } + + #endregion + + #region authheader + + private static Task findAuthhead3_30(byte[] data) + { + return Task.Factory.ContinueWhenAll(new[] + { + // jump 1 + Task.Factory.StartNew(() => findPattern(data, 0xd, "0f85b50000004883f90675e8")), // unpatched + Task.Factory.StartNew(() => findPattern(data, 0xd, "9090909090904883f90675e8")), // patched + // jump 2 + Task.Factory.StartNew(() => findPattern(data, 0x3, "0f84b800000084db0f85b0000000")), // unpatched + Task.Factory.StartNew(() => findPattern(data, 0x3, "90909090909084db0f85b0000000")), // patched + Task.Factory.StartNew(() => findPattern(data, 0x3, "0f84b500000084db0f85ad000000")), // unpatched gamepass + Task.Factory.StartNew(() => findPattern(data, 0x3, "90909090909084db0f85ad000000")), // patched gamepass + }, tasks => + { + // concat results for non-patched and patched + IEnumerable offsetsjump1 = tasks.Take(2).SelectMany(task => task.Result); + IEnumerable offsetsjump2 = tasks.Skip(2).Take(4).SelectMany(task => task.Result); + if (offsetsjump1.Count() != 1 || offsetsjump2.Count() != 1) + return null; + return new[] + { + new Patch(offsetsjump1.First(), "0F85B5000000", "909090909090", MemProtection.PAGE_EXECUTE_READ), + new Patch(offsetsjump2.First(), "0F84B8000000", "909090909090", MemProtection.PAGE_EXECUTE_READ) + }; + }); + } + + private static Task findAuthhead2_72(byte[] data) + { + return Task.Factory.ContinueWhenAll(new[] + { + // jump 1 + Task.Factory.StartNew(() => findPattern(data, 0x8, "? 18488d15bfb8ce00")), // v2.72 dx12 + Task.Factory.StartNew(() => findPattern(data, 0x8, "? 18488d15ff02cd00")), + Task.Factory.StartNew(() => findPattern(data, 0x8, "? 18488d155fd6cc00")), + Task.Factory.StartNew(() => findPattern(data, 0x7, "? 18488d152018cc00")), // v2.13 + // jump 2 + Task.Factory.StartNew(() => findPattern(data, 0xc, "0f84860000004584e4")), + Task.Factory.StartNew(() => findPattern(data, 0xc, "9090909090904584e4")), + Task.Factory.StartNew(() => findPattern(data, 0xb, "0f84830000004584e4")), // v2.13 + Task.Factory.StartNew(() => findPattern(data, 0xb, "9090909090904584e4")), // v2.13 + }, tasks => + { + // concat results for non-patched and patched + IEnumerable offsetsjump1 = tasks.Take(4).SelectMany(task => task.Result); + IEnumerable offsetsjump2 = tasks.Skip(4).Take(4).SelectMany(task => task.Result); + if (offsetsjump1.Count() != 1 || offsetsjump2.Count() != 1) + return null; + return new[] + { + new Patch(offsetsjump1.First(), "75", "EB", MemProtection.PAGE_EXECUTE_READ), + new Patch(offsetsjump2.First(), "0F8486000000", "909090909090", MemProtection.PAGE_EXECUTE_READ) + }; + }); + } + + private static Task findAuthhead1_15(byte[] data) + { + return Task.Factory.ContinueWhenAll(new[] + { + // jump 1 + Task.Factory.StartNew(() => findPattern(data, 0x5, "0f84b3000000498bcf")), + Task.Factory.StartNew(() => findPattern(data, 0x5, "909090909090498bcf")), + // jump 2 + Task.Factory.StartNew(() => findPattern(data, 0x5, "0f84a30000004584ed")), + Task.Factory.StartNew(() => findPattern(data, 0x5, "9090909090904584ed")) + }, tasks => + { + // concat results for non-patched and patched + IEnumerable offsetsjump1 = tasks.Take(2).SelectMany(task => task.Result); + IEnumerable offsetsjump2 = tasks.Skip(2).Take(2).SelectMany(task => task.Result); + if (offsetsjump1.Count() != 1 || offsetsjump2.Count() != 1) + return null; + return new[] + { + new Patch(offsetsjump1.First(), "0F84B3000000", "909090909090", MemProtection.PAGE_EXECUTE_READ), + new Patch(offsetsjump2.First(), "0F84A3000000", "909090909090", MemProtection.PAGE_EXECUTE_READ) + }; + }); + } + + #endregion + + #region configdomain + + private static Task findConfigdomain(byte[] data) + { + return Task.Factory.ContinueWhenAll(new[] + { + Task.Factory.StartNew(() => findPattern(data, 0xe, + "488905 ? ? ? 03488d0d ? ? ? 034883c4205b48ff25 ? ? ? 01")) + .ContinueWith(task => + task.Result.Select(addr => addr + 14 + BitConverter.ToInt32(data, addr + 10)).ToArray()), + Task.Factory.StartNew(() => findPattern(data, 0xd, + "83f06e69d093010001e8 ? ? 0400488d05 ? ? ? 01ba000100004c8d05 ? ? ? 01488905 ? ? ? 02488d0d ? ? ? 02")) + .ContinueWith(task => + task.Result.Select(addr => addr + 47 + BitConverter.ToInt32(data, addr + 43)).ToArray()), + Task.Factory.StartNew(() => findPattern(data, 0xd, + "83f16e69d193010001488d0d ? ? ? 02e8 ? ? 0400488d05 ? ? ? 01ba000100004c8d05 ? ? ? 01488905 ? ? ? 02488d0d ? ? ? 02")) + .ContinueWith(task => + task.Result.Select(addr => addr + 54 + BitConverter.ToInt32(data, addr + 50)).ToArray()), // 2.13: big boy + Task.Factory.StartNew(() => findPattern(data, 0x0, + "4883ec28baa71ace87488d0d ? ? ? 02e8 ? ? 0200488d05 ? ? ? 01ba000100004c8d05 ? ? ? 01488905 ? ? ? 02488d0d ? ? ? 02")) + .ContinueWith(task => + task.Result.Select(addr => addr + 54 + BitConverter.ToInt32(data, addr + 50)).ToArray()) // 1.15: big boy as well + }, tasks => + { + IEnumerable offsets = tasks.SelectMany(task => task.Result); + if (offsets.Count() != 1) + return null; + return new[] + { + new Patch(offsets.First(), "", "", MemProtection.PAGE_READWRITE, "configdomain") + }; + }); + } + + #endregion + + #region protocol + + private static Task findHttpsString(byte[] data) + { + return Task.Factory.ContinueWhenAll(new[] + { + Task.Factory.StartNew(() => findPattern(data, 0x8, "68747470733a2f2f7b307d00")), // "https://{0}" + Task.Factory.StartNew(() => findPattern(data, 0x8, "687474703a2f2f7b307d00")), // "http://{0}" + Task.Factory.StartNew(() => findPattern(data, 0x0, "68747470733a2f2f7b307d00")), // "https://{0}" + Task.Factory.StartNew(() => findPattern(data, 0x0, "687474703a2f2f7b307d00")), // "http://{0}" + }, tasks => + { + IEnumerable offsets = tasks.SelectMany(task => task.Result); + if (offsets.Count() != 1) + return null; + return new[] + { + new Patch(offsets.First(), Patch.https, Patch.http, MemProtection.PAGE_READONLY) + }; + }); + } + + private static Task findHttpsLengths1_16(byte[] data) + { + return Task.Factory.StartNew(() => findPattern(data, 0xC, "c745B7 ? 000080488d05 ? ? ? 0148894dd7")).ContinueWith(task => + { + if (task.Result.Length != 1) + return null; // pattern should occur only once + return new[] + { + new Patch(task.Result[0] + 3, "0B", "0A", MemProtection.PAGE_EXECUTE_READ), + }; + }); + } + + private static Task findHttpsLengthsPre3_30(byte[] data) + { + return Task.Factory.ContinueWhenAll(new[] + { + Task.Factory.StartNew(() => findPattern(data, 0xC, "488d55c7488d4d17e8 ? ? ? ff41b8")), + Task.Factory.StartNew(() => findPattern(data, 0x5, "488d55c7488d4d17e8 ? ? ? ff41b8")), + }, tasks => + { + IEnumerable offsets = tasks.SelectMany(task => task.Result); + if (offsets.Count() != 1) + return null; // pattern should occur only once + return new[] + { + new Patch(offsets.First() + 15, "0C", "0B", MemProtection.PAGE_EXECUTE_READ), + }; + }); + } + + private static Task findHttpsLengths3_30(byte[] data) + { + return Task.Factory.ContinueWhenAll(new[] + { + Task.Factory.StartNew(() => findPattern(data, 0x6, "c74547 ? 0000800fbae81f")), + Task.Factory.StartNew(() => findPattern(data, 0x9, "85d2750cc703 ? 000080")), + }, tasks => + { + int[][] offsets = tasks.Select(task => task.Result).ToArray(); + if (offsets[0].Length != 1 || offsets[1].Length != 1) + return null; // fail if not both patterns occur once + return new[] + { + new Patch(offsets[0][0] + 3, "0B", "0A", MemProtection.PAGE_EXECUTE_READ), + new Patch(offsets[1][0] + 6, "0B", "0A", MemProtection.PAGE_EXECUTE_READ), + }; + }); + } + + private static Task findProtocolCombined(byte[] data) + { + return Task.Factory.ContinueWhenAll(new[] + { + findHttpsString(data), + findHttpsLengths1_16(data), + findHttpsLengthsPre3_30(data), + findHttpsLengths3_30(data), + }, tasks => + { + if (tasks[0].Result == null) + return null; // fail if string offset not found + IEnumerable lengthPatches = tasks.Skip(1).Select(t => t.Result).Where(result => result != null); + if (lengthPatches.Count() != 1) + return null; // fail if not exactly one set of length offsets found + + return tasks[0].Result.Concat(lengthPatches.First()).ToArray(); + }); + } + + #endregion + + #region dynres_forceoffline + + private static Task findDynresForceoffline(byte[] data) + { + return Task.Factory.ContinueWhenAll(new[] + { + Task.Factory.StartNew(() => findPattern(data, 0x1, + "83f17269c193010001488d0d ? ? ? 0383f06569d093010001e8 ? ? 0400488d05 ? ? ? 01c705 ? ? ? 0301000000")) + .ContinueWith(task => + task.Result.Select(addr => addr + 47 + BitConverter.ToInt32(data, addr + 39)).ToArray()), + Task.Factory.StartNew(() => findPattern(data, 0xb, + "83f17269c193010001488d0d ? ? ? 0283f06569d093010001e8 ? ? 0400488d05 ? ? ? 01c705 ? ? ? 0201000000")) // 2.72 + .ContinueWith(task => + task.Result.Select(addr => addr + 47 + BitConverter.ToInt32(data, addr + 39)).ToArray()), + Task.Factory.StartNew(() => findPattern(data, 0x7, + "83f17269c193010001488d0d ? ? ? 0283f06569d093010001e8 ? ? 0400488d05 ? ? ? 01c705 ? ? ? 0201000000")) // 2.13 + .ContinueWith(task => + task.Result.Select(addr => addr + 47 + BitConverter.ToInt32(data, addr + 39)).ToArray()), + Task.Factory.StartNew(() => findPattern(data, 0x4, + "ba2734ff12488d0d ? ? ? 02e8 ? ? 0200488d05 ? ? ? 01c705 ? ? ? 0201000000")) // 1.15 + .ContinueWith(task => + task.Result.Select(addr => addr + 34 + BitConverter.ToInt32(data, addr + 26)).ToArray()) + }, tasks => + { + IEnumerable offsets = tasks.SelectMany(task => task.Result); + if (offsets.Count() != 1) + return null; + return new[] + { + new Patch(offsets.First(), "01", "00", MemProtection.PAGE_EXECUTE_READWRITE) + }; + }); + } + + #endregion + + private static int[] findPattern(byte[] data, byte alignment, string pattern) + { + List results = new List(); + int offset = 0; + + // convert string pattern to byte array + List bytepatternlist = new List(pattern.Length); + List wildcardslist = new List(pattern.Length); + pattern = pattern.Replace(" ", ""); // remove spaces + for (int i = 0; i < pattern.Length; i += 2) + { + string twochars = pattern.Substring(i, 2); + if (twochars.StartsWith("?")) + { + if (wildcardslist.Count == 0) // pattern starts with wildcard(s) + { + alignment += 1; + offset -= 1; + i -= 1; // step forward 1 less because it's one '?' instead of two hex chars + } + else + { + wildcardslist.Add(1); + for (int j = wildcardslist.Count - 2; wildcardslist[j] > 0; j--) + { + wildcardslist[j] += 1; + } + bytepatternlist.Add(0x00); // dummy value + i -= 1; // step forward 1 less because it's one '?' instead of two hex chars + } + } + else + { + wildcardslist.Add(0); + bytepatternlist.Add(Convert.ToByte(twochars, 16)); + } + } + byte[] bytepattern = bytepatternlist.ToArray(); + int[] wildcards = wildcardslist.ToArray(); + int patternLen = bytepattern.Length; + int searchEnd = data.Length - patternLen; + int k = 0; + int[] matched = new int[patternLen]; + byte firstbyte = bytepattern[0]; + + // search data for byte array + for (int i = alignment; i < searchEnd; i += 16) + { + if (data[i] == firstbyte) + { + k = 1; + k += wildcards[k]; + while (data[i + k] == bytepattern[k]) + { + k += 1; + if (k == patternLen) + { + results.Add(i + offset); + break; + } + k += wildcards[k]; + } + } + } + + return results.ToArray(); + } + } +} \ No newline at end of file