bump version to 1.0.4.0, add WeModConfig model for backward compatibility, add feature to enable/disable automatic patch updates

This commit is contained in:
kitbyte
2025-11-06 00:48:35 +02:00
parent 8f14ee2939
commit 54fe538ed9
14 changed files with 281 additions and 105 deletions
+2 -3
View File
@@ -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
+13 -42
View File
@@ -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<string, ELogType> 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");
}
}
}
}
+24 -17
View File
@@ -46,25 +46,23 @@ namespace WeModPatcher.Core
}
};
private readonly string _weModRootFolder;
private readonly WeModConfig _weModConfig;
private readonly Action<string, ELogType> _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<string, ELogType> logger, PatchConfig config)
public StaticPatcher(WeModConfig weModConfig, Action<string, ELogType> 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);
+102 -3
View File
@@ -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<EPatchType> PatchTypes { get; set; }
public EPatchProcessMethod PatchMethod { get; set; }
public string Path { get; set; }
}*/
public sealed class PatchConfig
{
private string _path;
public HashSet<EPatchType> 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<string, PatchConfig> _getRegistry()
{
var currentDir = Common.GetCurrentDir();
var registryPath = Path.Combine(currentDir, Constants.PatchRegistryName);
try
{
return JsonConvert.DeserializeObject<Dictionary<string, PatchConfig>>(
File.ReadAllText(registryPath)
);
}
catch
{
// ignored
}
return new Dictionary<string, PatchConfig>();
}
private static void _stashRegistry(Dictionary<string, PatchConfig> 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);
}*/
}
}
+19
View File
@@ -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;
}
}
}
+2
View File
@@ -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;
+2 -2
View File
@@ -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")]
[assembly: AssemblyVersion("1.0.4.0")]
[assembly: AssemblyFileVersion("1.0.4.0")]
+54
View File
@@ -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();
}
}
}
}
+27 -13
View File
@@ -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);
}
}
}
+1 -1
View File
@@ -90,7 +90,7 @@
<Grid Margin="10" Cursor="Hand" Background="Transparent">
<TextBox Style="{StaticResource TitledTextBox}"
Uid="Folder path" IsReadOnly="True"
Text="{Binding WeModPath}"
Text="{Binding WeModInfo.RootDirectory, Mode=OneWay}"
VerticalAlignment="Center" Tag="Folder not found">
</TextBox>
<Grid.InputBindings>
+22 -22
View File
@@ -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<LogEntry> LogList { get; set; } = new ObservableCollection<LogEntry>();
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);
}
@@ -23,6 +23,7 @@
<RowDefinition Height="27"/>
<RowDefinition Height="27"/>
<RowDefinition Height="27"/>
<RowDefinition Height="37"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
@@ -34,8 +35,15 @@
<TextBlock Grid.Row="2" VerticalAlignment="Center" Text="Disable updates"/>
<CheckBox Grid.Row="2" x:Name="DisableUpdateBox" HorizontalAlignment="Right" VerticalAlignment="Center"/>
<TextBlock ToolTip="Disable if you want to use older versions separately and manage versions manually via different shortcuts"
ToolTipService.InitialShowDelay="300"
Grid.Row="3" VerticalAlignment="Center">
Apply the patch to new versions <LineBreak/> automatically (hover to see more)
</TextBlock>
<CheckBox Grid.Row="3" x:Name="AutoUpdates" HorizontalAlignment="Right" VerticalAlignment="Center" IsChecked="True"/>
<Button Grid.Row="3" Padding="0 5 0 5" Margin="0 15 0 0" Content="Continue"
<Button Grid.Row="4" Padding="0 5 0 5" Margin="0 15 0 0" Content="Continue"
Click="NextClicked"/>
</Grid>
@@ -57,7 +57,8 @@ namespace WeModPatcher.View.Popups
_onApply(new PatchConfig
{
PatchTypes = result,
PatchMethod = method
PatchMethod = method,
AutoApplyPatches = AutoUpdates.IsChecked == true
});
}
+2
View File
@@ -67,12 +67,14 @@
<Compile Include="Converters\ToVisibilityConverter.cs" />
<Compile Include="Core\RuntimePatcher.cs" />
<Compile Include="Core\StaticPatcher.cs" />
<Compile Include="Models\WeModConfig.cs" />
<Compile Include="Models\PatchConfig.cs" />
<Compile Include="Models\Signature.cs" />
<Compile Include="Program.cs" />
<Compile Include="ReactiveUICore\AsyncRelayCommand.cs" />
<Compile Include="ReactiveUICore\ObservableObject.cs" />
<Compile Include="ReactiveUICore\RelayCommand.cs" />
<Compile Include="Utils\Common.cs" />
<Compile Include="Utils\Extensions.cs" />
<Compile Include="Utils\MemoryUtils.cs" />
<Compile Include="Utils\Updater.cs" />