Compare commits

...

4 Commits

25 changed files with 729 additions and 948 deletions
+1
View File
@@ -1 +1,2 @@
ko_fi: kitbyte
custom: ["https://www.paypal.com/ncp/payment/ZP3NPDYP6A34W", "https://www.paypal.com/donate/?hosted_button_id=QGGKZTFPDKMHC"]
+6 -5
View File
@@ -8,21 +8,22 @@ 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" };
public const string ProxyDllResouceName = "proxydll";
// cmp dword ptr [rdx], 0
// jnz loc_XXXXXXXX
// mov rsi, rdx
public static Signature ExePatchSignature = new Signature(
/*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)
@@ -14,7 +14,7 @@ using Application = System.Windows.Application;
namespace WeModPatcher.Core
{
public class StaticPatcher
public class Patcher
{
private class PatchEntry
{
@@ -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 Patcher(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)
@@ -140,53 +138,25 @@ namespace WeModPatcher.Core
}
}
private void PatchPe()
private void AttachProxyDll()
{
_logger("[PATCHER] Patching PE...", ELogType.Info);
var patchResult = MemoryUtils.PatchFile(_exePath,Constants.ExePatchSignature, Constants.ExePatchSignature.PatchBytes);
if(patchResult == -1)
var assembly = Assembly.GetExecutingAssembly();
var dll = assembly.GetManifestResourceStream(Constants.ProxyDllResouceName);
if (dll == null)
{
_logger("[PATCHER] Failed to patch PE", ELogType.Error);
return;
throw new Exception("[PATCHER] Proxy DLL resource not found");
}
_logger(patchResult == 0 ? "[PATCHER] PE already patched!" : "[PATCHER] PE patched successfully!", ELogType.Success);
}
private void CreateShortcut()
{
// invoke file dialog save file
var fileDialog = new SaveFileDialog()
var destPath = Path.Combine(_weModConfig.RootDirectory, "version.dll");
using (var fileStream = File.Create(destPath))
{
CheckPathExists = true,
AddExtension = true,
SupportMultiDottedExtensions = false,
FileName = Constants.WeModBrandName,
};
if(fileDialog.ShowDialog() != DialogResult.OK)
{
return;
dll.CopyTo(fileStream);
}
_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);
_logger("[PATCHER] Proxy DLL attached", ELogType.Info);
}
public void Patch()
{
RuntimePatcher.KillWeMod();
Common.TryKillProcess(_weModConfig.BrandName);
if (!File.Exists(_backupPath))
{
_logger("[PATCHER] Creating backup...", ELogType.Info);
@@ -225,15 +195,8 @@ namespace WeModPatcher.Core
{
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);
}
AttachProxyDll();
_logger("[PATCHER] Done!", ELogType.Success);
}
-173
View File
@@ -1,173 +0,0 @@
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 Dictionary<uint, bool>();
while (Imports.WaitForDebugEvent(ref debugEvent, uint.MaxValue))
{
uint continueStatus = Imports.DBG_CONTINUE;
var code = debugEvent.dwDebugEventCode;
// Console.WriteLine("Debug event code: " + code);
if (code == Imports.CREATE_PROCESS_DEBUG_EVENT)
{
// Console.WriteLine("Spawning process: " + debugEvent.dwProcessId);
processIds.Add(debugEvent.dwProcessId, false);
}
else if (code == Imports.EXIT_PROCESS_DEBUG_EVENT)
{
processIds.Remove(debugEvent.dwProcessId);
if(processIds.Count == 0)
{
break;
}
}
else if (code == Imports.EXCEPTION_DEBUG_EVENT)
{
// pass the exception to the process
continueStatus = Imports.DBG_EXCEPTION_NOT_HANDLED;
var exceptionInfo = Imports.MapUnmanagedStructure<Imports.EXCEPTION_DEBUG_INFO>(debugEvent.Union);
// Console.WriteLine("Exception code: " + exceptionInfo.ExceptionRecord.ExceptionCode);
if (exceptionInfo.ExceptionRecord.ExceptionCode == Imports.EXCEPTION_BREAKPOINT &&
processIds.TryGetValue(debugEvent.dwProcessId, out var wasPatched) && !wasPatched)
{
var process = Process.GetProcessById((int)debugEvent.dwProcessId);
// Console.WriteLine("Scanning process: " + process.ProcessName + " " + process.Id);
var address = MemoryUtils.ScanVirtualMemory(
process.Handle,
process.Modules[0].BaseAddress,
process.Modules[0].ModuleMemorySize,
Constants.ExePatchSignature.Sequence, Constants.ExePatchSignature.Mask
);
if (address != IntPtr.Zero)
{
processIds[debugEvent.dwProcessId] = 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, continueStatus);
}
foreach (var entry in processIds)
{
Imports.DebugActiveProcessStop(entry.Key);
}
Imports.CloseHandle(processInfo.hProcess);
}
public static void Patch(PatchConfig config, Action<string, ELogType> 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, Constants.WeModExeName))
.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");
}
}
}
}
+22 -10
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
{
@@ -9,18 +13,26 @@ namespace WeModPatcher.Models
DisableUpdates = 2,
DisableTelemetry = 4
}
public enum EPatchProcessMethod
{
None = 0,
Runtime = 1,
Static = 2
}
public sealed class PatchConfig
{
private string _path;
public HashSet<EPatchType> PatchTypes { get; set; }
public EPatchProcessMethod PatchMethod { get; set; }
public string Path { get; set; }
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");
}
}
}
}
+29 -6
View File
@@ -1,4 +1,4 @@
using WeModPatcher.Utils;
using System;
namespace WeModPatcher.Models
{
@@ -9,17 +9,40 @@ namespace WeModPatcher.Models
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);
Parse(signature, out Sequence, out Mask);
PatchBytes = patchBytes;
OriginalBytes = originalBytes;
Offset = offset;
}
private static void Parse(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;
}
}
}
}
}
+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;
}
}
}
+1 -28
View File
@@ -1,13 +1,7 @@
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
@@ -23,28 +17,7 @@ namespace WeModPatcher
List<LogEntry> logEntries = new List<LogEntry>();
if (args.Length > 0)
{
try
{
var patchConfig = JsonConvert.DeserializeObject<PatchConfig>(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
});
}
// TODO: Command line arguments handling
}
var application = new App();
+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.5.0")]
[assembly: AssemblyFileVersion("1.0.5.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);
}
}
}
-178
View File
@@ -1,178 +0,0 @@
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;
}
}
}
-260
View File
@@ -1,260 +0,0 @@
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<T>(byte[] debugInfo)
{
GCHandle handle = GCHandle.Alloc(debugInfo, GCHandleType.Pinned);
try
{
return Marshal.PtrToStructure<T>(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 EXIT_PROCESS_DEBUG_EVENT = 5;
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;
public const uint DBG_EXCEPTION_NOT_HANDLED = 0x80010001;
// Constants for VirtualProtectex
public const uint PAGE_EXECUTE_READWRITE = 0x40;
}
}
+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>
+58 -64
View File
@@ -1,13 +1,9 @@
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Threading;
using AsarSharp;
using WeModPatcher.Core;
using WeModPatcher.Models;
using WeModPatcher.ReactiveUICore;
@@ -17,31 +13,32 @@ using Application = System.Windows.Application;
namespace WeModPatcher.View.MainWindow
{
public class MainWindowVm : ObservableObject
{
private readonly MainWindow _view;
public ObservableCollection<LogEntry> LogList { get; set; } = new ObservableCollection<LogEntry>();
private static Updater _updater = new Updater();
private string _weModPath;
public string WeModPath
private WeModConfig _weModConfig;
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);
Log("WeMod already patched. If you want to patch again, please restore the backup first.",
ELogType.Warn);
IsPatchEnabled = false;
AlreadyPatched = true;
return;
}
Log("Ready for patching.", ELogType.Info);
IsPatchEnabled = true;
}
@@ -54,26 +51,28 @@ namespace WeModPatcher.View.MainWindow
get => _isPatchEnabled;
set => SetProperty(ref _isPatchEnabled, value);
}
private bool _alreadyPatched;
public bool AlreadyPatched
{
get => _alreadyPatched;
set => SetProperty(ref _alreadyPatched, value);
}
private bool _isUpdateAvailable;
public bool IsUpdateAvailable
{
get => _isUpdateAvailable;
set => SetProperty(ref _isUpdateAvailable, value);
}
public RelayCommand SetFolderPathCommand { get; }
public RelayCommand ApplyPatchCommand { get; }
public RelayCommand RestoreBackupCommand { get; }
public AsyncRelayCommand UpdateCommand { get; }
public RelayCommand UpdateCommand { get; }
private void OnFolderPathSelection(object obj)
{
using (var dialog = new FolderBrowserDialog())
@@ -86,9 +85,11 @@ namespace WeModPatcher.View.MainWindow
string selectedPath = dialog.SelectedPath;
string fileName = Path.GetFileName(selectedPath);
if (Extensions.CheckWeModPath(selectedPath))
var info = Extensions.CheckWeModPath(selectedPath);
if (info != null)
{
WeModPath = selectedPath;
WeModInfo = info;
return;
}
@@ -102,34 +103,25 @@ 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);
return;
}
try
{
// Try to lock the file to see if it's in use
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, Constants.WeModExeName),
Constants.ExePatchSignature, Constants.ExePatchSignature.OriginalBytes);
if (restoreExeResult == -1)
var proxyDllPath = Path.Combine(WeModInfo.RootDirectory, "version.dll");
if(File.Exists(proxyDllPath))
{
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"
: $"{Constants.WeModExeName} restored successfully", ELogType.Success);
File.Delete(proxyDllPath);
}
}
catch
@@ -137,8 +129,8 @@ namespace WeModPatcher.View.MainWindow
Log("Backup file is locked. Please close the WeMod and try again.", ELogType.Error);
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,13 +139,13 @@ 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;
}
MainWindow.Instance.OpenPopup(new PatchVectorsPopup( async config =>
MainWindow.Instance.OpenPopup(new PatchVectorsPopup(async config =>
{
MainWindow.Instance.ClosePopup();
IsPatchEnabled = false;
@@ -161,7 +153,7 @@ namespace WeModPatcher.View.MainWindow
{
try
{
new StaticPatcher(WeModPath, Log, config).Patch();
new Patcher(WeModInfo, Log, config).Patch();
AlreadyPatched = true;
}
catch (Exception e)
@@ -170,7 +162,6 @@ namespace WeModPatcher.View.MainWindow
IsPatchEnabled = true;
}
});
}), "What are we gonna patch?");
}
@@ -190,24 +181,27 @@ namespace WeModPatcher.View.MainWindow
});
}
private async Task OnUpdate(object param)
private void OnUpdate(object param)
{
await Task.Run(async () =>
MainWindow.Instance.OpenPopup(new UpdatePopup(() =>
{
try
Task.Run(async () =>
{
await _updater.Update();
}
catch (Exception e)
{
Log($"Failed to update: {e.Message}", ELogType.Error);
return;
}
Log("WeModPatcher updated successfully. Restarting...", ELogType.Success);
});
try
{
await _updater.Update();
}
catch (Exception e)
{
Log($"Failed to update: {e.Message}", ELogType.Error);
return;
}
Log("WeModPatcher updated successfully. Restarting...", ELogType.Success);
});
}), "Update available!");
}
public MainWindowVm(MainWindow view)
{
Task.Run(async () => IsUpdateAvailable = await _updater.CheckForUpdates());
@@ -215,10 +209,10 @@ namespace WeModPatcher.View.MainWindow
SetFolderPathCommand = new RelayCommand(OnFolderPathSelection);
ApplyPatchCommand = new RelayCommand(OnPatching);
RestoreBackupCommand = new RelayCommand(OnBackupRestoring);
UpdateCommand = new AsyncRelayCommand(OnUpdate);
WeModPath = Extensions.FindWeModDirectory();
if (WeModPath == null)
UpdateCommand = new RelayCommand(OnUpdate);
WeModInfo = Extensions.FindWeMod();
if (WeModInfo == null)
{
Log("WeMod directory not found.", ELogType.Error);
}
+28 -107
View File
@@ -11,116 +11,37 @@
Foreground="{DynamicResource MutedForeground}"
FontWeight="Medium"
FontSize="13">
<UserControl.Resources>
<Button x:Key="BackButton" Click="BackClicked" VerticalAlignment="Bottom" Padding="3"
Margin="0 0 15 0"
Width="35" Height="23" Style="{StaticResource IconButton}"
Tag="{StaticResource ArrowLeft}"/>
</UserControl.Resources>
<Grid>
<Grid x:Name="PatchVectors" Visibility="Visible" Margin="0 0 5 0">
<Grid Visibility="Visible" Margin="0 0 5 0">
<Grid.RowDefinitions>
<RowDefinition Height="27"/>
<RowDefinition Height="27"/>
<RowDefinition Height="27"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="27" />
<RowDefinition Height="27" />
<RowDefinition Height="27" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" VerticalAlignment="Center" Text="Activate WeMod Pro"/>
<CheckBox Grid.Row="0" x:Name="ActivateProBox" HorizontalAlignment="Right" VerticalAlignment="Center" IsChecked="True"/>
<TextBlock Grid.Row="1" VerticalAlignment="Center" Text="Disable telemetry"/>
<CheckBox Grid.Row="1" x:Name="DisableTelemetryBox" HorizontalAlignment="Right" VerticalAlignment="Center"/>
<TextBlock Grid.Row="2" VerticalAlignment="Center" Text="Disable updates"/>
<CheckBox Grid.Row="2" x:Name="DisableUpdateBox" HorizontalAlignment="Right" VerticalAlignment="Center"/>
<Button Grid.Row="3" Padding="0 5 0 5" Margin="0 15 0 0" Content="Continue"
Click="NextClicked"/>
</Grid>
<Grid x:Name="PatchMethod" Visibility="Collapsed" Width="650">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid>
<Border Background="{DynamicResource Muted}" HorizontalAlignment="Right" Width="2"
CornerRadius="10"/>
<StackPanel>
<TextBlock FontSize="16" Text="Static" Foreground="{DynamicResource Foreground}" HorizontalAlignment="Center" Margin="0 0 0 10"/>
<controls:InfoItem
IconColor="SpringGreen"
IconData="{StaticResource CheckDecagram}"
Text="Starting WeMod without this program" />
<controls:InfoItem
Margin="0 15 0 0"
IconColor="PaleVioletRed"
IconData="{StaticResource AlertDecagram}"
Text="Violation of WeMod digital signature (possibly marked by antiviruses, anti-cheats)" />
<controls:InfoItem
Margin="0 10 0 0"
IconColor="PaleVioletRed"
IconData="{StaticResource AlertDecagram}"
Text="Auto-patching after WeMod updates is not available" />
<controls:InfoItem
Margin="0 10 0 0"
IconColor="PaleVioletRed"
IconData="{StaticResource AlertDecagram}"
Text="Hotkeys will be broken" />
</StackPanel>
</Grid>
<Grid Grid.Row="0" Grid.Column="1">
<StackPanel Margin="10 0 0 0">
<TextBlock FontSize="16" Text="Runtime" Foreground="{DynamicResource Foreground}"
HorizontalAlignment="Center" Margin="-10 0 0 10"/>
<controls:InfoItem
IconColor="SpringGreen"
IconData="{StaticResource CheckDecagram}"
Text="Hotkeys still work" />
<controls:InfoItem Margin="0 10 0 0"
IconColor="SpringGreen"
IconData="{StaticResource CheckDecagram}"
Text="Does not break the digital signature (does not make changes to the original .exe)" />
<controls:InfoItem Margin="0 10 0 0"
IconColor="SpringGreen"
IconData="{StaticResource CheckDecagram}"
Text="Automatically applies patches to new versions (referring to your current selection)" />
<controls:InfoItem
Margin="0 10 0 0"
IconColor="Yellow"
IconData="{StaticResource AlertDecagram}"
Text="The WeMod startup process is controlled by the patcher. (Don't worry, you will no longer see this window. You will run WeMod as usual but using the shortcut that will be created after choosing this method). So you will want to keep this program. Make sure it's in a safe directory (not Temp, Downloads, etc)." />
<controls:InfoItem Margin="0 10 0 0"
IconColor="PaleVioletRed"
IconData="{StaticResource AlertDecagram}"
Text="Running WeMod directly through official WeMod.exe is not possible until you restore patch backup" />
</StackPanel>
</Grid>
<Button Grid.Column="0" Grid.Row="1" VerticalAlignment="Bottom" HorizontalAlignment="Center"
Padding="10 5" Margin="0 15 0 0"
Click="OnStaticSelected"
Content="Use static"/>
<Button Grid.Column="1" Grid.Row="1" VerticalAlignment="Bottom" HorizontalAlignment="Center"
Padding="10 5" Margin="0 15 0 0"
Click="OnRuntimeSelected"
Content="Use runtime"/>
<TextBlock Grid.Row="0" VerticalAlignment="Center" Text="Activate WeMod Pro" />
<CheckBox Grid.Row="0" x:Name="ActivateProBox" HorizontalAlignment="Right" VerticalAlignment="Center"
IsChecked="True" />
<TextBlock Grid.Row="1" VerticalAlignment="Center" Text="Disable telemetry" />
<CheckBox Grid.Row="1" x:Name="DisableTelemetryBox" HorizontalAlignment="Right" VerticalAlignment="Center" />
<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="Start"
Click="OnPatchButtonClick" />
</Grid>
</Grid>
</UserControl>
</UserControl>
@@ -7,42 +7,24 @@ using WeModPatcher.View.Controls;
namespace WeModPatcher.View.Popups
{
public partial class PatchVectorsPopup : UserControl, IDisposable
public partial class PatchVectorsPopup : UserControl
{
private readonly Action<PatchConfig> _onApply;
private readonly StackPanel _popupTitleContainer;
private string _originalTitle;
private readonly TextBlock _titleTextBlock;
public PatchVectorsPopup(Action<PatchConfig> onApply)
{
_onApply = onApply;
InitializeComponent();
_popupTitleContainer = MainWindow.MainWindow.Instance.PopupHost.TitleContainer;
_titleTextBlock = _popupTitleContainer.Children[0] as TextBlock;
}
private void BackClicked(object sender, RoutedEventArgs e)
{
Dispose();
PatchMethod.Visibility = Visibility.Collapsed;
PatchVectors.Visibility = Visibility.Visible;
}
private void OnRuntimeSelected(object sender, RoutedEventArgs e)
=> RaiseCallback(EPatchProcessMethod.Runtime);
private void OnStaticSelected(object sender, RoutedEventArgs e)
=> RaiseCallback(EPatchProcessMethod.Static);
private void RaiseCallback(EPatchProcessMethod method)
private void OnPatchButtonClick(object sender, RoutedEventArgs e)
{
if (ActivateProBox.IsChecked != true && DisableUpdateBox.IsChecked != true &&
DisableTelemetryBox.IsChecked != true)
{
return;
}
var result = new HashSet<EPatchType>();
if (ActivateProBox.IsChecked == true)
{
@@ -57,26 +39,8 @@ namespace WeModPatcher.View.Popups
_onApply(new PatchConfig
{
PatchTypes = result,
PatchMethod = method
AutoApplyPatches =/* AutoUpdates.IsChecked == true*/ false
});
}
private void NextClicked(object sender, RoutedEventArgs e)
{
_popupTitleContainer.Children.Insert(0, FindResource("BackButton") as Button);
_originalTitle = _titleTextBlock.Text;
_titleTextBlock.Text = "Patch method";
PatchMethod.Visibility = Visibility.Visible;
PatchVectors.Visibility = Visibility.Collapsed;
}
public void Dispose()
{
if (PatchVectors.Visibility == Visibility.Collapsed)
{
_popupTitleContainer.Children.RemoveAt(0);
_titleTextBlock.Text = _originalTitle;
}
}
}
}
+20
View File
@@ -0,0 +1,20 @@
<UserControl x:Class="WeModPatcher.View.Popups.UpdatePopup"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
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"
mc:Ignorable="d"
d:DesignHeight="Auto" d:DesignWidth="Auto"
Background="{DynamicResource Background}"
Foreground="{DynamicResource MutedForeground}"
FontWeight="Medium"
FontSize="13">
<StackPanel>
<TextBlock Foreground="Red" MaxWidth="320" TextAlignment="Center" Text="Before updating, it is strongly recommended to roll back patches if they have been applied" TextWrapping="Wrap" />
<Button Padding="0 5 0 5" Margin="0 15 0 0" Content="Update now"
Click="OnUpdateClick" />
</StackPanel>
</UserControl>
@@ -0,0 +1,22 @@
using System;
using System.Windows;
using System.Windows.Controls;
namespace WeModPatcher.View.Popups
{
public partial class UpdatePopup : UserControl
{
private readonly Action _onUpdate;
public UpdatePopup(Action onUpdate)
{
_onUpdate = onUpdate;
InitializeComponent();
}
private void OnUpdateClick(object sender, RoutedEventArgs e)
{
_onUpdate();
}
}
}
+24 -4
View File
@@ -39,6 +39,9 @@
</PropertyGroup>
<PropertyGroup>
<StartupObject>WeModPatcher.Program</StartupObject>
<CMakeSourceDir>..\tools\asar-fuses-bypass</CMakeSourceDir>
<CMakeBuildDir>$(CMakeSourceDir)\cmake-build-release</CMakeBuildDir>
<ProxyDllPath>$(CMakeBuildDir)\version.dll</ProxyDllPath>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
@@ -65,18 +68,17 @@
<Compile Include="Constants.cs" />
<Compile Include="Converters\BaseBooleanConverter.cs" />
<Compile Include="Converters\ToVisibilityConverter.cs" />
<Compile Include="Core\RuntimePatcher.cs" />
<Compile Include="Core\StaticPatcher.cs" />
<Compile Include="Core\Patcher.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" />
<Compile Include="Utils\Win32\Imports.cs" />
<Compile Include="Utils\Win32\Shortcut.cs" />
<Compile Include="View\Controls\InfoItem.xaml.cs">
<DependentUpon>InfoItem.xaml</DependentUpon>
@@ -92,6 +94,9 @@
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="View\Popups\UpdatePopup.xaml.cs">
<DependentUpon>UpdatePopup.xaml</DependentUpon>
</Compile>
<Page Include="Style\ColorScheme.xaml" />
<Page Include="Style\Icons.xaml" />
<Page Include="Style\Styles.xaml" />
@@ -99,6 +104,7 @@
<Page Include="View\Controls\PopupHost.xaml" />
<Page Include="View\MainWindow\MainWindow.xaml" />
<Page Include="View\Popups\PatchVectorsPopup.xaml" />
<Page Include="View\Popups\UpdatePopup.xaml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs">
@@ -129,7 +135,13 @@
<Project>{beaa604a-402a-4387-8903-a53fc913a26e}</Project>
<Name>AsarSharp</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="$(ProxyDllPath)">
<LogicalName>proxydll</LogicalName>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@@ -138,6 +150,14 @@
<Error Condition="!Exists('..\packages\ILRepack.2.0.41\build\ILRepack.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\ILRepack.2.0.41\build\ILRepack.props'))" />
</Target>
<Target Name="EmbedProxyDll" BeforeTargets="BeforeBuild">
<Error Text="Proxy DLL not found: $(ProxyDllPath)"
Condition="!Exists('$(ProxyDllPath)')" />
<Message Text="Embedding Proxy DLL as resource from $(ProxyDllPath)"
Importance="high" />
</Target>
<Target Name="ILRepack" AfterTargets="Build" Condition="'$(Configuration)' == 'Release'">
<PropertyGroup>
<ILRepackExe>..\packages\ILRepack.2.0.41\tools\ILRepack.exe</ILRepackExe>
+78
View File
@@ -0,0 +1,78 @@
# Build directories
/build/
/build-debug/
/build-release/
/out/
# CMake generated files
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
CTestTestfile.cmake
Makefile
install_manifest.txt
# Compiled binaries
*.o
*.obj
*.lo
*.la
*.a
*.so
*.so.*
*.dylib
*.dll
*.exe
*.out
*.app
# Debug files
*.pch
*.pdb
*.mod
*.map
# Generated configuration headers
config.h
config.hpp
# Logs
*.log
# IDE files
# VS Code
.vscode/
*.code-workspace
# CLion
.idea/
# Visual Studio
*.user
*.suo
*.vcxproj.user
*.vcxproj.*
*.sln
# Xcode
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
*.xcworkspace/
xcuserdata/
# OS junk
# macOS
.DS_Store
# Windows
Thumbs.db
ehthumbs.db
Desktop.ini
$RECYCLE.BIN/
# Backup files
*~
*.swp
*.tmp
+15
View File
@@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.16)
project(asar_fuses_bypass C)
set(CMAKE_C_STANDARD 11)
#[[
add_executable(asar_fuses_bypass main.c)
]]
set(CMAKE_SHARED_LIBRARY_PREFIX "")
set(CMAKE_STATIC_LIBRARY_PREFIX "")
add_link_options(-static -static-libgcc -static-libstdc++)
add_library(version SHARED library.c library.def fuses.c)
+190
View File
@@ -0,0 +1,190 @@
//
// Created by kitbyte on 30.11.2025.
//
#include <Windows.h>
#include <stdio.h>
#include <time.h>
#define ENABLE_LOGGING 0
#ifndef _DEBUG
#undef ENABLE_LOGGING
#define ENABLE_LOGGING 0
#endif
#define FUSE_SENTINEL_LENGTH 32
#define FUSE_VERSION_SUPPORTED 1
#define FUSE_MIN_WIRE_LENGTH 5
#define ALIGN8(ptr, mod) ((((ULONG_PTR)(ptr) + 7) & ~7) + ((mod) * 8))
#if defined(_WIN64)
#define SENTINEL_PART1 0x6E64474B70374C64ULL
#define SENTINEL_PART2 0x6262503639377A4EULL
#define SENTINEL_PART3 0x58486D4B4E57516AULL
#define SENTINEL_PART4 0x5873743942615A42ULL
#else
static const DWORD SENTINEL_PARTS[8] = {
0x70374C64, 0x6E64474B,
0x39377A4E, 0x62625036,
0x4E57516A, 0x58486D4B,
0x42615A42, 0x58737439
};
#endif
typedef enum {
FUSE_RUN_AS_NODE = 0,
FUSE_COOKIE_ENCRYPTION = 1,
FUSE_NODE_OPTIONS = 2,
FUSE_NODE_CLI_INSPECT = 3,
FUSE_ASAR_INTEGRITY_VALIDATION = 4,
FUSE_ONLY_LOAD_APP_FROM_ASAR = 5,
FUSE_LOAD_BROWSER_V8_SNAPSHOT = 6,
FUSE_GRANT_FILE_PROTOCOL = 7
} ElectronFuseIndex;
typedef enum {
FUSE_STATE_DISABLED = '0',
FUSE_STATE_ENABLED = '1',
FUSE_STATE_REMOVED = 'r'
} FuseState;
typedef struct {
char sentinel[FUSE_SENTINEL_LENGTH];
unsigned char version;
unsigned char wire_length;
unsigned char fuses[];
} FuseWire;
#if ENABLE_LOGGING
static FILE* g_logFile = NULL;
static void log_init(void) {
char path[MAX_PATH];
GetModuleFileNameA(NULL, path, MAX_PATH);
char* dot = strrchr(path, '.');
if (dot) strcpy(dot, ".log");
else strcat(path, ". log");
g_logFile = fopen(path, "a");
if (g_logFile) {
time_t now = time(NULL);
fprintf(g_logFile, "\n=== Session: %s", ctime(&now));
fflush(g_logFile);
}
}
static void log_close(void) {
if (g_logFile) {
fclose(g_logFile);
g_logFile = NULL;
}
}
static void log_msg(const char* fmt, .. .) {
if (!g_logFile) return;
va_list args;
va_start(args, fmt);
vfprintf(g_logFile, fmt, args);
va_end(args);
fflush(g_logFile);
}
#else
#define log_init() ((void)0)
#define log_close() ((void)0)
#define log_msg(...) ((void)0)
#endif
static FuseWire* find_fuse_wire(int offset) {
char* base = (char*)GetModuleHandleA(NULL);
if (!base) return NULL;
IMAGE_DOS_HEADER* dos = (IMAGE_DOS_HEADER*)base;
if (dos->e_magic != IMAGE_DOS_SIGNATURE) return NULL;
IMAGE_NT_HEADERS* nt = (IMAGE_NT_HEADERS*)(base + dos->e_lfanew);
if (nt->Signature != IMAGE_NT_SIGNATURE) return NULL;
DWORD size = nt->OptionalHeader.SizeOfImage;
char* start = (char*)ALIGN8(base, 1) + offset;
char* end = (char*)ALIGN8(base + size - FUSE_SENTINEL_LENGTH, -1) - offset;
#if defined(_WIN64)
for (DWORD64* p = (DWORD64*)start; p < (DWORD64*)end; p++) {
if (p[0] == SENTINEL_PART1 && p[1] == SENTINEL_PART2 &&
p[2] == SENTINEL_PART3 && p[3] == SENTINEL_PART4) {
log_msg("[+] Sentinel at: %p\n", p);
return (FuseWire*)p;
}
}
#else
for (DWORD* p = (DWORD*)start; p < (DWORD*)end; p += 2) {
if (p[0] == SENTINEL_PARTS[0] && p[1] == SENTINEL_PARTS[1] &&
p[2] == SENTINEL_PARTS[2] && p[3] == SENTINEL_PARTS[3] &&
p[4] == SENTINEL_PARTS[4] && p[5] == SENTINEL_PARTS[5] &&
p[6] == SENTINEL_PARTS[6] && p[7] == SENTINEL_PARTS[7]) {
log_msg("[+] Sentinel at: %p\n", p);
return (FuseWire*)p;
}
}
#endif
return NULL;
}
static BOOL patch_fuse(unsigned char* fuse) {
DWORD prot;
if (!VirtualProtect(fuse, 1, PAGE_READWRITE, &prot)) {
log_msg("[-] VirtualProtect failed: %lu\n", GetLastError());
return FALSE;
}
*fuse = FUSE_STATE_REMOVED;
VirtualProtect(fuse, 1, prot, &prot);
return TRUE;
}
BOOL disable_asar_integrity(void) {
log_init();
FuseWire* wire = find_fuse_wire(0);
if (! wire) wire = find_fuse_wire(4);
if (! wire) {
log_msg("[-] Fuse wire not found\n");
log_close();
return FALSE;
}
log_msg("[+] Wire at %p, ver=%d, len=%d\n", wire, wire->version, wire->wire_length);
if (wire->version != FUSE_VERSION_SUPPORTED) {
log_msg("[-] Unsupported version: %d\n", wire->version);
log_close();
return FALSE;
}
if (wire->wire_length < FUSE_MIN_WIRE_LENGTH) {
log_msg("[*] Wire too short, skip\n");
log_close();
return TRUE;
}
unsigned char* target = &wire->fuses[FUSE_ASAR_INTEGRITY_VALIDATION];
if (*target == FUSE_STATE_REMOVED) {
log_msg("[*] Already patched\n");
log_close();
return TRUE;
}
log_msg("[*] Patching fuse[%d]: 0x%02X -> 0x%02X\n",
FUSE_ASAR_INTEGRITY_VALIDATION, *target, FUSE_STATE_REMOVED);
BOOL result = patch_fuse(target);
log_msg(result ? "[+] Success\n" : "[-] Failed\n");
log_close();
return result;
}
+88
View File
@@ -0,0 +1,88 @@
//
// Created by kitbyte on 30.11.2025.
//
#include <Windows.h>
extern BOOL disable_asar_integrity(void);
#ifdef _WIN64
#define WRAPPER_GENFUNC(name) \
FARPROC orig_##name; \
void _##name(); \
__asm__( \
".global _" #name "\n" \
"_" #name ":\n" \
" movq orig_" #name "(%rip), %rax\n" \
" jmp *%rax\n" \
);
#else
#define WRAPPER_GENFUNC(name) \
FARPROC orig_##name; \
__declspec(naked) void _##name() \
{ \
asm("jmp *_orig_"#name); \
}
#endif
WRAPPER_GENFUNC(GetFileVersionInfoA)
WRAPPER_GENFUNC(GetFileVersionInfoByHandle)
WRAPPER_GENFUNC(GetFileVersionInfoExW)
WRAPPER_GENFUNC(GetFileVersionInfoExA)
WRAPPER_GENFUNC(GetFileVersionInfoSizeA)
WRAPPER_GENFUNC(GetFileVersionInfoSizeExA)
WRAPPER_GENFUNC(GetFileVersionInfoSizeExW)
WRAPPER_GENFUNC(GetFileVersionInfoSizeW)
WRAPPER_GENFUNC(GetFileVersionInfoW)
WRAPPER_GENFUNC(VerFindFileA)
WRAPPER_GENFUNC(VerFindFileW)
WRAPPER_GENFUNC(VerInstallFileA)
WRAPPER_GENFUNC(VerInstallFileW)
WRAPPER_GENFUNC(VerLanguageNameA)
WRAPPER_GENFUNC(VerLanguageNameW)
WRAPPER_GENFUNC(VerQueryValueA)
WRAPPER_GENFUNC(VerQueryValueW)
#define WRAPPER_FUNC(name) orig_##name = GetProcAddress(hOriginalDll, #name);
void SourceInit()
{
TCHAR source[MAX_PATH];
GetSystemDirectory(source, MAX_PATH);
strcat_s(source, sizeof source, "\\version.dll");
HMODULE hOriginalDll = LoadLibrary(source);
WRAPPER_FUNC(GetFileVersionInfoA);
WRAPPER_FUNC(GetFileVersionInfoByHandle);
WRAPPER_FUNC(GetFileVersionInfoExW);
WRAPPER_FUNC(GetFileVersionInfoExA);
WRAPPER_FUNC(GetFileVersionInfoSizeA);
WRAPPER_FUNC(GetFileVersionInfoSizeExW);
WRAPPER_FUNC(GetFileVersionInfoSizeExA);
WRAPPER_FUNC(GetFileVersionInfoSizeW);
WRAPPER_FUNC(GetFileVersionInfoW);
WRAPPER_FUNC(VerFindFileA);
WRAPPER_FUNC(VerFindFileW);
WRAPPER_FUNC(VerInstallFileA);
WRAPPER_FUNC(VerInstallFileW);
WRAPPER_FUNC(VerLanguageNameA);
WRAPPER_FUNC(VerLanguageNameW);
WRAPPER_FUNC(VerQueryValueA);
WRAPPER_FUNC(VerQueryValueW);
}
void Payload()
{
disable_asar_integrity();
}
BOOL WINAPI DllMain(HMODULE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
if (fdwReason == DLL_PROCESS_ATTACH)
{
DisableThreadLibraryCalls(hinstDLL);
SourceInit();
Payload();
}
return TRUE;
}
+20
View File
@@ -0,0 +1,20 @@
LIBRARY "VERSION"
EXPORTS
GetFileVersionInfoA = _GetFileVersionInfoA
GetFileVersionInfoByHandle = _GetFileVersionInfoByHandle
GetFileVersionInfoExA = _GetFileVersionInfoExA
GetFileVersionInfoExW = _GetFileVersionInfoExW
GetFileVersionInfoSizeA = _GetFileVersionInfoSizeA
GetFileVersionInfoSizeExA = _GetFileVersionInfoSizeExA
GetFileVersionInfoSizeExW = _GetFileVersionInfoSizeExW
GetFileVersionInfoSizeW = _GetFileVersionInfoSizeW
GetFileVersionInfoW = _GetFileVersionInfoW
VerFindFileA = _VerFindFileA
VerFindFileW = _VerFindFileW
VerInstallFileA = _VerInstallFileA
VerInstallFileW = _VerInstallFileW
VerLanguageNameA = _VerLanguageNameA
VerLanguageNameW = _VerLanguageNameW
VerQueryValueA = _VerQueryValueA
VerQueryValueW = _VerQueryValueW