diff --git a/WeModPatcher/Constants.cs b/WeModPatcher/Constants.cs index 8e1b2fc..45d1553 100644 --- a/WeModPatcher/Constants.cs +++ b/WeModPatcher/Constants.cs @@ -8,11 +8,10 @@ namespace WeModPatcher { public const string RepoName = "Wemod-Patcher"; public const string Owner = "k1tbyte"; + /*public const string PatchRegistryName = "patchRegistry.json";*/ public static readonly string RepositoryUrl = $"https://github.com/{Owner}/{RepoName}"; public static readonly Version Version; - public static readonly string WeModBrandName = "Wand"; - public static readonly string WeModExeName = "Wand.exe"; - public static readonly string[] WeModRootFolders = { "WeMod", "Wand" }; + public static readonly string[] WeModBrandNames = { "Wand", "WeMod" }; // cmp dword ptr [rdx], 0 // jnz loc_XXXXXXXX diff --git a/WeModPatcher/Core/RuntimePatcher.cs b/WeModPatcher/Core/RuntimePatcher.cs index 6c892c7..29067f5 100644 --- a/WeModPatcher/Core/RuntimePatcher.cs +++ b/WeModPatcher/Core/RuntimePatcher.cs @@ -3,8 +3,6 @@ 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; @@ -15,24 +13,24 @@ namespace WeModPatcher.Core public class RuntimePatcher { - private readonly string _exePath; + private readonly WeModConfig _config; - public RuntimePatcher(string exePath) + public RuntimePatcher(WeModConfig config) { - _exePath = exePath; + _config = config; } public void StartProcess() { - if(string.IsNullOrEmpty(_exePath)) + if(string.IsNullOrEmpty(_config?.ExecutablePath)) { throw new Exception("Path is not specified"); } - KillWeMod(); + Common.TryKillProcess(_config.BrandName); var startupInfo = new Imports.StartupInfo { cb = Marshal.SizeOf(typeof(Imports.StartupInfo)) }; - if(!Imports.CreateProcessA(_exePath, + if(!Imports.CreateProcessA(_config.ExecutablePath, null, IntPtr.Zero, IntPtr.Zero, @@ -118,56 +116,29 @@ namespace WeModPatcher.Core public static void Patch(PatchConfig config, Action logger) { - if (config.Path == null) + if (config.AppProps == null) { throw new Exception("Path is not specified"); } - var parent = Directory.GetParent(config.Path)?.FullName ?? config.Path; - var latestPath = Extensions.FindLatestWeMod(parent) ?? config.Path; + var parent = Directory.GetParent(config.AppProps.RootDirectory)?.FullName ?? config.AppProps.RootDirectory; + var latestWeModConfig = config.AutoApplyPatches ? Extensions.FindLatestWeMod(parent) ?? config.AppProps : config.AppProps; - if (!Extensions.CheckWeModPath(latestPath)) + if (Extensions.CheckWeModPath(latestWeModConfig.RootDirectory) == null) { throw new Exception("Invalid WeMod path"); } - if(!File.Exists(Path.Combine(latestPath, "resources", "app.asar.backup"))) + if(!File.Exists(Path.Combine(latestWeModConfig.RootDirectory, "resources", "app.asar.backup"))) { config.PatchMethod = EPatchProcessMethod.None; - new StaticPatcher(latestPath, logger, config).Patch(); + new StaticPatcher(latestWeModConfig, logger, config).Patch(); } - new RuntimePatcher(Path.Combine(latestPath, Constants.WeModExeName)) + new RuntimePatcher(latestWeModConfig) .StartProcess(); } - - public static void KillWeMod() - { - Process[] processes = Process.GetProcessesByName(Constants.WeModBrandName); - for (int i = 0; processes.Length > i || i < 5; i++) - { - foreach (var process in processes) - { - try - { - process.Kill(); - } - catch - { - // ignored - } - } - - processes = Process.GetProcessesByName(Constants.WeModBrandName); - Thread.Sleep(250); - } - - if (processes.Length > 0) - { - throw new Exception("Failed to kill WeMod"); - } - } } } \ No newline at end of file diff --git a/WeModPatcher/Core/StaticPatcher.cs b/WeModPatcher/Core/StaticPatcher.cs index 1fdd09a..40b862d 100644 --- a/WeModPatcher/Core/StaticPatcher.cs +++ b/WeModPatcher/Core/StaticPatcher.cs @@ -46,25 +46,23 @@ namespace WeModPatcher.Core } }; - private readonly string _weModRootFolder; + private readonly WeModConfig _weModConfig; private readonly Action _logger; 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 StaticPatcher(string weModRootFolder, Action logger, PatchConfig config) + public StaticPatcher(WeModConfig weModConfig, Action logger, PatchConfig config) { - _weModRootFolder = weModRootFolder; + _weModConfig = weModConfig; _logger = logger; _config = config; - _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, Constants.WeModExeName); + _asarPath = Path.Combine(weModConfig.RootDirectory, "resources", "app.asar"); + _unpackedPath = Path.Combine(weModConfig.RootDirectory, "resources", "app.asar.unpacked"); + _backupPath = Path.Combine(weModConfig.RootDirectory, "resources", "app.asar.backup"); } private static string GetFetchFieldName(string targetFunction) @@ -143,7 +141,11 @@ namespace WeModPatcher.Core private void PatchPe() { _logger("[PATCHER] Patching PE...", ELogType.Info); - var patchResult = MemoryUtils.PatchFile(_exePath,Constants.ExePatchSignature, Constants.ExePatchSignature.PatchBytes); + var patchResult = MemoryUtils.PatchFile( + _weModConfig.ExecutablePath, + Constants.ExePatchSignature, + Constants.ExePatchSignature.PatchBytes + ); if(patchResult == -1) { _logger("[PATCHER] Failed to patch PE", ELogType.Error); @@ -161,24 +163,29 @@ namespace WeModPatcher.Core CheckPathExists = true, AddExtension = true, SupportMultiDottedExtensions = false, - FileName = Constants.WeModBrandName, + FileName = _weModConfig.BrandName, }; if(fileDialog.ShowDialog() != DialogResult.OK) { return; } - - _config.Path = _weModRootFolder; - - var json = JsonConvert.SerializeObject(_config, Formatting.None); + + _config.Path = _weModConfig.RootDirectory; + var json = JsonConvert.SerializeObject(_config, new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore, + DefaultValueHandling = DefaultValueHandling.Ignore, + Formatting = Formatting.None + }); + Utils.Win32.Shortcut.CreateShortcut( fileName: fileDialog.FileName + ".lnk", targetPath: Assembly.GetExecutingAssembly().Location, arguments: Extensions.Base64Encode(json), - workingDirectory: Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), + workingDirectory: Common.GetCurrentDir(), description: null, - iconPath: _exePath + iconPath: _weModConfig.ExecutablePath ); _logger("[PATCHER] The shortcut has been created, now you should only run WeMod through this shortcut", ELogType.Success); @@ -186,7 +193,7 @@ namespace WeModPatcher.Core public void Patch() { - RuntimePatcher.KillWeMod(); + Common.TryKillProcess(_weModConfig.BrandName); if (!File.Exists(_backupPath)) { _logger("[PATCHER] Creating backup...", ELogType.Info); diff --git a/WeModPatcher/Models/PatchConfig.cs b/WeModPatcher/Models/PatchConfig.cs index 179c0cf..dff4d8f 100644 --- a/WeModPatcher/Models/PatchConfig.cs +++ b/WeModPatcher/Models/PatchConfig.cs @@ -1,4 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.IO; +using Newtonsoft.Json; +using WeModPatcher.Utils; namespace WeModPatcher.Models { @@ -16,11 +20,106 @@ namespace WeModPatcher.Models Runtime = 1, Static = 2 } - - public sealed class PatchConfig + + /*public sealed class PatchConfigOld { public HashSet PatchTypes { get; set; } public EPatchProcessMethod PatchMethod { get; set; } public string Path { get; set; } + }*/ + + public sealed class PatchConfig + { + private string _path; + public HashSet PatchTypes { get; set; } + public EPatchProcessMethod PatchMethod { get; set; } + + [JsonProperty("u")] + public bool AutoApplyPatches { get; set; } + + [JsonIgnore] + public WeModConfig AppProps { get; private set; } + + public string Path + { + get => _path; + set + { + _path = value; + AppProps = Extensions.CheckWeModPath(_path) ?? throw new Exception("Invalid WeMod path"); + } + } + + /*public static void PushConfig(PatchConfig config) + { + var hash = GetConfigHash(config); + var registry = _getRegistry(); + registry[hash] = config; + _stashRegistry(registry); + } + + public static void ActualizeRegistry() + { + var registry = _getRegistry(); + foreach (var entry in registry) + { + if(Extensions.CheckWeModPath(entry.Value.AppProps.RootDirectory) == null) + { + registry.Remove(entry.Key); + break; + } + } + _stashRegistry(registry); + } + + public static PatchConfig GetConfig(string hash) + { + var registry = _getRegistry(); + return registry.TryGetValue(hash, out var config) ? config : null; + } + + public static string GetConfigHash(PatchConfig config) + { + return Common.ComputeSha256Hash(config.AppProps.ExecutablePath); + } + + private static Dictionary _getRegistry() + { + var currentDir = Common.GetCurrentDir(); + var registryPath = Path.Combine(currentDir, Constants.PatchRegistryName); + try + { + return JsonConvert.DeserializeObject>( + File.ReadAllText(registryPath) + ); + } + catch + { + // ignored + } + + return new Dictionary(); + } + + private static void _stashRegistry(Dictionary registry) + { + + var currentDir = Common.GetCurrentDir(); + var registryPath = Path.Combine(currentDir, Constants.PatchRegistryName); + + if(registry.Count == 0) + { + if(File.Exists(registryPath)) + { + File.Delete(registryPath); + } + return; + } + + + var json = JsonConvert.SerializeObject(registry, Formatting.Indented); + File.WriteAllText(registryPath, json); + }*/ } + } \ No newline at end of file diff --git a/WeModPatcher/Models/WeModConfig.cs b/WeModPatcher/Models/WeModConfig.cs new file mode 100644 index 0000000..a124833 --- /dev/null +++ b/WeModPatcher/Models/WeModConfig.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace WeModPatcher.Models +{ + public class WeModConfig + { + public string BrandName { get; set; } + public string ExecutableName { get; set; } + public string RootDirectory { get; set; } + + [JsonIgnore] + public string ExecutablePath => System.IO.Path.Combine(RootDirectory, ExecutableName); + + public override string ToString() + { + return RootDirectory; + } + } +} \ No newline at end of file diff --git a/WeModPatcher/Program.cs b/WeModPatcher/Program.cs index 5880351..40277d9 100644 --- a/WeModPatcher/Program.cs +++ b/WeModPatcher/Program.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; +using System.IO; +using System.Reflection; using System.Threading.Tasks; using System.Windows.Forms; using Newtonsoft.Json; diff --git a/WeModPatcher/Properties/AssemblyInfo.cs b/WeModPatcher/Properties/AssemblyInfo.cs index ff97d8d..eef507e 100644 --- a/WeModPatcher/Properties/AssemblyInfo.cs +++ b/WeModPatcher/Properties/AssemblyInfo.cs @@ -51,5 +51,5 @@ using System.Windows; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.3.0")] -[assembly: AssemblyFileVersion("1.0.3.0")] \ No newline at end of file +[assembly: AssemblyVersion("1.0.4.0")] +[assembly: AssemblyFileVersion("1.0.4.0")] \ No newline at end of file diff --git a/WeModPatcher/Utils/Common.cs b/WeModPatcher/Utils/Common.cs new file mode 100644 index 0000000..4c97724 --- /dev/null +++ b/WeModPatcher/Utils/Common.cs @@ -0,0 +1,54 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Threading; + +namespace WeModPatcher.Utils +{ + public static class Common + { + public static void TryKillProcess(string processName) + { + Process[] processes = Process.GetProcessesByName(processName); + for (int i = 0; processes.Length > i || i < 5; i++) + { + foreach (var process in processes) + { + try + { + process.Kill(); + } + catch + { + // ignored + } + } + + processes = Process.GetProcessesByName(processName); + Thread.Sleep(250); + } + + if (processes.Length > 0) + { + throw new Exception("Failed to kill WeMod"); + } + } + + public static string GetCurrentDir() + { + var assemblyLocation = Assembly.GetExecutingAssembly().Location; + return Path.GetDirectoryName(assemblyLocation) ?? throw new InvalidOperationException(); + } + + public static string ComputeSha256Hash(string input) + { + using (var sha256 = System.Security.Cryptography.SHA256.Create()) + { + var bytes = System.Text.Encoding.UTF8.GetBytes(input); + var hashBytes = sha256.ComputeHash(bytes); + return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); + } + } + } +} \ No newline at end of file diff --git a/WeModPatcher/Utils/Extensions.cs b/WeModPatcher/Utils/Extensions.cs index b64f81e..e0f9628 100644 --- a/WeModPatcher/Utils/Extensions.cs +++ b/WeModPatcher/Utils/Extensions.cs @@ -2,29 +2,45 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; +using WeModPatcher.Models; namespace WeModPatcher.Utils { public static class Extensions { - public static bool CheckWeModPath(string root) + public static WeModConfig CheckWeModPath(string versionRoot) { try { - return File.Exists(Path.Combine(root, Constants.WeModExeName)) && - File.Exists(Path.Combine(root, "resources", "app.asar")); + + foreach (var name in Constants.WeModBrandNames) + { + var exeName = $"{name}.exe"; + var path = Path.Combine(versionRoot, exeName); + if (File.Exists(path) && File.Exists(Path.Combine(versionRoot, "resources", "app.asar"))) + { + return new WeModConfig + { + BrandName = name, + ExecutableName = exeName, + RootDirectory = versionRoot + }; + } + } } catch { - return false; + // ignored } + + return null; } - public static string FindWeModDirectory() + public static WeModConfig FindWeMod() { string localAppDataPath = Environment.GetEnvironmentVariable("LOCALAPPDATA"); - foreach (var folder in Constants.WeModRootFolders) + foreach (var folder in Constants.WeModBrandNames) { var weModDir = Path.Combine(localAppDataPath ?? "", folder); if(Directory.Exists(weModDir)) @@ -48,7 +64,7 @@ namespace WeModPatcher.Utils return System.Convert.ToBase64String(plainTextBytes); } - public static string FindLatestWeMod(string root) + public static WeModConfig FindLatestWeMod(string root) { var appFolders = Directory.EnumerateDirectories(root) .Select(folderPath => new DirectoryInfo(folderPath)) @@ -61,13 +77,11 @@ namespace WeModPatcher.Utils }) .OrderByDescending(item => item.LastModified) .ToList(); + - return ( - from folder - in appFolders - where CheckWeModPath(folder.Path) - select folder.Path - ).FirstOrDefault(); + return appFolders + .Select(folder => CheckWeModPath(folder.Path)) + .FirstOrDefault(config => config != null); } } } \ No newline at end of file diff --git a/WeModPatcher/View/MainWindow/MainWindow.xaml b/WeModPatcher/View/MainWindow/MainWindow.xaml index 15cf95e..d8b5c7f 100644 --- a/WeModPatcher/View/MainWindow/MainWindow.xaml +++ b/WeModPatcher/View/MainWindow/MainWindow.xaml @@ -90,7 +90,7 @@ diff --git a/WeModPatcher/View/MainWindow/MainWindowVm.cs b/WeModPatcher/View/MainWindow/MainWindowVm.cs index ee3b25a..cfe55f9 100644 --- a/WeModPatcher/View/MainWindow/MainWindowVm.cs +++ b/WeModPatcher/View/MainWindow/MainWindowVm.cs @@ -1,13 +1,8 @@ using System; 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.Core; using WeModPatcher.Models; using WeModPatcher.ReactiveUICore; @@ -24,18 +19,18 @@ namespace WeModPatcher.View.MainWindow public ObservableCollection LogList { get; set; } = new ObservableCollection(); private static Updater _updater = new Updater(); - private string _weModPath; + private WeModConfig _weModConfig; - public string WeModPath + public WeModConfig WeModInfo { - get => _weModPath; + get => _weModConfig; set { - SetProperty(ref _weModPath, value); + SetProperty(ref _weModConfig, value); if (value == null) return; - Log($"WeMod directory found at '{_weModPath}'", ELogType.Success); - if (File.Exists(Path.Combine(_weModPath, "resources", "app.asar.backup"))) + Log($"WeMod directory found at '{_weModConfig}' ({_weModConfig.ExecutableName})", ELogType.Success); + if (File.Exists(Path.Combine(_weModConfig.RootDirectory, "resources", "app.asar.backup"))) { Log("WeMod already patched. If you want to patch again, please restore the backup first.", ELogType.Warn); IsPatchEnabled = false; @@ -85,10 +80,12 @@ namespace WeModPatcher.View.MainWindow if (dialog.ShowDialog() != DialogResult.OK) return; string selectedPath = dialog.SelectedPath; string fileName = Path.GetFileName(selectedPath); + + var info = Extensions.CheckWeModPath(selectedPath); - if (Extensions.CheckWeModPath(selectedPath)) + if (info != null) { - WeModPath = selectedPath; + WeModInfo = info; return; } @@ -103,7 +100,7 @@ namespace WeModPatcher.View.MainWindow private void OnBackupRestoring(object param) { - var backupPath = Path.Combine(WeModPath, "resources", "app.asar.backup"); + var backupPath = Path.Combine(WeModInfo.RootDirectory, "resources", "app.asar.backup"); if (!File.Exists(backupPath)) { Log("Backup not found. Please dont delete it manually", ELogType.Error); @@ -119,8 +116,11 @@ namespace WeModPatcher.View.MainWindow // 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, Constants.WeModExeName), - Constants.ExePatchSignature, Constants.ExePatchSignature.OriginalBytes); + var restoreExeResult = MemoryUtils.PatchFile( + WeModInfo.ExecutablePath, + Constants.ExePatchSignature, + Constants.ExePatchSignature.OriginalBytes + ); if (restoreExeResult == -1) { Log("Failed to restore the backup. Please close the WeMod and try again.", ELogType.Error); @@ -129,7 +129,7 @@ namespace WeModPatcher.View.MainWindow { Log(restoreExeResult == 0 ? "Signature exe is original, does not require restoration" - : $"{Constants.WeModExeName} restored successfully", ELogType.Success); + : $"{WeModInfo.ExecutableName} restored successfully", ELogType.Success); } } catch @@ -138,7 +138,7 @@ namespace WeModPatcher.View.MainWindow return; } - File.Copy(backupPath, Path.Combine(WeModPath, "resources", "app.asar"), true); + File.Copy(backupPath, Path.Combine(WeModInfo.RootDirectory, "resources", "app.asar"), true); File.Delete(backupPath); Log("Backup restored successfully.", ELogType.Success); AlreadyPatched = false; @@ -147,7 +147,7 @@ namespace WeModPatcher.View.MainWindow private void OnPatching(object param) { - if (WeModPath == null) + if (WeModInfo == null) { Log("Can't be done. Please specify the directory first.", ELogType.Warn); return; @@ -161,7 +161,7 @@ namespace WeModPatcher.View.MainWindow { try { - new StaticPatcher(WeModPath, Log, config).Patch(); + new StaticPatcher(WeModInfo, Log, config).Patch(); AlreadyPatched = true; } catch (Exception e) @@ -217,8 +217,8 @@ namespace WeModPatcher.View.MainWindow RestoreBackupCommand = new RelayCommand(OnBackupRestoring); UpdateCommand = new AsyncRelayCommand(OnUpdate); - WeModPath = Extensions.FindWeModDirectory(); - if (WeModPath == null) + WeModInfo = Extensions.FindWeMod(); + if (WeModInfo == null) { Log("WeMod directory not found.", ELogType.Error); } diff --git a/WeModPatcher/View/Popups/PatchVectorsPopup.xaml b/WeModPatcher/View/Popups/PatchVectorsPopup.xaml index fad7c56..e045424 100644 --- a/WeModPatcher/View/Popups/PatchVectorsPopup.xaml +++ b/WeModPatcher/View/Popups/PatchVectorsPopup.xaml @@ -23,6 +23,7 @@ + @@ -34,8 +35,15 @@ + + + Apply the patch to new versions automatically (hover to see more) + + -