mirror of
https://github.com/k1tbyte/Wand-Enhancer.git
synced 2026-08-28 17:01:04 +00:00
feat: add support for DevTools hotkey, fix patching logic
This commit is contained in:
+4
-1
@@ -134,4 +134,7 @@ dist
|
|||||||
./WeModPatcher/bin/
|
./WeModPatcher/bin/
|
||||||
./AsarSharp/obj/
|
./AsarSharp/obj/
|
||||||
./AsarSharp/bin/
|
./AsarSharp/bin/
|
||||||
.idea
|
.idea
|
||||||
|
packages
|
||||||
|
*/bin/
|
||||||
|
*/obj/
|
||||||
@@ -16,35 +16,8 @@ namespace WeModPatcher.Core
|
|||||||
{
|
{
|
||||||
public class Patcher
|
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<EPatchType, PatchEntry> Patches = new Dictionary<EPatchType, PatchEntry>()
|
|
||||||
{
|
|
||||||
{
|
|
||||||
EPatchType.ActivatePro,
|
|
||||||
new PatchEntry
|
|
||||||
{
|
|
||||||
DynamicFieldResolve = true,
|
|
||||||
Target = new Regex(@"getUserAccount\(\)\{.*?return\s+this\.#\w+\.fetch\(\{.*?\}\)\}", RegexOptions.Singleline),
|
|
||||||
Patch = "getUserAccount(){return this.#<fetch_field_name>.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 WeModConfig _weModConfig;
|
||||||
private readonly Action<string, ELogType> _logger;
|
private readonly Action<string, ELogType> _logger;
|
||||||
@@ -52,7 +25,6 @@ namespace WeModPatcher.Core
|
|||||||
private readonly string _asarPath;
|
private readonly string _asarPath;
|
||||||
private readonly string _backupPath;
|
private readonly string _backupPath;
|
||||||
private readonly string _unpackedPath;
|
private readonly string _unpackedPath;
|
||||||
private int _sumOfPatches = 0;
|
|
||||||
|
|
||||||
public Patcher(WeModConfig weModConfig, Action<string, ELogType> logger, PatchConfig config)
|
public Patcher(WeModConfig weModConfig, Action<string, ELogType> logger, PatchConfig config)
|
||||||
{
|
{
|
||||||
@@ -65,49 +37,46 @@ namespace WeModPatcher.Core
|
|||||||
_backupPath = Path.Combine(weModConfig.RootDirectory, "resources", "app.asar.backup");
|
_backupPath = Path.Combine(weModConfig.RootDirectory, "resources", "app.asar.backup");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetFetchFieldName(string targetFunction)
|
private string ApplyJsPatch(string fileName, string js, PatcherConfig.PatchEntry patch, EPatchType patchType)
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
if (patch.Applied)
|
if (patch.Applied)
|
||||||
{
|
{
|
||||||
return;
|
return js;
|
||||||
}
|
}
|
||||||
|
|
||||||
var matches = patch.Target.Matches(js);
|
var matches = patch.Target.Matches(js);
|
||||||
if (matches.Count == 0)
|
if (matches.Count == 0)
|
||||||
{
|
{
|
||||||
return;
|
return js;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var prefix = $"[PATCHER] [{patchType} -> {patch.Name}]";
|
||||||
|
|
||||||
if(matches.Count > 1 && patch.SingleMatch)
|
if(matches.Count > 1 && patch.SingleMatch)
|
||||||
{
|
{
|
||||||
throw new Exception(
|
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);
|
string resolvedField = patch.Resolver.Handler(matches[0].Value);
|
||||||
if (string.IsNullOrEmpty(fetchFieldName))
|
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("<fetch_field_name>", 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));
|
string newJs = patch.Target.Replace(js, patch.Patch);
|
||||||
_logger($"[PATCHER] [{patchType}] Patch applied", ELogType.Success);
|
File.WriteAllText(fileName, newJs);
|
||||||
|
_logger($"{prefix} Patch applied", ELogType.Success);
|
||||||
patch.Applied = true;
|
patch.Applied = true;
|
||||||
_sumOfPatches -= (int)patchType;
|
|
||||||
|
return newJs;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PatchAsar()
|
private void PatchAsar()
|
||||||
@@ -121,21 +90,42 @@ namespace WeModPatcher.Core
|
|||||||
throw new Exception("[PATCHER] No app bundle found");
|
throw new Exception("[PATCHER] No app bundle found");
|
||||||
}
|
}
|
||||||
|
|
||||||
var requestedPatches = _config.PatchTypes.ToList();
|
// Track patches that still need to be completed
|
||||||
requestedPatches.ForEach(patch => _sumOfPatches += (int)patch);
|
var remainingPatches = new HashSet<EPatchType>(_config.PatchTypes);
|
||||||
|
var patcherConfig = PatcherConfig.GetInstance();
|
||||||
|
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
{
|
{
|
||||||
if (_sumOfPatches <= 0)
|
if (remainingPatches.Count == 0)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
string data = File.ReadAllText(item);
|
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()
|
private void AttachProxyDll()
|
||||||
|
|||||||
@@ -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<string, string> 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<EPatchType, PatchEntry[]> GetInstance()
|
||||||
|
{
|
||||||
|
return new Dictionary<EPatchType, PatchEntry[]>()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
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 = "<service_name>"
|
||||||
|
},
|
||||||
|
Name = "getUserAccount",
|
||||||
|
Target = new Regex(@"getUserAccount\(\)\{.*?return\s+this\.#\w+\.fetch\(\{.*?\}\)\}",
|
||||||
|
RegexOptions.Singleline),
|
||||||
|
Patch =
|
||||||
|
"getUserAccount(){return this.#<service_name>.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 = "<service_name>"
|
||||||
|
},
|
||||||
|
Name = "setAccountWandBrandExperience",
|
||||||
|
Target = new Regex(
|
||||||
|
@"setAccountWandBrandExperience\(\){.*?return\s+this\.#\w+\.post\(""/v3/account/brand_experience_wand""\)}",
|
||||||
|
RegexOptions.Singleline),
|
||||||
|
Patch =
|
||||||
|
"setAccountWandBrandExperience(){return this.#<service_name>.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 = "<dispatch_method>"
|
||||||
|
},
|
||||||
|
Target = new Regex(@"document\.addEventListener\(""keydown"",\s*\((?<arg>\w+)\s*=>\s*\{[^}]*?""ACTION_OPEN_DEV_TOOLS""[^}]*?\}\)\)", RegexOptions.Singleline),
|
||||||
|
Patch = "document.addEventListener(\"keydown\",(${arg}=>{\"F12\"!==${arg}.key||this.#<dispatch_method>(\"ACTION_OPEN_DEV_TOOLS\")}))"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,8 @@ namespace WeModPatcher.Models
|
|||||||
{
|
{
|
||||||
ActivatePro = 1,
|
ActivatePro = 1,
|
||||||
DisableUpdates = 2,
|
DisableUpdates = 2,
|
||||||
DisableTelemetry = 4
|
DisableTelemetry = 4,
|
||||||
|
DevToolsOnF12 = 8
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PatchConfig
|
public sealed class PatchConfig
|
||||||
|
|||||||
@@ -54,6 +54,12 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
|
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
|
||||||
|
<Button
|
||||||
|
Margin="0 0 5 0"
|
||||||
|
Width="25" Height="25" Padding="5.5"
|
||||||
|
Style="{StaticResource IconButton}"
|
||||||
|
Tag="{StaticResource CogIcon}"
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
Margin="9 0 15 0"
|
Margin="9 0 15 0"
|
||||||
Tag="{StaticResource CloseIcon}"
|
Tag="{StaticResource CloseIcon}"
|
||||||
|
|||||||
@@ -24,8 +24,8 @@
|
|||||||
<CheckBox Grid.Row="0" x:Name="ActivateProBox" HorizontalAlignment="Right" VerticalAlignment="Center"
|
<CheckBox Grid.Row="0" x:Name="ActivateProBox" HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||||
IsChecked="True" />
|
IsChecked="True" />
|
||||||
|
|
||||||
<TextBlock Grid.Row="1" VerticalAlignment="Center" Text="Disable telemetry" />
|
<TextBlock Grid.Row="1" VerticalAlignment="Center" Text="DevTools on F12" />
|
||||||
<CheckBox Grid.Row="1" x:Name="DisableTelemetryBox" HorizontalAlignment="Right" VerticalAlignment="Center" />
|
<CheckBox Grid.Row="1" x:Name="DevToolsHotkeyBox" HorizontalAlignment="Right" VerticalAlignment="Center" />
|
||||||
|
|
||||||
<TextBlock Grid.Row="2" VerticalAlignment="Center" Text="Disable updates" />
|
<TextBlock Grid.Row="2" VerticalAlignment="Center" Text="Disable updates" />
|
||||||
<CheckBox Grid.Row="2" x:Name="DisableUpdateBox" HorizontalAlignment="Right" VerticalAlignment="Center" />
|
<CheckBox Grid.Row="2" x:Name="DisableUpdateBox" HorizontalAlignment="Right" VerticalAlignment="Center" />
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ namespace WeModPatcher.View.Popups
|
|||||||
private void OnPatchButtonClick(object sender, RoutedEventArgs e)
|
private void OnPatchButtonClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (ActivateProBox.IsChecked != true && DisableUpdateBox.IsChecked != true &&
|
if (ActivateProBox.IsChecked != true && DisableUpdateBox.IsChecked != true &&
|
||||||
DisableTelemetryBox.IsChecked != true)
|
DevToolsHotkeyBox.IsChecked != true)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -36,6 +36,11 @@ namespace WeModPatcher.View.Popups
|
|||||||
result.Add(EPatchType.DisableUpdates);
|
result.Add(EPatchType.DisableUpdates);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (DevToolsHotkeyBox.IsChecked == true)
|
||||||
|
{
|
||||||
|
result.Add(EPatchType.DevToolsOnF12);
|
||||||
|
}
|
||||||
|
|
||||||
_onApply(new PatchConfig
|
_onApply(new PatchConfig
|
||||||
{
|
{
|
||||||
PatchTypes = result,
|
PatchTypes = result,
|
||||||
|
|||||||
@@ -69,6 +69,7 @@
|
|||||||
<Compile Include="Converters\BaseBooleanConverter.cs" />
|
<Compile Include="Converters\BaseBooleanConverter.cs" />
|
||||||
<Compile Include="Converters\ToVisibilityConverter.cs" />
|
<Compile Include="Converters\ToVisibilityConverter.cs" />
|
||||||
<Compile Include="Core\Patcher.cs" />
|
<Compile Include="Core\Patcher.cs" />
|
||||||
|
<Compile Include="Core\PatcherConfig.cs" />
|
||||||
<Compile Include="Models\WeModConfig.cs" />
|
<Compile Include="Models\WeModConfig.cs" />
|
||||||
<Compile Include="Models\PatchConfig.cs" />
|
<Compile Include="Models\PatchConfig.cs" />
|
||||||
<Compile Include="Models\Signature.cs" />
|
<Compile Include="Models\Signature.cs" />
|
||||||
|
|||||||
Reference in New Issue
Block a user