From d3ff13a528ae9eca13f0a254804009beffcf4133 Mon Sep 17 00:00:00 2001 From: kitbyte Date: Mon, 24 Mar 2025 14:51:58 +0200 Subject: [PATCH] added runtime patcher, added MemoryUtils, fixes --- WeModPatcher/App.xaml | 3 +- WeModPatcher/App.xaml.cs | 18 +- WeModPatcher/Constants.cs | 20 ++ WeModPatcher/Core/RuntimePatcher.cs | 155 +++++++++++ .../Patcher.cs => Core/StaticPatcher.cs} | 87 ++++-- WeModPatcher/MainWindow.xaml | 12 - WeModPatcher/MainWindow.xaml.cs | 13 - WeModPatcher/Models/PatchConfig.cs | 18 +- WeModPatcher/Models/Signature.cs | 25 ++ WeModPatcher/Program.cs | 73 +++++ .../AsyncRelayCommand.cs | 2 +- .../ObservableObject.cs | 2 +- .../RelayCommand.cs | 2 +- WeModPatcher/Style/Icons.xaml | 16 ++ WeModPatcher/Style/Styles.xaml | 4 +- WeModPatcher/Utils/Extensions.cs | 71 +++++ WeModPatcher/Utils/MemoryUtils.cs | 178 ++++++++++++ WeModPatcher/Utils/PatternScanner.cs | 101 ------- WeModPatcher/Utils/Win32/Imports.cs | 258 ++++++++++++++++++ WeModPatcher/Utils/Win32/Shortcut.cs | 61 +++++ WeModPatcher/View/Controls/InfoItem.xaml | 20 ++ WeModPatcher/View/Controls/InfoItem.xaml.cs | 42 +++ WeModPatcher/View/Controls/PopupHost.xaml | 8 +- WeModPatcher/View/Controls/PopupHost.xaml.cs | 4 + WeModPatcher/View/MainWindow/MainWindow.xaml | 6 +- .../View/MainWindow/MainWindow.xaml.cs | 7 +- WeModPatcher/View/MainWindow/MainWindowVm.cs | 90 +++--- .../View/Popups/PatchVectorsPopup.xaml | 127 +++++++-- .../View/Popups/PatchVectorsPopup.xaml.cs | 60 +++- WeModPatcher/WeModPatcher.csproj | 88 ++++-- WeModPatcher/packages.config | 10 + appicon.ico | Bin 105213 -> 0 bytes assets/appicon.ico | Bin 0 -> 90269 bytes 33 files changed, 1294 insertions(+), 287 deletions(-) create mode 100644 WeModPatcher/Core/RuntimePatcher.cs rename WeModPatcher/{Utils/Patcher.cs => Core/StaticPatcher.cs} (72%) delete mode 100644 WeModPatcher/MainWindow.xaml delete mode 100644 WeModPatcher/MainWindow.xaml.cs create mode 100644 WeModPatcher/Models/Signature.cs create mode 100644 WeModPatcher/Program.cs rename WeModPatcher/{ReactiveCore => ReactiveUICore}/AsyncRelayCommand.cs (97%) rename WeModPatcher/{ReactiveCore => ReactiveUICore}/ObservableObject.cs (94%) rename WeModPatcher/{ReactiveCore => ReactiveUICore}/RelayCommand.cs (95%) create mode 100644 WeModPatcher/Utils/Extensions.cs create mode 100644 WeModPatcher/Utils/MemoryUtils.cs delete mode 100644 WeModPatcher/Utils/PatternScanner.cs create mode 100644 WeModPatcher/Utils/Win32/Imports.cs create mode 100644 WeModPatcher/Utils/Win32/Shortcut.cs create mode 100644 WeModPatcher/View/Controls/InfoItem.xaml create mode 100644 WeModPatcher/View/Controls/InfoItem.xaml.cs create mode 100644 WeModPatcher/packages.config delete mode 100644 appicon.ico create mode 100644 assets/appicon.ico diff --git a/WeModPatcher/App.xaml b/WeModPatcher/App.xaml index c901bd6..c915d67 100644 --- a/WeModPatcher/App.xaml +++ b/WeModPatcher/App.xaml @@ -2,8 +2,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WeModPatcher" - xmlns:converters="clr-namespace:WeModPatcher.Converters" - StartupUri="/View/MainWindow/MainWindow.xaml"> + xmlns:converters="clr-namespace:WeModPatcher.Converters"> diff --git a/WeModPatcher/App.xaml.cs b/WeModPatcher/App.xaml.cs index 0d9be3e..3cb40f1 100644 --- a/WeModPatcher/App.xaml.cs +++ b/WeModPatcher/App.xaml.cs @@ -1,6 +1,8 @@ using System; using System.Threading.Tasks; using System.Windows; +using WeModPatcher.Core; +using WeModPatcher.View.MainWindow; using MessageBox = System.Windows.Forms.MessageBox; namespace WeModPatcher @@ -12,21 +14,7 @@ namespace WeModPatcher { protected override void OnStartup(StartupEventArgs e) { - AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; - TaskScheduler.UnobservedTaskException += OnUnobservedTaskException; - base.OnStartup(e); - } - - private void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) - { - MessageBox.Show(e.ToString()); - Shutdown(); - } - - private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e) - { - MessageBox.Show(e.ToString()); - Shutdown(); + this.MainWindow.Show(); } public new static void Shutdown() diff --git a/WeModPatcher/Constants.cs b/WeModPatcher/Constants.cs index 8c2d591..0bf9d70 100644 --- a/WeModPatcher/Constants.cs +++ b/WeModPatcher/Constants.cs @@ -1,5 +1,6 @@ using System; using System.Reflection; +using WeModPatcher.Models; namespace WeModPatcher { @@ -10,6 +11,25 @@ namespace WeModPatcher public static readonly string RepositoryUrl = $"https://github.com/{Owner}/{RepoName}"; public static readonly Version Version; + // cmp dword ptr [rdx], 0 + // jnz loc_XXXXXXXX + // mov rsi, rdx + public static Signature ExePatchSignature = new Signature( + "83 3A 00 0F ?? ?? 01 00 00 48 89 D6 48 B8", + 4, + new byte[]{ 0x84, 0x17 }, + new byte[]{ 0x85, 0x22 } + ); + + /*// ... + // test eax, eax (0x85 for r/m16/32/64) + // jnz short loc_1403A4DD2 (Integrity check failed) + // call near ptr funk_1445527E0 + // ... + private const string PatchSignature = "E8 ?? ?? ?? ?? ?? C0 75 ?? F6 C3 01 74 ?? 48 89 F9 E8 ?? ?? ?? ??"; + private static readonly byte[] PatchBytes = { 0x31 }; + private const int PatchOffset = 0x5;*/ + static Constants() { Version = Assembly.GetExecutingAssembly().GetName().Version; diff --git a/WeModPatcher/Core/RuntimePatcher.cs b/WeModPatcher/Core/RuntimePatcher.cs new file mode 100644 index 0000000..bf97196 --- /dev/null +++ b/WeModPatcher/Core/RuntimePatcher.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using WeModPatcher.Models; +using WeModPatcher.Utils; +using WeModPatcher.Utils.Win32; +using WeModPatcher.View.MainWindow; + +namespace WeModPatcher.Core +{ + + public class RuntimePatcher + { + private readonly string _exePath; + + public RuntimePatcher(string exePath) + { + _exePath = exePath; + } + + + public void StartProcess() + { + if(string.IsNullOrEmpty(_exePath)) + { + throw new Exception("Path is not specified"); + } + + KillWeMod(); + var startupInfo = new Imports.StartupInfo { cb = Marshal.SizeOf(typeof(Imports.StartupInfo)) }; + if(!Imports.CreateProcessA(_exePath, + null, + IntPtr.Zero, + IntPtr.Zero, + false, Imports.DEBUG_PROCESS, IntPtr.Zero, + null, ref startupInfo, out var processInfo)) + { + throw new Exception("Failed to create process, error code: " + Marshal.GetLastWin32Error()); + } + + var debugEvent = new Imports.DEBUG_EVENT(); + var processIds = new List(); + while (Imports.WaitForDebugEvent(ref debugEvent, 1000)) + { + var code = debugEvent.dwDebugEventCode; + if (code == Imports.CREATE_PROCESS_DEBUG_EVENT) + { + processIds.Add(debugEvent.dwProcessId); + } + else if (code == Imports.EXCEPTION_DEBUG_EVENT) + { + var exceptionInfo = Imports.MapUnmanagedStructure(debugEvent.Union); + + if (exceptionInfo.ExceptionRecord.ExceptionCode == Imports.EXCEPTION_BREAKPOINT) + { + var process = Process.GetProcessById((int)debugEvent.dwProcessId); + var address = MemoryUtils.ScanVirtualMemory( + process.Handle, + process.Modules[0].BaseAddress, + process.Modules[0].ModuleMemorySize, + Constants.ExePatchSignature.Sequence, Constants.ExePatchSignature.Mask + ); + + if (address != IntPtr.Zero) + { + MemoryUtils.SafeWriteVirtualMemory( + process.Handle, + address + Constants.ExePatchSignature.Offset, + Constants.ExePatchSignature.PatchBytes + ); + + /*byte[] patchedBytes = new byte[32]; + if (Imports.ReadProcessMemory(process.Handle, address, patchedBytes, patchedBytes.Length, out int bytesRead)) + { + Console.WriteLine("Bytes after patching: "); + for (int i = 0; i < bytesRead; i++) + { + Console.Write($"{patchedBytes[i]:X2} "); + } + Console.WriteLine(); + }*/ + } + } + } + + Imports.ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, Imports.DBG_CONTINUE); + } + + foreach (var processId in processIds) + { + Imports.DebugActiveProcessStop(processId); + } + + Imports.CloseHandle(processInfo.hProcess); + } + + public static void Patch(PatchConfig config, Action logger) + { + if (config.Path == null) + { + throw new Exception("Path is not specified"); + } + + var parent = Directory.GetParent(config.Path)?.FullName ?? config.Path; + var latestPath = Extensions.FindLatestWeMod(parent) ?? config.Path; + + if (!Extensions.CheckWeModPath(latestPath)) + { + throw new Exception("Invalid WeMod path"); + } + + if(!File.Exists(Path.Combine(latestPath, "resources", "app.asar.backup"))) + { + config.PatchMethod = EPatchProcessMethod.None; + new StaticPatcher(latestPath, logger, config).Patch(); + } + + new RuntimePatcher(Path.Combine(latestPath, "WeMod.exe")) + .StartProcess(); + } + + + + public static void KillWeMod() + { + Process[] processes = Process.GetProcessesByName("WeMod"); + for (int i = 0; processes.Length > i || i < 5; i++) + { + foreach (var process in processes) + { + try + { + process.Kill(); + } + catch + { + // ignored + } + } + + processes = Process.GetProcessesByName("WeMod"); + Thread.Sleep(250); + } + + if (processes.Length > 0) + { + throw new Exception("Failed to kill WeMod"); + } + } + } +} \ No newline at end of file diff --git a/WeModPatcher/Utils/Patcher.cs b/WeModPatcher/Core/StaticPatcher.cs similarity index 72% rename from WeModPatcher/Utils/Patcher.cs rename to WeModPatcher/Core/StaticPatcher.cs index 4400264..df43657 100644 --- a/WeModPatcher/Utils/Patcher.cs +++ b/WeModPatcher/Core/StaticPatcher.cs @@ -2,15 +2,19 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using System.Text.RegularExpressions; -using System.Threading.Tasks; +using System.Windows.Forms; using AsarSharp; +using Newtonsoft.Json; using WeModPatcher.Models; +using WeModPatcher.Utils; using WeModPatcher.View.MainWindow; +using Application = System.Windows.Application; -namespace WeModPatcher.Utils +namespace WeModPatcher.Core { - public class Patcher + public class StaticPatcher { private class PatchEntry { @@ -42,24 +46,16 @@ namespace WeModPatcher.Utils } }; - // ... - // test eax, eax (0x85 for r/m16/32/64) - // jnz short loc_1403A4DD2 (Integrity check failed) - // call near ptr funk_1445527E0 - // ... - private const string PatchSignature = "E8 ?? ?? ?? ?? ?? C0 75 ?? F6 C3 01 74 ?? 48 89 F9 E8 ?? ?? ?? ??"; - private static readonly byte[] PatchBytes = { 0x31 }; - private const int PatchOffset = 0x5; - private readonly string _weModRootFolder; private readonly Action _logger; - private readonly HashSet _config; + private readonly PatchConfig _config; private readonly string _asarPath; private readonly string _backupPath; private readonly string _unpackedPath; private int _sumOfPatches = 0; + private readonly string _exePath; - public Patcher(string weModRootFolder, Action logger, HashSet config) + public StaticPatcher(string weModRootFolder, Action logger, PatchConfig config) { _weModRootFolder = weModRootFolder; _logger = logger; @@ -68,6 +64,7 @@ namespace WeModPatcher.Utils _asarPath = Path.Combine(weModRootFolder, "resources", "app.asar"); _unpackedPath = Path.Combine(weModRootFolder, "resources", "app.asar.unpacked"); _backupPath = Path.Combine(weModRootFolder, "resources", "app.asar.backup"); + _exePath = Path.Combine(_weModRootFolder, "WeMod.exe"); } private static string GetFetchFieldName(string targetFunction) @@ -126,7 +123,7 @@ namespace WeModPatcher.Utils throw new Exception("[PATCHER] No app bundle found"); } - var requestedPatches = _config.ToList(); + var requestedPatches = _config.PatchTypes.ToList(); requestedPatches.ForEach(patch => _sumOfPatches += (int)patch); foreach (var item in items) { @@ -143,11 +140,10 @@ namespace WeModPatcher.Utils } } - private async Task PatchPE() + private void PatchPe() { _logger("[PATCHER] Patching PE...", ELogType.Info); - var pePath = Path.Combine(_weModRootFolder, "WeMod.exe"); - var patchResult = await PatternScanner.PatchBySignature(pePath, PatchSignature, PatchBytes, PatchOffset); + var patchResult = MemoryUtils.PatchFile(_exePath,Constants.ExePatchSignature, Constants.ExePatchSignature.PatchBytes); if(patchResult == -1) { _logger("[PATCHER] Failed to patch PE", ELogType.Error); @@ -156,8 +152,41 @@ namespace WeModPatcher.Utils _logger(patchResult == 0 ? "[PATCHER] PE already patched!" : "[PATCHER] PE patched successfully!", ELogType.Success); } - public async Task Patch() + private void CreateShortcut() { + // invoke file dialog save file + + var fileDialog = new SaveFileDialog() + { + CheckPathExists = true, + AddExtension = true, + SupportMultiDottedExtensions = false, + FileName = "WeMod", + }; + + if(fileDialog.ShowDialog() != DialogResult.OK) + { + return; + } + + _config.Path = _weModRootFolder; + + var json = JsonConvert.SerializeObject(_config, Formatting.None); + Utils.Win32.Shortcut.CreateShortcut( + fileName: fileDialog.FileName + ".lnk", + targetPath: Assembly.GetExecutingAssembly().Location, + arguments: Extensions.Base64Encode(json), + workingDirectory: Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), + description: null, + iconPath: _exePath + ); + + _logger("[PATCHER] The shortcut has been created, now you should only run WeMod through this shortcut", ELogType.Success); + } + + public void Patch() + { + RuntimePatcher.KillWeMod(); if (!File.Exists(_backupPath)) { _logger("[PATCHER] Creating backup...", ELogType.Info); @@ -170,8 +199,7 @@ namespace WeModPatcher.Utils if(!File.Exists(_asarPath)) { - _logger("[PATCHER] app.asar not found!", ELogType.Error); - return; + throw new Exception("app.asar not found"); } try @@ -181,8 +209,7 @@ namespace WeModPatcher.Utils } catch (Exception e) { - _logger($"[PATCHER] Failed to unpack app.asar: {e.Message}", ELogType.Error); - return; + throw new Exception($"[PATCHER] Failed to unpack app.asar: {e.Message}"); } PatchAsar(); @@ -196,11 +223,17 @@ namespace WeModPatcher.Utils } catch (Exception e) { - _logger($"[PATCHER] Failed to pack app.asar: {e.Message}", ELogType.Error); - return; + throw new Exception($"[PATCHER] Failed to pack app.asar: {e.Message}"); + } + + if (_config.PatchMethod == EPatchProcessMethod.Static) + { + PatchPe(); + } + else if(_config.PatchMethod == EPatchProcessMethod.Runtime) + { + Application.Current.Dispatcher.Invoke(CreateShortcut); } - - // await PatchPE(); _logger("[PATCHER] Done!", ELogType.Success); } diff --git a/WeModPatcher/MainWindow.xaml b/WeModPatcher/MainWindow.xaml deleted file mode 100644 index acf9872..0000000 --- a/WeModPatcher/MainWindow.xaml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/WeModPatcher/MainWindow.xaml.cs b/WeModPatcher/MainWindow.xaml.cs deleted file mode 100644 index b5a23dd..0000000 --- a/WeModPatcher/MainWindow.xaml.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace WeModPatcher -{ - /// - /// Interaction logic for MainWindow.xaml - /// - public partial class MainWindow - { - public MainWindow() - { - InitializeComponent(); - } - } -} \ No newline at end of file diff --git a/WeModPatcher/Models/PatchConfig.cs b/WeModPatcher/Models/PatchConfig.cs index deedf84..179c0cf 100644 --- a/WeModPatcher/Models/PatchConfig.cs +++ b/WeModPatcher/Models/PatchConfig.cs @@ -1,4 +1,6 @@ -namespace WeModPatcher.Models +using System.Collections.Generic; + +namespace WeModPatcher.Models { public enum EPatchType @@ -7,4 +9,18 @@ DisableUpdates = 2, DisableTelemetry = 4 } + + public enum EPatchProcessMethod + { + None = 0, + Runtime = 1, + Static = 2 + } + + public sealed class PatchConfig + { + public HashSet PatchTypes { get; set; } + public EPatchProcessMethod PatchMethod { get; set; } + public string Path { get; set; } + } } \ No newline at end of file diff --git a/WeModPatcher/Models/Signature.cs b/WeModPatcher/Models/Signature.cs new file mode 100644 index 0000000..f22655b --- /dev/null +++ b/WeModPatcher/Models/Signature.cs @@ -0,0 +1,25 @@ +using WeModPatcher.Utils; + +namespace WeModPatcher.Models +{ + public sealed class Signature + { + public readonly byte[] OriginalBytes; + public readonly byte[] PatchBytes; + public readonly byte[] Sequence; + public readonly byte[] Mask; + public readonly int Offset; + + public int Length => Sequence.Length; + + public static implicit operator byte[](Signature signature) => signature.Sequence; + + public Signature(string signature, int offset, byte[] patchBytes, byte[] originalBytes) + { + MemoryUtils.ParseSignature(signature, out Sequence, out Mask); + PatchBytes = patchBytes; + OriginalBytes = originalBytes; + Offset = offset; + } + } +} \ No newline at end of file diff --git a/WeModPatcher/Program.cs b/WeModPatcher/Program.cs new file mode 100644 index 0000000..5880351 --- /dev/null +++ b/WeModPatcher/Program.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Threading.Tasks; +using System.Windows.Forms; +using Newtonsoft.Json; +using WeModPatcher.Core; +using WeModPatcher.Models; +using WeModPatcher.Utils; +using WeModPatcher.View.MainWindow; + +namespace WeModPatcher +{ + public static class Program + { + [STAThread] + public static void Main(string[] args) + { + AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; + TaskScheduler.UnobservedTaskException += OnUnobservedTaskException; + + List logEntries = new List(); + if (args.Length > 0) + { + try + { + var patchConfig = JsonConvert.DeserializeObject(Extensions.Base64Decode(args[0])); + RuntimePatcher.Patch(patchConfig, (message, type) => + { + logEntries.Add(new LogEntry + { + Message = message, + LogType = type + }); + }); + Environment.Exit(0); + } + catch (Exception e) + { + logEntries.Add(new LogEntry + { + Message = "Runtime patching failed: " + e.Message, + LogType = ELogType.Error + }); + } + + } + + var application = new App(); + application.InitializeComponent(); + application.MainWindow = new MainWindow(); + foreach (var logEntry in logEntries) + { + MainWindow.Instance.ViewModel.LogList.Add(logEntry); + } + application.Run(); + } + + + private static void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) + { + MessageBox.Show(e.Exception.ToString()); + Environment.Exit(1); + } + + private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e) + { + MessageBox.Show(e.ExceptionObject.ToString()); + Environment.Exit(1); + } + } +} \ No newline at end of file diff --git a/WeModPatcher/ReactiveCore/AsyncRelayCommand.cs b/WeModPatcher/ReactiveUICore/AsyncRelayCommand.cs similarity index 97% rename from WeModPatcher/ReactiveCore/AsyncRelayCommand.cs rename to WeModPatcher/ReactiveUICore/AsyncRelayCommand.cs index e351090..c9bda08 100644 --- a/WeModPatcher/ReactiveCore/AsyncRelayCommand.cs +++ b/WeModPatcher/ReactiveUICore/AsyncRelayCommand.cs @@ -3,7 +3,7 @@ using System.Threading; using System.Threading.Tasks; using System.Windows.Input; -namespace WeModPatcher.ReactiveCore +namespace WeModPatcher.ReactiveUICore { public sealed class AsyncRelayCommand : ICommand { diff --git a/WeModPatcher/ReactiveCore/ObservableObject.cs b/WeModPatcher/ReactiveUICore/ObservableObject.cs similarity index 94% rename from WeModPatcher/ReactiveCore/ObservableObject.cs rename to WeModPatcher/ReactiveUICore/ObservableObject.cs index 7e3c129..6aa372d 100644 --- a/WeModPatcher/ReactiveCore/ObservableObject.cs +++ b/WeModPatcher/ReactiveUICore/ObservableObject.cs @@ -1,7 +1,7 @@ using System.ComponentModel; using System.Runtime.CompilerServices; -namespace WeModPatcher.ReactiveCore +namespace WeModPatcher.ReactiveUICore { public class ObservableObject : INotifyPropertyChanged { diff --git a/WeModPatcher/ReactiveCore/RelayCommand.cs b/WeModPatcher/ReactiveUICore/RelayCommand.cs similarity index 95% rename from WeModPatcher/ReactiveCore/RelayCommand.cs rename to WeModPatcher/ReactiveUICore/RelayCommand.cs index 5592ed2..7eab727 100644 --- a/WeModPatcher/ReactiveCore/RelayCommand.cs +++ b/WeModPatcher/ReactiveUICore/RelayCommand.cs @@ -1,7 +1,7 @@ using System; using System.Windows.Input; -namespace WeModPatcher.ReactiveCore +namespace WeModPatcher.ReactiveUICore { public sealed class RelayCommand : ICommand { diff --git a/WeModPatcher/Style/Icons.xaml b/WeModPatcher/Style/Icons.xaml index 7e3af1a..79243b2 100644 --- a/WeModPatcher/Style/Icons.xaml +++ b/WeModPatcher/Style/Icons.xaml @@ -15,4 +15,20 @@ M12 2A10 10 0 0122 12c0 4.42-2.86 8.16-6.83 9.5-.51.09-.67-.23-.67-.5 0-.32 0-1.4 0-2.74 0-.93-.33-1.54-.69-1.85 2.23-.25 4.57-1.09 4.57-4.91 0-1.11-.38-2-1.03-2.71.1-.25.45-1.29-.1-2.64 0 0-.84-.27-2.75 1.02-.79-.22-1.65-.33-2.5-.33s-1.71.11-2.5.33C7.59 5.88 6.75 6.15 6.75 6.15c-.55 1.35-.2 2.39-.1 2.64-.65.71-1.03 1.6-1.03 2.71 0 3.81 2.33 4.67 4.55 4.92-.28.25-.54.69-.63 1.34-.57.24-2.04.69-2.91-.83 0 0-.53-.96-1.53-1.03 0 0-.98-.02-.07.6 0 0 .65.31 1.11 1.47 0 0 .59 1.94 3.36 1.34 0 .83 0 1.46 0 1.69 0 .27-.16.58-.66.5C4.87 20.17 2 16.42 2 12A10 10 0 0112 2Z + + + M10 17l8-8-1.41-1.42L10 14.17 7.41 11.59 6 13l4 4Zm13-5-2.44 2.78.34 3.68-3.61.82-1.89 3.18L12 21 8.6 22.47 6.71 19.29 3.1 18.47l.34-3.69L1 12 3.44 9.21 3.1 5.53l3.61-.81L8.6 1.54 12 3l3.4-1.46 1.89 3.18 3.61.82-.34 3.68L23 12 + + + + M13 13V7H11v6h2Zm0 4V15H11v2h2m10-5-2.44 2.78.34 3.68-3.61.82-1.89 3.18L12 21 8.6 22.47 6.71 19.29 3.1 18.47l.34-3.69L1 12 3.44 9.21 3.1 5.53l3.61-.81L8.6 1.54 12 3l3.4-1.46 1.89 3.18 3.61.82-.34 3.68L23 12 + + + + M5.05 11.94l5-5v3.99H19l-.03 2.01H10.05v4Z + + + \ No newline at end of file diff --git a/WeModPatcher/Style/Styles.xaml b/WeModPatcher/Style/Styles.xaml index 0433452..e132072 100644 --- a/WeModPatcher/Style/Styles.xaml +++ b/WeModPatcher/Style/Styles.xaml @@ -210,9 +210,9 @@ + BorderThickness="0 0 1 0" IsHitTestVisible="False"> diff --git a/WeModPatcher/Utils/Extensions.cs b/WeModPatcher/Utils/Extensions.cs new file mode 100644 index 0000000..89a73af --- /dev/null +++ b/WeModPatcher/Utils/Extensions.cs @@ -0,0 +1,71 @@ +using System; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; + +namespace WeModPatcher.Utils +{ + public static class Extensions + { + public static bool CheckWeModPath(string root) + { + try + { + return File.Exists(Path.Combine(root, "WeMod.exe")) && + File.Exists(Path.Combine(root, "resources", "app.asar")); + } + catch + { + return false; + } + } + + public static string FindWeModDirectory() + { + string localAppDataPath = Environment.GetEnvironmentVariable("LOCALAPPDATA"); + + string defaultDir = Path.Combine(localAppDataPath ?? "", "WeMod"); + + if (!Directory.Exists(defaultDir)) + { + return null; + } + + return FindLatestWeMod(defaultDir); + } + + public static string Base64Decode(string base64EncodedData) + { + var base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData); + return System.Text.Encoding.UTF8.GetString(base64EncodedBytes); + } + + public static string Base64Encode(string plainText) + { + var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText); + return System.Convert.ToBase64String(plainTextBytes); + } + + public static string FindLatestWeMod(string root) + { + var appFolders = Directory.EnumerateDirectories(root) + .Select(folderPath => new DirectoryInfo(folderPath)) + .Where(dirInfo => Regex.IsMatch(dirInfo.Name, @"^app-\w+")) + .Select(dirInfo => new + { + Name = dirInfo.Name, + Path = dirInfo.FullName, + LastModified = dirInfo.LastWriteTime + }) + .OrderByDescending(item => item.LastModified) + .ToList(); + + return ( + from folder + in appFolders + where CheckWeModPath(folder.Path) + select folder.Path + ).FirstOrDefault(); + } + } +} \ No newline at end of file diff --git a/WeModPatcher/Utils/MemoryUtils.cs b/WeModPatcher/Utils/MemoryUtils.cs new file mode 100644 index 0000000..95032c1 --- /dev/null +++ b/WeModPatcher/Utils/MemoryUtils.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using WeModPatcher.Models; +using WeModPatcher.Utils.Win32; + +namespace WeModPatcher.Utils +{ +public class MemoryUtils +{ + public static int ScanMemoryBlock(byte[] buffer, int bufferLength, byte[] pattern, byte[] mask) + { + var patternLength = pattern.Length; + if (bufferLength < patternLength) + { + return -1; + } + + // Make a length of length outside the first cycle for optimization + var searchEnd = bufferLength - patternLength; + + // first pass - use the first non-empty byte of the mask for a quick check + var firstValidIndex = -1; + for (var i = 0; i < patternLength; i++) + { + if (mask[i] == 1) + { + firstValidIndex = i; + break; + } + } + + if (firstValidIndex == -1) + { + return 0; + } + + var firstByte = pattern[firstValidIndex]; + + for (var i = 0; i <= searchEnd; i++) + { + // quick check by the first byte before full comparison + if (buffer[i + firstValidIndex] != firstByte) + continue; + + var found = true; + + // check only those positions where mask = 1 + for (var j = 0; j < patternLength; j++) + { + if (mask[j] == 0 || buffer[i + j] == pattern[j]) + { + continue; + } + + found = false; + break; + } + + if (found) + { + return i; + } + } + + return -1; + } + + public static void ParseSignature(string signatureStr, out byte[] pattern, out byte[] mask) + { + var parts = signatureStr.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); + var length = parts.Length; + + pattern = new byte[length]; + mask = new byte[length]; + + for (var i = 0; i < length; i++) + { + if (parts[i] == "??" || parts[i] == "?") + { + pattern[i] = 0; + // wildcard byte + mask[i] = 0; + continue; + } + + pattern[i] = Convert.ToByte(parts[i], 16); + mask[i] = 1; + } + } + + public static bool SafeWriteVirtualMemory(IntPtr hProcess, IntPtr address, byte[] bytes) + { + if (!Imports.VirtualProtectEx(hProcess, address, (IntPtr)1, 0x40, out uint oldProtect)) + { + return false; + } + + bool result = Imports.WriteProcessMemory(hProcess, address, bytes, bytes.Length, out _); + + // Restore the previous access rights + Imports.VirtualProtectEx(hProcess, address, (IntPtr)1, oldProtect, out _); + return result; + } + + public static IntPtr ScanVirtualMemory(IntPtr hProcess, IntPtr startAddress, int searchSize, byte[] signature, byte[] mask) + { + const int BUFFER_SIZE = 4096; + byte[] buffer = new byte[BUFFER_SIZE]; + + // We can't copy all the crap of the process into a byte array at once. Don't try this + for (long currentAddress = startAddress.ToInt64(); + currentAddress < startAddress.ToInt64() + searchSize; + currentAddress += BUFFER_SIZE - signature.Length) + { + if (!Imports.ReadProcessMemory(hProcess, new IntPtr(currentAddress), buffer, BUFFER_SIZE, out int bytesRead) || bytesRead == 0) + { + // Read error or end of memory, throw mb? + continue; + } + + var i = ScanMemoryBlock(buffer, bytesRead, signature, mask); + if (i != -1) + { + return new IntPtr(currentAddress + i); + } + } + + return IntPtr.Zero; + } + + public static int PatchFile(string filePath, Signature signature, byte[] patchBytes) + { + const int bufferSize = 8192; + var buffer = new byte[bufferSize + signature.Length - 1]; + + using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)) + { + int filePosition = 0; + while (true) + { + int bytesRead = fileStream.Read(buffer, 0, bufferSize); + if (bytesRead == 0) break; + + int matchIndex = ScanMemoryBlock(buffer, bytesRead, signature, signature.Mask); + if (matchIndex != -1) + { + int functionStartPosition = filePosition + matchIndex; + + var checkBuffer = new byte[patchBytes.Length]; + fileStream.Seek(functionStartPosition + signature.Offset, SeekOrigin.Begin); + fileStream.Read(checkBuffer, 0, patchBytes.Length); + + if (checkBuffer.SequenceEqual(patchBytes)) + { + return 0; // Memory already patched + } + + // Go to patch position + fileStream.Seek(functionStartPosition + signature.Offset, SeekOrigin.Begin); + fileStream.Write(patchBytes, 0, patchBytes.Length); + + return functionStartPosition; // Return the address of the function start by signature + } + + filePosition += bytesRead; + Array.Copy(buffer, bufferSize, buffer, 0, signature.Length - 1); + } + } + + return -1; + } +} +} \ No newline at end of file diff --git a/WeModPatcher/Utils/PatternScanner.cs b/WeModPatcher/Utils/PatternScanner.cs deleted file mode 100644 index 05fb04d..0000000 --- a/WeModPatcher/Utils/PatternScanner.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace WeModPatcher.Utils -{ -public class PatternScanner -{ - public static int FindPatternInBuffer(byte[] buffer, int bytesRead, byte[] signature, string mask) - { - int bufferLength = bytesRead + signature.Length - 1; - - for (int i = 0; i <= bytesRead - signature.Length; i++) - { - if (IsMatch(buffer, signature, mask, i)) - return i; - } - - return -1; - } - - private static bool IsMatch(byte[] buffer, byte[] signature, string mask, int offset) - { - for (int i = 0; i < signature.Length; i++) - { - if (mask[i] == 'x' && buffer[offset + i] != signature[i]) - return false; - } - return true; - } - - public static (byte[] signature, string mask) ParseSignature(string signature) - { - var signatureBytes = new List(); - var mask = new StringBuilder(); - - var tokens = signature.Split(' '); - foreach (var token in tokens) - { - if (token == "??" || token == "?") - { - signatureBytes.Add(0); - mask.Append('?'); - } - else - { - signatureBytes.Add(Convert.ToByte(token, 16)); - mask.Append('x'); - } - } - - return (signatureBytes.ToArray(), mask.ToString()); - } - - public static async Task PatchBySignature(string filePath, string functionSignature, byte[] patchBytes, int patchOffset) - { - var (signature, mask) = ParseSignature(functionSignature); - const int bufferSize = 8192; - var buffer = new byte[bufferSize + signature.Length - 1]; - - using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)) - { - int filePosition = 0; - while (true) - { - int bytesRead = await fileStream.ReadAsync(buffer, 0, bufferSize); - if (bytesRead == 0) break; - - int matchIndex = FindPatternInBuffer(buffer, bytesRead, signature, mask); - if (matchIndex != -1) - { - int functionStartPosition = filePosition + matchIndex; - - var checkBuffer = new byte[patchBytes.Length]; - fileStream.Seek(functionStartPosition + patchOffset, SeekOrigin.Begin); - await fileStream.ReadAsync(checkBuffer, 0, patchBytes.Length); - - if (checkBuffer.SequenceEqual(patchBytes)) - { - return 0; // Memory already patched - } - - // Go to patch position - fileStream.Seek(functionStartPosition + patchOffset, SeekOrigin.Begin); - await fileStream.WriteAsync(patchBytes, 0, patchBytes.Length); - - return functionStartPosition; // Return the address of the function start by signature - } - - filePosition += bytesRead; - Array.Copy(buffer, bufferSize, buffer, 0, signature.Length - 1); - } - } - - return -1; - } -} -} \ No newline at end of file diff --git a/WeModPatcher/Utils/Win32/Imports.cs b/WeModPatcher/Utils/Win32/Imports.cs new file mode 100644 index 0000000..1e3de8e --- /dev/null +++ b/WeModPatcher/Utils/Win32/Imports.cs @@ -0,0 +1,258 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace WeModPatcher.Utils.Win32 +{ + public static class Imports + { + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool ReadProcessMemory( + IntPtr hProcess, + IntPtr lpBaseAddress, + [Out] byte[] lpBuffer, + int dwSize, + out int lpNumberOfBytesRead); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool WriteProcessMemory( + IntPtr hProcess, + IntPtr lpBaseAddress, + byte[] lpBuffer, + int nSize, + out IntPtr lpNumberOfBytesWritten); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool VirtualProtectEx( + IntPtr hProcess, + IntPtr lpAddress, + IntPtr dwSize, + uint flNewProtect, + out uint lpflOldProtect); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool WaitForDebugEvent(ref DEBUG_EVENT lpDebugEvent, uint dwMilliseconds); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool CloseHandle(IntPtr hObject); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern uint ResumeThread(IntPtr hThread); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool DebugActiveProcessStop(uint dwProcessId); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool DebugActiveProcess(int dwProcessId); + + [DllImport("psapi.dll", SetLastError = true)] + public static extern bool EnumProcessModules( + IntPtr hProcess, + IntPtr lphModule, + uint cb, + out uint lpcbNeeded); + + [DllImport("psapi.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern int GetModuleFileNameEx( + IntPtr hProcess, + IntPtr hModule, + StringBuilder lpFilename, + int nSize); + + [DllImport("psapi.dll", SetLastError = true)] + public static extern bool GetModuleInformation(IntPtr hProcess, IntPtr hModule, out MODULEINFO lpmodinfo, uint cb); + + + [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)] + public static extern bool CreateProcessA + ( + String lpApplicationName, + String lpCommandLine, + IntPtr lpProcessAttributes, + IntPtr lpThreadAttributes, + Boolean bInheritHandles, + uint dwCreationFlags, + IntPtr lpEnvironment, + String lpCurrentDirectory, + [In] ref StartupInfo lpStartupInfo, + out ProcessInformation lpProcessInformation + ); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool ContinueDebugEvent(uint dwProcessId, uint dwThreadId, uint dwContinueStatus); + + [StructLayout(LayoutKind.Sequential)] + public struct StartupInfo + { + public Int32 cb ; + public IntPtr lpReserved ; + public IntPtr lpDesktop ; + public IntPtr lpTitle ; + public Int32 dwX ; + public Int32 dwY ; + public Int32 dwXSize ; + public Int32 dwYSize ; + public Int32 dwXCountChars ; + public Int32 dwYCountChars ; + public Int32 dwFillAttribute ; + public Int32 dwFlags ; + public Int16 wShowWindow ; + public Int16 cbReserved2 ; + public IntPtr lpReserved2 ; + public IntPtr hStdInput ; + public IntPtr hStdOutput ; + public IntPtr hStdError ; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ProcessInformation + { + public IntPtr hProcess; + public IntPtr hThread; + public Int32 dwProcessId; + public Int32 dwThreadId; + } + + #region Debug event structures + + [StructLayout(LayoutKind.Explicit)] + public struct DEBUG_EVENT + { + [FieldOffset(0)] + public uint dwDebugEventCode; + [FieldOffset(4)] + public uint dwProcessId; + [FieldOffset(8)] + public uint dwThreadId; + [FieldOffset(16)] + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 160)] + public byte[] Union; + } + + [StructLayout(LayoutKind.Sequential, Pack = 8)] + public struct EXCEPTION_DEBUG_INFO + { + public EXCEPTION_RECORD ExceptionRecord; + public uint dwFirstChance; + } + + [StructLayout(LayoutKind.Sequential, Pack = 8)] + public struct EXCEPTION_RECORD + { + public uint ExceptionCode; + public uint ExceptionFlags; + public IntPtr pExceptionRecord; + public IntPtr ExceptionAddress; + public uint NumberParameters; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 15)] + public IntPtr[] ExceptionInformation; + } + + [StructLayout(LayoutKind.Sequential, Pack = 8)] + public struct CREATE_THREAD_DEBUG_INFO + { + public IntPtr hThread; + public IntPtr lpThreadLocalBase; + public IntPtr lpStartAddress; + } + + [StructLayout(LayoutKind.Sequential, Pack = 8)] + public struct CREATE_PROCESS_DEBUG_INFO + { + public IntPtr hFile; + public IntPtr hProcess; + public IntPtr hThread; + public IntPtr lpBaseOfImage; + public uint dwDebugInfoFileOffset; + public uint nDebugInfoSize; + public IntPtr lpThreadLocalBase; + public IntPtr lpStartAddress; + public IntPtr lpImageName; + public ushort fUnicode; + } + + [StructLayout(LayoutKind.Sequential, Pack = 8)] + public struct MODULEINFO + { + public IntPtr lpBaseOfDll; + public uint SizeOfImage; + public IntPtr EntryPoint; + } + + [StructLayout(LayoutKind.Sequential, Pack = 8)] + public struct EXIT_THREAD_DEBUG_INFO + { + public uint dwExitCode; + } + + [StructLayout(LayoutKind.Sequential)] + public struct EXIT_PROCESS_DEBUG_INFO + { + public uint dwExitCode; + } + + [StructLayout(LayoutKind.Sequential)] + public struct LOAD_DLL_DEBUG_INFO + { + public IntPtr hFile; + public IntPtr lpBaseOfDll; + public uint dwDebugInfoFileOffset; + public uint nDebugInfoSize; + public IntPtr lpImageName; + public ushort fUnicode; + } + + [StructLayout(LayoutKind.Sequential)] + public struct UNLOAD_DLL_DEBUG_INFO + { + public IntPtr lpBaseOfDll; + } + + [StructLayout(LayoutKind.Sequential)] + public struct OUTPUT_DEBUG_STRING_INFO + { + public IntPtr lpDebugStringData; + public ushort fUnicode; + public ushort nDebugStringLength; + } + + [StructLayout(LayoutKind.Sequential)] + public struct RIP_INFO + { + public uint dwError; + public uint dwType; + } + + + public static T MapUnmanagedStructure(byte[] debugInfo) + { + GCHandle handle = GCHandle.Alloc(debugInfo, GCHandleType.Pinned); + try + { + return Marshal.PtrToStructure(handle.AddrOfPinnedObject()); + } + finally + { + handle.Free(); + } + } + + #endregion + + // Determining constants for debugging + public const uint INFINITE = 0xFFFFFFFF; + public const uint DEBUG_PROCESS = 0x00000001; + public const uint DBG_CONTINUE = 0x00010002; + public const uint CREATE_PROCESS_DEBUG_EVENT = 3; + public const uint EXCEPTION_DEBUG_EVENT = 1; + public const uint LOAD_DLL_DEBUG_EVENT = 6; + public const uint OUTPUT_DEBUG_STRING_EVENT = 8; + public const uint EXCEPTION_BREAKPOINT = 0x80000003; + + // Constants for VirtualProtectex + public const uint PAGE_EXECUTE_READWRITE = 0x40; + } +} \ No newline at end of file diff --git a/WeModPatcher/Utils/Win32/Shortcut.cs b/WeModPatcher/Utils/Win32/Shortcut.cs new file mode 100644 index 0000000..bcb271c --- /dev/null +++ b/WeModPatcher/Utils/Win32/Shortcut.cs @@ -0,0 +1,61 @@ +using System; +using System.Runtime.InteropServices; + +namespace WeModPatcher.Utils.Win32 +{ + public class Shortcut + { + public class ShortcutParams + { + public string FileName { get; set; } + public string TargetPath { get; set; } + public string Arguments { get; set; } + public string WorkingDirectory { get; set; } + public string Description { get; set; } + public string Hotkey { get; set; } + public string IconPath { get; set; } + }; + + private static readonly Type m_type = Type.GetTypeFromProgID("WScript.Shell"); + private static readonly object m_shell = Activator.CreateInstance(m_type); + + [ComImport, TypeLibType(0x1040), Guid("F935DC23-1CF0-11D0-ADB9-00C04FD58A0B")] + private interface IWshShortcut + { + [DispId(0)] + string FullName { [return: MarshalAs(UnmanagedType.BStr)][DispId(0)] get; } + [DispId(0x3e8)] + string Arguments { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3e8)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3e8)] set; } + [DispId(0x3e9)] + string Description { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3e9)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3e9)] set; } + [DispId(0x3ea)] + string Hotkey { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3ea)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3ea)] set; } + [DispId(0x3eb)] + string IconLocation { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3eb)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3eb)] set; } + [DispId(0x3ec)] + string RelativePath { [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3ec)] set; } + [DispId(0x3ed)] + string TargetPath { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3ed)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3ed)] set; } + [DispId(0x3ee)] + int WindowStyle { [DispId(0x3ee)] get; [param: In][DispId(0x3ee)] set; } + [DispId(0x3ef)] + string WorkingDirectory { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3ef)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3ef)] set; } + [TypeLibFunc((short)0x40), DispId(0x7d0)] + void Load([In, MarshalAs(UnmanagedType.BStr)] string PathLink); + [DispId(0x7d1)] + void Save(); + } + + public static void CreateShortcut(string fileName, string targetPath, string arguments, string workingDirectory, string description, string iconPath) + { + IWshShortcut shortcut = (IWshShortcut)m_type.InvokeMember("CreateShortcut", System.Reflection.BindingFlags.InvokeMethod, null, m_shell, new object[] { fileName }); + shortcut.Description = description; + shortcut.TargetPath = targetPath; + shortcut.WorkingDirectory = workingDirectory; + shortcut.Arguments = arguments; + if (!string.IsNullOrEmpty(iconPath)) + shortcut.IconLocation = iconPath; + shortcut.Save(); + } + } +} \ No newline at end of file diff --git a/WeModPatcher/View/Controls/InfoItem.xaml b/WeModPatcher/View/Controls/InfoItem.xaml new file mode 100644 index 0000000..0fd1729 --- /dev/null +++ b/WeModPatcher/View/Controls/InfoItem.xaml @@ -0,0 +1,20 @@ + + + + + + + + + + + + diff --git a/WeModPatcher/View/Controls/InfoItem.xaml.cs b/WeModPatcher/View/Controls/InfoItem.xaml.cs new file mode 100644 index 0000000..dda3017 --- /dev/null +++ b/WeModPatcher/View/Controls/InfoItem.xaml.cs @@ -0,0 +1,42 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace WeModPatcher.View.Controls +{ + public partial class InfoItem : UserControl + { + public static readonly DependencyProperty IconDataProperty = + DependencyProperty.Register(nameof(IconData), typeof(Geometry), typeof(InfoItem)); + + public static readonly DependencyProperty IconColorProperty = + DependencyProperty.Register(nameof(IconColor), typeof(Brush), typeof(InfoItem)); + + public static readonly DependencyProperty TextProperty = + DependencyProperty.Register(nameof(Text), typeof(string), typeof(InfoItem)); + + public Geometry IconData + { + get => (Geometry)GetValue(IconDataProperty); + set => SetValue(IconDataProperty, value); + } + + public Brush IconColor + { + get => (Brush)GetValue(IconColorProperty); + set => SetValue(IconColorProperty, value); + } + + public string Text + { + get => (string)GetValue(TextProperty); + set => SetValue(TextProperty, value); + } + + public InfoItem() + { + InitializeComponent(); + this.DataContext = this; + } + } +} \ No newline at end of file diff --git a/WeModPatcher/View/Controls/PopupHost.xaml b/WeModPatcher/View/Controls/PopupHost.xaml index d4d8906..837223f 100644 --- a/WeModPatcher/View/Controls/PopupHost.xaml +++ b/WeModPatcher/View/Controls/PopupHost.xaml @@ -37,9 +37,11 @@ - + + + diff --git a/WeModPatcher/View/Controls/PopupHost.xaml.cs b/WeModPatcher/View/Controls/PopupHost.xaml.cs index 372ffad..cd1b09f 100644 --- a/WeModPatcher/View/Controls/PopupHost.xaml.cs +++ b/WeModPatcher/View/Controls/PopupHost.xaml.cs @@ -65,6 +65,10 @@ namespace WeModPatcher.View.Controls Visibility = Visibility.Collapsed; Closed?.Invoke(); + if (PopupContent is IDisposable disposable) + { + disposable.Dispose(); + } PopupContent = null; Closed = null; OpenedSemaphore.Release(); diff --git a/WeModPatcher/View/MainWindow/MainWindow.xaml b/WeModPatcher/View/MainWindow/MainWindow.xaml index 15f95dc..15cf95e 100644 --- a/WeModPatcher/View/MainWindow/MainWindow.xaml +++ b/WeModPatcher/View/MainWindow/MainWindow.xaml @@ -88,14 +88,14 @@ - - - + + + diff --git a/WeModPatcher/View/MainWindow/MainWindow.xaml.cs b/WeModPatcher/View/MainWindow/MainWindow.xaml.cs index 7d93096..37beae4 100644 --- a/WeModPatcher/View/MainWindow/MainWindow.xaml.cs +++ b/WeModPatcher/View/MainWindow/MainWindow.xaml.cs @@ -11,16 +11,19 @@ namespace WeModPatcher.View.MainWindow public partial class MainWindow { public static MainWindow Instance; + public readonly MainWindowVm ViewModel; public MainWindow() { InitializeComponent(); - this.DataContext = new MainWindowVm(this); + this.ViewModel = new MainWindowVm(this); + this.DataContext = ViewModel; VersionLabel.Text = Constants.Version.ToString(); Instance = this; + } - public void OpenPopup(object content, string title = null) + public void OpenPopup(FrameworkElement content, string title = null) { this.PopupHost.PopupContent = content; PopupHost.Title.Text = title; diff --git a/WeModPatcher/View/MainWindow/MainWindowVm.cs b/WeModPatcher/View/MainWindow/MainWindowVm.cs index 890f412..f4b14da 100644 --- a/WeModPatcher/View/MainWindow/MainWindowVm.cs +++ b/WeModPatcher/View/MainWindow/MainWindowVm.cs @@ -2,12 +2,15 @@ using System.Collections.ObjectModel; using System.IO; using System.Linq; +using System.Security.Cryptography.X509Certificates; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows.Forms; using System.Windows.Threading; using AsarSharp; -using WeModPatcher.ReactiveCore; +using WeModPatcher.Core; +using WeModPatcher.Models; +using WeModPatcher.ReactiveUICore; using WeModPatcher.Utils; using WeModPatcher.View.Popups; using Application = System.Windows.Application; @@ -18,7 +21,7 @@ namespace WeModPatcher.View.MainWindow public class MainWindowVm : ObservableObject { private readonly MainWindow _view; - public ObservableCollection LogList { get; } = new ObservableCollection(); + public ObservableCollection LogList { get; set; } = new ObservableCollection(); private static Updater _updater = new Updater(); private string _weModPath; @@ -71,50 +74,6 @@ namespace WeModPatcher.View.MainWindow public RelayCommand RestoreBackupCommand { get; } public AsyncRelayCommand UpdateCommand { get; } - private static bool CheckWeModPath(string root) - { - try - { - return File.Exists(Path.Combine(root, "WeMod.exe")) && - File.Exists(Path.Combine(root, "resources", "app.asar")); - } - catch - { - return false; - } - } - - public string FindWeModDirectory() - { - string localAppDataPath = Environment.GetEnvironmentVariable("LOCALAPPDATA"); - - string defaultDir = Path.Combine(localAppDataPath ?? "", "WeMod"); - - if (!Directory.Exists(defaultDir)) - { - return null; - } - - var appFolders = Directory.EnumerateDirectories(defaultDir) - .Select(folderPath => new DirectoryInfo(folderPath)) - .Where(dirInfo => Regex.IsMatch(dirInfo.Name, @"^app-\w+")) - .Select(dirInfo => new - { - Name = dirInfo.Name, - Path = dirInfo.FullName, - LastModified = dirInfo.LastWriteTime - }) - .OrderByDescending(item => item.LastModified) - .ToList(); - - return ( - from folder - in appFolders - where CheckWeModPath(folder.Path) - select folder.Path - ).FirstOrDefault(); - } - private void OnFolderPathSelection(object obj) { using (var dialog = new FolderBrowserDialog()) @@ -127,7 +86,7 @@ namespace WeModPatcher.View.MainWindow string selectedPath = dialog.SelectedPath; string fileName = Path.GetFileName(selectedPath); - if (CheckWeModPath(selectedPath)) + if (Extensions.CheckWeModPath(selectedPath)) { WeModPath = selectedPath; return; @@ -143,6 +102,7 @@ namespace WeModPatcher.View.MainWindow private void OnBackupRestoring(object param) { + var backupPath = Path.Combine(WeModPath, "resources", "app.asar.backup"); if (!File.Exists(backupPath)) { @@ -155,6 +115,22 @@ namespace WeModPatcher.View.MainWindow using (File.Open(backupPath, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) { } + + // This shit doesn't look at the hash and verify() always returns true + //using X509Certificate2 cert = new X509Certificate2(X509Certificate.CreateFromSignedFile(filePath)); + + var restoreExeResult = MemoryUtils.PatchFile( Path.Combine( WeModPath, "WeMod.exe"), + Constants.ExePatchSignature, Constants.ExePatchSignature.OriginalBytes); + if (restoreExeResult == -1) + { + Log("Failed to restore the backup. Please close the WeMod and try again.", ELogType.Error); + } + else + { + Log(restoreExeResult == 0 ? + "Signature exe is original, does not require restoration" + : "WeMod.exe restored successfully", ELogType.Success); + } } catch { @@ -177,12 +153,24 @@ namespace WeModPatcher.View.MainWindow return; } - MainWindow.Instance.OpenPopup(new PatchVectorsPopup(async config => + MainWindow.Instance.OpenPopup(new PatchVectorsPopup( async config => { MainWindow.Instance.ClosePopup(); IsPatchEnabled = false; - await Task.Run(() => new Patcher(WeModPath, Log, config).Patch()); - IsPatchEnabled = true; + await Task.Run(() => + { + try + { + new StaticPatcher(WeModPath, Log, config).Patch(); + AlreadyPatched = true; + } + catch (Exception e) + { + Log($"Failed to patch: {e.Message}", ELogType.Error); + IsPatchEnabled = true; + } + }); + }), "What are we gonna patch?"); } @@ -229,7 +217,7 @@ namespace WeModPatcher.View.MainWindow RestoreBackupCommand = new RelayCommand(OnBackupRestoring); UpdateCommand = new AsyncRelayCommand(OnUpdate); - WeModPath = FindWeModDirectory(); + WeModPath = Extensions.FindWeModDirectory(); if (WeModPath == null) { Log("WeMod directory not found.", ELogType.Error); diff --git a/WeModPatcher/View/Popups/PatchVectorsPopup.xaml b/WeModPatcher/View/Popups/PatchVectorsPopup.xaml index dccee50..fad7c56 100644 --- a/WeModPatcher/View/Popups/PatchVectorsPopup.xaml +++ b/WeModPatcher/View/Popups/PatchVectorsPopup.xaml @@ -4,28 +4,123 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:WeModPatcher.View.Popups" + xmlns:controls="clr-namespace:WeModPatcher.View.Controls" mc:Ignorable="d" - d:DesignHeight="300" d:DesignWidth="300" + d:DesignHeight="Auto" d:DesignWidth="Auto" + Background="{DynamicResource Background}" Foreground="{DynamicResource MutedForeground}" FontWeight="Medium" FontSize="13"> - - - - - - - - - + +