Files
Wand-Enhancer-k1tbyte/WandEnhancer/Core/JavaScriptPatchApplier.cs
T
kitbyte 6716da5c80 feat(patch-engine): locate patches structurally instead of by signature
Anchor each patch on a stable string (API endpoint, IPC channel, method name)
and walk the delimiter structure via JsCursor to the edit site, reading
minified identifiers out of the located region.

Move injected JavaScript into WandEnhancer/Patches/*.js as embedded resources.
Declare patches as PatchEntry rows in EnhancerConfig with CandidateFileNames,
SearchHints and optional CapabilityHints.

Recognise keyword-preceded regex literals in JsCursor so `return/re/.test(x)`
no longer desynchronises the scan.
Require the remote setValue anchor to match exactly once; Wand ships sibling
call sites for other sources.
Require both backup halves in IsPatched so a partial backup no longer blocks
patch and restore at the same time.
Chain inner exceptions when unpack or pack fails.
Rename Common to ProcessTerminator and Utils.Extensions to WeModInstalls.
2026-08-29 16:56:09 +03:00

83 lines
2.7 KiB
C#

using System;
using System.IO;
using System.Linq;
using WandEnhancer.Core.Js;
using WandEnhancer.Models;
using WandEnhancer.View.MainWindow;
namespace WandEnhancer.Core
{
internal sealed class JavaScriptPatchApplier
{
private readonly Action<string, ELogType> _logger;
public JavaScriptPatchApplier(Action<string, ELogType> logger)
{
_logger = logger;
}
public string Apply(string fileName, string source, EnhancerConfig.PatchEntry patch, EPatchType patchType, out bool patchApplied)
{
patchApplied = false;
if (patch.Applied || !CanSearchFile(fileName, patch))
{
return source;
}
patch.CapabilityDetected |= ContainsAny(source, patch.CapabilityHints);
if (!ContainsAny(source, patch.SearchHints))
{
return source;
}
string label = FormatLabel(patchType, patch);
JsEdit[] edits;
try
{
edits = patch.Locate(new JsCursor(source));
}
catch (Exception e)
{
throw new Exception($"[ENHANCER] [{label}] {e.Message}. The version may not be supported.", e);
}
if (edits == null || edits.Length == 0)
{
return source;
}
_logger($"[ENHANCER] [{label}] Found target in: {Path.GetFileName(fileName)}", ELogType.Info);
foreach (var edit in edits.OrderByDescending(edit => edit.Start))
{
source = edit.ApplyTo(source);
}
_logger($"[ENHANCER] [{label}] Patch applied", ELogType.Success);
patch.Applied = true;
patchApplied = true;
return source;
}
public static string FormatLabel(EPatchType patchType, EnhancerConfig.PatchEntry patch)
{
return string.IsNullOrEmpty(patch.Name) ? patchType.ToString() : $"{patchType} -> {patch.Name}";
}
public static bool CanSearchFile(string filePath, EnhancerConfig.PatchEntry patch)
{
if (patch.CandidateFileNames == null || patch.CandidateFileNames.Length == 0)
{
return true;
}
string fileName = Path.GetFileName(filePath);
return patch.CandidateFileNames.Any(candidate => fileName.Equals(candidate, StringComparison.OrdinalIgnoreCase));
}
private static bool ContainsAny(string source, string[] hints)
{
return hints != null && hints.Any(hint => source.IndexOf(hint, StringComparison.Ordinal) >= 0);
}
}
}