mirror of
https://github.com/k1tbyte/Wand-Enhancer.git
synced 2026-09-04 16:23:17 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a5d3799bf | ||
|
|
d02b84a414 | ||
|
|
a943b91f5f | ||
|
|
a5583fd4f7 | ||
|
|
13e26c3a70 | ||
|
|
d8d0abd448 | ||
|
|
54fe538ed9 | ||
|
|
8f14ee2939 | ||
|
|
9ba60a09a7 | ||
|
|
108feeafa8 | ||
|
|
b283c63fd7 | ||
|
|
9ed3d270e3 | ||
|
|
e534c08f88 | ||
|
|
fcf47673af | ||
|
|
d3ff13a528 | ||
|
|
123307aed7 | ||
|
|
68cbbee275 | ||
|
|
4049ade4b1 |
@@ -1 +1,2 @@
|
|||||||
ko_fi: kitbyte
|
ko_fi: kitbyte
|
||||||
|
custom: ["https://www.paypal.com/ncp/payment/ZP3NPDYP6A34W", "https://www.paypal.com/donate/?hosted_button_id=QGGKZTFPDKMHC"]
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: bug
|
||||||
|
assignees: k1tbyte
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**WeMod version**: X.X.X
|
||||||
|
**Patcher version**: X.X.X
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
+14
@@ -128,3 +128,17 @@ dist
|
|||||||
.yarn/build-state.yml
|
.yarn/build-state.yml
|
||||||
.yarn/install-state.gz
|
.yarn/install-state.gz
|
||||||
.pnp.*
|
.pnp.*
|
||||||
|
|
||||||
|
|
||||||
|
./WeModPatcher/obj/
|
||||||
|
./WeModPatcher/bin/
|
||||||
|
./AsarSharp/obj/
|
||||||
|
./AsarSharp/bin/
|
||||||
|
.idea
|
||||||
|
packages
|
||||||
|
*/bin/
|
||||||
|
*/obj/
|
||||||
|
*/obj/.nuget/
|
||||||
|
|
||||||
|
# App settings (user preferences)
|
||||||
|
appsettings.json
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using AsarSharp.AsarFileSystem;
|
||||||
|
using AsarSharp.Utils;
|
||||||
|
|
||||||
|
namespace AsarSharp
|
||||||
|
{
|
||||||
|
|
||||||
|
public class CreateOptions
|
||||||
|
{
|
||||||
|
public Regex Unpack { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AsarCreator
|
||||||
|
{
|
||||||
|
private readonly string _folderPath;
|
||||||
|
private readonly string _destPath;
|
||||||
|
private readonly CreateOptions _options;
|
||||||
|
private List<string> _filenames = new List<string>();
|
||||||
|
private Dictionary<string, CrawledFileType> _metadata = new Dictionary<string, CrawledFileType>();
|
||||||
|
|
||||||
|
public AsarCreator(string folderPath, string destPath, CreateOptions options)
|
||||||
|
{
|
||||||
|
_folderPath = folderPath ?? throw new ArgumentNullException(nameof(folderPath));
|
||||||
|
_destPath = destPath ?? throw new ArgumentNullException(nameof(destPath));
|
||||||
|
_options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreatePackageWithOptions()
|
||||||
|
{
|
||||||
|
var result = FileSystemCrawler.CrawlFileSystem(_folderPath);
|
||||||
|
_filenames = result.filenames;
|
||||||
|
_metadata = result.metadata;
|
||||||
|
CreatePackageFromFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void CreatePackageFromFiles()
|
||||||
|
{
|
||||||
|
var filesystem = new Filesystem(_folderPath);
|
||||||
|
var files = new List<Disk.BasicFileInfo>();
|
||||||
|
|
||||||
|
var filenamesSorted = _filenames.ToList();
|
||||||
|
|
||||||
|
foreach (var filename in filenamesSorted)
|
||||||
|
{
|
||||||
|
HandleFile(filesystem, filename, files);
|
||||||
|
}
|
||||||
|
|
||||||
|
InsertsDone(filesystem, files);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private void HandleFile(Filesystem filesystem, string filename, List<Disk.BasicFileInfo> files)
|
||||||
|
{
|
||||||
|
if (!_metadata.ContainsKey(filename))
|
||||||
|
{
|
||||||
|
var fileType = FileSystemCrawler.DetermineFileType(filename);
|
||||||
|
_metadata[filename] = fileType ?? throw new Exception("Unknown file type for file: " + filename);
|
||||||
|
}
|
||||||
|
var file = _metadata[filename];
|
||||||
|
|
||||||
|
switch (file.Type)
|
||||||
|
{
|
||||||
|
case FileType.Directory:
|
||||||
|
filesystem.InsertDirectory(filename, false);
|
||||||
|
break;
|
||||||
|
case FileType.File:
|
||||||
|
var shouldUnpack = ShouldUnpackPath(Extensions.GetRelativePath(_folderPath, Path.GetDirectoryName(filename)));
|
||||||
|
files.Add(new Disk.BasicFileInfo { Filename = filename, Unpack = shouldUnpack });
|
||||||
|
filesystem.InsertFile(filename, shouldUnpack, file);
|
||||||
|
break;
|
||||||
|
case FileType.Link:
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ShouldUnpackPath(string relativePath)
|
||||||
|
{
|
||||||
|
return _options.Unpack?.IsMatch(relativePath) == true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InsertsDone(Filesystem filesystem, List<Disk.BasicFileInfo> files)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(_destPath) ?? throw new InvalidOperationException());
|
||||||
|
Disk.WriteFileSystem(_destPath, filesystem, new Disk.FilesystemFilesAndLinks { Files = files, Links = null }, _metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using AsarSharp.AsarFileSystem;
|
||||||
|
using AsarSharp.Utils;
|
||||||
|
|
||||||
|
namespace AsarSharp
|
||||||
|
{
|
||||||
|
public class AsarExtractor
|
||||||
|
{
|
||||||
|
public static void ExtractAll(string archivePath, string dest)
|
||||||
|
{
|
||||||
|
var filesystem = Disk.ReadFilesystemSync(archivePath);
|
||||||
|
var filenames = filesystem.ListFiles();
|
||||||
|
|
||||||
|
// under windows just extract links as regular files
|
||||||
|
bool followLinks = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||||
|
|
||||||
|
// create destination directory
|
||||||
|
Directory.CreateDirectory(dest);
|
||||||
|
|
||||||
|
var extractionErrors = new List<Exception>();
|
||||||
|
foreach (var fullPath in filenames)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Remove leading slash
|
||||||
|
var filename = fullPath.Substring(1);
|
||||||
|
var destFilename = Path.Combine(dest, filename);
|
||||||
|
var file = filesystem.GetFile(filename, followLinks);
|
||||||
|
|
||||||
|
// Check that the file is not written outside the specified destination folder
|
||||||
|
string relativePath = Extensions.GetRelativePath(dest, destFilename);
|
||||||
|
if (relativePath.StartsWith(".."))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"{fullPath}: file \"{destFilename}\" writes out of the package");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.IsDirectory)
|
||||||
|
{
|
||||||
|
// it's a directory, create it and continue with the next entry
|
||||||
|
Directory.CreateDirectory(destFilename);
|
||||||
|
}
|
||||||
|
// TODO (LINK NOT SUPPORTED)
|
||||||
|
else if (file.IsLink)
|
||||||
|
{
|
||||||
|
// it's a symlink, create a symlink
|
||||||
|
var linkSrcPath = Extensions.GetDirectoryName(Path.Combine(dest, file.Link));
|
||||||
|
var linkDestPath = Extensions.GetDirectoryName(destFilename);
|
||||||
|
var relativeLinkPath = Extensions.GetRelativePath(linkDestPath, linkSrcPath);
|
||||||
|
|
||||||
|
// try to delete output file, because we can't overwrite a link
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete(destFilename);
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// Ignore errors during file link deletion
|
||||||
|
}
|
||||||
|
|
||||||
|
var linkTo = Path.Combine(relativeLinkPath, Path.GetFileName(file.Link));
|
||||||
|
|
||||||
|
if (Extensions.GetRelativePath(dest, linkSrcPath).StartsWith(".."))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"{fullPath}: file \"{file.Link}\" links out of the package to \"{linkSrcPath}\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
// On Windows, creating symlinks requires additional permissions or enabling Developer Mode,
|
||||||
|
// so just copy the contents of the file
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
var targetPath = Path.Combine(linkSrcPath, Path.GetFileName(file.Link));
|
||||||
|
if (Directory.Exists(targetPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(destFilename);
|
||||||
|
Extensions.CopyDirectory(targetPath, destFilename);
|
||||||
|
}
|
||||||
|
else if (File.Exists(targetPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(Extensions.GetDirectoryName(destFilename));
|
||||||
|
File.Copy(targetPath, destFilename, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// On Unix systems we use symlinks
|
||||||
|
Directory.CreateDirectory(Extensions.GetDirectoryName(destFilename));
|
||||||
|
Extensions.CreateSymbolicLink(linkTo, destFilename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (file.IsFile)
|
||||||
|
{
|
||||||
|
// it's a file, try to extract it
|
||||||
|
try
|
||||||
|
{
|
||||||
|
byte[] content;
|
||||||
|
|
||||||
|
content = Disk.ReadFileSync(filesystem, filename, file);
|
||||||
|
|
||||||
|
File.WriteAllBytes(destFilename, content);
|
||||||
|
|
||||||
|
if (file.Executable == true && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
Extensions.SetUnixFilePermission(destFilename, "755");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
extractionErrors.Add(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
extractionErrors.Add(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extractionErrors.Count > 0)
|
||||||
|
{
|
||||||
|
throw new AggregateException(
|
||||||
|
"Unable to extract some files:\n\n" +
|
||||||
|
string.Join("\n\n", extractionErrors.Select(e => e.ToString())),
|
||||||
|
extractionErrors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using AsarSharp.PickleTools;
|
||||||
|
using AsarSharp.Utils;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace AsarSharp.AsarFileSystem
|
||||||
|
{
|
||||||
|
public static class Disk
|
||||||
|
{
|
||||||
|
private static Dictionary<string, Filesystem> _filesystemCache = new Dictionary<string, Filesystem>();
|
||||||
|
|
||||||
|
public class ArchiveHeader
|
||||||
|
{
|
||||||
|
public FilesystemEntry Header { get; set; }
|
||||||
|
public string HeaderString { get; set; }
|
||||||
|
public int HeaderSize { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FilesystemFilesAndLinks
|
||||||
|
{
|
||||||
|
public List<BasicFileInfo> Files { get; set; } = new List<BasicFileInfo>();
|
||||||
|
public List<BasicFileInfo> Links { get; set; } = new List<BasicFileInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BasicFileInfo
|
||||||
|
{
|
||||||
|
public string Filename { get; set; }
|
||||||
|
public bool Unpack { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region Reading
|
||||||
|
|
||||||
|
public static ArchiveHeader ReadArchiveHeaderSync(string archivePath)
|
||||||
|
{
|
||||||
|
using (FileStream fs = File.OpenRead(archivePath))
|
||||||
|
{
|
||||||
|
// read the size of the header (8 bytes)
|
||||||
|
byte[] sizeBuf = new byte[8];
|
||||||
|
if (fs.Read(sizeBuf, 0, 8) != 8)
|
||||||
|
{
|
||||||
|
throw new Exception("Unable to read header size");
|
||||||
|
}
|
||||||
|
|
||||||
|
var sizePickle = Pickle.CreateFromBuffer(sizeBuf);
|
||||||
|
var size = sizePickle.CreateIterator().ReadUInt32();
|
||||||
|
|
||||||
|
// Read the header of the specified size
|
||||||
|
var headerBuf = new byte[size];
|
||||||
|
if(fs.Read(headerBuf, 0, (int)size) != size)
|
||||||
|
{
|
||||||
|
throw new Exception("Unable to read header");
|
||||||
|
}
|
||||||
|
|
||||||
|
var headerPickle = Pickle.CreateFromBuffer(headerBuf);
|
||||||
|
var header = headerPickle.CreateIterator().ReadString();
|
||||||
|
|
||||||
|
var headerObj = JsonConvert.DeserializeObject<FilesystemEntry>(header);
|
||||||
|
|
||||||
|
return new ArchiveHeader
|
||||||
|
{
|
||||||
|
Header = headerObj,
|
||||||
|
HeaderString = header,
|
||||||
|
HeaderSize = (int)size
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static Filesystem ReadFilesystemSync(string archivePath)
|
||||||
|
{
|
||||||
|
if (!_filesystemCache.ContainsKey(archivePath) || _filesystemCache[archivePath] == null)
|
||||||
|
{
|
||||||
|
ArchiveHeader header = ReadArchiveHeaderSync(archivePath);
|
||||||
|
Filesystem filesystem = new Filesystem(archivePath);
|
||||||
|
filesystem.SetHeader(header.Header, header.HeaderSize);
|
||||||
|
_filesystemCache[archivePath] = filesystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _filesystemCache[archivePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] ReadFileSync(Filesystem filesystem, string filename, FilesystemEntry info)
|
||||||
|
{
|
||||||
|
if (!info.IsFile || !info.Size.HasValue)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Entry is not a file", nameof(info));
|
||||||
|
}
|
||||||
|
|
||||||
|
long size = info.Size.Value;
|
||||||
|
byte[] buffer = new byte[size];
|
||||||
|
|
||||||
|
if (size <= 0)
|
||||||
|
{
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.Unpacked == true)
|
||||||
|
{
|
||||||
|
// It's an unpacked file, read it directly
|
||||||
|
string filePath = Path.Combine($"{filesystem.GetRootPath()}.unpacked", filename);
|
||||||
|
return File.ReadAllBytes(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read from the ASAR archive
|
||||||
|
using (FileStream fs = File.OpenRead(filesystem.GetRootPath()))
|
||||||
|
{
|
||||||
|
// Important: the offset must take into account the size of the Pickle header (8 bytes)
|
||||||
|
// and the size of the header itself
|
||||||
|
long offset = 8 + filesystem.GetHeaderSize() + long.Parse(info.Offset);
|
||||||
|
fs.Position = offset;
|
||||||
|
|
||||||
|
// Read the whole file at once
|
||||||
|
int bytesRead = fs.Read(buffer, 0, (int)size);
|
||||||
|
if (bytesRead != size)
|
||||||
|
{
|
||||||
|
throw new Exception($"Failed to read entire file, got {bytesRead} bytes instead of {size}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public static bool UncacheFilesystem(string archivePath)
|
||||||
|
{
|
||||||
|
if (_filesystemCache.ContainsKey(archivePath))
|
||||||
|
{
|
||||||
|
_filesystemCache.Remove(archivePath);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UncacheAll()
|
||||||
|
{
|
||||||
|
_filesystemCache.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CopyFile(string dest, string rootPath, string filename)
|
||||||
|
{
|
||||||
|
if(dest == null || rootPath == null || filename == null)
|
||||||
|
throw new ArgumentNullException();
|
||||||
|
|
||||||
|
if (dest == rootPath)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string sourcePath = Path.Combine(rootPath, filename);
|
||||||
|
string destPath = Path.Combine(dest, filename);
|
||||||
|
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(destPath) ?? throw new InvalidOperationException());
|
||||||
|
using (var sourceStream = new FileStream(sourcePath, FileMode.Open, FileAccess.Read))
|
||||||
|
using (var destinationStream = new FileStream(destPath, FileMode.Create, FileAccess.Write))
|
||||||
|
{
|
||||||
|
sourceStream.CopyTo(destinationStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void WriteFileSystem(string dest, Filesystem fileSystem,
|
||||||
|
FilesystemFilesAndLinks lists,
|
||||||
|
Dictionary<string, CrawledFileType> metadata)
|
||||||
|
{
|
||||||
|
var fsHeader = fileSystem.GetHeader();
|
||||||
|
var headerPickle = Pickle.CreateEmpty();
|
||||||
|
var serializerSettings = new JsonSerializerSettings()
|
||||||
|
{ NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore } ;
|
||||||
|
|
||||||
|
var headerJson = JsonConvert.SerializeObject(fsHeader,serializerSettings);
|
||||||
|
headerPickle.WriteString(headerJson);
|
||||||
|
var headerBuf = headerPickle.ToBuffer();
|
||||||
|
|
||||||
|
var sizePickle = Pickle.CreateEmpty();
|
||||||
|
sizePickle.WriteUInt32((uint)headerBuf.Length);
|
||||||
|
var sizeBuf = sizePickle.ToBuffer();
|
||||||
|
|
||||||
|
using (FileStream fs = File.Create(dest))
|
||||||
|
{
|
||||||
|
fs.Write(sizeBuf, 0, sizeBuf.Length);
|
||||||
|
fs.Write(headerBuf, 0, headerBuf.Length);
|
||||||
|
|
||||||
|
foreach (var file in lists.Files)
|
||||||
|
{
|
||||||
|
if (file.Unpack)
|
||||||
|
{
|
||||||
|
var filename = Extensions.GetRelativePath(fileSystem.GetRootPath(), file.Filename);
|
||||||
|
CopyFile($"{dest}.unpacked", fileSystem.GetRootPath(), filename);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
using (var transformedFileStream = new FileStream(file.Filename, FileMode.Open, FileAccess.Read))
|
||||||
|
{
|
||||||
|
transformedFileStream.CopyTo(fs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using AsarSharp.Integrity;
|
||||||
|
using AsarSharp.Utils;
|
||||||
|
|
||||||
|
namespace AsarSharp.AsarFileSystem
|
||||||
|
{
|
||||||
|
public class Filesystem
|
||||||
|
{
|
||||||
|
private readonly string _src;
|
||||||
|
private FilesystemEntry _header;
|
||||||
|
private int _headerSize;
|
||||||
|
private long _offset;
|
||||||
|
|
||||||
|
private const uint UINT32_MAX = 0xFFFFFFFF; // 2^32 - 1
|
||||||
|
|
||||||
|
public Filesystem(string src)
|
||||||
|
{
|
||||||
|
_src = Path.GetFullPath(src);
|
||||||
|
_header = new FilesystemEntry { Files = new Dictionary<string, FilesystemEntry>(StringComparer.Ordinal) };
|
||||||
|
_headerSize = 0;
|
||||||
|
_offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetRootPath()
|
||||||
|
{
|
||||||
|
return _src;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilesystemEntry GetHeader()
|
||||||
|
{
|
||||||
|
return _header;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHeaderSize()
|
||||||
|
{
|
||||||
|
return _headerSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetHeader(FilesystemEntry header, int headerSize)
|
||||||
|
{
|
||||||
|
_header = header;
|
||||||
|
_headerSize = headerSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilesystemEntry SearchNodeFromDirectory(string p)
|
||||||
|
{
|
||||||
|
FilesystemEntry json = _header;
|
||||||
|
|
||||||
|
// Normalize path delimiters to system delimiters
|
||||||
|
p = p.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar);
|
||||||
|
|
||||||
|
string[] dirs = p.Split(Path.DirectorySeparatorChar);
|
||||||
|
|
||||||
|
foreach (string dir in dirs)
|
||||||
|
{
|
||||||
|
if (dir == "." || string.IsNullOrEmpty(dir)) continue;
|
||||||
|
|
||||||
|
if (json.IsDirectory)
|
||||||
|
{
|
||||||
|
if (!json.Files.ContainsKey(dir))
|
||||||
|
{
|
||||||
|
json.Files[dir] = new FilesystemEntry { Files = new Dictionary<string, FilesystemEntry>(StringComparer.Ordinal) };
|
||||||
|
}
|
||||||
|
json = json.Files[dir];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception($"Unexpected directory state while traversing: {p}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<string> ListFiles(bool isPack = false)
|
||||||
|
{
|
||||||
|
var files = new List<string>();
|
||||||
|
|
||||||
|
FillFilesFromMetadata("/", _header);
|
||||||
|
return files;
|
||||||
|
|
||||||
|
void FillFilesFromMetadata(string basePath, FilesystemEntry metadata)
|
||||||
|
{
|
||||||
|
if (!metadata.IsDirectory)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var entry in metadata.Files)
|
||||||
|
{
|
||||||
|
string childPath = entry.Key;
|
||||||
|
FilesystemEntry childMetadata = entry.Value;
|
||||||
|
string fullPath = Path.Combine(basePath, childPath).Replace('\\', '/');
|
||||||
|
|
||||||
|
string packState =
|
||||||
|
childMetadata.Unpacked == true ? "unpack" : "pack ";
|
||||||
|
|
||||||
|
files.Add(isPack ? $"{packState} : {fullPath}" : fullPath);
|
||||||
|
FillFilesFromMetadata(fullPath, childMetadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilesystemEntry GetNode(string p, bool followLinks = true)
|
||||||
|
{
|
||||||
|
// Normalize path delimiters
|
||||||
|
p = p.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar);
|
||||||
|
|
||||||
|
FilesystemEntry node = SearchNodeFromDirectory(Extensions.GetDirectoryName(p));
|
||||||
|
string name = Path.GetFileName(p);
|
||||||
|
|
||||||
|
// Process symbolic links
|
||||||
|
if (node.IsLink && followLinks)
|
||||||
|
{
|
||||||
|
return GetNode(Path.Combine(node.Link, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(name))
|
||||||
|
{
|
||||||
|
if (node.IsDirectory && node.Files.TryGetValue(name, out var entry))
|
||||||
|
{
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilesystemEntry GetFile(string p, bool followLinks = true)
|
||||||
|
{
|
||||||
|
FilesystemEntry info = GetNode(p, followLinks);
|
||||||
|
|
||||||
|
if (info == null)
|
||||||
|
{
|
||||||
|
throw new Exception($"\"{p}\" was not found in this archive");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If followLinks=false, do not allow symbolic links (TODO)
|
||||||
|
if (info.IsLink && followLinks)
|
||||||
|
{
|
||||||
|
return GetFile(info.Link, followLinks);
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ReadLink(string path)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
return Path.GetFileName(path);
|
||||||
|
// TODO , NOT IMPLEMENTED
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region Writing
|
||||||
|
|
||||||
|
public FilesystemEntry SearchNodeFromPath(string p)
|
||||||
|
{
|
||||||
|
p = Extensions.GetRelativePath(_src, p);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(p))
|
||||||
|
{
|
||||||
|
return _header;
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = Path.GetFileName(p);
|
||||||
|
var node = SearchNodeFromDirectory(Extensions.GetDirectoryName(p));
|
||||||
|
|
||||||
|
if (node.Files == null)
|
||||||
|
{
|
||||||
|
node.Files = new Dictionary<string, FilesystemEntry>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!node.Files.ContainsKey(name))
|
||||||
|
{
|
||||||
|
node.Files[name] = new FilesystemEntry();
|
||||||
|
}
|
||||||
|
|
||||||
|
return node.Files[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InsertDirectory(string p, bool unpack)
|
||||||
|
{
|
||||||
|
FilesystemEntry node = SearchNodeFromPath(p);
|
||||||
|
node.Files = node.Files ?? new Dictionary<string, FilesystemEntry>();
|
||||||
|
node.Unpacked = unpack;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InsertFile(string path, bool shouldUnpack, CrawledFileType file)
|
||||||
|
{
|
||||||
|
var dirName = Path.GetDirectoryName(path);
|
||||||
|
var dirNode = SearchNodeFromPath(dirName);
|
||||||
|
var node = SearchNodeFromPath(path);
|
||||||
|
|
||||||
|
long size = 0;
|
||||||
|
if (file.Stat is FileInfo fileInfo)
|
||||||
|
{
|
||||||
|
size = fileInfo.Length;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception($"{path}: stat is not a file");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldUnpack || dirNode.Unpacked == true)
|
||||||
|
{
|
||||||
|
node.Size = size;
|
||||||
|
node.Unpacked = true;
|
||||||
|
node.Integrity = IntegrityHelper.GetFileIntegrity(path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the file size does not exceed UINT32_MAX
|
||||||
|
if (size > UINT32_MAX)
|
||||||
|
{
|
||||||
|
throw new Exception($"{path}: file size cannot be larger than 4.2GB");
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Size = size;
|
||||||
|
node.Offset = _offset.ToString();
|
||||||
|
node.Integrity = IntegrityHelper.GetFileIntegrity(path);
|
||||||
|
if (!Extensions.IsWindowsPlatform() && (file.Stat.Attributes & FileAttributes.Hidden) != 0)
|
||||||
|
{
|
||||||
|
node.Executable = true;
|
||||||
|
}
|
||||||
|
_offset += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using AsarSharp.Utils;
|
||||||
|
|
||||||
|
namespace AsarSharp.AsarFileSystem
|
||||||
|
{
|
||||||
|
public class CrawledFileType
|
||||||
|
{
|
||||||
|
public FileType Type { get; set; }
|
||||||
|
public FileSystemInfo Stat { get; set; }
|
||||||
|
public TransformedFile Transformed { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TransformedFile
|
||||||
|
{
|
||||||
|
public string Path { get; set; }
|
||||||
|
public FileSystemInfo Stat { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum FileType
|
||||||
|
{
|
||||||
|
File,
|
||||||
|
Directory,
|
||||||
|
Link
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class FileSystemCrawler
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
public static CrawledFileType DetermineFileType(string filename)
|
||||||
|
{
|
||||||
|
var fileInfo = new FileInfo(filename);
|
||||||
|
if (fileInfo.Exists)
|
||||||
|
{
|
||||||
|
return new CrawledFileType { Type = FileType.File, Stat = fileInfo };
|
||||||
|
}
|
||||||
|
|
||||||
|
var directoryInfo = new DirectoryInfo(filename);
|
||||||
|
if (directoryInfo.Exists)
|
||||||
|
{
|
||||||
|
return new CrawledFileType { Type = FileType.Directory, Stat = directoryInfo };
|
||||||
|
}
|
||||||
|
|
||||||
|
var linkInfo = new FileInfo(filename);
|
||||||
|
if (linkInfo.Exists && (linkInfo.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
|
||||||
|
{
|
||||||
|
return new CrawledFileType { Type = FileType.Link, Stat = linkInfo };
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static (List<string> filenames, Dictionary<string, CrawledFileType> metadata) CrawlFileSystem(string dir)
|
||||||
|
{
|
||||||
|
var metadata = new Dictionary<string, CrawledFileType>();
|
||||||
|
var crawled = CrawlIterative(dir);
|
||||||
|
var results = crawled.Select(filename => new { filename, type = DetermineFileType(filename) }).ToList();
|
||||||
|
|
||||||
|
var links = new List<string>();
|
||||||
|
var filenames = new List<string>();
|
||||||
|
|
||||||
|
foreach (var result in results.Where(result => result.type != null))
|
||||||
|
{
|
||||||
|
metadata[result.filename] = result.type;
|
||||||
|
if (result.type.Type == FileType.Link)
|
||||||
|
{
|
||||||
|
links.Add(result.filename);
|
||||||
|
}
|
||||||
|
filenames.Add(result.filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
var filteredFilenames = new List<string>();
|
||||||
|
|
||||||
|
foreach (var filename in filenames)
|
||||||
|
{
|
||||||
|
var exactLinkIndex = links.FindIndex(link => filename == link);
|
||||||
|
var isValid = true;
|
||||||
|
|
||||||
|
for (var i = 0; i < links.Count; i++)
|
||||||
|
{
|
||||||
|
if (i == exactLinkIndex)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var link = links[i];
|
||||||
|
var isFileWithinSymlinkDir = filename.StartsWith(link, StringComparison.OrdinalIgnoreCase);
|
||||||
|
var relativePath = Extensions.GetRelativePath(link, Path.GetDirectoryName(filename) ?? string.Empty);
|
||||||
|
|
||||||
|
if (isFileWithinSymlinkDir && !relativePath.StartsWith("..", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
isValid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isValid)
|
||||||
|
{
|
||||||
|
filteredFilenames.Add(filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (filteredFilenames, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// (File order is not important!!!)
|
||||||
|
public static List<string> CrawlIterative(string dir)
|
||||||
|
{
|
||||||
|
var result = new List<string>();
|
||||||
|
var stack = new Stack<string>();
|
||||||
|
|
||||||
|
|
||||||
|
string basePath = Extensions.GetBasePath(dir);
|
||||||
|
|
||||||
|
if (!Directory.Exists(basePath))
|
||||||
|
return result;
|
||||||
|
|
||||||
|
// Add only the base directory to the stack, but not to the result
|
||||||
|
stack.Push(basePath);
|
||||||
|
|
||||||
|
while (stack.Count > 0)
|
||||||
|
{
|
||||||
|
string currentDir = stack.Pop();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Add all files from the current directory
|
||||||
|
result.AddRange(Directory.GetFiles(currentDir, "*", SearchOption.TopDirectoryOnly));
|
||||||
|
|
||||||
|
// Add subdirectories to the results and to the stack
|
||||||
|
foreach (var directory in Directory.GetDirectories(currentDir, "*",
|
||||||
|
SearchOption.TopDirectoryOnly))
|
||||||
|
{
|
||||||
|
// Add subdirectories to the result
|
||||||
|
if (directory != basePath) // Do not add a base directory
|
||||||
|
{
|
||||||
|
result.Add(directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to the stack for processing
|
||||||
|
stack.Push(directory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
// Skip directories to which there is no access
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using AsarSharp.Integrity;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace AsarSharp.AsarFileSystem
|
||||||
|
{
|
||||||
|
public class FilesystemEntry
|
||||||
|
{
|
||||||
|
[JsonProperty("files")]
|
||||||
|
public Dictionary<string, FilesystemEntry> Files { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
[JsonProperty("executable")]
|
||||||
|
public bool? Executable { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("size")]
|
||||||
|
public long? Size { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("offset")]
|
||||||
|
public string Offset { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("unpacked")]
|
||||||
|
public bool? Unpacked { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("integrity")]
|
||||||
|
public IntegrityHelper.FileIntegrity Integrity { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("link")]
|
||||||
|
public string Link { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool IsDirectory => Files != null;
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool IsFile => Size.HasValue;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool IsLink => Link != null;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"Offset: {Offset}, Size: {Size} Unpacked: {Unpacked}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ShouldSerializeUnpacked()
|
||||||
|
{
|
||||||
|
return Unpacked == true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"
|
||||||
|
Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')"/>
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{BEAA604A-402A-4387-8903-A53FC913A26E}</ProjectGuid>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
|
<RootNamespace>AsarSharp</RootNamespace>
|
||||||
|
<AssemblyName>AsarSharp</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System"/>
|
||||||
|
<Reference Include="System.Core"/>
|
||||||
|
<Reference Include="System.Data"/>
|
||||||
|
<Reference Include="System.Xml"/>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="AsarCreator.cs" />
|
||||||
|
<Compile Include="AsarExtractor.cs" />
|
||||||
|
<Compile Include="AsarFileSystem\Disk.cs" />
|
||||||
|
<Compile Include="AsarFileSystem\FileSystem.cs" />
|
||||||
|
<Compile Include="AsarFileSystem\FileSystemCrawler.cs" />
|
||||||
|
<Compile Include="AsarFileSystem\FilesystemEntry.cs" />
|
||||||
|
<Compile Include="Integrity\IntegrityHelper.cs" />
|
||||||
|
<Compile Include="PickleTools\Pickle.cs" />
|
||||||
|
<Compile Include="PickleTools\PickleIterator.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs"/>
|
||||||
|
<Compile Include="Utils\Extensions.cs" />
|
||||||
|
<Compile Include="Utils\NativeMethods.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="packages.config" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets"/>
|
||||||
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
<Target Name="BeforeBuild">
|
||||||
|
</Target>
|
||||||
|
<Target Name="AfterBuild">
|
||||||
|
</Target>
|
||||||
|
-->
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace AsarSharp.Integrity
|
||||||
|
{
|
||||||
|
public static class IntegrityHelper
|
||||||
|
{
|
||||||
|
private const string ALGORITHM = "SHA256";
|
||||||
|
// 4MB default block size
|
||||||
|
private const int BLOCK_SIZE = 4 * 1024 * 1024;
|
||||||
|
|
||||||
|
public class FileIntegrity
|
||||||
|
{
|
||||||
|
[JsonProperty("algorithm")]
|
||||||
|
public string Algorithm { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("hash")]
|
||||||
|
public string Hash { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("blockSize")]
|
||||||
|
public int BlockSize { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("blocks")]
|
||||||
|
public List<string> Blocks { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FileIntegrity GetFileIntegrity(string path)
|
||||||
|
{
|
||||||
|
using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||||
|
using(var fileHash = SHA256.Create())
|
||||||
|
{
|
||||||
|
|
||||||
|
var blockHashes = new List<string>();
|
||||||
|
var buffer = new byte[BLOCK_SIZE];
|
||||||
|
int bytesRead;
|
||||||
|
|
||||||
|
while ((bytesRead = fileStream.Read(buffer, 0, BLOCK_SIZE)) > 0)
|
||||||
|
{
|
||||||
|
var block = new byte[bytesRead];
|
||||||
|
Array.Copy(buffer, block, bytesRead);
|
||||||
|
blockHashes.Add(HashBlock(block));
|
||||||
|
fileHash.TransformBlock(block, 0, block.Length, null, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fileHash.TransformFinalBlock(Array.Empty<byte>(), 0, 0);
|
||||||
|
|
||||||
|
return new FileIntegrity
|
||||||
|
{
|
||||||
|
Algorithm = ALGORITHM,
|
||||||
|
Hash = BitConverter.ToString(fileHash.Hash).Replace("-", "").ToLowerInvariant(),
|
||||||
|
BlockSize = BLOCK_SIZE,
|
||||||
|
Blocks = blockHashes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string HashBlock(byte[] block)
|
||||||
|
{
|
||||||
|
using (var sha256 = SHA256.Create())
|
||||||
|
{
|
||||||
|
var hash = sha256.ComputeHash(block);
|
||||||
|
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,425 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace AsarSharp.PickleTools
|
||||||
|
{
|
||||||
|
public class Pickle
|
||||||
|
{
|
||||||
|
public const int SIZE_INT32 = 4;
|
||||||
|
public const int SIZE_UINT32 = 4;
|
||||||
|
public const int SIZE_INT64 = 8;
|
||||||
|
public const int SIZE_UINT64 = 8;
|
||||||
|
public const int SIZE_FLOAT = 4;
|
||||||
|
public const int SIZE_DOUBLE = 8;
|
||||||
|
|
||||||
|
// Size of memory allocation unit for payload
|
||||||
|
public const int PAYLOAD_UNIT = 64;
|
||||||
|
|
||||||
|
// Maximum value for read-only
|
||||||
|
public const long CAPACITY_READ_ONLY = 9007199254740992;
|
||||||
|
|
||||||
|
private byte[] _header;
|
||||||
|
private int _headerSize;
|
||||||
|
private long _capacityAfterHeader;
|
||||||
|
private int _writeOffset;
|
||||||
|
|
||||||
|
private Pickle(byte[] buffer = null)
|
||||||
|
{
|
||||||
|
if (buffer != null)
|
||||||
|
{
|
||||||
|
_header = buffer;
|
||||||
|
_headerSize = buffer.Length - GetPayloadSize();
|
||||||
|
_capacityAfterHeader = CAPACITY_READ_ONLY;
|
||||||
|
_writeOffset = 0;
|
||||||
|
|
||||||
|
if (_headerSize > buffer.Length)
|
||||||
|
{
|
||||||
|
_headerSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_headerSize != AlignInt(_headerSize, SIZE_UINT32))
|
||||||
|
{
|
||||||
|
_headerSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_headerSize == 0)
|
||||||
|
{
|
||||||
|
_header = new byte[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_header = new byte[0];
|
||||||
|
_headerSize = SIZE_UINT32;
|
||||||
|
_capacityAfterHeader = 0;
|
||||||
|
_writeOffset = 0;
|
||||||
|
Resize(PAYLOAD_UNIT);
|
||||||
|
SetPayloadSize(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Pickle CreateEmpty()
|
||||||
|
{
|
||||||
|
return new Pickle();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Pickle CreateFromBuffer(byte[] buffer)
|
||||||
|
{
|
||||||
|
return new Pickle(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] GetHeader()
|
||||||
|
{
|
||||||
|
return _header;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHeaderSize()
|
||||||
|
{
|
||||||
|
return _headerSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PickleIterator CreateIterator()
|
||||||
|
{
|
||||||
|
return new PickleIterator(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts Pickle to a byte array
|
||||||
|
/// </summary>
|
||||||
|
public byte[] ToBuffer()
|
||||||
|
{
|
||||||
|
int resultSize = _headerSize + GetPayloadSize();
|
||||||
|
byte[] result = new byte[resultSize];
|
||||||
|
Array.Copy(_header, 0, result, 0, resultSize);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public bool WriteBool(bool value)
|
||||||
|
{
|
||||||
|
return WriteInt(value ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool WriteInt(int value)
|
||||||
|
{
|
||||||
|
EnsureCapacity(SIZE_INT32);
|
||||||
|
|
||||||
|
var dataLength = AlignInt(SIZE_INT32, SIZE_UINT32);
|
||||||
|
var newSize = _writeOffset + dataLength;
|
||||||
|
|
||||||
|
if (newSize > _capacityAfterHeader)
|
||||||
|
{
|
||||||
|
Resize(Math.Max((int)_capacityAfterHeader * 2, newSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteInt32LE(value, _headerSize + _writeOffset);
|
||||||
|
|
||||||
|
var endOffset = _headerSize + _writeOffset + SIZE_INT32;
|
||||||
|
for (int i = endOffset; i < endOffset + dataLength - SIZE_INT32; i++)
|
||||||
|
{
|
||||||
|
_header[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetPayloadSize(newSize);
|
||||||
|
_writeOffset = newSize;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public bool WriteUInt32(uint value)
|
||||||
|
{
|
||||||
|
EnsureCapacity(SIZE_UINT32);
|
||||||
|
|
||||||
|
var dataLength = AlignInt(SIZE_UINT32, SIZE_UINT32);
|
||||||
|
var newSize = _writeOffset + dataLength;
|
||||||
|
|
||||||
|
if (newSize > _capacityAfterHeader)
|
||||||
|
{
|
||||||
|
Resize(Math.Max((int)_capacityAfterHeader * 2, newSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteUInt32LE(value, _headerSize + _writeOffset);
|
||||||
|
|
||||||
|
var endOffset = _headerSize + _writeOffset + SIZE_UINT32;
|
||||||
|
for (int i = endOffset; i < endOffset + dataLength - SIZE_UINT32; i++)
|
||||||
|
{
|
||||||
|
_header[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetPayloadSize(newSize);
|
||||||
|
_writeOffset = newSize;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool WriteInt64(long value)
|
||||||
|
{
|
||||||
|
EnsureCapacity(SIZE_INT64);
|
||||||
|
|
||||||
|
var dataLength = AlignInt(SIZE_INT64, SIZE_UINT32);
|
||||||
|
var newSize = _writeOffset + dataLength;
|
||||||
|
|
||||||
|
if (newSize > _capacityAfterHeader)
|
||||||
|
{
|
||||||
|
Resize(Math.Max((int)_capacityAfterHeader * 2, newSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteInt64LE(value, _headerSize + _writeOffset);
|
||||||
|
|
||||||
|
var endOffset = _headerSize + _writeOffset + SIZE_INT64;
|
||||||
|
for (int i = endOffset; i < endOffset + dataLength - SIZE_INT64; i++)
|
||||||
|
{
|
||||||
|
_header[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetPayloadSize(newSize);
|
||||||
|
_writeOffset = newSize;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public bool WriteUInt64(ulong value)
|
||||||
|
{
|
||||||
|
EnsureCapacity(SIZE_UINT64);
|
||||||
|
|
||||||
|
var dataLength = AlignInt(SIZE_UINT64, SIZE_UINT32);
|
||||||
|
var newSize = _writeOffset + dataLength;
|
||||||
|
|
||||||
|
if (newSize > _capacityAfterHeader)
|
||||||
|
{
|
||||||
|
Resize(Math.Max((int)_capacityAfterHeader * 2, newSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteUInt64LE(value, _headerSize + _writeOffset);
|
||||||
|
|
||||||
|
var endOffset = _headerSize + _writeOffset + SIZE_UINT64;
|
||||||
|
for (int i = endOffset; i < endOffset + dataLength - SIZE_UINT64; i++)
|
||||||
|
{
|
||||||
|
_header[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetPayloadSize(newSize);
|
||||||
|
_writeOffset = newSize;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool WriteFloat(float value)
|
||||||
|
{
|
||||||
|
EnsureCapacity(SIZE_FLOAT);
|
||||||
|
|
||||||
|
var dataLength = AlignInt(SIZE_FLOAT, SIZE_UINT32);
|
||||||
|
var newSize = _writeOffset + dataLength;
|
||||||
|
|
||||||
|
if (newSize > _capacityAfterHeader)
|
||||||
|
{
|
||||||
|
Resize(Math.Max((int)_capacityAfterHeader * 2, newSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] bytes = BitConverter.GetBytes(value);
|
||||||
|
if (!BitConverter.IsLittleEndian)
|
||||||
|
{
|
||||||
|
Array.Reverse(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.Copy(bytes, 0, _header, _headerSize + _writeOffset, SIZE_FLOAT);
|
||||||
|
|
||||||
|
var endOffset = _headerSize + _writeOffset + SIZE_FLOAT;
|
||||||
|
for (int i = endOffset; i < endOffset + dataLength - SIZE_FLOAT; i++)
|
||||||
|
{
|
||||||
|
_header[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetPayloadSize(newSize);
|
||||||
|
_writeOffset = newSize;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool WriteDouble(double value)
|
||||||
|
{
|
||||||
|
EnsureCapacity(SIZE_DOUBLE);
|
||||||
|
|
||||||
|
var dataLength = AlignInt(SIZE_DOUBLE, SIZE_UINT32);
|
||||||
|
var newSize = _writeOffset + dataLength;
|
||||||
|
|
||||||
|
if (newSize > _capacityAfterHeader)
|
||||||
|
{
|
||||||
|
Resize(Math.Max((int)_capacityAfterHeader * 2, newSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] bytes = BitConverter.GetBytes(value);
|
||||||
|
if (!BitConverter.IsLittleEndian)
|
||||||
|
{
|
||||||
|
Array.Reverse(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.Copy(bytes, 0, _header, _headerSize + _writeOffset, SIZE_DOUBLE);
|
||||||
|
|
||||||
|
var endOffset = _headerSize + _writeOffset + SIZE_DOUBLE;
|
||||||
|
for (int i = endOffset; i < endOffset + dataLength - SIZE_DOUBLE; i++)
|
||||||
|
{
|
||||||
|
_header[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetPayloadSize(newSize);
|
||||||
|
_writeOffset = newSize;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool WriteString(string value)
|
||||||
|
{
|
||||||
|
byte[] strBytes = Encoding.UTF8.GetBytes(value);
|
||||||
|
int length = strBytes.Length;
|
||||||
|
|
||||||
|
if (!WriteInt(length))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataLength = AlignInt(length, SIZE_UINT32);
|
||||||
|
var newSize = _writeOffset + dataLength;
|
||||||
|
|
||||||
|
if (newSize > _capacityAfterHeader)
|
||||||
|
{
|
||||||
|
Resize(Math.Max((int)_capacityAfterHeader * 2, newSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.Copy(strBytes, 0, _header, _headerSize + _writeOffset, length);
|
||||||
|
|
||||||
|
var endOffset = _headerSize + _writeOffset + length;
|
||||||
|
for (int i = endOffset; i < endOffset + dataLength - length; i++)
|
||||||
|
{
|
||||||
|
_header[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetPayloadSize(newSize);
|
||||||
|
_writeOffset = newSize;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPayloadSize(int payloadSize)
|
||||||
|
{
|
||||||
|
WriteUInt32LE((uint)payloadSize, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetPayloadSize()
|
||||||
|
{
|
||||||
|
return (int)ReadUInt32LE(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Resize(int newCapacity)
|
||||||
|
{
|
||||||
|
newCapacity = AlignInt(newCapacity, PAYLOAD_UNIT);
|
||||||
|
byte[] newHeader = new byte[_header.Length + newCapacity];
|
||||||
|
Array.Copy(_header, 0, newHeader, 0, _header.Length);
|
||||||
|
_header = newHeader;
|
||||||
|
_capacityAfterHeader = newCapacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int AlignInt(int i, int alignment)
|
||||||
|
{
|
||||||
|
return i + ((alignment - (i % alignment)) % alignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureCapacity(int additionalSize)
|
||||||
|
{
|
||||||
|
var dataLength = AlignInt(additionalSize, SIZE_UINT32);
|
||||||
|
var newSize = _writeOffset + dataLength;
|
||||||
|
|
||||||
|
if (newSize > _capacityAfterHeader)
|
||||||
|
{
|
||||||
|
Resize(Math.Max((int)_capacityAfterHeader * 2, newSize));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Auxiliary methods for reading/writing values in Little Endian
|
||||||
|
|
||||||
|
private uint ReadUInt32LE(int offset)
|
||||||
|
{
|
||||||
|
if (BitConverter.IsLittleEndian)
|
||||||
|
{
|
||||||
|
return BitConverter.ToUInt32(_header, offset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (uint)(_header[offset] |
|
||||||
|
(_header[offset + 1] << 8) |
|
||||||
|
(_header[offset + 2] << 16) |
|
||||||
|
(_header[offset + 3] << 24));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteInt32LE(int value, int offset)
|
||||||
|
{
|
||||||
|
if (BitConverter.IsLittleEndian)
|
||||||
|
{
|
||||||
|
byte[] bytes = BitConverter.GetBytes(value);
|
||||||
|
Array.Copy(bytes, 0, _header, offset, 4);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_header[offset] = (byte)value;
|
||||||
|
_header[offset + 1] = (byte)(value >> 8);
|
||||||
|
_header[offset + 2] = (byte)(value >> 16);
|
||||||
|
_header[offset + 3] = (byte)(value >> 24);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteUInt32LE(uint value, int offset)
|
||||||
|
{
|
||||||
|
if (BitConverter.IsLittleEndian)
|
||||||
|
{
|
||||||
|
byte[] bytes = BitConverter.GetBytes(value);
|
||||||
|
Array.Copy(bytes, 0, _header, offset, 4);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_header[offset] = (byte)value;
|
||||||
|
_header[offset + 1] = (byte)(value >> 8);
|
||||||
|
_header[offset + 2] = (byte)(value >> 16);
|
||||||
|
_header[offset + 3] = (byte)(value >> 24);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteInt64LE(long value, int offset)
|
||||||
|
{
|
||||||
|
if (BitConverter.IsLittleEndian)
|
||||||
|
{
|
||||||
|
byte[] bytes = BitConverter.GetBytes(value);
|
||||||
|
Array.Copy(bytes, 0, _header, offset, 8);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_header[offset] = (byte)value;
|
||||||
|
_header[offset + 1] = (byte)(value >> 8);
|
||||||
|
_header[offset + 2] = (byte)(value >> 16);
|
||||||
|
_header[offset + 3] = (byte)(value >> 24);
|
||||||
|
_header[offset + 4] = (byte)(value >> 32);
|
||||||
|
_header[offset + 5] = (byte)(value >> 40);
|
||||||
|
_header[offset + 6] = (byte)(value >> 48);
|
||||||
|
_header[offset + 7] = (byte)(value >> 56);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteUInt64LE(ulong value, int offset)
|
||||||
|
{
|
||||||
|
if (BitConverter.IsLittleEndian)
|
||||||
|
{
|
||||||
|
byte[] bytes = BitConverter.GetBytes(value);
|
||||||
|
Array.Copy(bytes, 0, _header, offset, 8);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_header[offset] = (byte)value;
|
||||||
|
_header[offset + 1] = (byte)(value >> 8);
|
||||||
|
_header[offset + 2] = (byte)(value >> 16);
|
||||||
|
_header[offset + 3] = (byte)(value >> 24);
|
||||||
|
_header[offset + 4] = (byte)(value >> 32);
|
||||||
|
_header[offset + 5] = (byte)(value >> 40);
|
||||||
|
_header[offset + 6] = (byte)(value >> 48);
|
||||||
|
_header[offset + 7] = (byte)(value >> 56);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace AsarSharp.PickleTools
|
||||||
|
{
|
||||||
|
public class PickleIterator
|
||||||
|
{
|
||||||
|
private readonly byte[] _payload;
|
||||||
|
private readonly int _payloadOffset;
|
||||||
|
private int _readIndex;
|
||||||
|
private readonly int _endIndex;
|
||||||
|
|
||||||
|
public PickleIterator(Pickle pickle)
|
||||||
|
{
|
||||||
|
_payload = pickle.GetHeader();
|
||||||
|
_payloadOffset = pickle.GetHeaderSize();
|
||||||
|
_readIndex = 0;
|
||||||
|
_endIndex = pickle.GetPayloadSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ReadBool()
|
||||||
|
{
|
||||||
|
return ReadInt() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ReadInt()
|
||||||
|
{
|
||||||
|
return ReadBytes(Pickle.SIZE_INT32, BitConverter.ToInt32);
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint ReadUInt32()
|
||||||
|
{
|
||||||
|
return ReadBytes(Pickle.SIZE_UINT32, BitConverter.ToUInt32);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long ReadInt64()
|
||||||
|
{
|
||||||
|
return ReadBytes(Pickle.SIZE_INT64, BitConverter.ToInt64);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong ReadUInt64()
|
||||||
|
{
|
||||||
|
return ReadBytes(Pickle.SIZE_UINT64, BitConverter.ToUInt64);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float ReadFloat()
|
||||||
|
{
|
||||||
|
return ReadBytes(Pickle.SIZE_FLOAT, BitConverter.ToSingle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double ReadDouble()
|
||||||
|
{
|
||||||
|
return ReadBytes(Pickle.SIZE_DOUBLE, BitConverter.ToDouble);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReadString()
|
||||||
|
{
|
||||||
|
int length = ReadInt();
|
||||||
|
return Encoding.UTF8.GetString(ReadBytes(length));
|
||||||
|
}
|
||||||
|
|
||||||
|
private T ReadBytes<T>(int length, Func<byte[], int, T> converter)
|
||||||
|
{
|
||||||
|
int readPayloadOffset = GetReadPayloadOffsetAndAdvance(length);
|
||||||
|
return converter(_payload, readPayloadOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] ReadBytes(int length)
|
||||||
|
{
|
||||||
|
int readPayloadOffset = GetReadPayloadOffsetAndAdvance(length);
|
||||||
|
byte[] result = new byte[length];
|
||||||
|
Array.Copy(_payload, readPayloadOffset, result, 0, length);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetReadPayloadOffsetAndAdvance(int length)
|
||||||
|
{
|
||||||
|
if (length > _endIndex - _readIndex)
|
||||||
|
{
|
||||||
|
_readIndex = _endIndex;
|
||||||
|
throw new InvalidOperationException($"Failed to read data with length of {length}");
|
||||||
|
}
|
||||||
|
int readPayloadOffset = _payloadOffset + _readIndex;
|
||||||
|
Advance(length);
|
||||||
|
return readPayloadOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Advance(int size)
|
||||||
|
{
|
||||||
|
int alignedSize = Pickle.AlignInt(size, Pickle.SIZE_UINT32);
|
||||||
|
if (_endIndex - _readIndex < alignedSize)
|
||||||
|
{
|
||||||
|
_readIndex = _endIndex;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_readIndex += alignedSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyTitle("AsarSharp")]
|
||||||
|
[assembly: AssemblyDescription("")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("AsarSharp")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © 2025")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("BEAA604A-402A-4387-8903-A53FC913A26E")]
|
||||||
|
|
||||||
|
// Version information for an assembly consists of the following four values:
|
||||||
|
//
|
||||||
|
// Major Version
|
||||||
|
// Minor Version
|
||||||
|
// Build Number
|
||||||
|
// Revision
|
||||||
|
//
|
||||||
|
// 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.0.0")]
|
||||||
|
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace AsarSharp.Utils
|
||||||
|
{
|
||||||
|
internal static class Extensions
|
||||||
|
{
|
||||||
|
public static string GetRelativePath(string relativeTo, string path)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(relativeTo))
|
||||||
|
throw new ArgumentNullException(nameof(relativeTo));
|
||||||
|
if (string.IsNullOrEmpty(path))
|
||||||
|
throw new ArgumentNullException(nameof(path));
|
||||||
|
|
||||||
|
var fullRelativeTo = Path.GetFullPath(relativeTo);
|
||||||
|
var fullPath = Path.GetFullPath(path);
|
||||||
|
|
||||||
|
if (string.Equals(fullRelativeTo, fullPath, StringComparison.OrdinalIgnoreCase))
|
||||||
|
return "";
|
||||||
|
|
||||||
|
var relativeToUri = new Uri(fullRelativeTo.EndsWith(Path.DirectorySeparatorChar.ToString())
|
||||||
|
? fullRelativeTo
|
||||||
|
: fullRelativeTo + Path.DirectorySeparatorChar);
|
||||||
|
var pathUri = new Uri(fullPath.EndsWith(Path.DirectorySeparatorChar.ToString()) && !File.Exists(fullPath)
|
||||||
|
? fullPath
|
||||||
|
: fullPath + (Directory.Exists(fullPath) ? Path.DirectorySeparatorChar.ToString() : ""));
|
||||||
|
|
||||||
|
var relativeUri = relativeToUri.MakeRelativeUri(pathUri);
|
||||||
|
var relativePath = Uri.UnescapeDataString(relativeUri.ToString())
|
||||||
|
.Replace('/', Path.DirectorySeparatorChar);
|
||||||
|
|
||||||
|
return relativePath.TrimEnd(Path.DirectorySeparatorChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetDirectoryName(string path)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(path))
|
||||||
|
return ".";
|
||||||
|
|
||||||
|
string result = Path.GetDirectoryName(path);
|
||||||
|
|
||||||
|
// If the result is an empty string, return “.” as in Node.js
|
||||||
|
if (string.IsNullOrEmpty(result))
|
||||||
|
return ".";
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CopyDirectory(string sourceDir, string destinationDir)
|
||||||
|
{
|
||||||
|
// Create the destination directory
|
||||||
|
Directory.CreateDirectory(destinationDir);
|
||||||
|
|
||||||
|
// Get all files in the source directory
|
||||||
|
foreach (var file in Directory.GetFiles(sourceDir))
|
||||||
|
{
|
||||||
|
var destFile = Path.Combine(destinationDir, Path.GetFileName(file));
|
||||||
|
File.Copy(file, destFile, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively copy all subdirectories
|
||||||
|
foreach (var dir in Directory.GetDirectories(sourceDir))
|
||||||
|
{
|
||||||
|
var destDir = Path.Combine(destinationDir, Path.GetFileName(dir));
|
||||||
|
CopyDirectory(dir, destDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetBasePath(string dir)
|
||||||
|
{
|
||||||
|
// Look for the last path delimiter before any pattern
|
||||||
|
int wildcardIndex = dir.IndexOfAny(new[] { '*', '?' });
|
||||||
|
if (wildcardIndex == -1)
|
||||||
|
{
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lastSeparatorIndex = dir.LastIndexOf(Path.DirectorySeparatorChar, wildcardIndex);
|
||||||
|
if (lastSeparatorIndex == -1)
|
||||||
|
{
|
||||||
|
return ".";
|
||||||
|
}
|
||||||
|
|
||||||
|
return dir.Substring(0, lastSeparatorIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetUnixFilePermission(string filePath, string permission)
|
||||||
|
{
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Use chmod
|
||||||
|
var process = new System.Diagnostics.Process
|
||||||
|
{
|
||||||
|
StartInfo = new System.Diagnostics.ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "chmod",
|
||||||
|
Arguments = $"{permission} \"{filePath}\"",
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
CreateNoWindow = true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
process.Start();
|
||||||
|
process.WaitForExit();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void CreateSymbolicLink(string linkTarget, string linkPath)
|
||||||
|
{
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
// On Windows, creating symlinks requires special privileges,
|
||||||
|
// so on many systems it simply won't work without administrator privileges
|
||||||
|
NativeMethods.CreateSymbolicLink(linkPath, linkTarget,
|
||||||
|
Directory.Exists(linkTarget)
|
||||||
|
? NativeMethods.SymLinkFlag.Directory
|
||||||
|
: NativeMethods.SymLinkFlag.File);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In Unix systems we use the corresponding system call
|
||||||
|
var process = new System.Diagnostics.Process
|
||||||
|
{
|
||||||
|
StartInfo = new System.Diagnostics.ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "ln",
|
||||||
|
Arguments = $"-s \"{linkTarget}\" \"{linkPath}\"",
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
CreateNoWindow = true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
process.Start();
|
||||||
|
process.WaitForExit();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static bool IsWindowsPlatform()
|
||||||
|
{
|
||||||
|
return Environment.OSVersion.Platform == PlatformID.Win32NT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace AsarSharp.Utils
|
||||||
|
{
|
||||||
|
internal static class NativeMethods
|
||||||
|
{
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
|
public static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, SymLinkFlag dwFlags);
|
||||||
|
|
||||||
|
public enum SymLinkFlag
|
||||||
|
{
|
||||||
|
File = 0,
|
||||||
|
Directory = 1,
|
||||||
|
AllowUnprivilegedCreate = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<packages>
|
||||||
|
<package id="Newtonsoft.Json" version="13.0.3" targetFramework="net48" />
|
||||||
|
</packages>
|
||||||
+111
@@ -0,0 +1,111 @@
|
|||||||
|
# Contributing to WeMod Patcher
|
||||||
|
|
||||||
|
Thank you for your interest in the WeMod Patcher project! This document provides guidelines for contributing to the project.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Development Environment Setup](#development-environment-setup)
|
||||||
|
- [Bug Reports](#bug-reports)
|
||||||
|
- [Feature Suggestions](#feature-suggestions)
|
||||||
|
- [Creating a Pull Request](#creating-a-pull-request)
|
||||||
|
- [Code Style](#code-style)
|
||||||
|
- [Testing](#testing)
|
||||||
|
- [License](#license)
|
||||||
|
|
||||||
|
## Code of Conduct
|
||||||
|
|
||||||
|
By participating in this project, you commit to maintaining respectful interactions with all community members. Any form of insults, harassment, or other unacceptable behavior will not be tolerated.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
The project consists of the following main components:
|
||||||
|
|
||||||
|
- **WeModPatcher** - Main project containing the patcher logic and user interface
|
||||||
|
- **AsarSharp** - Library for working with ASAR archives (used for unpacking and modifying WeMod files)
|
||||||
|
- **Core** - Core of the patcher, including static and dynamic patching
|
||||||
|
- **Models** - Data models used in the project
|
||||||
|
- **View** - User interface components
|
||||||
|
|
||||||
|
## Development Environment Setup
|
||||||
|
|
||||||
|
1. Clone the repository:
|
||||||
|
```
|
||||||
|
git clone https://github.com/k1tbyte/Wemod-Patcher.git
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Open the solution `Wemod-Patcher.sln` in Visual Studio or JetBrains Rider.
|
||||||
|
|
||||||
|
3. Restore NuGet packages.
|
||||||
|
|
||||||
|
4. Build the project.
|
||||||
|
|
||||||
|
## Bug Reports
|
||||||
|
|
||||||
|
If you've found a bug, please create an Issue with a detailed description:
|
||||||
|
|
||||||
|
- WeMod Patcher version
|
||||||
|
- WeMod version where the problem occurred
|
||||||
|
- Detailed steps to reproduce the bug
|
||||||
|
- Expected and actual behavior
|
||||||
|
- Screenshots or error logs (if available)
|
||||||
|
|
||||||
|
## Feature Suggestions
|
||||||
|
|
||||||
|
Suggestions for new features or improvements are welcome! Create an Issue describing your idea, explaining:
|
||||||
|
|
||||||
|
- What problem the proposed improvement solves
|
||||||
|
- How you envision implementing this feature
|
||||||
|
- Potential alternatives you've considered
|
||||||
|
|
||||||
|
## Creating a Pull Request
|
||||||
|
|
||||||
|
1. Fork the repository.
|
||||||
|
2. Create a branch with a descriptive name:
|
||||||
|
```
|
||||||
|
git checkout -b feature/feature-name
|
||||||
|
```
|
||||||
|
or
|
||||||
|
```
|
||||||
|
git checkout -b fix/fix-name
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Make the necessary changes and commit with clear, descriptive messages.
|
||||||
|
|
||||||
|
4. Ensure your code follows the project's style.
|
||||||
|
|
||||||
|
5. Push the branch to your fork:
|
||||||
|
```
|
||||||
|
git push origin your-branch-name
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Create a Pull Request to the main repository.
|
||||||
|
|
||||||
|
7. In the Pull Request description, explain the changes made and why they're necessary.
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
|
||||||
|
- Use C# naming conventions:
|
||||||
|
- PascalCase for class, method, and property names
|
||||||
|
- camelCase for local variables and parameters
|
||||||
|
- _camelCase for private fields
|
||||||
|
|
||||||
|
- Add comments for complex code sections or patching methods
|
||||||
|
|
||||||
|
- Follow SOLID and DRY principles
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Before submitting a Pull Request, ensure that:
|
||||||
|
|
||||||
|
1. Your code compiles without errors
|
||||||
|
2. You've manually tested the functionality
|
||||||
|
3. The patch works with the current version of WeMod
|
||||||
|
4. Changes don't break existing functionality
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
By contributing, you agree that your contributions will be licensed under the [Apache License 2.0](LICENSE.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Thank you for contributing to the WeMod Patcher project!
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
<h1>WeMod Patcher</h1>
|
<h1>WeMod Patcher</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -15,11 +19,14 @@ With this patch you will be able to use the latest version together with Pro.
|
|||||||
## 💫 What features will be available?
|
## 💫 What features will be available?
|
||||||
|
|
||||||
✅ Unlimited usage time <br/>
|
✅ Unlimited usage time <br/>
|
||||||
✅ No ads <br/>
|
✅ Disabling automatic updates (optional) <br/>
|
||||||
|
✅ Automatic patching of new WeMod versions <br/>
|
||||||
✅ AI Game guides <br/>
|
✅ AI Game guides <br/>
|
||||||
✅ Saving mods <br/>
|
✅ Saving mods <br/>
|
||||||
|
✅ Exclusive to pro subscription customization for hacks <br/>
|
||||||
|
✅ Hotkeys (hotkey functionality is broken after static patching for unknown reason) <br/>
|
||||||
❌ Connect phone <br/>
|
❌ Connect phone <br/>
|
||||||
❌ Hotkeys (hotkey functionality breaks after patch for unknown reason)
|
|
||||||
|
|
||||||
## 👀 How to use?
|
## 👀 How to use?
|
||||||
|
|
||||||
@@ -34,15 +41,28 @@ With this patch you will be able to use the latest version together with Pro.
|
|||||||
- I applied the patch but when I inject I get stuck on 'Loading mods...'.
|
- I applied the patch but when I inject I get stuck on 'Loading mods...'.
|
||||||
- Just close WeMod and try again
|
- Just close WeMod and try again
|
||||||
- During the game, some hacks are enabled without my input
|
- During the game, some hacks are enabled without my input
|
||||||
- This is a bug after the patch, you have to turn off hotkeys in WeMod settings
|
- This is a bug after the static patch, you have to turn off hotkeys in WeMod settings
|
||||||
- Why is the patch executable file size so large? It seems to me that you want to harm my system.
|
- VirusTotal claims that this program is a malware/trojan.
|
||||||
- The thing is that the application is written in Electron, so it also puts chromium, nodejs and some libraries in the exe. Maybe Electron is a temporary solution and in the future I will consider another option
|
- Perhaps the patcher does have the same signatures as malware (virtual memory patching). But this is a false positive, you can look at the source code or even build the patcher yourself.
|
||||||
|
- Does this application transfer any data to the Internet from my computer?
|
||||||
|
- The short answer is NO. This application does not need access to the Internet. The most it does is download updates if you want it to.
|
||||||
|
- What makes this application better than other patchers?
|
||||||
|
- All actions related to patches are performed on your computer. No files of unknown origin will be downloaded.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🖼️ Screenshots
|
||||||
|

|
||||||
|

|
||||||
|
---
|
||||||
|
|
||||||
## 📜 License
|
## 📜 License
|
||||||
This project is licensed under the Apache-2.0 - see the [LICENSE](LICENSE.txt) file for details.
|
This project is licensed under the Apache-2.0 - see the [LICENSE](LICENSE.md) file for details.
|
||||||
|
|
||||||
---
|
---
|
||||||
## ❤️ Support
|
## ❤️ Support
|
||||||
[](https://ko-fi.com/kitbyte)
|
[](https://ko-fi.com/kitbyte)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[](https://www.star-history.com/#k1tbyte/Wemod-Patcher&Date)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<configuration>
|
||||||
|
<startup>
|
||||||
|
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
|
||||||
|
</startup>
|
||||||
|
</configuration>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<Application x:Class="WeModPatcher.App"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="clr-namespace:WeModPatcher"
|
||||||
|
xmlns:converters="clr-namespace:WeModPatcher.Converters">
|
||||||
|
<Application.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceDictionary Source="Locale/lang.en-US.xaml"/>
|
||||||
|
<ResourceDictionary Source="Style/ColorScheme.xaml"/>
|
||||||
|
<ResourceDictionary Source="Style/Styles.xaml"/>
|
||||||
|
<ResourceDictionary Source="Style/Icons.xaml"/>
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
|
||||||
|
<FontFamily x:Key="Inter" >pack://application:,,,/Style/#Inter 18pt 18pt</FontFamily>
|
||||||
|
|
||||||
|
<converters:ToVisibilityConverter x:Key="ToVisibilityConverter"/>
|
||||||
|
<converters:ToVisibilityInvertedConverter x:Key="ToVisibilityInvertedConverter"/>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</Application.Resources>
|
||||||
|
</Application>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using WeModPatcher.Core;
|
||||||
|
using WeModPatcher.Core.Services;
|
||||||
|
using WeModPatcher.View.MainWindow;
|
||||||
|
using MessageBox = System.Windows.Forms.MessageBox;
|
||||||
|
|
||||||
|
namespace WeModPatcher
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for App.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class App
|
||||||
|
{
|
||||||
|
protected override void OnStartup(StartupEventArgs e)
|
||||||
|
{
|
||||||
|
LocalizationManager.Initialize();
|
||||||
|
this.MainWindow.Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public new static void Shutdown()
|
||||||
|
{
|
||||||
|
Current.Dispatcher.Invoke(() => Current.Shutdown());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using WeModPatcher.Models;
|
||||||
|
|
||||||
|
namespace WeModPatcher
|
||||||
|
{
|
||||||
|
public static class Constants
|
||||||
|
{
|
||||||
|
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[] WeModBrandNames = { "Wand", "WeMod" };
|
||||||
|
public const string AppSettingsFileName = "appsettings.json";
|
||||||
|
|
||||||
|
public const string ProxyDllResouceName = "proxydll";
|
||||||
|
|
||||||
|
// cmp dword ptr [rdx], 0
|
||||||
|
// jnz loc_XXXXXXXX
|
||||||
|
// mov rsi, rdx
|
||||||
|
/*public static Signature ExePatchSignature = new Signature(
|
||||||
|
"83 3A 00 0F ?? ?? 01 00 00 48 89 D6 48 B8",
|
||||||
|
4,
|
||||||
|
new byte[]{ 0x84, 0x17 },
|
||||||
|
new byte[]{ 0x85, 0x22 }
|
||||||
|
);*/
|
||||||
|
|
||||||
|
/*// ...
|
||||||
|
// test eax, eax (0x85 for r/m16/32/64)
|
||||||
|
// jnz short loc_1403A4DD2 (Integrity check failed)
|
||||||
|
// call near ptr funk_1445527E0
|
||||||
|
// ...
|
||||||
|
private const string PatchSignature = "E8 ?? ?? ?? ?? ?? C0 75 ?? F6 C3 01 74 ?? 48 89 F9 E8 ?? ?? ?? ??";
|
||||||
|
private static readonly byte[] PatchBytes = { 0x31 };
|
||||||
|
private const int PatchOffset = 0x5;*/
|
||||||
|
|
||||||
|
static Constants()
|
||||||
|
{
|
||||||
|
Version = Assembly.GetExecutingAssembly().GetName().Version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace WeModPatcher.Converters
|
||||||
|
{
|
||||||
|
public abstract class BaseBooleanConverter<T> : IValueConverter
|
||||||
|
{
|
||||||
|
protected BaseBooleanConverter(T trueValue, T falseValue)
|
||||||
|
{
|
||||||
|
True = trueValue;
|
||||||
|
False = falseValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected T True { get; set; }
|
||||||
|
protected T False { get; set; }
|
||||||
|
|
||||||
|
public virtual object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case null:
|
||||||
|
return False;
|
||||||
|
case bool booleanValue:
|
||||||
|
return booleanValue ? True : False;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(value is int intValue))
|
||||||
|
{
|
||||||
|
return True;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (parameter)
|
||||||
|
{
|
||||||
|
case null:
|
||||||
|
return intValue == 0 ? False : True;
|
||||||
|
case int param:
|
||||||
|
return intValue > param ? True : False;
|
||||||
|
default:
|
||||||
|
//Because object not null
|
||||||
|
return True;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
return value is T t && EqualityComparer<T>.Default.Equals(t, True);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace WeModPatcher.Converters
|
||||||
|
{
|
||||||
|
internal sealed class ToVisibilityConverter : BaseBooleanConverter<Visibility>
|
||||||
|
{
|
||||||
|
public ToVisibilityConverter() :
|
||||||
|
base(Visibility.Visible, Visibility.Collapsed)
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class ToVisibilityInvertedConverter : BaseBooleanConverter<Visibility>
|
||||||
|
{
|
||||||
|
public ToVisibilityInvertedConverter() :
|
||||||
|
base(Visibility.Collapsed, Visibility.Visible)
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using AsarSharp;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using WeModPatcher.Models;
|
||||||
|
using WeModPatcher.Utils;
|
||||||
|
using WeModPatcher.View.MainWindow;
|
||||||
|
using Application = System.Windows.Application;
|
||||||
|
|
||||||
|
namespace WeModPatcher.Core
|
||||||
|
{
|
||||||
|
public class Patcher
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
public Patcher(WeModConfig weModConfig, Action<string, ELogType> logger, PatchConfig config)
|
||||||
|
{
|
||||||
|
_weModConfig = weModConfig;
|
||||||
|
_logger = logger;
|
||||||
|
_config = config;
|
||||||
|
|
||||||
|
_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 string ApplyJsPatch(string fileName, string js, PatcherConfig.PatchEntry patch, EPatchType patchType)
|
||||||
|
{
|
||||||
|
if (patch.Applied)
|
||||||
|
{
|
||||||
|
return js;
|
||||||
|
}
|
||||||
|
|
||||||
|
var matches = patch.Target.Matches(js);
|
||||||
|
if (matches.Count == 0)
|
||||||
|
{
|
||||||
|
return js;
|
||||||
|
}
|
||||||
|
|
||||||
|
var prefix = $"[PATCHER] [{patchType} -> {patch.Name}]";
|
||||||
|
|
||||||
|
if(matches.Count > 1 && patch.SingleMatch)
|
||||||
|
{
|
||||||
|
throw new Exception(
|
||||||
|
$"{prefix} Patch failed. Multiple target functions found. Looks like the version is not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (patch.Resolver != null)
|
||||||
|
{
|
||||||
|
string resolvedField = patch.Resolver.Handler(matches[0].Value);
|
||||||
|
if (string.IsNullOrEmpty(resolvedField))
|
||||||
|
{
|
||||||
|
throw new Exception($"{prefix} Resolver failed to find field name");
|
||||||
|
}
|
||||||
|
|
||||||
|
patch.Patch = patch.Patch.Replace(patch.Resolver.Placeholder, resolvedField);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger($"{prefix} Found target function in: " + Path.GetFileName(fileName), ELogType.Info);
|
||||||
|
|
||||||
|
string newJs = patch.Target.Replace(js, patch.Patch);
|
||||||
|
File.WriteAllText(fileName, newJs);
|
||||||
|
_logger($"{prefix} Patch applied", ELogType.Success);
|
||||||
|
patch.Applied = true;
|
||||||
|
|
||||||
|
return newJs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PatchAsar()
|
||||||
|
{
|
||||||
|
var items = Directory.EnumerateFiles(_unpackedPath)
|
||||||
|
.Where(file => !Directory.Exists(file) && Regex.IsMatch(Path.GetFileName(file), @"^app-\w+|index\.js"))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (!items.Any())
|
||||||
|
{
|
||||||
|
throw new Exception("[PATCHER] No app bundle found");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track patches that still need to be completed
|
||||||
|
var remainingPatches = new HashSet<EPatchType>(_config.PatchTypes);
|
||||||
|
var patcherConfig = PatcherConfig.GetInstance();
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
if (remainingPatches.Count == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
string data = File.ReadAllText(item);
|
||||||
|
|
||||||
|
// Iterate over a copy of the list so we can modify the HashSet
|
||||||
|
foreach (var entry in remainingPatches.ToList())
|
||||||
|
{
|
||||||
|
var entries = patcherConfig[entry];
|
||||||
|
foreach (var patchEntry in entries)
|
||||||
|
{
|
||||||
|
// Update data in memory so subsequent patches in the same file work on latest content
|
||||||
|
data = ApplyJsPatch(item, data, patchEntry, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if all entries for this patch type are applied
|
||||||
|
if (entries.All(x => x.Applied))
|
||||||
|
{
|
||||||
|
remainingPatches.Remove(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(remainingPatches.Count > 0)
|
||||||
|
{
|
||||||
|
var failedPatches = string.Join(", ", remainingPatches.Select(p => p.ToString()));
|
||||||
|
throw new Exception($"[PATCHER] Failed to apply patches: {failedPatches}. The version may not be supported.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AttachProxyDll()
|
||||||
|
{
|
||||||
|
var assembly = Assembly.GetExecutingAssembly();
|
||||||
|
var dll = assembly.GetManifestResourceStream(Constants.ProxyDllResouceName);
|
||||||
|
if (dll == null)
|
||||||
|
{
|
||||||
|
throw new Exception("[PATCHER] Proxy DLL resource not found");
|
||||||
|
}
|
||||||
|
var destPath = Path.Combine(_weModConfig.RootDirectory, "version.dll");
|
||||||
|
using (var fileStream = File.Create(destPath))
|
||||||
|
{
|
||||||
|
dll.CopyTo(fileStream);
|
||||||
|
}
|
||||||
|
_logger("[PATCHER] Proxy DLL attached", ELogType.Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Patch()
|
||||||
|
{
|
||||||
|
Common.TryKillProcess(_weModConfig.BrandName);
|
||||||
|
if (!File.Exists(_backupPath))
|
||||||
|
{
|
||||||
|
_logger("[PATCHER] Creating backup...", ELogType.Info);
|
||||||
|
File.Copy(_asarPath, _backupPath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger("[PATCHER] Backup already exists", ELogType.Warn);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!File.Exists(_asarPath))
|
||||||
|
{
|
||||||
|
throw new Exception("app.asar not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger("[PATCHER] Extracting app.asar...", ELogType.Info);
|
||||||
|
AsarExtractor.ExtractAll(_asarPath, _unpackedPath);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new Exception($"[PATCHER] Failed to unpack app.asar: {e.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
PatchAsar();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
new AsarCreator(_unpackedPath, _asarPath, new CreateOptions
|
||||||
|
{
|
||||||
|
Unpack = new Regex(@"^static\\unpacked.*$")
|
||||||
|
}).CreatePackageWithOptions();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new Exception($"[PATCHER] Failed to pack app.asar: {e.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
AttachProxyDll();
|
||||||
|
|
||||||
|
_logger("[PATCHER] Done!", ELogType.Success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using WeModPatcher.Models;
|
||||||
|
|
||||||
|
namespace WeModPatcher.Core
|
||||||
|
{
|
||||||
|
public static class PatcherConfig
|
||||||
|
{
|
||||||
|
public class ResolveContext
|
||||||
|
{
|
||||||
|
public string Placeholder { get; set; }
|
||||||
|
public Func<string, string> Handler { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PatchEntry
|
||||||
|
{
|
||||||
|
public Regex Target { get; set; }
|
||||||
|
public string Patch { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public bool Applied { get; set; }
|
||||||
|
public bool SingleMatch { get; set; } = true;
|
||||||
|
public ResolveContext Resolver { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Dictionary<EPatchType, PatchEntry[]> GetInstance()
|
||||||
|
{
|
||||||
|
return new Dictionary<EPatchType, PatchEntry[]>()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
EPatchType.ActivatePro,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new PatchEntry
|
||||||
|
{
|
||||||
|
Resolver = new ResolveContext
|
||||||
|
{
|
||||||
|
Handler = (targetFunction) =>
|
||||||
|
{
|
||||||
|
var fetchMatch = Regex.Match(targetFunction, @"return\s+this\.#(\w+)\.fetch");
|
||||||
|
return fetchMatch.Success ? fetchMatch.Groups[1].Value : null;
|
||||||
|
},
|
||||||
|
Placeholder = "<service_name>"
|
||||||
|
},
|
||||||
|
Name = "getUserAccount",
|
||||||
|
Target = new Regex(@"getUserAccount\(\)\{.*?return\s+this\.#\w+\.fetch\(\{.*?\}\)\}",
|
||||||
|
RegexOptions.Singleline),
|
||||||
|
Patch =
|
||||||
|
"getUserAccount(){return this.#<service_name>.fetch({endpoint:\"/v3/account\",method:\"GET\",name:\"/v3/account\",collectMetrics:0}).then(response=>{response.subscription={period:\"yearly\",state:\"active\"};return response;})}"
|
||||||
|
},
|
||||||
|
new PatchEntry
|
||||||
|
{
|
||||||
|
Resolver = new ResolveContext
|
||||||
|
{
|
||||||
|
Handler = (targetFunction) =>
|
||||||
|
{
|
||||||
|
var match = Regex.Match(targetFunction, @"return\s+this\.#(\w+)\.post");
|
||||||
|
return match.Success ? match.Groups[1].Value : null;
|
||||||
|
},
|
||||||
|
Placeholder = "<service_name>"
|
||||||
|
},
|
||||||
|
Name = "setAccountWandBrandExperience",
|
||||||
|
Target = new Regex(
|
||||||
|
@"setAccountWandBrandExperience\(\){.*?return\s+this\.#\w+\.post\(""/v3/account/brand_experience_wand""\)}",
|
||||||
|
RegexOptions.Singleline),
|
||||||
|
Patch =
|
||||||
|
"setAccountWandBrandExperience(){return this.#<service_name>.post(\"/v3/account/brand_experience_wand\").then(response=>{response.subscription={period:\"yearly\",state:\"active\"};return response;})}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EPatchType.DisableUpdates,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new PatchEntry
|
||||||
|
{
|
||||||
|
Target = new Regex(@"registerHandler\(""ACTION_CHECK_FOR_UPDATE"".*?\)\)\)\)",
|
||||||
|
RegexOptions.Singleline),
|
||||||
|
Patch = "registerHandler(\"ACTION_CHECK_FOR_UPDATE\",(e=>expectUpdateFeedUrl(e,(e=>null)))"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EPatchType.DevToolsOnF12,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new PatchEntry
|
||||||
|
{
|
||||||
|
Resolver = new ResolveContext
|
||||||
|
{
|
||||||
|
Handler = (matchContent) => {
|
||||||
|
var match = Regex.Match(matchContent, @"this\.#(\w+)\(""ACTION_OPEN_DEV_TOOLS""\)");
|
||||||
|
return match.Success ? match.Groups[1].Value : null;
|
||||||
|
},
|
||||||
|
Placeholder = "<dispatch_method>"
|
||||||
|
},
|
||||||
|
Target = new Regex(@"document\.addEventListener\(""keydown"",\s*\((?<arg>\w+)\s*=>\s*\{[^}]*?""ACTION_OPEN_DEV_TOOLS""[^}]*?\}\)\)", RegexOptions.Singleline),
|
||||||
|
Patch = "document.addEventListener(\"keydown\",(${arg}=>{\"F12\"!==${arg}.key||this.#<dispatch_method>(\"ACTION_OPEN_DEV_TOOLS\")}))"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace WeModPatcher.Core.Services
|
||||||
|
{
|
||||||
|
public static class LocalizationManager
|
||||||
|
{
|
||||||
|
public static readonly List<CultureInfo> SupportedLanguages = new List<CultureInfo>
|
||||||
|
{
|
||||||
|
new CultureInfo("en-US"),
|
||||||
|
new CultureInfo("zh-CN"),
|
||||||
|
new CultureInfo("de-DE"),
|
||||||
|
new CultureInfo("fr-FR"),
|
||||||
|
new CultureInfo("es-ES"),
|
||||||
|
new CultureInfo("it-IT"),
|
||||||
|
new CultureInfo("pt-BR"),
|
||||||
|
new CultureInfo("pl-PL"),
|
||||||
|
new CultureInfo("ru-RU"),
|
||||||
|
new CultureInfo("uk-UA"),
|
||||||
|
new CultureInfo("ja-JP"),
|
||||||
|
new CultureInfo("tr-TR")
|
||||||
|
};
|
||||||
|
|
||||||
|
private static CultureInfo _currentLanguage;
|
||||||
|
private static ResourceDictionary _englishBaseDictionary;
|
||||||
|
|
||||||
|
public static CultureInfo CurrentLanguage
|
||||||
|
{
|
||||||
|
get => _currentLanguage;
|
||||||
|
set => SetLanguage(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Initialize()
|
||||||
|
{
|
||||||
|
// Load English as the base fallback dictionary
|
||||||
|
_englishBaseDictionary = new ResourceDictionary
|
||||||
|
{
|
||||||
|
Source = new Uri("Locale/lang.en-US.xaml", UriKind.Relative)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try to load saved language preference
|
||||||
|
var savedLanguage = SettingsManager.LoadSettings()?.Language;
|
||||||
|
CultureInfo targetCulture = null;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(savedLanguage))
|
||||||
|
{
|
||||||
|
targetCulture = SupportedLanguages.FirstOrDefault(c => c.Name == savedLanguage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetCulture == null)
|
||||||
|
{
|
||||||
|
// Fall back to system culture detection
|
||||||
|
var systemCulture = Thread.CurrentThread.CurrentUICulture;
|
||||||
|
targetCulture = SupportedLanguages.FirstOrDefault(c =>
|
||||||
|
c.Name == systemCulture.Name ||
|
||||||
|
c.TwoLetterISOLanguageName == systemCulture.TwoLetterISOLanguageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
SetLanguage(targetCulture ?? SupportedLanguages[0], saveSettings: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetLanguage(CultureInfo culture, bool saveSettings = true)
|
||||||
|
{
|
||||||
|
if (culture == null)
|
||||||
|
throw new ArgumentNullException(nameof(culture));
|
||||||
|
|
||||||
|
if (Equals(culture, _currentLanguage))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var supportedCulture = SupportedLanguages.FirstOrDefault(c => c.Name == culture.Name);
|
||||||
|
if (supportedCulture == null)
|
||||||
|
{
|
||||||
|
supportedCulture = SupportedLanguages[0]; // Default to English
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentLanguage = supportedCulture;
|
||||||
|
Thread.CurrentThread.CurrentUICulture = supportedCulture;
|
||||||
|
|
||||||
|
// Create the locale dictionary with English as base for fallback
|
||||||
|
var localeDict = new ResourceDictionary();
|
||||||
|
|
||||||
|
// First, add English base dictionary for fallback
|
||||||
|
if (_englishBaseDictionary != null && supportedCulture.Name != SupportedLanguages[0].Name)
|
||||||
|
{
|
||||||
|
foreach (var key in _englishBaseDictionary.Keys)
|
||||||
|
{
|
||||||
|
localeDict[key] = _englishBaseDictionary[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then overlay with the selected language (will override English keys)
|
||||||
|
var targetDict = new ResourceDictionary
|
||||||
|
{
|
||||||
|
Source = new Uri($"Locale/lang.{supportedCulture.Name}.xaml", UriKind.Relative)
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (DictionaryEntry entry in targetDict)
|
||||||
|
{
|
||||||
|
localeDict[entry.Key] = targetDict[entry.Key];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find and replace the old locale dictionary
|
||||||
|
var oldDict = Application.Current.Resources.MergedDictionaries
|
||||||
|
.FirstOrDefault(d => d.Source != null && d.Source.OriginalString.StartsWith("Locale/lang."));
|
||||||
|
|
||||||
|
if (oldDict != null)
|
||||||
|
{
|
||||||
|
var index = Application.Current.Resources.MergedDictionaries.IndexOf(oldDict);
|
||||||
|
Application.Current.Resources.MergedDictionaries.Remove(oldDict);
|
||||||
|
Application.Current.Resources.MergedDictionaries.Insert(index, localeDict);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Application.Current.Resources.MergedDictionaries.Add(localeDict);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (saveSettings)
|
||||||
|
{
|
||||||
|
SettingsManager.SaveSettings(new AppSettings { Language = supportedCulture.Name });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetLanguageDisplayName(CultureInfo culture)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var dict = new ResourceDictionary
|
||||||
|
{
|
||||||
|
Source = new Uri($"Locale/lang.{culture.Name}.xaml", UriKind.Relative)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dict.Contains("language_display_name"))
|
||||||
|
{
|
||||||
|
return dict["language_display_name"] as string ?? culture.NativeName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Fallback to native name if loading fails
|
||||||
|
}
|
||||||
|
|
||||||
|
return culture.NativeName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace WeModPatcher.Core.Services
|
||||||
|
{
|
||||||
|
public class AppSettings
|
||||||
|
{
|
||||||
|
public string Language { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SettingsManager
|
||||||
|
{
|
||||||
|
private static readonly string SettingsPath = Path.Combine(
|
||||||
|
AppDomain.CurrentDomain.BaseDirectory,
|
||||||
|
Constants.AppSettingsFileName);
|
||||||
|
|
||||||
|
public static AppSettings LoadSettings()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(SettingsPath))
|
||||||
|
{
|
||||||
|
var json = File.ReadAllText(SettingsPath);
|
||||||
|
return JsonConvert.DeserializeObject<AppSettings>(json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// Settings loading is non-critical - silently fall back to defaults
|
||||||
|
// This can fail due to file permissions, corrupted JSON, etc.
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SaveSettings(AppSettings settings)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = JsonConvert.SerializeObject(settings, Formatting.Indented);
|
||||||
|
File.WriteAllText(SettingsPath, json);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// Settings saving is non-critical - silently ignore errors
|
||||||
|
// This can fail due to file permissions or read-only directories
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:s="clr-namespace:System;assembly=mscorlib">
|
||||||
|
|
||||||
|
<!--#region Language Metadata -->
|
||||||
|
<s:String x:Key="language_display_name">Deutsch</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region MainWindow -->
|
||||||
|
<s:String x:Key="mw_title">WeMod Patcher</s:String>
|
||||||
|
<s:String x:Key="mw_update_available">Eine neue Version ist verfügbar</s:String>
|
||||||
|
<s:String x:Key="mw_folder_path">Ordnerpfad</s:String>
|
||||||
|
<s:String x:Key="mw_folder_not_found">Ordner nicht gefunden</s:String>
|
||||||
|
<s:String x:Key="mw_patch">Patch</s:String>
|
||||||
|
<s:String x:Key="mw_restore">Wiederherstellen</s:String>
|
||||||
|
<s:String x:Key="mw_source_code">Quellcode</s:String>
|
||||||
|
<s:String x:Key="mw_made_by">Mit ❤️ von k1tbyte erstellt</s:String>
|
||||||
|
<s:String x:Key="mw_star_hint">Gib einen Stern, wenn dir das geholfen hat ;)</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region Settings -->
|
||||||
|
<s:String x:Key="settings_title">Einstellungen</s:String>
|
||||||
|
<s:String x:Key="settings_language">Sprache</s:String>
|
||||||
|
<s:String x:Key="settings_save">Speichern</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region PatchVectorsPopup -->
|
||||||
|
<s:String x:Key="pv_activate_pro">WeMod Pro aktivieren</s:String>
|
||||||
|
<s:String x:Key="pv_devtools">DevTools mit F12</s:String>
|
||||||
|
<s:String x:Key="pv_disable_updates">Updates deaktivieren</s:String>
|
||||||
|
<s:String x:Key="pv_start">Starten</s:String>
|
||||||
|
<s:String x:Key="pv_popup_title">Was werden wir patchen?</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region UpdatePopup -->
|
||||||
|
<s:String x:Key="up_warning">Vor dem Update wird dringend empfohlen, Patches rückgängig zu machen, falls sie angewendet wurden</s:String>
|
||||||
|
<s:String x:Key="up_update_now">Jetzt aktualisieren</s:String>
|
||||||
|
<s:String x:Key="up_popup_title">Update verfügbar!</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:s="clr-namespace:System;assembly=mscorlib">
|
||||||
|
|
||||||
|
<!--#region Language Metadata -->
|
||||||
|
<s:String x:Key="language_display_name">English</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region MainWindow -->
|
||||||
|
<s:String x:Key="mw_title">WeMod Patcher</s:String>
|
||||||
|
<s:String x:Key="mw_update_available">A new version is available</s:String>
|
||||||
|
<s:String x:Key="mw_folder_path">Folder path</s:String>
|
||||||
|
<s:String x:Key="mw_folder_not_found">Folder not found</s:String>
|
||||||
|
<s:String x:Key="mw_patch">Patch</s:String>
|
||||||
|
<s:String x:Key="mw_restore">Restore</s:String>
|
||||||
|
<s:String x:Key="mw_source_code">Source code</s:String>
|
||||||
|
<s:String x:Key="mw_made_by">Made with ❤️ by k1tbyte</s:String>
|
||||||
|
<s:String x:Key="mw_star_hint">Put a star if you found this helpful ;)</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region Settings -->
|
||||||
|
<s:String x:Key="settings_title">Settings</s:String>
|
||||||
|
<s:String x:Key="settings_language">Language</s:String>
|
||||||
|
<s:String x:Key="settings_save">Save</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region PatchVectorsPopup -->
|
||||||
|
<s:String x:Key="pv_activate_pro">Activate WeMod Pro</s:String>
|
||||||
|
<s:String x:Key="pv_devtools">DevTools on F12</s:String>
|
||||||
|
<s:String x:Key="pv_disable_updates">Disable updates</s:String>
|
||||||
|
<s:String x:Key="pv_start">Start</s:String>
|
||||||
|
<s:String x:Key="pv_popup_title">What are we gonna patch?</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region UpdatePopup -->
|
||||||
|
<s:String x:Key="up_warning">Before updating, it is strongly recommended to roll back patches if they have been applied</s:String>
|
||||||
|
<s:String x:Key="up_update_now">Update now</s:String>
|
||||||
|
<s:String x:Key="up_popup_title">Update available!</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:s="clr-namespace:System;assembly=mscorlib">
|
||||||
|
|
||||||
|
<!--#region Language Metadata -->
|
||||||
|
<s:String x:Key="language_display_name">Español</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region MainWindow -->
|
||||||
|
<s:String x:Key="mw_title">WeMod Patcher</s:String>
|
||||||
|
<s:String x:Key="mw_update_available">Una nueva versión está disponible</s:String>
|
||||||
|
<s:String x:Key="mw_folder_path">Ruta de la carpeta</s:String>
|
||||||
|
<s:String x:Key="mw_folder_not_found">Carpeta no encontrada</s:String>
|
||||||
|
<s:String x:Key="mw_patch">Parchear</s:String>
|
||||||
|
<s:String x:Key="mw_restore">Restaurar</s:String>
|
||||||
|
<s:String x:Key="mw_source_code">Código fuente</s:String>
|
||||||
|
<s:String x:Key="mw_made_by">Hecho con ❤️ por k1tbyte</s:String>
|
||||||
|
<s:String x:Key="mw_star_hint">Pon una estrella si te fue útil ;)</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region Settings -->
|
||||||
|
<s:String x:Key="settings_title">Configuración</s:String>
|
||||||
|
<s:String x:Key="settings_language">Idioma</s:String>
|
||||||
|
<s:String x:Key="settings_save">Guardar</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region PatchVectorsPopup -->
|
||||||
|
<s:String x:Key="pv_activate_pro">Activar WeMod Pro</s:String>
|
||||||
|
<s:String x:Key="pv_devtools">DevTools en F12</s:String>
|
||||||
|
<s:String x:Key="pv_disable_updates">Desactivar actualizaciones</s:String>
|
||||||
|
<s:String x:Key="pv_start">Iniciar</s:String>
|
||||||
|
<s:String x:Key="pv_popup_title">¿Qué vamos a parchear?</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region UpdatePopup -->
|
||||||
|
<s:String x:Key="up_warning">Antes de actualizar, se recomienda encarecidamente revertir los parches si se han aplicado</s:String>
|
||||||
|
<s:String x:Key="up_update_now">Actualizar ahora</s:String>
|
||||||
|
<s:String x:Key="up_popup_title">¡Actualización disponible!</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:s="clr-namespace:System;assembly=mscorlib">
|
||||||
|
|
||||||
|
<!--#region Language Metadata -->
|
||||||
|
<s:String x:Key="language_display_name">Français</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region MainWindow -->
|
||||||
|
<s:String x:Key="mw_title">WeMod Patcher</s:String>
|
||||||
|
<s:String x:Key="mw_update_available">Une nouvelle version est disponible</s:String>
|
||||||
|
<s:String x:Key="mw_folder_path">Chemin du dossier</s:String>
|
||||||
|
<s:String x:Key="mw_folder_not_found">Dossier non trouvé</s:String>
|
||||||
|
<s:String x:Key="mw_patch">Patcher</s:String>
|
||||||
|
<s:String x:Key="mw_restore">Restaurer</s:String>
|
||||||
|
<s:String x:Key="mw_source_code">Code source</s:String>
|
||||||
|
<s:String x:Key="mw_made_by">Fait avec ❤️ par k1tbyte</s:String>
|
||||||
|
<s:String x:Key="mw_star_hint">Mettez une étoile si cela vous a aidé ;)</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region Settings -->
|
||||||
|
<s:String x:Key="settings_title">Paramètres</s:String>
|
||||||
|
<s:String x:Key="settings_language">Langue</s:String>
|
||||||
|
<s:String x:Key="settings_save">Enregistrer</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region PatchVectorsPopup -->
|
||||||
|
<s:String x:Key="pv_activate_pro">Activer WeMod Pro</s:String>
|
||||||
|
<s:String x:Key="pv_devtools">DevTools sur F12</s:String>
|
||||||
|
<s:String x:Key="pv_disable_updates">Désactiver les mises à jour</s:String>
|
||||||
|
<s:String x:Key="pv_start">Démarrer</s:String>
|
||||||
|
<s:String x:Key="pv_popup_title">Qu'allons-nous patcher ?</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region UpdatePopup -->
|
||||||
|
<s:String x:Key="up_warning">Avant la mise à jour, il est fortement recommandé d'annuler les patchs s'ils ont été appliqués</s:String>
|
||||||
|
<s:String x:Key="up_update_now">Mettre à jour maintenant</s:String>
|
||||||
|
<s:String x:Key="up_popup_title">Mise à jour disponible !</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:s="clr-namespace:System;assembly=mscorlib">
|
||||||
|
|
||||||
|
<!--#region Language Metadata -->
|
||||||
|
<s:String x:Key="language_display_name">Italiano</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region MainWindow -->
|
||||||
|
<s:String x:Key="mw_title">WeMod Patcher</s:String>
|
||||||
|
<s:String x:Key="mw_update_available">È disponibile una nuova versione</s:String>
|
||||||
|
<s:String x:Key="mw_folder_path">Percorso cartella</s:String>
|
||||||
|
<s:String x:Key="mw_folder_not_found">Cartella non trovata</s:String>
|
||||||
|
<s:String x:Key="mw_patch">Patch</s:String>
|
||||||
|
<s:String x:Key="mw_restore">Ripristina</s:String>
|
||||||
|
<s:String x:Key="mw_source_code">Codice sorgente</s:String>
|
||||||
|
<s:String x:Key="mw_made_by">Creato con ❤️ da k1tbyte</s:String>
|
||||||
|
<s:String x:Key="mw_star_hint">Metti una stella se ti è stato utile ;)</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region Settings -->
|
||||||
|
<s:String x:Key="settings_title">Impostazioni</s:String>
|
||||||
|
<s:String x:Key="settings_language">Lingua</s:String>
|
||||||
|
<s:String x:Key="settings_save">Salva</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region PatchVectorsPopup -->
|
||||||
|
<s:String x:Key="pv_activate_pro">Attiva WeMod Pro</s:String>
|
||||||
|
<s:String x:Key="pv_devtools">DevTools su F12</s:String>
|
||||||
|
<s:String x:Key="pv_disable_updates">Disattiva aggiornamenti</s:String>
|
||||||
|
<s:String x:Key="pv_start">Avvia</s:String>
|
||||||
|
<s:String x:Key="pv_popup_title">Cosa patcheremo?</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region UpdatePopup -->
|
||||||
|
<s:String x:Key="up_warning">Prima dell'aggiornamento, si consiglia vivamente di annullare le patch se sono state applicate</s:String>
|
||||||
|
<s:String x:Key="up_update_now">Aggiorna ora</s:String>
|
||||||
|
<s:String x:Key="up_popup_title">Aggiornamento disponibile!</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:s="clr-namespace:System;assembly=mscorlib">
|
||||||
|
|
||||||
|
<!--#region Language Metadata -->
|
||||||
|
<s:String x:Key="language_display_name">日本語</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region MainWindow -->
|
||||||
|
<s:String x:Key="mw_title">WeMod Patcher</s:String>
|
||||||
|
<s:String x:Key="mw_update_available">新しいバージョンが利用可能です</s:String>
|
||||||
|
<s:String x:Key="mw_folder_path">フォルダパス</s:String>
|
||||||
|
<s:String x:Key="mw_folder_not_found">フォルダが見つかりません</s:String>
|
||||||
|
<s:String x:Key="mw_patch">パッチ</s:String>
|
||||||
|
<s:String x:Key="mw_restore">復元</s:String>
|
||||||
|
<s:String x:Key="mw_source_code">ソースコード</s:String>
|
||||||
|
<s:String x:Key="mw_made_by">k1tbyte が ❤️ を込めて作成</s:String>
|
||||||
|
<s:String x:Key="mw_star_hint">役に立ったらスターをつけてください ;)</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region Settings -->
|
||||||
|
<s:String x:Key="settings_title">設定</s:String>
|
||||||
|
<s:String x:Key="settings_language">言語</s:String>
|
||||||
|
<s:String x:Key="settings_save">保存</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region PatchVectorsPopup -->
|
||||||
|
<s:String x:Key="pv_activate_pro">WeMod Pro を有効化</s:String>
|
||||||
|
<s:String x:Key="pv_devtools">F12でDevTools</s:String>
|
||||||
|
<s:String x:Key="pv_disable_updates">アップデートを無効化</s:String>
|
||||||
|
<s:String x:Key="pv_start">開始</s:String>
|
||||||
|
<s:String x:Key="pv_popup_title">何をパッチしますか?</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region UpdatePopup -->
|
||||||
|
<s:String x:Key="up_warning">アップデート前に、パッチが適用されている場合はロールバックすることを強くお勧めします</s:String>
|
||||||
|
<s:String x:Key="up_update_now">今すぐ更新</s:String>
|
||||||
|
<s:String x:Key="up_popup_title">アップデート利用可能!</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:s="clr-namespace:System;assembly=mscorlib">
|
||||||
|
|
||||||
|
<!--#region Language Metadata -->
|
||||||
|
<s:String x:Key="language_display_name">Polski</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region MainWindow -->
|
||||||
|
<s:String x:Key="mw_title">WeMod Patcher</s:String>
|
||||||
|
<s:String x:Key="mw_update_available">Dostępna jest nowa wersja</s:String>
|
||||||
|
<s:String x:Key="mw_folder_path">Ścieżka folderu</s:String>
|
||||||
|
<s:String x:Key="mw_folder_not_found">Folder nie znaleziony</s:String>
|
||||||
|
<s:String x:Key="mw_patch">Patchuj</s:String>
|
||||||
|
<s:String x:Key="mw_restore">Przywróć</s:String>
|
||||||
|
<s:String x:Key="mw_source_code">Kod źródłowy</s:String>
|
||||||
|
<s:String x:Key="mw_made_by">Wykonane z ❤️ przez k1tbyte</s:String>
|
||||||
|
<s:String x:Key="mw_star_hint">Daj gwiazdkę, jeśli ci pomogło ;)</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region Settings -->
|
||||||
|
<s:String x:Key="settings_title">Ustawienia</s:String>
|
||||||
|
<s:String x:Key="settings_language">Język</s:String>
|
||||||
|
<s:String x:Key="settings_save">Zapisz</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region PatchVectorsPopup -->
|
||||||
|
<s:String x:Key="pv_activate_pro">Aktywuj WeMod Pro</s:String>
|
||||||
|
<s:String x:Key="pv_devtools">DevTools na F12</s:String>
|
||||||
|
<s:String x:Key="pv_disable_updates">Wyłącz aktualizacje</s:String>
|
||||||
|
<s:String x:Key="pv_start">Rozpocznij</s:String>
|
||||||
|
<s:String x:Key="pv_popup_title">Co będziemy patchować?</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region UpdatePopup -->
|
||||||
|
<s:String x:Key="up_warning">Przed aktualizacją zdecydowanie zaleca się cofnięcie patchów, jeśli zostały zastosowane</s:String>
|
||||||
|
<s:String x:Key="up_update_now">Aktualizuj teraz</s:String>
|
||||||
|
<s:String x:Key="up_popup_title">Dostępna aktualizacja!</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:s="clr-namespace:System;assembly=mscorlib">
|
||||||
|
|
||||||
|
<!--#region Language Metadata -->
|
||||||
|
<s:String x:Key="language_display_name">Português</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region MainWindow -->
|
||||||
|
<s:String x:Key="mw_title">WeMod Patcher</s:String>
|
||||||
|
<s:String x:Key="mw_update_available">Uma nova versão está disponível</s:String>
|
||||||
|
<s:String x:Key="mw_folder_path">Caminho da pasta</s:String>
|
||||||
|
<s:String x:Key="mw_folder_not_found">Pasta não encontrada</s:String>
|
||||||
|
<s:String x:Key="mw_patch">Patch</s:String>
|
||||||
|
<s:String x:Key="mw_restore">Restaurar</s:String>
|
||||||
|
<s:String x:Key="mw_source_code">Código fonte</s:String>
|
||||||
|
<s:String x:Key="mw_made_by">Feito com ❤️ por k1tbyte</s:String>
|
||||||
|
<s:String x:Key="mw_star_hint">Dê uma estrela se isso te ajudou ;)</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region Settings -->
|
||||||
|
<s:String x:Key="settings_title">Configurações</s:String>
|
||||||
|
<s:String x:Key="settings_language">Idioma</s:String>
|
||||||
|
<s:String x:Key="settings_save">Salvar</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region PatchVectorsPopup -->
|
||||||
|
<s:String x:Key="pv_activate_pro">Ativar WeMod Pro</s:String>
|
||||||
|
<s:String x:Key="pv_devtools">DevTools no F12</s:String>
|
||||||
|
<s:String x:Key="pv_disable_updates">Desativar atualizações</s:String>
|
||||||
|
<s:String x:Key="pv_start">Iniciar</s:String>
|
||||||
|
<s:String x:Key="pv_popup_title">O que vamos patchear?</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region UpdatePopup -->
|
||||||
|
<s:String x:Key="up_warning">Antes de atualizar, é altamente recomendável reverter os patches se eles foram aplicados</s:String>
|
||||||
|
<s:String x:Key="up_update_now">Atualizar agora</s:String>
|
||||||
|
<s:String x:Key="up_popup_title">Atualização disponível!</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:s="clr-namespace:System;assembly=mscorlib">
|
||||||
|
|
||||||
|
<!--#region Language Metadata -->
|
||||||
|
<s:String x:Key="language_display_name">Русский</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region MainWindow -->
|
||||||
|
<s:String x:Key="mw_title">WeMod Patcher</s:String>
|
||||||
|
<s:String x:Key="mw_update_available">Доступна новая версия</s:String>
|
||||||
|
<s:String x:Key="mw_folder_path">Путь к папке</s:String>
|
||||||
|
<s:String x:Key="mw_folder_not_found">Папка не найдена</s:String>
|
||||||
|
<s:String x:Key="mw_patch">Патч</s:String>
|
||||||
|
<s:String x:Key="mw_restore">Восстановить</s:String>
|
||||||
|
<s:String x:Key="mw_source_code">Исходный код</s:String>
|
||||||
|
<s:String x:Key="mw_made_by">Сделано с ❤️ by k1tbyte</s:String>
|
||||||
|
<s:String x:Key="mw_star_hint">Поставьте звезду, если это было полезно ;)</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region Settings -->
|
||||||
|
<s:String x:Key="settings_title">Настройки</s:String>
|
||||||
|
<s:String x:Key="settings_language">Язык</s:String>
|
||||||
|
<s:String x:Key="settings_save">Сохранить</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region PatchVectorsPopup -->
|
||||||
|
<s:String x:Key="pv_activate_pro">Активировать WeMod Pro</s:String>
|
||||||
|
<s:String x:Key="pv_devtools">DevTools на F12</s:String>
|
||||||
|
<s:String x:Key="pv_disable_updates">Отключить обновления</s:String>
|
||||||
|
<s:String x:Key="pv_start">Начать</s:String>
|
||||||
|
<s:String x:Key="pv_popup_title">Что будем патчить?</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region UpdatePopup -->
|
||||||
|
<s:String x:Key="up_warning">Перед обновлением настоятельно рекомендуется откатить патчи, если они были применены</s:String>
|
||||||
|
<s:String x:Key="up_update_now">Обновить сейчас</s:String>
|
||||||
|
<s:String x:Key="up_popup_title">Доступно обновление!</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:s="clr-namespace:System;assembly=mscorlib">
|
||||||
|
|
||||||
|
<!--#region Language Metadata -->
|
||||||
|
<s:String x:Key="language_display_name">Türkçe</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region MainWindow -->
|
||||||
|
<s:String x:Key="mw_title">WeMod Patcher</s:String>
|
||||||
|
<s:String x:Key="mw_update_available">Yeni bir sürüm mevcut</s:String>
|
||||||
|
<s:String x:Key="mw_folder_path">Klasör yolu</s:String>
|
||||||
|
<s:String x:Key="mw_folder_not_found">Klasör bulunamadı</s:String>
|
||||||
|
<s:String x:Key="mw_patch">Yama</s:String>
|
||||||
|
<s:String x:Key="mw_restore">Geri Yükle</s:String>
|
||||||
|
<s:String x:Key="mw_source_code">Kaynak kodu</s:String>
|
||||||
|
<s:String x:Key="mw_made_by">k1tbyte tarafından ❤️ ile yapıldı</s:String>
|
||||||
|
<s:String x:Key="mw_star_hint">Yardımcı olduysa yıldız verin ;)</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region Settings -->
|
||||||
|
<s:String x:Key="settings_title">Ayarlar</s:String>
|
||||||
|
<s:String x:Key="settings_language">Dil</s:String>
|
||||||
|
<s:String x:Key="settings_save">Kaydet</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region PatchVectorsPopup -->
|
||||||
|
<s:String x:Key="pv_activate_pro">WeMod Pro'yu Etkinleştir</s:String>
|
||||||
|
<s:String x:Key="pv_devtools">F12 ile DevTools</s:String>
|
||||||
|
<s:String x:Key="pv_disable_updates">Güncellemeleri devre dışı bırak</s:String>
|
||||||
|
<s:String x:Key="pv_start">Başlat</s:String>
|
||||||
|
<s:String x:Key="pv_popup_title">Ne yamalayacağız?</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region UpdatePopup -->
|
||||||
|
<s:String x:Key="up_warning">Güncellemeden önce, yamalar uygulandıysa geri almak şiddetle tavsiye edilir</s:String>
|
||||||
|
<s:String x:Key="up_update_now">Şimdi güncelle</s:String>
|
||||||
|
<s:String x:Key="up_popup_title">Güncelleme mevcut!</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:s="clr-namespace:System;assembly=mscorlib">
|
||||||
|
|
||||||
|
<!--#region Language Metadata -->
|
||||||
|
<s:String x:Key="language_display_name">Українська</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region MainWindow -->
|
||||||
|
<s:String x:Key="mw_title">WeMod Patcher</s:String>
|
||||||
|
<s:String x:Key="mw_update_available">Доступна нова версія</s:String>
|
||||||
|
<s:String x:Key="mw_folder_path">Шлях до папки</s:String>
|
||||||
|
<s:String x:Key="mw_folder_not_found">Папку не знайдено</s:String>
|
||||||
|
<s:String x:Key="mw_patch">Патч</s:String>
|
||||||
|
<s:String x:Key="mw_restore">Відновити</s:String>
|
||||||
|
<s:String x:Key="mw_source_code">Вихідний код</s:String>
|
||||||
|
<s:String x:Key="mw_made_by">Зроблено з ❤️ by k1tbyte</s:String>
|
||||||
|
<s:String x:Key="mw_star_hint">Поставте зірку, якщо це було корисно ;)</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region Settings -->
|
||||||
|
<s:String x:Key="settings_title">Налаштування</s:String>
|
||||||
|
<s:String x:Key="settings_language">Мова</s:String>
|
||||||
|
<s:String x:Key="settings_save">Зберегти</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region PatchVectorsPopup -->
|
||||||
|
<s:String x:Key="pv_activate_pro">Активувати WeMod Pro</s:String>
|
||||||
|
<s:String x:Key="pv_devtools">DevTools на F12</s:String>
|
||||||
|
<s:String x:Key="pv_disable_updates">Вимкнути оновлення</s:String>
|
||||||
|
<s:String x:Key="pv_start">Почати</s:String>
|
||||||
|
<s:String x:Key="pv_popup_title">Що будемо патчити?</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region UpdatePopup -->
|
||||||
|
<s:String x:Key="up_warning">Перед оновленням наполегливо рекомендується відкотити патчі, якщо вони були застосовані</s:String>
|
||||||
|
<s:String x:Key="up_update_now">Оновити зараз</s:String>
|
||||||
|
<s:String x:Key="up_popup_title">Доступне оновлення!</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:s="clr-namespace:System;assembly=mscorlib">
|
||||||
|
|
||||||
|
<!--#region Language Metadata -->
|
||||||
|
<s:String x:Key="language_display_name">简体中文</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region MainWindow -->
|
||||||
|
<s:String x:Key="mw_title">WeMod Patcher</s:String>
|
||||||
|
<s:String x:Key="mw_update_available">有新版本可用</s:String>
|
||||||
|
<s:String x:Key="mw_folder_path">文件夹路径</s:String>
|
||||||
|
<s:String x:Key="mw_folder_not_found">未找到文件夹</s:String>
|
||||||
|
<s:String x:Key="mw_patch">补丁</s:String>
|
||||||
|
<s:String x:Key="mw_restore">恢复</s:String>
|
||||||
|
<s:String x:Key="mw_source_code">源代码</s:String>
|
||||||
|
<s:String x:Key="mw_made_by">由 k1tbyte 用 ❤️ 制作</s:String>
|
||||||
|
<s:String x:Key="mw_star_hint">如果这对您有帮助,请给个星标 ;)</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region Settings -->
|
||||||
|
<s:String x:Key="settings_title">设置</s:String>
|
||||||
|
<s:String x:Key="settings_language">语言</s:String>
|
||||||
|
<s:String x:Key="settings_save">保存</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region PatchVectorsPopup -->
|
||||||
|
<s:String x:Key="pv_activate_pro">激活 WeMod Pro</s:String>
|
||||||
|
<s:String x:Key="pv_devtools">按 F12 打开开发者工具</s:String>
|
||||||
|
<s:String x:Key="pv_disable_updates">禁用更新</s:String>
|
||||||
|
<s:String x:Key="pv_start">开始</s:String>
|
||||||
|
<s:String x:Key="pv_popup_title">我们要打什么补丁?</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
<!--#region UpdatePopup -->
|
||||||
|
<s:String x:Key="up_warning">在更新之前,强烈建议回滚已应用的补丁</s:String>
|
||||||
|
<s:String x:Key="up_update_now">立即更新</s:String>
|
||||||
|
<s:String x:Key="up_popup_title">有更新可用!</s:String>
|
||||||
|
<!--#endregion -->
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using WeModPatcher.Utils;
|
||||||
|
|
||||||
|
namespace WeModPatcher.Models
|
||||||
|
{
|
||||||
|
|
||||||
|
public enum EPatchType
|
||||||
|
{
|
||||||
|
ActivatePro = 1,
|
||||||
|
DisableUpdates = 2,
|
||||||
|
DisableTelemetry = 4,
|
||||||
|
DevToolsOnF12 = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class PatchConfig
|
||||||
|
{
|
||||||
|
private string _path;
|
||||||
|
public HashSet<EPatchType> PatchTypes { 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace WeModPatcher.Models
|
||||||
|
{
|
||||||
|
public sealed class Signature
|
||||||
|
{
|
||||||
|
public readonly byte[] OriginalBytes;
|
||||||
|
public readonly byte[] PatchBytes;
|
||||||
|
public readonly byte[] Sequence;
|
||||||
|
public readonly byte[] Mask;
|
||||||
|
public readonly int Offset;
|
||||||
|
|
||||||
|
public int Length => Sequence.Length;
|
||||||
|
|
||||||
|
public static implicit operator byte[](Signature signature) => signature.Sequence;
|
||||||
|
|
||||||
|
public Signature(string signature, int offset, byte[] patchBytes, byte[] originalBytes)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using WeModPatcher.View.MainWindow;
|
||||||
|
|
||||||
|
namespace WeModPatcher
|
||||||
|
{
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
[STAThread]
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
||||||
|
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
|
||||||
|
|
||||||
|
List<LogEntry> logEntries = new List<LogEntry>();
|
||||||
|
if (args.Length > 0)
|
||||||
|
{
|
||||||
|
// TODO: Command line arguments handling
|
||||||
|
}
|
||||||
|
|
||||||
|
var application = new App();
|
||||||
|
application.InitializeComponent();
|
||||||
|
application.MainWindow = new MainWindow();
|
||||||
|
foreach (var logEntry in logEntries)
|
||||||
|
{
|
||||||
|
MainWindow.Instance.ViewModel.LogList.Add(logEntry);
|
||||||
|
}
|
||||||
|
application.Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
|
||||||
|
{
|
||||||
|
MessageBox.Show(e.Exception.ToString());
|
||||||
|
Environment.Exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||||
|
{
|
||||||
|
MessageBox.Show(e.ExceptionObject.ToString());
|
||||||
|
Environment.Exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Resources;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyTitle("WeModPatcher")]
|
||||||
|
[assembly: AssemblyDescription("")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("WeModPatcher")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © 2025")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
//In order to begin building localizable applications, set
|
||||||
|
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
|
||||||
|
//inside a <PropertyGroup>. For example, if you are using US english
|
||||||
|
//in your source files, set the <UICulture> to en-US. Then uncomment
|
||||||
|
//the NeutralResourceLanguage attribute below. Update the "en-US" in
|
||||||
|
//the line below to match the UICulture setting in the project file.
|
||||||
|
|
||||||
|
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
|
||||||
|
|
||||||
|
|
||||||
|
[assembly: ThemeInfo(
|
||||||
|
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// or application resource dictionaries)
|
||||||
|
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// app, or any theme specific resource dictionaries)
|
||||||
|
)]
|
||||||
|
|
||||||
|
|
||||||
|
// Version information for an assembly consists of the following four values:
|
||||||
|
//
|
||||||
|
// Major Version
|
||||||
|
// Minor Version
|
||||||
|
// Build Number
|
||||||
|
// Revision
|
||||||
|
//
|
||||||
|
// 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.6.0")]
|
||||||
|
[assembly: AssemblyFileVersion("1.0.6.0")]
|
||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This code was generated by a tool.
|
||||||
|
// Runtime Version:4.0.30319.42000
|
||||||
|
//
|
||||||
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
|
// the code is regenerated.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace WeModPatcher.Properties
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||||
|
/// </summary>
|
||||||
|
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||||
|
// class via a tool like ResGen or Visual Studio.
|
||||||
|
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||||
|
// with the /str option, or rebuild your VS project.
|
||||||
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder",
|
||||||
|
"4.0.0.0")]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
|
internal class Resources
|
||||||
|
{
|
||||||
|
private static global::System.Resources.ResourceManager resourceMan;
|
||||||
|
|
||||||
|
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||||
|
|
||||||
|
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance",
|
||||||
|
"CA1811:AvoidUncalledPrivateCode")]
|
||||||
|
internal Resources()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the cached ResourceManager instance used by this class.
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState
|
||||||
|
.Advanced)]
|
||||||
|
internal static global::System.Resources.ResourceManager ResourceManager
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if ((resourceMan == null))
|
||||||
|
{
|
||||||
|
global::System.Resources.ResourceManager temp =
|
||||||
|
new global::System.Resources.ResourceManager("WeModPatcher.Properties.Resources",
|
||||||
|
typeof(Resources).Assembly);
|
||||||
|
resourceMan = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceMan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overrides the current thread's CurrentUICulture property for all
|
||||||
|
/// resource lookups using this strongly typed resource class.
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState
|
||||||
|
.Advanced)]
|
||||||
|
internal static global::System.Globalization.CultureInfo Culture
|
||||||
|
{
|
||||||
|
get { return resourceCulture; }
|
||||||
|
set { resourceCulture = value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
</root>
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace WeModPatcher.ReactiveUICore
|
||||||
|
{
|
||||||
|
public sealed class AsyncRelayCommand : ICommand
|
||||||
|
{
|
||||||
|
private readonly Func<object, Task> _execute;
|
||||||
|
private readonly Func<object, bool> _canExecute;
|
||||||
|
|
||||||
|
private long _isExecuting;
|
||||||
|
|
||||||
|
public AsyncRelayCommand(Func<object, Task> execute, Func<object, bool> canExecute = null)
|
||||||
|
{
|
||||||
|
this._execute = execute;
|
||||||
|
this._canExecute = canExecute ?? (o => true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler CanExecuteChanged
|
||||||
|
{
|
||||||
|
add => CommandManager.RequerySuggested += value;
|
||||||
|
remove => CommandManager.RequerySuggested -= value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RaiseCanExecuteChanged() => CommandManager.InvalidateRequerySuggested();
|
||||||
|
|
||||||
|
public bool CanExecute(object parameter) => Interlocked.Read(ref _isExecuting) == 0 && _canExecute(parameter);
|
||||||
|
|
||||||
|
public async void Execute(object parameter)
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref _isExecuting, 1);
|
||||||
|
RaiseCanExecuteChanged();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _execute(parameter);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref _isExecuting, 0);
|
||||||
|
RaiseCanExecuteChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace WeModPatcher.ReactiveUICore
|
||||||
|
{
|
||||||
|
public class ObservableObject : INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
|
protected void OnPropertyChanged([CallerMemberName] string name = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
||||||
|
|
||||||
|
protected virtual bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
|
||||||
|
{
|
||||||
|
if (Equals(field, value)) return false;
|
||||||
|
field = value;
|
||||||
|
OnPropertyChanged(propertyName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace WeModPatcher.ReactiveUICore
|
||||||
|
{
|
||||||
|
public sealed class RelayCommand : ICommand
|
||||||
|
{
|
||||||
|
private readonly Action<object> _execute;
|
||||||
|
private readonly Func<object, bool> _canExecute;
|
||||||
|
|
||||||
|
public event EventHandler CanExecuteChanged
|
||||||
|
{
|
||||||
|
add => CommandManager.RequerySuggested += value;
|
||||||
|
remove => CommandManager.RequerySuggested -= value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
|
||||||
|
{
|
||||||
|
_execute = execute;
|
||||||
|
_canExecute = canExecute;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
|
||||||
|
public void Execute(object parameter) => _execute(parameter);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<Brush x:Key="Background">#09090b</Brush>
|
||||||
|
<Brush x:Key="Foreground">#FAFAFA</Brush>
|
||||||
|
<Brush x:Key="Card">#27272A</Brush>
|
||||||
|
<Brush x:Key="CardForeground">#FAFAFA</Brush>
|
||||||
|
<Brush x:Key="Popover">#09090b</Brush>
|
||||||
|
<Brush x:Key="PopoverForeground">#FAFAFA</Brush>
|
||||||
|
<Brush x:Key="Primary">#FAFAFA</Brush>
|
||||||
|
<Brush x:Key="PrimaryForeground">#18181B</Brush>
|
||||||
|
<Brush x:Key="Secondary">#27272A</Brush>
|
||||||
|
<Brush x:Key="SecondaryForeground">#FAFAFA</Brush>
|
||||||
|
<Brush x:Key="Muted">#18181a</Brush>
|
||||||
|
<Brush x:Key="MutedForeground">#A1A1AA</Brush>
|
||||||
|
<Brush x:Key="Accent">#27272A</Brush>
|
||||||
|
<Brush x:Key="AccentForeground">#FAFAFA</Brush>
|
||||||
|
<Brush x:Key="Destructive">Red</Brush>
|
||||||
|
<Brush x:Key="DestructiveForeground">#FAFAFA</Brush>
|
||||||
|
<Brush x:Key="Border">#27272A</Brush>
|
||||||
|
<Brush x:Key="Input">#27272A</Brush>
|
||||||
|
<Brush x:Key="Ring">#D4D4D8</Brush>
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<Geometry x:Key="CloseIcon">
|
||||||
|
M13.46,12L19,17.54V19H17.54L12,13.46L6.46,19H5V17.54L10.54,12L5,6.46V5H6.46L12,10.54L17.54,5H19V6.46L13.46,12Z
|
||||||
|
</Geometry>
|
||||||
|
|
||||||
|
<Geometry x:Key="CogIcon">
|
||||||
|
M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z
|
||||||
|
</Geometry>
|
||||||
|
|
||||||
|
<Geometry x:Key="Logo">
|
||||||
|
M47.845,22.185l-20.03,-20.03c-1.543,-1.543 -4.046,-1.553 -5.729,0.002l-19.931,20.028c-1.542,1.542 -1.554,4.045 0,5.727l19.934,19.934c0.772,0.772 1.785,1.16 2.816,1.16c1.026,0 2.07,-0.385 2.91,-1.16l19.933,-19.934c1.605,-1.605 1.648,-4.175 0.097,-5.727zM18,27c-1.105,0 -2,-0.895 -2,-2c0,-1.105 0.895,-2 2,-2c1.105,0 2,0.895 2,2c0,1.105 -0.895,2 -2,2zM25,34c-1.105,0 -2,-0.895 -2,-2c0,-1.105 0.895,-2 2,-2c1.105,0 2,0.895 2,2c0,1.105 -0.895,2 -2,2zM25,20c-1.105,0 -2,-0.895 -2,-2c0,-1.105 0.895,-2 2,-2c1.105,0 2,0.895 2,2c0,1.105 -0.895,2 -2,2zM32,27c-1.105,0 -2,-0.895 -2,-2c0,-1.105 0.895,-2 2,-2c1.105,0 2,0.895 2,2c0,1.105 -0.895,2 -2,2z
|
||||||
|
</Geometry>
|
||||||
|
|
||||||
|
<Geometry x:Key="GitHub">
|
||||||
|
M12 2A10 10 0 0122 12c0 4.42-2.86 8.16-6.83 9.5-.51.09-.67-.23-.67-.5 0-.32 0-1.4 0-2.74 0-.93-.33-1.54-.69-1.85 2.23-.25 4.57-1.09 4.57-4.91 0-1.11-.38-2-1.03-2.71.1-.25.45-1.29-.1-2.64 0 0-.84-.27-2.75 1.02-.79-.22-1.65-.33-2.5-.33s-1.71.11-2.5.33C7.59 5.88 6.75 6.15 6.75 6.15c-.55 1.35-.2 2.39-.1 2.64-.65.71-1.03 1.6-1.03 2.71 0 3.81 2.33 4.67 4.55 4.92-.28.25-.54.69-.63 1.34-.57.24-2.04.69-2.91-.83 0 0-.53-.96-1.53-1.03 0 0-.98-.02-.07.6 0 0 .65.31 1.11 1.47 0 0 .59 1.94 3.36 1.34 0 .83 0 1.46 0 1.69 0 .27-.16.58-.66.5C4.87 20.17 2 16.42 2 12A10 10 0 0112 2Z
|
||||||
|
</Geometry>
|
||||||
|
|
||||||
|
<Geometry x:Key="CheckDecagram">
|
||||||
|
M10 17l8-8-1.41-1.42L10 14.17 7.41 11.59 6 13l4 4Zm13-5-2.44 2.78.34 3.68-3.61.82-1.89 3.18L12 21 8.6 22.47 6.71 19.29 3.1 18.47l.34-3.69L1 12 3.44 9.21 3.1 5.53l3.61-.81L8.6 1.54 12 3l3.4-1.46 1.89 3.18 3.61.82-.34 3.68L23 12
|
||||||
|
</Geometry>
|
||||||
|
|
||||||
|
<Geometry x:Key="AlertDecagram">
|
||||||
|
M13 13V7H11v6h2Zm0 4V15H11v2h2m10-5-2.44 2.78.34 3.68-3.61.82-1.89 3.18L12 21 8.6 22.47 6.71 19.29 3.1 18.47l.34-3.69L1 12 3.44 9.21 3.1 5.53l3.61-.81L8.6 1.54 12 3l3.4-1.46 1.89 3.18 3.61.82-.34 3.68L23 12
|
||||||
|
</Geometry>
|
||||||
|
|
||||||
|
<Geometry x:Key="ArrowLeft">
|
||||||
|
M5.05 11.94l5-5v3.99H19l-.03 2.01H10.05v4Z
|
||||||
|
</Geometry>
|
||||||
|
|
||||||
|
<!--<Geometry x:Key="">
|
||||||
|
|
||||||
|
</Geometry>-->
|
||||||
|
</ResourceDictionary>
|
||||||
Binary file not shown.
@@ -0,0 +1,354 @@
|
|||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
|
||||||
|
<CircleEase EasingMode="EaseInOut" x:Key="BaseAnimationFunction"/>
|
||||||
|
|
||||||
|
<Style TargetType="{x:Type Button}">
|
||||||
|
<Style.Resources>
|
||||||
|
<CornerRadius x:Key="CornerRadius">3 3 3 3</CornerRadius>
|
||||||
|
</Style.Resources>
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource Border}"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource Foreground}"/>
|
||||||
|
<Setter Property="Cursor" Value="Hand"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="Button">
|
||||||
|
<Border CornerRadius="{DynamicResource CornerRadius}"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
Padding="{TemplateBinding Padding}"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
|
Background="{TemplateBinding Background}">
|
||||||
|
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource Secondary}"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="ColoredButton" TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource Primary}"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource PrimaryForeground}"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsEnabled" Value="False">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource Muted}"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource MutedForeground}"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Margin" Value="0 2 0 2"/>
|
||||||
|
<Setter Property="Background" Value="{DynamicResource Primary}"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="IconButton" TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="Button">
|
||||||
|
<Border CornerRadius="{DynamicResource CornerRadius}"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
Padding="{TemplateBinding Padding}"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
|
Background="{TemplateBinding Background}">
|
||||||
|
<Viewbox HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||||
|
<Path x:Name="Icon" Stretch="Fill" Data="{TemplateBinding Tag}" Fill="{TemplateBinding Foreground}"/>
|
||||||
|
</Viewbox>
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<CircleEase EasingMode="EaseIn" x:Key="DefaultAnimationFunction"/>
|
||||||
|
|
||||||
|
<Style TargetType="{x:Type ContextMenu}">
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||||
|
<Setter Property="OverridesDefaultStyle" Value="True"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type ContextMenu}">
|
||||||
|
<Border x:Name="Content" CornerRadius="5" Margin="5"
|
||||||
|
Background="{StaticResource Background}"
|
||||||
|
BorderThickness="1"
|
||||||
|
BorderBrush="{DynamicResource Border}"
|
||||||
|
Padding="4">
|
||||||
|
<StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Cycle" />
|
||||||
|
<Border.Effect>
|
||||||
|
<DropShadowEffect BlurRadius="5" ShadowDepth="0" Color="Black" Opacity="0.4"/>
|
||||||
|
</Border.Effect>
|
||||||
|
<Border.RenderTransform>
|
||||||
|
<ScaleTransform ScaleX="0" ScaleY="0"/>
|
||||||
|
</Border.RenderTransform>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<EventTrigger RoutedEvent="Loaded">
|
||||||
|
<BeginStoryboard>
|
||||||
|
<Storyboard>
|
||||||
|
<DoubleAnimation Duration="0:0:0.15" Storyboard.TargetName="Content" EasingFunction="{StaticResource DefaultAnimationFunction}"
|
||||||
|
Storyboard.TargetProperty="(Border.RenderTransform).(ScaleTransform.ScaleY)" From="0" To="1"/>
|
||||||
|
<DoubleAnimation Duration="0:0:0.15" Storyboard.TargetName="Content" EasingFunction="{StaticResource DefaultAnimationFunction}"
|
||||||
|
Storyboard.TargetProperty="(Border.RenderTransform).(ScaleTransform.ScaleX)" From="0" To="1"/>
|
||||||
|
</Storyboard>
|
||||||
|
</BeginStoryboard>
|
||||||
|
</EventTrigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="{x:Type MenuItem}">
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||||
|
<Setter Property="OverridesDefaultStyle" Value="True"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource Foreground}"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource Border}"/>
|
||||||
|
<Setter Property="Cursor" Value="Hand"/>
|
||||||
|
<Setter Property="FontWeight" Value="Medium"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type MenuItem}">
|
||||||
|
<Border Name="Root" Height="30" Background="Transparent" CornerRadius="4">
|
||||||
|
<ContentPresenter Name="HeaderHost" Margin="10,0,10,0"
|
||||||
|
ContentSource="Header" MinWidth="100"
|
||||||
|
RecognizesAccessKey="True"
|
||||||
|
HorizontalAlignment="Left" VerticalAlignment="Center"
|
||||||
|
TextOptions.TextRenderingMode="ClearType" TextBlock.FontSize="12" TextBlock.FontWeight="{TemplateBinding FontWeight}" TextBlock.Foreground="{TemplateBinding Foreground}" TextOptions.TextFormattingMode="Display"/>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource Foreground}"/>
|
||||||
|
<Setter TargetName="Root" Property="Background" Value="{DynamicResource Accent}"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsPressed" Value="True">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource Background}"/>
|
||||||
|
<Setter TargetName="Root" Property="Background" Value="{DynamicResource Foreground}"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="LabelCard" TargetType="{x:Type Border}">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource Card}"/>
|
||||||
|
<Setter Property="Opacity" Value="0.9"/>
|
||||||
|
<Setter Property="CornerRadius" Value="10"/>
|
||||||
|
<Setter Property="Padding" Value="12 6"/>
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="Label" TargetType="{x:Type TextBlock}">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource Foreground}"/>
|
||||||
|
<Setter Property="FontWeight" Value="Medium"/>
|
||||||
|
<Setter Property="FontSize" Value="13"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="TextAlignment" Value="Center"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
|
||||||
|
<Style TargetType="CheckBox">
|
||||||
|
<Setter Property="Cursor" Value="Hand"></Setter>
|
||||||
|
<Setter Property="Content" Value=""/>
|
||||||
|
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type CheckBox}">
|
||||||
|
<Border x:Name="Border" Height="17" Width="17"
|
||||||
|
CornerRadius="3"
|
||||||
|
Background="{DynamicResource Foreground}" BorderBrush="{DynamicResource Border}"
|
||||||
|
BorderThickness="0">
|
||||||
|
<TextBlock x:Name="Text" VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||||
|
Foreground="{DynamicResource PrimaryForeground}"></TextBlock>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsChecked" Value="False">
|
||||||
|
<Setter TargetName="Border"
|
||||||
|
Property="Background" Value="Transparent"/>
|
||||||
|
<Setter TargetName="Border"
|
||||||
|
Property="BorderThickness" Value="1"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsChecked" Value="True">
|
||||||
|
<Setter TargetName="Text"
|
||||||
|
Property="Text" Value="✓"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsChecked" Value="{x:Null}">
|
||||||
|
<Setter TargetName="Text"
|
||||||
|
Property="Text" Value="–"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
|
||||||
|
<Style TargetType="TextBox" x:Key="TitledTextBox">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="Height" Value="30" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource Foreground}" />
|
||||||
|
<Setter Property="FontSize" Value="13" />
|
||||||
|
<Setter Property="CaretBrush" Value="{DynamicResource MutedForeground}" />
|
||||||
|
<Setter Property="SelectionBrush" Value="{DynamicResource MutedForeground}" />
|
||||||
|
<Setter Property="FontWeight" Value="Medium" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type TextBox}">
|
||||||
|
<Border BorderBrush="{StaticResource Border}" Cursor="IBeam"
|
||||||
|
BorderThickness="1" CornerRadius="3">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Border BorderBrush="{DynamicResource Border}"
|
||||||
|
BorderThickness="0 0 1 0" IsHitTestVisible="False">
|
||||||
|
<TextBlock Text="{DynamicResource mw_folder_path}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontSize="12"
|
||||||
|
Foreground="{DynamicResource MutedForeground}"
|
||||||
|
Padding="10 0" />
|
||||||
|
</Border>
|
||||||
|
<ScrollViewer
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="5 0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
x:Name="PART_ContentHost" />
|
||||||
|
<TextBlock IsHitTestVisible="False"
|
||||||
|
Grid.Column="1"
|
||||||
|
Opacity="0.3"
|
||||||
|
Text="{DynamicResource mw_folder_not_found}"
|
||||||
|
Margin="7 0 5 1"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Visibility="Collapsed"
|
||||||
|
x:Name="Placeholder" />
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="Text" Value="">
|
||||||
|
<Setter TargetName="Placeholder"
|
||||||
|
Property="Visibility" Value="Visible" />
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="{x:Type ComboBox}">
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource Border}"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource Foreground}"/>
|
||||||
|
<Setter Property="Height" Value="30"/>
|
||||||
|
<Setter Property="Cursor" Value="Hand"/>
|
||||||
|
<Setter Property="Padding" Value="10 0"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type ComboBox}">
|
||||||
|
<Grid>
|
||||||
|
<ToggleButton x:Name="ToggleButton"
|
||||||
|
Focusable="False"
|
||||||
|
Background="Transparent"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
|
||||||
|
ClickMode="Press">
|
||||||
|
<ToggleButton.Template>
|
||||||
|
<ControlTemplate TargetType="{x:Type ToggleButton}">
|
||||||
|
<Border x:Name="Border" CornerRadius="3"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
|
Background="Transparent">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition Width="20"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Border Grid.Column="1" BorderBrush="{DynamicResource Border}"
|
||||||
|
BorderThickness="1 0 0 0" Margin="0 5"/>
|
||||||
|
<Path x:Name="Arrow" Grid.Column="1"
|
||||||
|
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||||
|
Data="M0,0 L4,4 L8,0" Stroke="{DynamicResource MutedForeground}"
|
||||||
|
StrokeThickness="1.5"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="Border" Property="Background" Value="{DynamicResource Secondary}"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</ToggleButton.Template>
|
||||||
|
</ToggleButton>
|
||||||
|
<ContentPresenter x:Name="ContentSite"
|
||||||
|
IsHitTestVisible="False"
|
||||||
|
Content="{TemplateBinding SelectionBoxItem}"
|
||||||
|
ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
|
||||||
|
ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"
|
||||||
|
Margin="10,0,25,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Left"/>
|
||||||
|
<Popup x:Name="Popup"
|
||||||
|
Placement="Bottom"
|
||||||
|
IsOpen="{TemplateBinding IsDropDownOpen}"
|
||||||
|
AllowsTransparency="True"
|
||||||
|
Focusable="False"
|
||||||
|
PopupAnimation="Slide">
|
||||||
|
<Grid x:Name="DropDown"
|
||||||
|
SnapsToDevicePixels="True"
|
||||||
|
MinWidth="{TemplateBinding ActualWidth}"
|
||||||
|
MaxHeight="{TemplateBinding MaxDropDownHeight}">
|
||||||
|
<Border x:Name="DropDownBorder"
|
||||||
|
CornerRadius="3"
|
||||||
|
Margin="0 2 0 0"
|
||||||
|
Background="{DynamicResource Background}"
|
||||||
|
BorderBrush="{DynamicResource Border}"
|
||||||
|
BorderThickness="1">
|
||||||
|
<ScrollViewer Margin="4" SnapsToDevicePixels="True">
|
||||||
|
<StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Contained"/>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Popup>
|
||||||
|
</Grid>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="{x:Type ComboBoxItem}">
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||||
|
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource Foreground}"/>
|
||||||
|
<Setter Property="Cursor" Value="Hand"/>
|
||||||
|
<Setter Property="Padding" Value="8 5"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type ComboBoxItem}">
|
||||||
|
<Border x:Name="Border" CornerRadius="3" Padding="{TemplateBinding Padding}"
|
||||||
|
Background="Transparent">
|
||||||
|
<ContentPresenter/>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsHighlighted" Value="True">
|
||||||
|
<Setter TargetName="Border" Property="Background" Value="{DynamicResource Secondary}"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsSelected" Value="True">
|
||||||
|
<Setter TargetName="Border" Property="Background" Value="{DynamicResource Accent}"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using WeModPatcher.Models;
|
||||||
|
|
||||||
|
namespace WeModPatcher.Utils
|
||||||
|
{
|
||||||
|
public static class Extensions
|
||||||
|
{
|
||||||
|
public static WeModConfig CheckWeModPath(string versionRoot)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WeModConfig FindWeMod()
|
||||||
|
{
|
||||||
|
string localAppDataPath = Environment.GetEnvironmentVariable("LOCALAPPDATA");
|
||||||
|
|
||||||
|
foreach (var folder in Constants.WeModBrandNames)
|
||||||
|
{
|
||||||
|
var weModDir = Path.Combine(localAppDataPath ?? "", folder);
|
||||||
|
if(Directory.Exists(weModDir))
|
||||||
|
{
|
||||||
|
return FindLatestWeMod(weModDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Base64Decode(string base64EncodedData)
|
||||||
|
{
|
||||||
|
var base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData);
|
||||||
|
return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Base64Encode(string plainText)
|
||||||
|
{
|
||||||
|
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
|
||||||
|
return System.Convert.ToBase64String(plainTextBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WeModConfig FindLatestWeMod(string root)
|
||||||
|
{
|
||||||
|
var appFolders = Directory.EnumerateDirectories(root)
|
||||||
|
.Select(folderPath => new DirectoryInfo(folderPath))
|
||||||
|
.Where(dirInfo => Regex.IsMatch(dirInfo.Name, @"^app-\w+"))
|
||||||
|
.Select(dirInfo => new
|
||||||
|
{
|
||||||
|
Name = dirInfo.Name,
|
||||||
|
Path = dirInfo.FullName,
|
||||||
|
LastModified = dirInfo.LastWriteTime
|
||||||
|
})
|
||||||
|
.OrderByDescending(item => item.LastModified)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
|
||||||
|
return appFolders
|
||||||
|
.Select(folder => CheckWeModPath(folder.Path))
|
||||||
|
.FirstOrDefault(config => config != null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Windows;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace WeModPatcher.Utils
|
||||||
|
{
|
||||||
|
public class GitHubRelease
|
||||||
|
{
|
||||||
|
public class AssetsType
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("browser_download_url")]
|
||||||
|
public string Url { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonProperty("tag_name")]
|
||||||
|
public string TagName { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("assets")]
|
||||||
|
public AssetsType[] Assets { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Updater
|
||||||
|
{
|
||||||
|
private GitHubRelease _release = null;
|
||||||
|
private static readonly HttpClient _httpClient = new HttpClient()
|
||||||
|
{
|
||||||
|
DefaultRequestHeaders =
|
||||||
|
{
|
||||||
|
{ "User-Agent", "GitHub-Updater" }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly string ApiUrl = $"https://api.github.com/repos/{Constants.Owner}/{Constants.RepoName}/releases/latest";
|
||||||
|
public async Task<bool> CheckForUpdates()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var currentVersion = Assembly.GetExecutingAssembly().GetName().Version;
|
||||||
|
var response = await _httpClient.GetAsync(ApiUrl);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
_release = JsonConvert.DeserializeObject<GitHubRelease>(await response.Content.ReadAsStringAsync());
|
||||||
|
|
||||||
|
if (_release == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var latestVersion = new Version(_release.TagName);
|
||||||
|
|
||||||
|
return latestVersion > currentVersion;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Update()
|
||||||
|
{
|
||||||
|
if (_release == null)
|
||||||
|
{
|
||||||
|
throw new Exception("No release found");
|
||||||
|
}
|
||||||
|
|
||||||
|
var asset = _release.Assets.FirstOrDefault(o => o.Name.EndsWith(".exe"));
|
||||||
|
if(asset == null)
|
||||||
|
{
|
||||||
|
throw new Exception("No asset found");
|
||||||
|
}
|
||||||
|
|
||||||
|
// download to temp
|
||||||
|
var downloadPath = Path.Combine(Path.GetTempPath(), asset.Name);
|
||||||
|
|
||||||
|
using(var response = await _httpClient.GetAsync(asset.Url))
|
||||||
|
using(var fileStream = File.Create(downloadPath))
|
||||||
|
{
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
await response.Content.CopyToAsync(fileStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyUpdate(downloadPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static void ApplyUpdate(string filePath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var currentExecutable = Assembly.GetExecutingAssembly().Location;
|
||||||
|
|
||||||
|
var psCommand = $"Start-Sleep -Seconds 2; " +
|
||||||
|
$"Copy-Item -Path '{filePath}' -Destination '{currentExecutable}' -Force; " +
|
||||||
|
$"Remove-Item -Path '{filePath}' -Force; " +
|
||||||
|
$"Start-Sleep -Seconds 1; " +
|
||||||
|
$"Start-Process -FilePath '{currentExecutable}';";
|
||||||
|
|
||||||
|
var startInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "powershell.exe",
|
||||||
|
Arguments = $"-WindowStyle Hidden -ExecutionPolicy Bypass -Command \"{psCommand}\"",
|
||||||
|
UseShellExecute = true,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
WindowStyle = ProcessWindowStyle.Hidden
|
||||||
|
};
|
||||||
|
|
||||||
|
Process.Start(startInfo);
|
||||||
|
|
||||||
|
Task.Delay(500).ContinueWith(_ =>
|
||||||
|
{
|
||||||
|
App.Shutdown();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception($"Update failed: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace WeModPatcher.Utils.Win32
|
||||||
|
{
|
||||||
|
public class Shortcut
|
||||||
|
{
|
||||||
|
public class ShortcutParams
|
||||||
|
{
|
||||||
|
public string FileName { get; set; }
|
||||||
|
public string TargetPath { get; set; }
|
||||||
|
public string Arguments { get; set; }
|
||||||
|
public string WorkingDirectory { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string Hotkey { get; set; }
|
||||||
|
public string IconPath { get; set; }
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly Type m_type = Type.GetTypeFromProgID("WScript.Shell");
|
||||||
|
private static readonly object m_shell = Activator.CreateInstance(m_type);
|
||||||
|
|
||||||
|
[ComImport, TypeLibType(0x1040), Guid("F935DC23-1CF0-11D0-ADB9-00C04FD58A0B")]
|
||||||
|
private interface IWshShortcut
|
||||||
|
{
|
||||||
|
[DispId(0)]
|
||||||
|
string FullName { [return: MarshalAs(UnmanagedType.BStr)][DispId(0)] get; }
|
||||||
|
[DispId(0x3e8)]
|
||||||
|
string Arguments { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3e8)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3e8)] set; }
|
||||||
|
[DispId(0x3e9)]
|
||||||
|
string Description { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3e9)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3e9)] set; }
|
||||||
|
[DispId(0x3ea)]
|
||||||
|
string Hotkey { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3ea)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3ea)] set; }
|
||||||
|
[DispId(0x3eb)]
|
||||||
|
string IconLocation { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3eb)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3eb)] set; }
|
||||||
|
[DispId(0x3ec)]
|
||||||
|
string RelativePath { [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3ec)] set; }
|
||||||
|
[DispId(0x3ed)]
|
||||||
|
string TargetPath { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3ed)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3ed)] set; }
|
||||||
|
[DispId(0x3ee)]
|
||||||
|
int WindowStyle { [DispId(0x3ee)] get; [param: In][DispId(0x3ee)] set; }
|
||||||
|
[DispId(0x3ef)]
|
||||||
|
string WorkingDirectory { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3ef)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3ef)] set; }
|
||||||
|
[TypeLibFunc((short)0x40), DispId(0x7d0)]
|
||||||
|
void Load([In, MarshalAs(UnmanagedType.BStr)] string PathLink);
|
||||||
|
[DispId(0x7d1)]
|
||||||
|
void Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CreateShortcut(string fileName, string targetPath, string arguments, string workingDirectory, string description, string iconPath)
|
||||||
|
{
|
||||||
|
IWshShortcut shortcut = (IWshShortcut)m_type.InvokeMember("CreateShortcut", System.Reflection.BindingFlags.InvokeMethod, null, m_shell, new object[] { fileName });
|
||||||
|
shortcut.Description = description;
|
||||||
|
shortcut.TargetPath = targetPath;
|
||||||
|
shortcut.WorkingDirectory = workingDirectory;
|
||||||
|
shortcut.Arguments = arguments;
|
||||||
|
if (!string.IsNullOrEmpty(iconPath))
|
||||||
|
shortcut.IconLocation = iconPath;
|
||||||
|
shortcut.Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<UserControl x:Class="WeModPatcher.View.Controls.InfoItem"
|
||||||
|
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.Controls"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="300" d:DesignWidth="300">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Viewbox Width="20" Height="20" VerticalAlignment="Top">
|
||||||
|
<Path Fill="{Binding IconColor}" Data="{Binding IconData}"/>
|
||||||
|
</Viewbox>
|
||||||
|
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="5 0 5 0" TextWrapping="Wrap"
|
||||||
|
FontSize="12" Text="{Binding Text}"/>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace WeModPatcher.View.Controls
|
||||||
|
{
|
||||||
|
public partial class InfoItem : UserControl
|
||||||
|
{
|
||||||
|
public static readonly DependencyProperty IconDataProperty =
|
||||||
|
DependencyProperty.Register(nameof(IconData), typeof(Geometry), typeof(InfoItem));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty IconColorProperty =
|
||||||
|
DependencyProperty.Register(nameof(IconColor), typeof(Brush), typeof(InfoItem));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty TextProperty =
|
||||||
|
DependencyProperty.Register(nameof(Text), typeof(string), typeof(InfoItem));
|
||||||
|
|
||||||
|
public Geometry IconData
|
||||||
|
{
|
||||||
|
get => (Geometry)GetValue(IconDataProperty);
|
||||||
|
set => SetValue(IconDataProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Brush IconColor
|
||||||
|
{
|
||||||
|
get => (Brush)GetValue(IconColorProperty);
|
||||||
|
set => SetValue(IconColorProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text
|
||||||
|
{
|
||||||
|
get => (string)GetValue(TextProperty);
|
||||||
|
set => SetValue(TextProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InfoItem()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
this.DataContext = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<Grid x:Class="WeModPatcher.View.Controls.PopupHost"
|
||||||
|
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.Controls"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Visibility="Collapsed">
|
||||||
|
<Border x:Name="Splash" Background="Black" CornerRadius="7"
|
||||||
|
Opacity="0.45"
|
||||||
|
MouseLeftButtonDown="HidePopup"/>
|
||||||
|
|
||||||
|
<Border Background="{DynamicResource Background}" d:Margin="0"
|
||||||
|
Margin="0 40 0 40" x:Name="PopupPresenter" Width="Auto" Height="Auto"
|
||||||
|
BorderBrush="{DynamicResource Border}" BorderThickness="1" MinWidth="300"
|
||||||
|
VerticalAlignment="Center" HorizontalAlignment="Center" CornerRadius="4" Padding="15 10 10 15">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Button BorderThickness="0" BorderBrush="Transparent"
|
||||||
|
Tag="{StaticResource CloseIcon}"
|
||||||
|
Width="25" Height="25" Padding="8" Background="Transparent"
|
||||||
|
HorizontalAlignment="Right" VerticalAlignment="Top" Click="HidePopup"
|
||||||
|
x:Name="cancel">
|
||||||
|
<Button.Style>
|
||||||
|
<Style BasedOn="{StaticResource IconButton}" TargetType="Button">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource MutedForeground}"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource Foreground}"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Button.Style>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="0" x:Name="TitleContainer" Orientation="Horizontal">
|
||||||
|
<TextBlock x:Name="Title" Text="This is title" Foreground="{DynamicResource Foreground}"
|
||||||
|
HorizontalAlignment="Left" FontWeight="Bold" FontSize="16"
|
||||||
|
VerticalAlignment="Bottom"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<ContentPresenter x:Name="Presenter" Margin="0 20 0 0"
|
||||||
|
Content="{Binding PopupContent}" Grid.Row="2"/>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media.Animation;
|
||||||
|
|
||||||
|
namespace WeModPatcher.View.Controls
|
||||||
|
{
|
||||||
|
public partial class PopupHost : Grid
|
||||||
|
{
|
||||||
|
internal Action Closed;
|
||||||
|
|
||||||
|
public static readonly DependencyProperty PopupContentProperty =
|
||||||
|
DependencyProperty.Register("PopupContent", typeof(object), typeof(PopupHost), new PropertyMetadata(null));
|
||||||
|
|
||||||
|
internal readonly SemaphoreSlim OpenedSemaphore = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
|
private DoubleAnimation OpeningAnimation;
|
||||||
|
private DoubleAnimation ClosingAnimation;
|
||||||
|
|
||||||
|
|
||||||
|
public bool IsOpen
|
||||||
|
{
|
||||||
|
get => this.Visibility == Visibility.Visible;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
if(OpenedSemaphore.CurrentCount == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
Visibility = Visibility.Visible;
|
||||||
|
cancel.Focus();
|
||||||
|
PopupPresenter.BeginAnimation(OpacityProperty, OpeningAnimation);
|
||||||
|
OpenedSemaphore.Wait();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PopupPresenter.BeginAnimation(OpacityProperty, ClosingAnimation);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public object PopupContent
|
||||||
|
{
|
||||||
|
get => GetValue(PopupContentProperty);
|
||||||
|
set => SetValue(PopupContentProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HidePopup(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (OpenedSemaphore.CurrentCount == 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
IsOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClosing(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (PopupContent == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Visibility = Visibility.Collapsed;
|
||||||
|
Closed?.Invoke();
|
||||||
|
if (PopupContent is IDisposable disposable)
|
||||||
|
{
|
||||||
|
disposable.Dispose();
|
||||||
|
}
|
||||||
|
PopupContent = null;
|
||||||
|
Closed = null;
|
||||||
|
OpenedSemaphore.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PopupHost()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
PreviewKeyDown += (sender, e) =>
|
||||||
|
{
|
||||||
|
if (e.Key != Key.Escape)
|
||||||
|
return;
|
||||||
|
|
||||||
|
HidePopup(null, null);
|
||||||
|
e.Handled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
OpeningAnimation = new DoubleAnimation(0, 1, new Duration(TimeSpan.FromSeconds(0.4)))
|
||||||
|
{
|
||||||
|
EasingFunction = App.Current.FindResource("BaseAnimationFunction") as IEasingFunction
|
||||||
|
};
|
||||||
|
OpeningAnimation.Freeze();
|
||||||
|
|
||||||
|
ClosingAnimation = new DoubleAnimation(1, 0, new Duration(TimeSpan.FromSeconds(0.2)));
|
||||||
|
ClosingAnimation.Completed += OnClosing;
|
||||||
|
ClosingAnimation.Freeze();
|
||||||
|
|
||||||
|
this.Splash.DataContext = this;
|
||||||
|
this.PopupPresenter.DataContext = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
namespace WeModPatcher.View.MainWindow
|
||||||
|
{
|
||||||
|
public enum ELogType
|
||||||
|
{
|
||||||
|
Info,
|
||||||
|
Warn,
|
||||||
|
Error,
|
||||||
|
Success
|
||||||
|
}
|
||||||
|
public class LogEntry
|
||||||
|
{
|
||||||
|
public ELogType LogType { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
<Window x:Class="WeModPatcher.View.MainWindow.MainWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:local="clr-namespace:WeModPatcher.View.MainWindow"
|
||||||
|
xmlns:controls="clr-namespace:WeModPatcher.View.Controls"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DataContext="{d:DesignInstance local:MainWindowVm}"
|
||||||
|
Title="WeMod Patcher"
|
||||||
|
Height="510" MaxHeight="510"
|
||||||
|
Width="780" MaxWidth="780"
|
||||||
|
Opacity="0.97"
|
||||||
|
Background="Transparent"
|
||||||
|
WindowStyle="None"
|
||||||
|
FontFamily="{StaticResource Inter}"
|
||||||
|
AllowsTransparency="True">
|
||||||
|
<Border CornerRadius="7" Background="{DynamicResource Background}"
|
||||||
|
BorderBrush="{DynamicResource Border}"
|
||||||
|
BorderThickness="1" Margin="10">
|
||||||
|
<Border.Effect>
|
||||||
|
<DropShadowEffect BlurRadius="15" Direction="-90"
|
||||||
|
RenderingBias="Quality" ShadowDepth="2"/>
|
||||||
|
</Border.Effect>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Grid Background="Transparent" VerticalAlignment="Top"
|
||||||
|
MouseLeftButtonDown="OnDragMove" Height="55">
|
||||||
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="25 0 0 0">
|
||||||
|
<Viewbox VerticalAlignment="Center" Width="32" Height="32">
|
||||||
|
<Path
|
||||||
|
Fill="White" Data="{StaticResource Logo}"/>
|
||||||
|
</Viewbox>
|
||||||
|
<TextBlock Foreground="{DynamicResource Foreground}"
|
||||||
|
FontWeight="SemiBold" Opacity="0.9"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontSize="18" Margin="10 0 0 0">
|
||||||
|
<Bold>
|
||||||
|
WeMod Patcher
|
||||||
|
</Bold>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock x:Name="VersionLabel" VerticalAlignment="Bottom"
|
||||||
|
Opacity="0.7" FontSize="10" Margin="5 0 0 5"
|
||||||
|
Foreground="{DynamicResource Foreground}">
|
||||||
|
v 1.0.0
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
|
<Button Background="SpringGreen" Foreground="{DynamicResource Muted}"
|
||||||
|
FontWeight="Medium" Padding="20 0" Margin="10 5 20 5"
|
||||||
|
ToolTip="Click to update"
|
||||||
|
Command="{Binding UpdateCommand}"
|
||||||
|
Visibility="{Binding IsUpdateAvailable, Converter={StaticResource ToVisibilityConverter}}"
|
||||||
|
Content="{DynamicResource mw_update_available}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
|
||||||
|
<Button
|
||||||
|
Margin="0 0 5 0"
|
||||||
|
Width="25" Height="25" Padding="5.5"
|
||||||
|
Style="{StaticResource IconButton}"
|
||||||
|
Tag="{StaticResource CogIcon}"
|
||||||
|
Command="{Binding OpenSettingsCommand}"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
Margin="9 0 15 0"
|
||||||
|
Tag="{StaticResource CloseIcon}"
|
||||||
|
Width="25" Height="25" Padding="6.5"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Click="OnClosing"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<Button.Resources>
|
||||||
|
<CornerRadius x:Key="CornerRadius">5 5 5 5</CornerRadius>
|
||||||
|
</Button.Resources>
|
||||||
|
<Button.Style>
|
||||||
|
<Style BasedOn="{StaticResource IconButton}" TargetType="Button">
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource Secondary}"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource Destructive}"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Button.Style>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Border Background="{DynamicResource Border}" Height="1" VerticalAlignment="Bottom"></Border>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
<Grid Margin="0 55 0 0">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition Height="50"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid Margin="10" Cursor="Hand" Background="Transparent">
|
||||||
|
<TextBox Style="{StaticResource TitledTextBox}"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Text="{Binding WeModInfo.RootDirectory, Mode=OneWay}"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
</TextBox>
|
||||||
|
<Grid.InputBindings>
|
||||||
|
<MouseBinding Gesture="LeftClick" Command="{Binding SetFolderPathCommand}" />
|
||||||
|
</Grid.InputBindings>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
|
||||||
|
<Border Grid.Row="1" BorderBrush="{DynamicResource Border}" BorderThickness="1"
|
||||||
|
Margin="10 0 10 10"
|
||||||
|
CornerRadius="5">
|
||||||
|
<ListBox ItemsSource="{Binding LogList}" SelectionMode="Single"
|
||||||
|
BorderBrush="Transparent" BorderThickness="0"
|
||||||
|
Background="Transparent"
|
||||||
|
x:Name="LogList"
|
||||||
|
Padding="6"
|
||||||
|
ScrollViewer.VerticalScrollBarVisibility="Hidden"
|
||||||
|
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||||
|
ScrollViewer.CanContentScroll="False">
|
||||||
|
<ListBox.ItemContainerStyle>
|
||||||
|
<Style TargetType="ListBoxItem">
|
||||||
|
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||||
|
<Setter Property="Margin" Value="0 0 0 5"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="ListBoxItem">
|
||||||
|
<Border x:Name="Card" HorizontalAlignment="Left"
|
||||||
|
Background="#08fc81"
|
||||||
|
BorderBrush="{DynamicResource Border}"
|
||||||
|
Padding="5 3 5 3" CornerRadius="3">
|
||||||
|
<TextBox Text="{Binding Message}"
|
||||||
|
Cursor="IBeam" FontSize="13"
|
||||||
|
Background="Transparent" TextWrapping="Wrap"
|
||||||
|
BorderThickness="0"
|
||||||
|
IsReadOnly="True"/>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding LogType}" Value="Error">
|
||||||
|
<Setter TargetName="Card" Property="Background" Value="#f04343"></Setter>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding LogType}" Value="Info">
|
||||||
|
<Setter TargetName="Card" Property="Background" Value="#FFF"></Setter>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding LogType}" Value="Warn">
|
||||||
|
<Setter TargetName="Card" Property="Background" Value="#facc15"></Setter>
|
||||||
|
</DataTrigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</ListBox.ItemContainerStyle>
|
||||||
|
</ListBox>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
|
||||||
|
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal" Grid.Row="2" Margin="10 0 10 10">
|
||||||
|
<Grid>
|
||||||
|
<Grid HorizontalAlignment="Right" >
|
||||||
|
<Grid.Style>
|
||||||
|
<Style TargetType="{x:Type Grid}">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsPatchEnabled}" Value="False">
|
||||||
|
<Setter Property="Cursor" Value="No"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Grid.Style>
|
||||||
|
<Button Style="{StaticResource ColoredButton}"
|
||||||
|
IsEnabled="{Binding IsPatchEnabled}"
|
||||||
|
FontWeight="Bold" FontSize="16" Width="200"
|
||||||
|
Command="{Binding ApplyPatchCommand}"
|
||||||
|
Content="{DynamicResource mw_patch}"/>
|
||||||
|
</Grid>
|
||||||
|
<Button HorizontalAlignment="Right"
|
||||||
|
Command="{Binding RestoreBackupCommand }"
|
||||||
|
FontWeight="Bold" FontSize="16" Width="200"
|
||||||
|
Style="{StaticResource ColoredButton}"
|
||||||
|
Visibility="{Binding AlreadyPatched, Converter={StaticResource ToVisibilityConverter}}"
|
||||||
|
Content="{DynamicResource mw_restore}"/>
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<DockPanel Grid.Row="2" Margin="10 0 10 10">
|
||||||
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"
|
||||||
|
Cursor="Hand"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
MouseDown="OpenSourceClicked"
|
||||||
|
Background="Transparent">
|
||||||
|
<Viewbox VerticalAlignment="Center" Width="32" Height="32">
|
||||||
|
<Path
|
||||||
|
Fill="White" Data="{StaticResource GitHub}"/>
|
||||||
|
</Viewbox>
|
||||||
|
<Grid>
|
||||||
|
<TextBlock Margin="8 0 0 0" FontSize="10" Foreground="{DynamicResource AccentForeground}">
|
||||||
|
<Hyperlink Foreground="{DynamicResource AccentForeground}"><Run Text="{DynamicResource mw_source_code}"/></Hyperlink>
|
||||||
|
<LineBreak/>
|
||||||
|
<Run Text="{DynamicResource mw_made_by}"/>
|
||||||
|
|
||||||
|
|
||||||
|
<LineBreak/>
|
||||||
|
<Run Foreground="{DynamicResource MutedForeground}" Text="{DynamicResource mw_star_hint}"/>
|
||||||
|
</TextBlock>
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
</DockPanel>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<controls:PopupHost x:Name="PopupHost"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace WeModPatcher.View.MainWindow
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for MainWindow.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class MainWindow
|
||||||
|
{
|
||||||
|
public static MainWindow Instance;
|
||||||
|
public readonly MainWindowVm ViewModel;
|
||||||
|
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
this.ViewModel = new MainWindowVm(this);
|
||||||
|
this.DataContext = ViewModel;
|
||||||
|
VersionLabel.Text = Constants.Version.ToString();
|
||||||
|
Instance = this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenPopup(FrameworkElement content, string title = null)
|
||||||
|
{
|
||||||
|
this.PopupHost.PopupContent = content;
|
||||||
|
PopupHost.Title.Text = title;
|
||||||
|
PopupHost.IsOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDragMove(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
this.DragMove();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClosing(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Application.Current.Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClosePopup()
|
||||||
|
{
|
||||||
|
PopupHost.IsOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenSourceClicked(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Process.Start(Constants.RepositoryUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using WeModPatcher.Core;
|
||||||
|
using WeModPatcher.Models;
|
||||||
|
using WeModPatcher.ReactiveUICore;
|
||||||
|
using WeModPatcher.Utils;
|
||||||
|
using WeModPatcher.View.Popups;
|
||||||
|
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 WeModConfig _weModConfig;
|
||||||
|
|
||||||
|
public WeModConfig WeModInfo
|
||||||
|
{
|
||||||
|
get => _weModConfig;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetProperty(ref _weModConfig, value);
|
||||||
|
if (value == null) return;
|
||||||
|
|
||||||
|
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;
|
||||||
|
AlreadyPatched = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log("Ready for patching.", ELogType.Info);
|
||||||
|
IsPatchEnabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _isPatchEnabled;
|
||||||
|
|
||||||
|
public bool IsPatchEnabled
|
||||||
|
{
|
||||||
|
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 RelayCommand UpdateCommand { get; }
|
||||||
|
public RelayCommand OpenSettingsCommand { get; }
|
||||||
|
|
||||||
|
private void OnFolderPathSelection(object obj)
|
||||||
|
{
|
||||||
|
using (var dialog = new FolderBrowserDialog())
|
||||||
|
{
|
||||||
|
dialog.SelectedPath = Environment.GetEnvironmentVariable("LOCALAPPDATA");
|
||||||
|
dialog.Description = "Select the WeMod directory";
|
||||||
|
dialog.ShowNewFolderButton = false;
|
||||||
|
|
||||||
|
if (dialog.ShowDialog() != DialogResult.OK) return;
|
||||||
|
string selectedPath = dialog.SelectedPath;
|
||||||
|
string fileName = Path.GetFileName(selectedPath);
|
||||||
|
|
||||||
|
var info = Extensions.CheckWeModPath(selectedPath);
|
||||||
|
|
||||||
|
if (info != null)
|
||||||
|
{
|
||||||
|
WeModInfo = info;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogList.Add(new LogEntry
|
||||||
|
{
|
||||||
|
LogType = ELogType.Error,
|
||||||
|
Message = $"The selected folder '{fileName}' is not a valid WeMod directory."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBackupRestoring(object param)
|
||||||
|
{
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
var proxyDllPath = Path.Combine(WeModInfo.RootDirectory, "version.dll");
|
||||||
|
|
||||||
|
if(File.Exists(proxyDllPath))
|
||||||
|
{
|
||||||
|
File.Delete(proxyDllPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Log("Backup file is locked. Please close the WeMod and try again.", ELogType.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File.Copy(backupPath, Path.Combine(WeModInfo.RootDirectory, "resources", "app.asar"), true);
|
||||||
|
File.Delete(backupPath);
|
||||||
|
Log("Backup restored successfully.", ELogType.Success);
|
||||||
|
AlreadyPatched = false;
|
||||||
|
IsPatchEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPatching(object param)
|
||||||
|
{
|
||||||
|
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.ClosePopup();
|
||||||
|
IsPatchEnabled = false;
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
new Patcher(WeModInfo, Log, config).Patch();
|
||||||
|
AlreadyPatched = true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log($"Failed to patch: {e.Message}", ELogType.Error);
|
||||||
|
IsPatchEnabled = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}), Application.Current.FindResource("pv_popup_title") as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Log(string message, ELogType logType)
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
message = $"[{logType.ToString().ToUpper()}] {message}";
|
||||||
|
|
||||||
|
var entry = new LogEntry
|
||||||
|
{
|
||||||
|
LogType = logType,
|
||||||
|
Message = message
|
||||||
|
};
|
||||||
|
LogList.Add(entry);
|
||||||
|
_view.LogList.ScrollIntoView(entry);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUpdate(object param)
|
||||||
|
{
|
||||||
|
MainWindow.Instance.OpenPopup(new UpdatePopup(() =>
|
||||||
|
{
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _updater.Update();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log($"Failed to update: {e.Message}", ELogType.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log("WeModPatcher updated successfully. Restarting...", ELogType.Success);
|
||||||
|
});
|
||||||
|
}), Application.Current.FindResource("up_popup_title") as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnOpenSettings(object param)
|
||||||
|
{
|
||||||
|
MainWindow.Instance.OpenPopup(new SettingsPopup(), Application.Current.FindResource("settings_title") as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MainWindowVm(MainWindow view)
|
||||||
|
{
|
||||||
|
Task.Run(async () => IsUpdateAvailable = await _updater.CheckForUpdates());
|
||||||
|
_view = view;
|
||||||
|
SetFolderPathCommand = new RelayCommand(OnFolderPathSelection);
|
||||||
|
ApplyPatchCommand = new RelayCommand(OnPatching);
|
||||||
|
RestoreBackupCommand = new RelayCommand(OnBackupRestoring);
|
||||||
|
UpdateCommand = new RelayCommand(OnUpdate);
|
||||||
|
OpenSettingsCommand = new RelayCommand(OnOpenSettings);
|
||||||
|
|
||||||
|
WeModInfo = Extensions.FindWeMod();
|
||||||
|
if (WeModInfo == null)
|
||||||
|
{
|
||||||
|
Log("WeMod directory not found.", ELogType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<UserControl x:Class="WeModPatcher.View.Popups.PatchVectorsPopup"
|
||||||
|
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"
|
||||||
|
xmlns:controls="clr-namespace:WeModPatcher.View.Controls"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="Auto" d:DesignWidth="Auto"
|
||||||
|
Background="{DynamicResource Background}"
|
||||||
|
Foreground="{DynamicResource MutedForeground}"
|
||||||
|
FontWeight="Medium"
|
||||||
|
FontSize="13">
|
||||||
|
<Grid>
|
||||||
|
<Grid Visibility="Visible" Margin="0 0 5 0">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="27" />
|
||||||
|
<RowDefinition Height="27" />
|
||||||
|
<RowDefinition Height="27" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="0" VerticalAlignment="Center" Text="{DynamicResource pv_activate_pro}" />
|
||||||
|
<CheckBox Grid.Row="0" x:Name="ActivateProBox" HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||||
|
IsChecked="True" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="1" VerticalAlignment="Center" Text="{DynamicResource pv_devtools}" />
|
||||||
|
<CheckBox Grid.Row="1" x:Name="DevToolsHotkeyBox" HorizontalAlignment="Right" VerticalAlignment="Center" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="2" VerticalAlignment="Center" Text="{DynamicResource pv_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="{DynamicResource pv_start}"
|
||||||
|
Click="OnPatchButtonClick" />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using WeModPatcher.Models;
|
||||||
|
using WeModPatcher.View.Controls;
|
||||||
|
|
||||||
|
namespace WeModPatcher.View.Popups
|
||||||
|
{
|
||||||
|
public partial class PatchVectorsPopup : UserControl
|
||||||
|
{
|
||||||
|
private readonly Action<PatchConfig> _onApply;
|
||||||
|
|
||||||
|
public PatchVectorsPopup(Action<PatchConfig> onApply)
|
||||||
|
{
|
||||||
|
_onApply = onApply;
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPatchButtonClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (ActivateProBox.IsChecked != true && DisableUpdateBox.IsChecked != true &&
|
||||||
|
DevToolsHotkeyBox.IsChecked != true)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = new HashSet<EPatchType>();
|
||||||
|
if (ActivateProBox.IsChecked == true)
|
||||||
|
{
|
||||||
|
result.Add(EPatchType.ActivatePro);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DisableUpdateBox.IsChecked == true)
|
||||||
|
{
|
||||||
|
result.Add(EPatchType.DisableUpdates);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DevToolsHotkeyBox.IsChecked == true)
|
||||||
|
{
|
||||||
|
result.Add(EPatchType.DevToolsOnF12);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onApply(new PatchConfig
|
||||||
|
{
|
||||||
|
PatchTypes = result,
|
||||||
|
AutoApplyPatches =/* AutoUpdates.IsChecked == true*/ false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<UserControl x:Class="WeModPatcher.View.Popups.SettingsPopup"
|
||||||
|
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"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="Auto" d:DesignWidth="Auto"
|
||||||
|
Background="{DynamicResource Background}"
|
||||||
|
Foreground="{DynamicResource MutedForeground}"
|
||||||
|
FontWeight="Medium"
|
||||||
|
FontSize="13">
|
||||||
|
<Grid MinWidth="250">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid Grid.Row="0" Margin="0 0 0 15">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<TextBlock Grid.Column="0" VerticalAlignment="Center"
|
||||||
|
Text="{DynamicResource settings_language}" />
|
||||||
|
<ComboBox Grid.Column="1" x:Name="LanguageComboBox"
|
||||||
|
Width="130"
|
||||||
|
SelectionChanged="OnLanguageSelectionChanged">
|
||||||
|
<ComboBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock Text="{Binding DisplayName}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Button Grid.Row="1" Padding="0 5 0 5" Margin="0 5 0 0"
|
||||||
|
Content="{DynamicResource settings_save}"
|
||||||
|
Click="OnSaveClick" />
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using WeModPatcher.Core;
|
||||||
|
using WeModPatcher.Core.Services;
|
||||||
|
using WeModPatcher.View.MainWindow;
|
||||||
|
|
||||||
|
namespace WeModPatcher.View.Popups
|
||||||
|
{
|
||||||
|
public partial class SettingsPopup : UserControl
|
||||||
|
{
|
||||||
|
private CultureInfo _selectedLanguage;
|
||||||
|
|
||||||
|
public SettingsPopup()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
LoadLanguages();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadLanguages()
|
||||||
|
{
|
||||||
|
var items = LocalizationManager.SupportedLanguages
|
||||||
|
.Select(c => new LanguageItem
|
||||||
|
{
|
||||||
|
Culture = c,
|
||||||
|
DisplayName = LocalizationManager.GetLanguageDisplayName(c)
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
LanguageComboBox.ItemsSource = items;
|
||||||
|
|
||||||
|
var currentItem = items.FirstOrDefault(i => i.Culture.Name == LocalizationManager.CurrentLanguage?.Name);
|
||||||
|
if (currentItem != null)
|
||||||
|
{
|
||||||
|
LanguageComboBox.SelectedItem = currentItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
_selectedLanguage = LocalizationManager.CurrentLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLanguageSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (LanguageComboBox.SelectedItem is LanguageItem item)
|
||||||
|
{
|
||||||
|
_selectedLanguage = item.Culture;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSaveClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_selectedLanguage != null &&
|
||||||
|
(LocalizationManager.CurrentLanguage == null ||
|
||||||
|
_selectedLanguage.Name != LocalizationManager.CurrentLanguage.Name))
|
||||||
|
{
|
||||||
|
LocalizationManager.CurrentLanguage = _selectedLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
|
MainWindow.MainWindow.Instance.ClosePopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LanguageItem
|
||||||
|
{
|
||||||
|
public CultureInfo Culture { get; set; }
|
||||||
|
public string DisplayName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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="{DynamicResource up_warning}" TextWrapping="Wrap" />
|
||||||
|
|
||||||
|
<Button Padding="0 5 0 5" Margin="0 15 0 0" Content="{DynamicResource up_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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="..\packages\ILRepack.2.0.41\build\ILRepack.props" Condition="Exists('..\packages\ILRepack.2.0.41\build\ILRepack.props')" />
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{106D3E44-ECBB-4EF3-84B2-5FC6BCF77727}</ProjectGuid>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<RootNamespace>WeModPatcher</RootNamespace>
|
||||||
|
<AssemblyName>WeModPatcher</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
|
<ApplicationIcon>..\assets\appicon.ico</ApplicationIcon>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
<Prefer32bit>false</Prefer32bit>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
|
<DebugType>none</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
<Prefer32bit>false</Prefer32bit>
|
||||||
|
</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">
|
||||||
|
<HintPath>..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.Data" />
|
||||||
|
<Reference Include="System.Windows.Forms" />
|
||||||
|
<Reference Include="System.Xml" />
|
||||||
|
<Reference Include="System.Net.Http" />
|
||||||
|
<Reference Include="System.Xaml">
|
||||||
|
<RequiredTargetFramework>4.0</RequiredTargetFramework>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="WindowsBase" />
|
||||||
|
<Reference Include="PresentationCore" />
|
||||||
|
<Reference Include="PresentationFramework" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ApplicationDefinition Include="App.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</ApplicationDefinition>
|
||||||
|
<Compile Include="Constants.cs" />
|
||||||
|
<Compile Include="Converters\BaseBooleanConverter.cs" />
|
||||||
|
<Compile Include="Converters\ToVisibilityConverter.cs" />
|
||||||
|
<Compile Include="Core\Patcher.cs" />
|
||||||
|
<Compile Include="Core\PatcherConfig.cs" />
|
||||||
|
<Compile Include="Core\Services\LocalizationManager.cs" />
|
||||||
|
<Compile Include="Core\Services\SettingsManager.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\Updater.cs" />
|
||||||
|
<Compile Include="Utils\Win32\Shortcut.cs" />
|
||||||
|
<Compile Include="View\Controls\InfoItem.xaml.cs">
|
||||||
|
<DependentUpon>InfoItem.xaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="View\Controls\PopupHost.xaml.cs" />
|
||||||
|
<Compile Include="View\MainWindow\Logs.cs" />
|
||||||
|
<Compile Include="View\MainWindow\MainWindow.xaml.cs" />
|
||||||
|
<Compile Include="View\MainWindow\MainWindowVm.cs" />
|
||||||
|
<Compile Include="View\Popups\PatchVectorsPopup.xaml.cs">
|
||||||
|
<DependentUpon>PatchVectorsPopup.xaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="View\Popups\SettingsPopup.xaml.cs">
|
||||||
|
<DependentUpon>SettingsPopup.xaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="App.xaml.cs">
|
||||||
|
<DependentUpon>App.xaml</DependentUpon>
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="View\Popups\UpdatePopup.xaml.cs">
|
||||||
|
<DependentUpon>UpdatePopup.xaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Page Include="Locale\lang.en-US.xaml" />
|
||||||
|
<Page Include="Locale\lang.zh-CN.xaml" />
|
||||||
|
<Page Include="Locale\lang.de-DE.xaml" />
|
||||||
|
<Page Include="Locale\lang.fr-FR.xaml" />
|
||||||
|
<Page Include="Locale\lang.es-ES.xaml" />
|
||||||
|
<Page Include="Locale\lang.it-IT.xaml" />
|
||||||
|
<Page Include="Locale\lang.pt-BR.xaml" />
|
||||||
|
<Page Include="Locale\lang.pl-PL.xaml" />
|
||||||
|
<Page Include="Locale\lang.ru-RU.xaml" />
|
||||||
|
<Page Include="Locale\lang.uk-UA.xaml" />
|
||||||
|
<Page Include="Locale\lang.ja-JP.xaml" />
|
||||||
|
<Page Include="Locale\lang.tr-TR.xaml" />
|
||||||
|
<Page Include="Style\ColorScheme.xaml" />
|
||||||
|
<Page Include="Style\Icons.xaml" />
|
||||||
|
<Page Include="Style\Styles.xaml" />
|
||||||
|
<Page Include="View\Controls\InfoItem.xaml" />
|
||||||
|
<Page Include="View\Controls\PopupHost.xaml" />
|
||||||
|
<Page Include="View\MainWindow\MainWindow.xaml" />
|
||||||
|
<Page Include="View\Popups\PatchVectorsPopup.xaml" />
|
||||||
|
<Page Include="View\Popups\SettingsPopup.xaml" />
|
||||||
|
<Page Include="View\Popups\UpdatePopup.xaml" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs">
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Properties\Resources.Designer.cs">
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DesignTime>True</DesignTime>
|
||||||
|
<DependentUpon>Resources.resx</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<EmbeddedResource Include="Properties\Resources.resx">
|
||||||
|
<Generator>ResXFileCodeGenerator</Generator>
|
||||||
|
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||||
|
</EmbeddedResource>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="..\assets\appicon.ico">
|
||||||
|
<Link>appicon.ico</Link>
|
||||||
|
</None>
|
||||||
|
<None Include="App.config" />
|
||||||
|
<None Include="packages.config" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="Style\Inter_18pt-Regular.ttf" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\AsarSharp\AsarSharp.csproj">
|
||||||
|
<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>
|
||||||
|
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}.</ErrorText>
|
||||||
|
</PropertyGroup>
|
||||||
|
<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>
|
||||||
|
<MainAssembly>$(OutputPath)$(AssemblyName).exe</MainAssembly>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<AssemblyList Include="$(OutputPath)*.dll" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<DllList>@(AssemblyList->'%(FullPath)', ' ')</DllList>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<Exec Command=""$(ILRepackExe)" /allowMultiple /copyattrs /out:"$(OutputPath)$(AssemblyName).exe" "$(MainAssembly)" $(DllList)" />
|
||||||
|
</Target>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<packages>
|
||||||
|
<package id="ILRepack" version="2.0.41" targetFramework="net48" developmentDependency="true" />
|
||||||
|
<package id="Microsoft.Build.Framework" version="15.9.20" targetFramework="net48" />
|
||||||
|
<package id="Microsoft.Build.Utilities.Core" version="15.9.20" targetFramework="net48" />
|
||||||
|
<package id="Microsoft.VisualStudio.Setup.Configuration.Interop" version="1.16.30" targetFramework="net48" developmentDependency="true" />
|
||||||
|
<package id="Newtonsoft.Json" version="13.0.3" targetFramework="net48" />
|
||||||
|
<package id="System.Collections.Immutable" version="1.5.0" targetFramework="net48" />
|
||||||
|
<package id="System.Runtime.InteropServices.RuntimeInformation" version="4.3.0" targetFramework="net48" />
|
||||||
|
</packages>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WeModPatcher", "WeModPatcher\WeModPatcher.csproj", "{106D3E44-ECBB-4EF3-84B2-5FC6BCF77727}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsarSharp", "AsarSharp\AsarSharp.csproj", "{BEAA604A-402A-4387-8903-A53FC913A26E}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{106D3E44-ECBB-4EF3-84B2-5FC6BCF77727}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{106D3E44-ECBB-4EF3-84B2-5FC6BCF77727}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{106D3E44-ECBB-4EF3-84B2-5FC6BCF77727}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{106D3E44-ECBB-4EF3-84B2-5FC6BCF77727}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{BEAA604A-402A-4387-8903-A53FC913A26E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{BEAA604A-402A-4387-8903-A53FC913A26E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{BEAA604A-402A-4387-8903-A53FC913A26E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{BEAA604A-402A-4387-8903-A53FC913A26E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 103 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0,0,256,256" width="170px" height="170px" fill-rule="nonzero"><g transform="translate(17.92,17.92) scale(0.86,0.86)"><g fill="#27272a" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><path d="M53.5814,276.83721c-41.10026,0 -74.4186,-33.31834 -74.4186,-74.4186v-148.83721c0,-41.10026 33.31834,-74.4186 74.4186,-74.4186h148.83721c41.10026,0 74.4186,33.31834 74.4186,74.4186v148.83721c0,41.10026 -33.31834,74.4186 -74.4186,74.4186z" id="shape"></path></g><g fill="#ffffff" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(5.12,5.12)"><path d="M47.845,22.185l-20.03,-20.03c-1.543,-1.543 -4.046,-1.553 -5.729,0.002l-19.931,20.028c-1.542,1.542 -1.554,4.045 0,5.727l19.934,19.934c0.772,0.772 1.785,1.16 2.816,1.16c1.026,0 2.07,-0.385 2.91,-1.16l19.933,-19.934c1.605,-1.605 1.648,-4.175 0.097,-5.727zM18,27c-1.105,0 -2,-0.895 -2,-2c0,-1.105 0.895,-2 2,-2c1.105,0 2,0.895 2,2c0,1.105 -0.895,2 -2,2zM25,34c-1.105,0 -2,-0.895 -2,-2c0,-1.105 0.895,-2 2,-2c1.105,0 2,0.895 2,2c0,1.105 -0.895,2 -2,2zM25,20c-1.105,0 -2,-0.895 -2,-2c0,-1.105 0.895,-2 2,-2c1.105,0 2,0.895 2,2c0,1.105 -0.895,2 -2,2zM32,27c-1.105,0 -2,-0.895 -2,-2c0,-1.105 0.895,-2 2,-2c1.105,0 2,0.895 2,2c0,1.105 -0.895,2 -2,2z"></path></g></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 115 KiB |
-48
@@ -1,48 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>WeMod Patcher</title>
|
|
||||||
<link rel="stylesheet" href="styles.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div class="main-section">
|
|
||||||
<div class="header">
|
|
||||||
<h1>WeMod Patcher</h1>
|
|
||||||
<p>Select the folder with the WeMod version like: “app-X.XX.X” and click patch</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="path-section">
|
|
||||||
<label for="file-path">Folder path:</label>
|
|
||||||
<div class="path-input-container">
|
|
||||||
<input type="text" id="file-path" class="path-input" placeholder="Select file..." readonly>
|
|
||||||
<button class="browse-btn" id="browse-btn">Browse</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button id="patch-btn" class="patch-btn" disabled>
|
|
||||||
Patch
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="log-section">
|
|
||||||
<div class="log-entry info">Waiting for action...</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<footer class="footer">
|
|
||||||
<div>
|
|
||||||
<span id="version-label"></span>
|
|
||||||
<button id="updateBtn" class="hidden">
|
|
||||||
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<svg id="sourceBtn" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="40" height="40" viewBox="0 0 24 24">
|
|
||||||
<path fill="#ffff" d="M10.9,2.1c-4.6,0.5-8.3,4.2-8.8,8.7c-0.5,4.7,2.2,8.9,6.3,10.5C8.7,21.4,9,21.2,9,20.8v-1.6c0,0-0.4,0.1-0.9,0.1 c-1.4,0-2-1.2-2.1-1.9c-0.1-0.4-0.3-0.7-0.6-1C5.1,16.3,5,16.3,5,16.2C5,16,5.3,16,5.4,16c0.6,0,1.1,0.7,1.3,1c0.5,0.8,1.1,1,1.4,1 c0.4,0,0.7-0.1,0.9-0.2c0.1-0.7,0.4-1.4,1-1.8c-2.3-0.5-4-1.8-4-4c0-1.1,0.5-2.2,1.2-3C7.1,8.8,7,8.3,7,7.6C7,7.2,7,6.6,7.3,6 c0,0,1.4,0,2.8,1.3C10.6,7.1,11.3,7,12,7s1.4,0.1,2,0.3C15.3,6,16.8,6,16.8,6C17,6.6,17,7.2,17,7.6c0,0.8-0.1,1.2-0.2,1.4 c0.7,0.8,1.2,1.8,1.2,3c0,2.2-1.7,3.5-4,4c0.6,0.5,1,1.4,1,2.3v2.6c0,0.3,0.3,0.6,0.7,0.5c3.7-1.5,6.3-5.1,6.3-9.3 C22,6.1,16.9,1.4,10.9,2.1z"></path>
|
|
||||||
</svg>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script src="renderer.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
const { app, BrowserWindow, ipcMain, dialog } = require('electron');
|
|
||||||
const path = require('path');
|
|
||||||
const fs = require("fs");
|
|
||||||
const Unlocker = require("./unlocker");
|
|
||||||
const GitHubUpdater = require("./updater");
|
|
||||||
|
|
||||||
const packageJsonPath = path.join(__dirname, 'package.json');
|
|
||||||
const packageData = require(packageJsonPath)
|
|
||||||
const updater = new GitHubUpdater(packageData.author, packageData.name);
|
|
||||||
|
|
||||||
|
|
||||||
function createWindow() {
|
|
||||||
const win = new BrowserWindow({
|
|
||||||
width: 800,
|
|
||||||
height: 600,
|
|
||||||
resizable: false,
|
|
||||||
autoHideMenuBar: true,
|
|
||||||
webPreferences: {
|
|
||||||
nodeIntegration: true,
|
|
||||||
contextIsolation: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
win.loadFile('index.html');
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkWeModPath = (root) => fs.existsSync(path.join(root, 'WeMod.exe')) &&
|
|
||||||
fs.existsSync(path.join(root, 'resources/app.asar'))
|
|
||||||
|
|
||||||
function log(message, type = 'info') {
|
|
||||||
BrowserWindow.getAllWindows().forEach(win => {
|
|
||||||
win.webContents.send('log', { message, type });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
app.whenReady().then(createWindow);
|
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
|
||||||
if (process.platform !== 'darwin') {
|
|
||||||
app.quit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('select-file', async () => {
|
|
||||||
const result = await dialog.showOpenDialog({
|
|
||||||
properties: ["openDirectory"],
|
|
||||||
defaultPath: process.env.LOCALAPPDATA || path.join(process.env.HOME || process.env.USERPROFILE, 'AppData', 'Local')
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result.canceled && result.filePaths.length > 0) {
|
|
||||||
return {
|
|
||||||
filePath: result.filePaths[0],
|
|
||||||
fileName: path.basename(result.filePaths[0]),
|
|
||||||
valid: checkWeModPath(result.filePaths[0])
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('apply-patch', async (event, path) => {
|
|
||||||
const unlocker = new Unlocker(path, (e) => log(e))
|
|
||||||
await unlocker.start()
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.handle('resolve-default-path', async () => {
|
|
||||||
const defaultDir = path.join(process.env.LOCALAPPDATA || path.join(process.env.HOME || process.env.USERPROFILE, 'AppData', 'Local'), 'WeMod');
|
|
||||||
|
|
||||||
if (!fs.existsSync(defaultDir)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const items = fs.readdirSync(defaultDir, {withFileTypes: true});
|
|
||||||
const appFolders = items
|
|
||||||
.filter(item => item.isDirectory() && /^app-\w+/.test(item.name))
|
|
||||||
.map(item => {
|
|
||||||
const folderPath = path.join(defaultDir, item.name);
|
|
||||||
const stats = fs.statSync(folderPath);
|
|
||||||
return {
|
|
||||||
name: item.name,
|
|
||||||
path: folderPath,
|
|
||||||
mtime: stats.mtime,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
let appDir = null;
|
|
||||||
appFolders.sort((a, b) => b.mtime - a.mtime);
|
|
||||||
for (const folder of appFolders) {
|
|
||||||
if (checkWeModPath(folder.path)) {
|
|
||||||
appDir = folder.path;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return appDir;
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.handle('start-patch', async (event, filePath) => {
|
|
||||||
try {
|
|
||||||
// stub
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
message: 'Patch completed successfully!'
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: error.message
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle("get-current-version", () => {
|
|
||||||
return packageData.version
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
ipcMain.handle("check-updates", async () => {
|
|
||||||
return await updater.checkForUpdates()
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('open-link', () => {
|
|
||||||
require('electron').shell.openExternal("https://github.com/k1tbyte/Wemod-Patcher")
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on("apply-update", async (event, source) => {
|
|
||||||
try {
|
|
||||||
log("Downloading update ...")
|
|
||||||
const path = await updater.downloadUpdate(source)
|
|
||||||
log("Preparation")
|
|
||||||
updater.applyUpdate(path)
|
|
||||||
} catch (err) {
|
|
||||||
log(err, "error")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
const fs = require("fs");
|
|
||||||
|
|
||||||
function findPatternInBuffer(buffer, bytesRead, signature, mask) {
|
|
||||||
const bufferLength = bytesRead + signature.length - 1;
|
|
||||||
|
|
||||||
for (let i = 0; i <= bytesRead - signature.length; i++) {
|
|
||||||
if (isMatch(buffer, signature, mask, i)) return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function isMatch(buffer, signature, mask, offset) {
|
|
||||||
for (let i = 0; i < signature.length; i++) {
|
|
||||||
if (mask[i] === "x" && buffer[offset + i] !== signature[i]) return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseSignature(signature) {
|
|
||||||
const signatureBytes = [];
|
|
||||||
let mask = "";
|
|
||||||
|
|
||||||
const tokens = signature.split(" ");
|
|
||||||
tokens.forEach((token) => {
|
|
||||||
if (token === "??" || token === "?") {
|
|
||||||
signatureBytes.push(0);
|
|
||||||
mask += "?";
|
|
||||||
} else {
|
|
||||||
signatureBytes.push(parseInt(token, 16));
|
|
||||||
mask += "x";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return { signature: Buffer.from(signatureBytes), mask };
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function patchBySignature(filePath, functionSignature, patchBytes, patchOffset) {
|
|
||||||
const { signature, mask } = parseSignature(functionSignature);
|
|
||||||
|
|
||||||
const bufferSize = 8192;
|
|
||||||
const buffer = Buffer.alloc(bufferSize + signature.length - 1);
|
|
||||||
|
|
||||||
const fileHandle = await fs.promises.open(filePath, "r+");
|
|
||||||
|
|
||||||
let filePosition = 0;
|
|
||||||
try {
|
|
||||||
while (true) {
|
|
||||||
const { bytesRead } = await fileHandle.read(buffer, 0, bufferSize, filePosition);
|
|
||||||
if (bytesRead === 0) break;
|
|
||||||
|
|
||||||
const matchIndex = findPatternInBuffer(buffer, bytesRead, signature, mask);
|
|
||||||
if (matchIndex !== -1) {
|
|
||||||
const functionStartPosition = filePosition + matchIndex;
|
|
||||||
|
|
||||||
const checkBuffer = Buffer.alloc(patchBytes.length);
|
|
||||||
await fileHandle.read(checkBuffer, 0, patchBytes.length, functionStartPosition + patchOffset);
|
|
||||||
|
|
||||||
if (Buffer.compare(checkBuffer, Buffer.from(patchBytes)) === 0) {
|
|
||||||
return 0; // Memory already patched
|
|
||||||
}
|
|
||||||
|
|
||||||
// Go to patch position
|
|
||||||
await fileHandle.write(Buffer.from(patchBytes), 0, patchBytes.length, functionStartPosition + patchOffset);
|
|
||||||
|
|
||||||
return functionStartPosition; // Return the address of the function start by signature
|
|
||||||
}
|
|
||||||
|
|
||||||
filePosition += bytesRead;
|
|
||||||
buffer.copy(buffer, 0, bufferSize, bufferSize + signature.length - 1);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
await fileHandle.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = patchBySignature;
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "wemod-patcher",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"start": "electron .",
|
|
||||||
"build": "electron-builder --win portable"
|
|
||||||
},
|
|
||||||
"keywords": [],
|
|
||||||
"author": "k1tbyte",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"description": "",
|
|
||||||
"dependencies": {
|
|
||||||
"asar": "^3.2.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"electron": "^33.2.1",
|
|
||||||
"electron-builder": "^25.1.8"
|
|
||||||
},
|
|
||||||
"build": {
|
|
||||||
"appId": "kitbyte.wemod-patcher",
|
|
||||||
"productName": "WeMod Patcher",
|
|
||||||
"directories": {
|
|
||||||
"output": "dist"
|
|
||||||
},
|
|
||||||
"win": {
|
|
||||||
"icon": "./appicon.ico",
|
|
||||||
"target": [{
|
|
||||||
"target": "portable",
|
|
||||||
"arch": ["x64"]
|
|
||||||
}],
|
|
||||||
"artifactName": "${productName}.exe"
|
|
||||||
},
|
|
||||||
"nsis": {
|
|
||||||
"oneClick": false,
|
|
||||||
"perMachine": false,
|
|
||||||
"allowToChangeInstallationDirectory": true,
|
|
||||||
"installerIcon": "./appicon.ico",
|
|
||||||
"uninstallerIcon": "./appicon.ico",
|
|
||||||
"installerHeaderIcon": "./appicon.ico"
|
|
||||||
},
|
|
||||||
"compression": "maximum",
|
|
||||||
"removePackageScripts": true,
|
|
||||||
"removePackageKeywords": true,
|
|
||||||
"extraResources": false,
|
|
||||||
"electronLanguages": ["en-US"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-112
@@ -1,112 +0,0 @@
|
|||||||
const { ipcRenderer } = require('electron');
|
|
||||||
|
|
||||||
class PatcherUI {
|
|
||||||
constructor() {
|
|
||||||
this.selectedPath = '';
|
|
||||||
this.initializeElements();
|
|
||||||
this.bindEvents();
|
|
||||||
|
|
||||||
ipcRenderer.on('log', (event, { message, type }) => {
|
|
||||||
this.addLog(message, type);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
initializeElements() {
|
|
||||||
this.filePathInput = document.getElementById('file-path');
|
|
||||||
this.browseBtn = document.getElementById('browse-btn');
|
|
||||||
this.patchBtn = document.getElementById('patch-btn');
|
|
||||||
this.updateBtn = document.getElementById('updateBtn');
|
|
||||||
this.versionLabel = document.getElementById('version-label');
|
|
||||||
this.logSection = document.querySelector('.log-section');
|
|
||||||
document.getElementById(`sourceBtn`).addEventListener('click', () => {
|
|
||||||
ipcRenderer.send("open-link")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
bindEvents() {
|
|
||||||
this.browseBtn.addEventListener('click', () => this.selectFile());
|
|
||||||
this.patchBtn.addEventListener('click', () => this.startPatch());
|
|
||||||
}
|
|
||||||
|
|
||||||
setPath(path) {
|
|
||||||
this.filePathInput.value = this.selectedPath = path
|
|
||||||
this.patchBtn.disabled = !path;
|
|
||||||
}
|
|
||||||
|
|
||||||
async selectFile() {
|
|
||||||
try {
|
|
||||||
const result = await ipcRenderer.invoke('select-file');
|
|
||||||
if (!result?.filePath) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!result.valid) {
|
|
||||||
this.addLog(`The folder “${result.filePath}” is not recognized as a Wemod directory`, 'error')
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setPath(result.filePath)
|
|
||||||
this.addLog('Folder selected: ' + result.fileName, 'info');
|
|
||||||
} catch (error) {
|
|
||||||
this.addLog('Error selecting file: ' + error.message, 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addLog(message, type = 'info') {
|
|
||||||
const entry = document.createElement('div');
|
|
||||||
entry.className = `log-entry ${type}`;
|
|
||||||
entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
|
|
||||||
this.logSection.appendChild(entry);
|
|
||||||
this.logSection.scrollTop = this.logSection.scrollHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
async startPatch() {
|
|
||||||
if (!this.selectedPath) return;
|
|
||||||
|
|
||||||
this.patchBtn.disabled = true;
|
|
||||||
this.addLog('Starting patch process...', 'info');
|
|
||||||
|
|
||||||
try {
|
|
||||||
await ipcRenderer.invoke(
|
|
||||||
'apply-patch',
|
|
||||||
this.selectedPath
|
|
||||||
);
|
|
||||||
this.addLog("Success", 'success');
|
|
||||||
} catch (error) {
|
|
||||||
this.addLog('Patch failed: ' + error.message, 'error');
|
|
||||||
} finally {
|
|
||||||
this.patchBtn.disabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveDefault() {
|
|
||||||
|
|
||||||
ipcRenderer.invoke("get-current-version").then((v) =>
|
|
||||||
this.versionLabel.textContent = `Current version: ${v}`);
|
|
||||||
|
|
||||||
ipcRenderer.invoke("resolve-default-path").then(path => {
|
|
||||||
this.addLog(path ? 'The WeMod folder has been found!' :
|
|
||||||
"WeMod folder was not found. You need to specify the path manually", path ? "success" : "warning"
|
|
||||||
);
|
|
||||||
this.setPath(path)
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcRenderer.invoke("check-updates").then(result => {
|
|
||||||
if(!result) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateBtn.className = ""
|
|
||||||
this.updateBtn.textContent = `Update to ${result.version}`
|
|
||||||
this.updateBtn.addEventListener('click', () => {
|
|
||||||
this.updateBtn.className = "hidden"
|
|
||||||
ipcRenderer.send("apply-update", result);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', async() => {
|
|
||||||
const ui = new PatcherUI();
|
|
||||||
await ui.resolveDefault()
|
|
||||||
});
|
|
||||||
-188
@@ -1,188 +0,0 @@
|
|||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background: #191a33;
|
|
||||||
color: #beb2b2;
|
|
||||||
padding: 0 50px 0 50px;
|
|
||||||
height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
background: #21223d;
|
|
||||||
padding: 24px 24px 15px;
|
|
||||||
border: 1px solid #1e2039;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 1000px;
|
|
||||||
margin: auto;
|
|
||||||
gap: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-section {
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
justify-content: center;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header h1 {
|
|
||||||
font-size: 26px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
color: #6d67fd;
|
|
||||||
background: #3a3881;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 5px 10px;
|
|
||||||
width: fit-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header p {
|
|
||||||
color: #beb2b2;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.path-section {
|
|
||||||
background: #26254b;
|
|
||||||
padding: 16px;
|
|
||||||
margin-top: 20px;
|
|
||||||
border-radius: 6px;
|
|
||||||
border: 1px solid #1e2039;
|
|
||||||
}
|
|
||||||
|
|
||||||
.path-section label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
font-size: 13px;
|
|
||||||
color: #f8f6f6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.path-input-container {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.path-input {
|
|
||||||
flex: 1;
|
|
||||||
background: #21223d;
|
|
||||||
color: #beb2b2;
|
|
||||||
padding: 8px 12px;
|
|
||||||
border: 1px solid #6d67fd;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.browse-btn {
|
|
||||||
background: #6d67fd;
|
|
||||||
border: none;
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #f8f6f6;
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.browse-btn:hover {
|
|
||||||
background: #6d67fd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.patch-btn {
|
|
||||||
margin: 15px 0;
|
|
||||||
background: #6d67fd;
|
|
||||||
color: #f8f6f6;
|
|
||||||
border: none;
|
|
||||||
padding: 12px 24px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.patch-btn:hover:enabled {
|
|
||||||
scale: 105%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.patch-btn:disabled {
|
|
||||||
background: #3a3881;
|
|
||||||
cursor: not-allowed;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-section {
|
|
||||||
background: #26254b;
|
|
||||||
color: #beb2b2;
|
|
||||||
padding: 16px;
|
|
||||||
border-radius: 6px;
|
|
||||||
height: 100%;
|
|
||||||
font-family: monospace;
|
|
||||||
font-size: 14px;
|
|
||||||
overflow-y: auto;
|
|
||||||
border: 1px solid #1e2039;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-entry {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success {
|
|
||||||
color: #08fc81;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
color: #f04343;
|
|
||||||
}
|
|
||||||
|
|
||||||
.warning {
|
|
||||||
color: #facc15;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
|
||||||
color: #6d67fd;
|
|
||||||
}
|
|
||||||
|
|
||||||
#sourceBtn {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
margin-top: 10px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer span {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#updateBtn {
|
|
||||||
background: springgreen;
|
|
||||||
color: #1e2039;
|
|
||||||
font-weight: bold;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 10px;
|
|
||||||
margin-left: 10px;
|
|
||||||
padding: 10px 25px;
|
|
||||||
transition: all 0.1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
#updateBtn:hover {
|
|
||||||
scale: 105%
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
-112
@@ -1,112 +0,0 @@
|
|||||||
const path = require('path');
|
|
||||||
const fs = require('fs');
|
|
||||||
const asar = require('asar');
|
|
||||||
const { execSync } = require("child_process");
|
|
||||||
const patchBySignature = require("./memoryScanner");
|
|
||||||
|
|
||||||
|
|
||||||
const regex = /getUserAccount\(\)\{.*?return\s+this\.#\w+\.fetch\(\{.*?}\).*?}/g;
|
|
||||||
const asarPatch = "getUserAccount(){return this.#<fetch_field_name>.fetch({endpoint:\"/v3/account\",method:\"GET\",name:\"/v3/account\",collectMetrics:0}).then(response=>{response.subscription={period:\"yearly\",state:\"active\"};response.flags=78;return response;})}"
|
|
||||||
const signature = "E8 ?? ?? ?? ?? ?? C0 75 ?? F6 C3 01 74 ?? 48 89 F9 E8 ?? ?? ?? ??"
|
|
||||||
const patchBytes = [0x31]
|
|
||||||
const patchOffset = 0x5
|
|
||||||
// ...
|
|
||||||
// test eax, eax (0x85 for r/m16/32/64)
|
|
||||||
// jnz short loc_1403A4DD2 (Integrity check failed)
|
|
||||||
// call near ptr funk_1445527E0
|
|
||||||
// ...
|
|
||||||
|
|
||||||
class Unlocker {
|
|
||||||
|
|
||||||
constructor(appDir, logger) {
|
|
||||||
this.appDir = appDir;
|
|
||||||
this.logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
#getFetchFieldName(code) {
|
|
||||||
const match = code.match(/this\.#([a-zA-Z_$][0-9a-zA-Z_$]*)\.fetch/);
|
|
||||||
return match ? match[1] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
#patchAsar (unpackedPath) {
|
|
||||||
let items = fs.readdirSync(unpackedPath,{ withFileTypes: true })
|
|
||||||
items = items.filter(item => !item.isDirectory() && /^app-\w+/.test(item.name));
|
|
||||||
if(items.length === 0) {
|
|
||||||
throw new Error(" - No app bundle found");
|
|
||||||
}
|
|
||||||
|
|
||||||
let asarPatchApplied = false;
|
|
||||||
for (const item of items) {
|
|
||||||
const data = fs.readFileSync(path.join(unpackedPath, item.name), { encoding: 'utf8'})
|
|
||||||
|
|
||||||
const matches = data.match(regex)
|
|
||||||
if(!matches) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(matches.length > 1) {
|
|
||||||
throw new Error(" - Multiple target functions found. Looks like the version is not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchFieldName = this.#getFetchFieldName(matches[0]);
|
|
||||||
if(!fetchFieldName) {
|
|
||||||
throw new Error(" - Fetch field name not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
const patch = asarPatch.replace(/<fetch_field_name>/g, fetchFieldName)
|
|
||||||
|
|
||||||
this.logger(" - Found target function in: " + item.name)
|
|
||||||
this.logger(" - Patching asar...")
|
|
||||||
fs.writeFileSync(path.join(unpackedPath, item.name), data.replace(regex, patch), {encoding: 'utf8'})
|
|
||||||
this.logger(" - Patch applied")
|
|
||||||
asarPatchApplied = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!asarPatchApplied) {
|
|
||||||
throw new Error("Failed to apply patch");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async #patchPE () {
|
|
||||||
this.logger(" - Patching PE...")
|
|
||||||
const pePath = path.join(this.appDir, 'WeMod.exe')
|
|
||||||
const procStart = await patchBySignature(pePath, signature, patchBytes, patchOffset)
|
|
||||||
if(procStart === -1) {
|
|
||||||
throw new Error(" - Signature not found or already patched")
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger(procStart === 0 ? " - PE already patched" : " - Patch saved")
|
|
||||||
}
|
|
||||||
|
|
||||||
async start () {
|
|
||||||
this.logger(" - Extracting asar...")
|
|
||||||
const asarPath = path.join(this.appDir, 'resources', 'app.asar')
|
|
||||||
const unpackedPath = path.join(this.appDir, 'resources', 'app.asar.unpacked')
|
|
||||||
const backupPath = path.join(this.appDir, 'resources', 'app.asar.backup')
|
|
||||||
|
|
||||||
if(fs.existsSync(backupPath)) {
|
|
||||||
this.logger(" - Backup already exists")
|
|
||||||
} else {
|
|
||||||
execSync(`copy "${asarPath}" "${backupPath}"`, { encoding: "utf-8" });
|
|
||||||
this.logger(" - Backup saved")
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
asar.extractAll(asarPath, unpackedPath)
|
|
||||||
} catch(e) {
|
|
||||||
throw new Error("Failed to extract asar: " + e)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#patchAsar(unpackedPath)
|
|
||||||
|
|
||||||
await asar.createPackageWithOptions(unpackedPath, asarPath,
|
|
||||||
{ unpack: path.join(unpackedPath,"static/unpacked/**") })
|
|
||||||
this.logger(" - Patch saved")
|
|
||||||
|
|
||||||
await this.#patchPE()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Unlocker;
|
|
||||||
|
|
||||||
-105
@@ -1,105 +0,0 @@
|
|||||||
const { app } = require("electron");
|
|
||||||
const fs = require("fs");
|
|
||||||
const path = require("path");
|
|
||||||
const { exec } = require("child_process");
|
|
||||||
|
|
||||||
class GitHubUpdater {
|
|
||||||
constructor(owner, repo, currentVersion) {
|
|
||||||
this.owner = owner;
|
|
||||||
this.repo = repo;
|
|
||||||
this.currentVersion = currentVersion || app.getVersion();
|
|
||||||
this.apiUrl = `https://api.github.com/repos/${owner}/${repo}/releases/latest`;
|
|
||||||
this.downloadDir = path.join(app.getPath("temp"), "update");
|
|
||||||
}
|
|
||||||
|
|
||||||
async checkForUpdates() {
|
|
||||||
const response = await fetch(this.apiUrl, {
|
|
||||||
headers: { "User-Agent": "GitHub-Updater" },
|
|
||||||
});
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`An error occurred while checking for an update: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const release = await response.json();
|
|
||||||
const latestVersion = release.tag_name;
|
|
||||||
const assets = release.assets;
|
|
||||||
|
|
||||||
if (this.currentVersion === latestVersion) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const asset = assets.find(a => a.name.endsWith(".exe"));
|
|
||||||
if (!asset) {
|
|
||||||
throw new Error("Unable to find files to update");
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
version: latestVersion,
|
|
||||||
url: asset.browser_download_url,
|
|
||||||
name: asset.name,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async downloadUpdate(updateInfo) {
|
|
||||||
const response = await fetch(updateInfo.url);
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Error downloading file: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fs.existsSync(this.downloadDir)) {
|
|
||||||
fs.mkdirSync(this.downloadDir, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
const filePath = path.join(this.downloadDir, updateInfo.name);
|
|
||||||
const fileStream = fs.createWriteStream(filePath);
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
const downloadStream = response.body.getReader();
|
|
||||||
|
|
||||||
const pump = async () => {
|
|
||||||
try {
|
|
||||||
while (true) {
|
|
||||||
const { done, value } = await downloadStream.read();
|
|
||||||
if (done) {
|
|
||||||
fileStream.end();
|
|
||||||
resolve();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
fileStream.write(value);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fileStream.on("error", (error) => {
|
|
||||||
reject(new Error(`File write error: ${error.message}`));
|
|
||||||
});
|
|
||||||
|
|
||||||
pump();
|
|
||||||
});
|
|
||||||
|
|
||||||
return filePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
async applyUpdate(filePath) {
|
|
||||||
try {
|
|
||||||
const currentExecutable = process.env.PORTABLE_EXECUTABLE_FILE;
|
|
||||||
const updateScript = `Start-Sleep -Seconds 3; Copy-Item -Path '${filePath}' -Destination '${currentExecutable}' -Force; Remove-Item -Path '${filePath}' -Force; Start-Sleep -Seconds 2; Start-Process -FilePath '${currentExecutable}';`;
|
|
||||||
|
|
||||||
exec(`start /b "" powershell -WindowStyle Hidden -Command "${updateScript}"`, {
|
|
||||||
windowsHide: true,
|
|
||||||
stdio: 'ignore'
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
app.quit();
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`Update failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = GitHubUpdater;
|
|
||||||
Reference in New Issue
Block a user