diff --git a/.gitignore b/.gitignore index 77452a3..6014c60 100644 --- a/.gitignore +++ b/.gitignore @@ -134,4 +134,7 @@ dist ./WeModPatcher/bin/ ./AsarSharp/obj/ ./AsarSharp/bin/ -.idea \ No newline at end of file +.idea +packages +*/bin/ +*/obj/ \ No newline at end of file diff --git a/WeModPatcher/Core/Patcher.cs b/WeModPatcher/Core/Patcher.cs index 3e1d374..7cefb41 100644 --- a/WeModPatcher/Core/Patcher.cs +++ b/WeModPatcher/Core/Patcher.cs @@ -16,35 +16,8 @@ namespace WeModPatcher.Core { public class Patcher { - private class PatchEntry - { - public Regex Target { get; set; } - public string Patch { get; set; } - public bool Applied { get; set; } - public bool SingleMatch { get; set; } = true; - public bool DynamicFieldResolve { get; set; } - } - private static readonly Dictionary Patches = new Dictionary() - { - { - EPatchType.ActivatePro, - new PatchEntry - { - DynamicFieldResolve = true, - Target = new Regex(@"getUserAccount\(\)\{.*?return\s+this\.#\w+\.fetch\(\{.*?\}\)\}", RegexOptions.Singleline), - Patch = "getUserAccount(){return this.#.fetch({endpoint:\"/v3/account\",method:\"GET\",name:\"/v3/account\",collectMetrics:0}).then(response=>{response.subscription={period:\"yearly\",state:\"active\"};response.flags=78;return response;})}" - } - }, - { - EPatchType.DisableUpdates, - new PatchEntry - { - Target = new Regex(@"registerHandler\(""ACTION_CHECK_FOR_UPDATE"".*?\)\)\)\)", RegexOptions.Singleline), - Patch = "registerHandler(\"ACTION_CHECK_FOR_UPDATE\",(e=>expectUpdateFeedUrl(e,(e=>null)))" - } - } - }; + private readonly WeModConfig _weModConfig; private readonly Action _logger; @@ -52,7 +25,6 @@ namespace WeModPatcher.Core private readonly string _asarPath; private readonly string _backupPath; private readonly string _unpackedPath; - private int _sumOfPatches = 0; public Patcher(WeModConfig weModConfig, Action logger, PatchConfig config) { @@ -65,49 +37,46 @@ namespace WeModPatcher.Core _backupPath = Path.Combine(weModConfig.RootDirectory, "resources", "app.asar.backup"); } - private static string GetFetchFieldName(string targetFunction) - { - var fetchMatch = Regex.Match(targetFunction, @"return\s+this\.#(\w+)\.fetch"); - return fetchMatch.Success ? fetchMatch.Groups[1].Value : null; - } - - private void ApplyJsPatch(string fileName, string js, PatchEntry patch, EPatchType patchType) + private string ApplyJsPatch(string fileName, string js, PatcherConfig.PatchEntry patch, EPatchType patchType) { if (patch.Applied) { - return; + return js; } var matches = patch.Target.Matches(js); if (matches.Count == 0) { - return; + return js; } + var prefix = $"[PATCHER] [{patchType} -> {patch.Name}]"; + if(matches.Count > 1 && patch.SingleMatch) { throw new Exception( - $"[PATCHER] [{patchType}] Patch failed. Multiple target functions found. Looks like the version is not supported"); + $"{prefix} Patch failed. Multiple target functions found. Looks like the version is not supported"); } - if (patch.DynamicFieldResolve) + if (patch.Resolver != null) { - string fetchFieldName = GetFetchFieldName(matches[0].Value); - if (string.IsNullOrEmpty(fetchFieldName)) + string resolvedField = patch.Resolver.Handler(matches[0].Value); + if (string.IsNullOrEmpty(resolvedField)) { - throw new Exception($"[PATCHER] [{patchType}] Fetch field name not found"); + throw new Exception($"{prefix} Resolver failed to find field name"); } - patch.Patch = patch.Patch.Replace("", fetchFieldName); + patch.Patch = patch.Patch.Replace(patch.Resolver.Placeholder, resolvedField); } - _logger($"[PATCHER] [{patchType}] Found target function in: " + Path.GetFileName(fileName), ELogType.Info); - + _logger($"{prefix} Found target function in: " + Path.GetFileName(fileName), ELogType.Info); - File.WriteAllText(fileName, patch.Target.Replace(js, patch.Patch)); - _logger($"[PATCHER] [{patchType}] Patch applied", ELogType.Success); + string newJs = patch.Target.Replace(js, patch.Patch); + File.WriteAllText(fileName, newJs); + _logger($"{prefix} Patch applied", ELogType.Success); patch.Applied = true; - _sumOfPatches -= (int)patchType; + + return newJs; } private void PatchAsar() @@ -121,21 +90,42 @@ namespace WeModPatcher.Core throw new Exception("[PATCHER] No app bundle found"); } - var requestedPatches = _config.PatchTypes.ToList(); - requestedPatches.ForEach(patch => _sumOfPatches += (int)patch); + // Track patches that still need to be completed + var remainingPatches = new HashSet(_config.PatchTypes); + var patcherConfig = PatcherConfig.GetInstance(); + foreach (var item in items) { - if (_sumOfPatches <= 0) + if (remainingPatches.Count == 0) { break; } string data = File.ReadAllText(item); - foreach (var entry in requestedPatches) + + // Iterate over a copy of the list so we can modify the HashSet + foreach (var entry in remainingPatches.ToList()) { - ApplyJsPatch(item, data, Patches[entry], entry); + var entries = patcherConfig[entry]; + foreach (var patchEntry in entries) + { + // Update data in memory so subsequent patches in the same file work on latest content + data = ApplyJsPatch(item, data, patchEntry, entry); + } + + // Check if all entries for this patch type are applied + if (entries.All(x => x.Applied)) + { + remainingPatches.Remove(entry); + } } } + + if(remainingPatches.Count > 0) + { + var failedPatches = string.Join(", ", remainingPatches.Select(p => p.ToString())); + throw new Exception($"[PATCHER] Failed to apply patches: {failedPatches}. The version may not be supported."); + } } private void AttachProxyDll() diff --git a/WeModPatcher/Core/PatcherConfig.cs b/WeModPatcher/Core/PatcherConfig.cs new file mode 100644 index 0000000..410e2a4 --- /dev/null +++ b/WeModPatcher/Core/PatcherConfig.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using WeModPatcher.Models; + +namespace WeModPatcher.Core +{ + public static class PatcherConfig + { + public class ResolveContext + { + public string Placeholder { get; set; } + public Func Handler { get; set; } + } + + public class PatchEntry + { + public Regex Target { get; set; } + public string Patch { get; set; } + public string Name { get; set; } + public bool Applied { get; set; } + public bool SingleMatch { get; set; } = true; + public ResolveContext Resolver { get; set; } + } + + public static Dictionary GetInstance() + { + return new Dictionary() + { + { + EPatchType.ActivatePro, + new[] + { + new PatchEntry + { + Resolver = new ResolveContext + { + Handler = (targetFunction) => + { + var fetchMatch = Regex.Match(targetFunction, @"return\s+this\.#(\w+)\.fetch"); + return fetchMatch.Success ? fetchMatch.Groups[1].Value : null; + }, + Placeholder = "" + }, + Name = "getUserAccount", + Target = new Regex(@"getUserAccount\(\)\{.*?return\s+this\.#\w+\.fetch\(\{.*?\}\)\}", + RegexOptions.Singleline), + Patch = + "getUserAccount(){return this.#.fetch({endpoint:\"/v3/account\",method:\"GET\",name:\"/v3/account\",collectMetrics:0}).then(response=>{response.subscription={period:\"yearly\",state:\"active\"};return response;})}" + }, + new PatchEntry + { + Resolver = new ResolveContext + { + Handler = (targetFunction) => + { + var match = Regex.Match(targetFunction, @"return\s+this\.#(\w+)\.post"); + return match.Success ? match.Groups[1].Value : null; + }, + Placeholder = "" + }, + Name = "setAccountWandBrandExperience", + Target = new Regex( + @"setAccountWandBrandExperience\(\){.*?return\s+this\.#\w+\.post\(""/v3/account/brand_experience_wand""\)}", + RegexOptions.Singleline), + Patch = + "setAccountWandBrandExperience(){return this.#.post(\"/v3/account/brand_experience_wand\").then(response=>{response.subscription={period:\"yearly\",state:\"active\"};return response;})}" + } + } + }, + { + EPatchType.DisableUpdates, + new[] + { + new PatchEntry + { + Target = new Regex(@"registerHandler\(""ACTION_CHECK_FOR_UPDATE"".*?\)\)\)\)", + RegexOptions.Singleline), + Patch = "registerHandler(\"ACTION_CHECK_FOR_UPDATE\",(e=>expectUpdateFeedUrl(e,(e=>null)))" + } + } + }, + { + EPatchType.DevToolsOnF12, + new[] + { + new PatchEntry + { + Resolver = new ResolveContext + { + Handler = (matchContent) => { + var match = Regex.Match(matchContent, @"this\.#(\w+)\(""ACTION_OPEN_DEV_TOOLS""\)"); + return match.Success ? match.Groups[1].Value : null; + }, + Placeholder = "" + }, + Target = new Regex(@"document\.addEventListener\(""keydown"",\s*\((?\w+)\s*=>\s*\{[^}]*?""ACTION_OPEN_DEV_TOOLS""[^}]*?\}\)\)", RegexOptions.Singleline), + Patch = "document.addEventListener(\"keydown\",(${arg}=>{\"F12\"!==${arg}.key||this.#(\"ACTION_OPEN_DEV_TOOLS\")}))" + } + } + } + }; + } + } +} \ No newline at end of file diff --git a/WeModPatcher/Models/PatchConfig.cs b/WeModPatcher/Models/PatchConfig.cs index 3a8c699..629fb10 100644 --- a/WeModPatcher/Models/PatchConfig.cs +++ b/WeModPatcher/Models/PatchConfig.cs @@ -11,7 +11,8 @@ namespace WeModPatcher.Models { ActivatePro = 1, DisableUpdates = 2, - DisableTelemetry = 4 + DisableTelemetry = 4, + DevToolsOnF12 = 8 } public sealed class PatchConfig diff --git a/WeModPatcher/View/MainWindow/MainWindow.xaml b/WeModPatcher/View/MainWindow/MainWindow.xaml index d8b5c7f..5882bf3 100644 --- a/WeModPatcher/View/MainWindow/MainWindow.xaml +++ b/WeModPatcher/View/MainWindow/MainWindow.xaml @@ -54,6 +54,12 @@ +