├── .github └── FUNDING.yml ├── .gitignore ├── AsarSharp ├── AsarCreator.cs ├── AsarExtractor.cs ├── AsarFileSystem │ ├── Disk.cs │ ├── FileSystem.cs │ ├── FileSystemCrawler.cs │ └── FilesystemEntry.cs ├── AsarSharp.csproj ├── Integrity │ └── IntegrityHelper.cs ├── PickleTools │ ├── Pickle.cs │ └── PickleIterator.cs ├── Properties │ └── AssemblyInfo.cs ├── Utils │ ├── Extensions.cs │ └── NativeMethods.cs └── packages.config ├── LICENSE.md ├── README.md ├── WeModPatcher ├── App.config ├── App.xaml ├── App.xaml.cs ├── Constants.cs ├── Converters │ ├── BaseBooleanConverter.cs │ └── ToVisibilityConverter.cs ├── Core │ ├── RuntimePatcher.cs │ └── StaticPatcher.cs ├── Models │ ├── PatchConfig.cs │ └── Signature.cs ├── Program.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── ReactiveUICore │ ├── AsyncRelayCommand.cs │ ├── ObservableObject.cs │ └── RelayCommand.cs ├── Style │ ├── ColorScheme.xaml │ ├── Icons.xaml │ ├── Inter_18pt-Regular.ttf │ └── Styles.xaml ├── Utils │ ├── Extensions.cs │ ├── MemoryUtils.cs │ ├── Updater.cs │ └── Win32 │ │ ├── Imports.cs │ │ └── Shortcut.cs ├── View │ ├── Controls │ │ ├── InfoItem.xaml │ │ ├── InfoItem.xaml.cs │ │ ├── PopupHost.xaml │ │ └── PopupHost.xaml.cs │ ├── MainWindow │ │ ├── Logs.cs │ │ ├── MainWindow.xaml │ │ ├── MainWindow.xaml.cs │ │ └── MainWindowVm.cs │ └── Popups │ │ ├── PatchVectorsPopup.xaml │ │ └── PatchVectorsPopup.xaml.cs ├── WeModPatcher.csproj └── packages.config ├── Wemod-Patcher.sln └── assets ├── appicon.ico ├── icon.svg └── screenshots ├── app1.png └── app2.png /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: kitbyte 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | 132 | 133 | ./WeModPatcher/obj/ 134 | ./WeModPatcher/bin/ 135 | ./AsarSharp/obj/ 136 | ./AsarSharp/bin/ 137 | .idea -------------------------------------------------------------------------------- /AsarSharp/AsarCreator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text.RegularExpressions; 6 | using AsarSharp.AsarFileSystem; 7 | using AsarSharp.Utils; 8 | 9 | namespace AsarSharp 10 | { 11 | 12 | public class CreateOptions 13 | { 14 | public Regex Unpack { get; set; } 15 | } 16 | 17 | public class AsarCreator 18 | { 19 | private readonly string _folderPath; 20 | private readonly string _destPath; 21 | private readonly CreateOptions _options; 22 | private List _filenames = new List(); 23 | private Dictionary _metadata = new Dictionary(); 24 | 25 | public AsarCreator(string folderPath, string destPath, CreateOptions options) 26 | { 27 | _folderPath = folderPath ?? throw new ArgumentNullException(nameof(folderPath)); 28 | _destPath = destPath ?? throw new ArgumentNullException(nameof(destPath)); 29 | _options = options; 30 | } 31 | 32 | public void CreatePackageWithOptions() 33 | { 34 | var result = FileSystemCrawler.CrawlFileSystem(_folderPath); 35 | _filenames = result.filenames; 36 | _metadata = result.metadata; 37 | CreatePackageFromFiles(); 38 | } 39 | 40 | 41 | public void CreatePackageFromFiles() 42 | { 43 | var filesystem = new Filesystem(_folderPath); 44 | var files = new List(); 45 | 46 | var filenamesSorted = _filenames.ToList(); 47 | 48 | foreach (var filename in filenamesSorted) 49 | { 50 | HandleFile(filesystem, filename, files); 51 | } 52 | 53 | InsertsDone(filesystem, files); 54 | } 55 | 56 | 57 | 58 | private void HandleFile(Filesystem filesystem, string filename, List files) 59 | { 60 | if (!_metadata.ContainsKey(filename)) 61 | { 62 | var fileType = FileSystemCrawler.DetermineFileType(filename); 63 | _metadata[filename] = fileType ?? throw new Exception("Unknown file type for file: " + filename); 64 | } 65 | var file = _metadata[filename]; 66 | 67 | switch (file.Type) 68 | { 69 | case FileType.Directory: 70 | filesystem.InsertDirectory(filename, false); 71 | break; 72 | case FileType.File: 73 | var shouldUnpack = ShouldUnpackPath(Extensions.GetRelativePath(_folderPath, Path.GetDirectoryName(filename))); 74 | files.Add(new Disk.BasicFileInfo { Filename = filename, Unpack = shouldUnpack }); 75 | filesystem.InsertFile(filename, shouldUnpack, file); 76 | break; 77 | case FileType.Link: 78 | throw new NotImplementedException(); 79 | } 80 | } 81 | 82 | private bool ShouldUnpackPath(string relativePath) 83 | { 84 | return _options.Unpack?.IsMatch(relativePath) == true; 85 | } 86 | 87 | private void InsertsDone(Filesystem filesystem, List files) 88 | { 89 | Directory.CreateDirectory(Path.GetDirectoryName(_destPath) ?? throw new InvalidOperationException()); 90 | Disk.WriteFileSystem(_destPath, filesystem, new Disk.FilesystemFilesAndLinks { Files = files, Links = null }, _metadata); 91 | } 92 | 93 | } 94 | } -------------------------------------------------------------------------------- /AsarSharp/AsarExtractor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using AsarSharp.AsarFileSystem; 7 | using AsarSharp.Utils; 8 | 9 | namespace AsarSharp 10 | { 11 | public class AsarExtractor 12 | { 13 | public static void ExtractAll(string archivePath, string dest) 14 | { 15 | var filesystem = Disk.ReadFilesystemSync(archivePath); 16 | var filenames = filesystem.ListFiles(); 17 | 18 | // under windows just extract links as regular files 19 | bool followLinks = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); 20 | 21 | // create destination directory 22 | Directory.CreateDirectory(dest); 23 | 24 | var extractionErrors = new List(); 25 | foreach (var fullPath in filenames) 26 | { 27 | try 28 | { 29 | // Remove leading slash 30 | var filename = fullPath.Substring(1); 31 | var destFilename = Path.Combine(dest, filename); 32 | var file = filesystem.GetFile(filename, followLinks); 33 | 34 | // Check that the file is not written outside the specified destination folder 35 | string relativePath = Extensions.GetRelativePath(dest, destFilename); 36 | if (relativePath.StartsWith("..")) 37 | { 38 | throw new InvalidOperationException($"{fullPath}: file \"{destFilename}\" writes out of the package"); 39 | } 40 | 41 | if (file.IsDirectory) 42 | { 43 | // it's a directory, create it and continue with the next entry 44 | Directory.CreateDirectory(destFilename); 45 | } 46 | // TODO (LINK NOT SUPPORTED) 47 | else if (file.IsLink) 48 | { 49 | // it's a symlink, create a symlink 50 | var linkSrcPath = Extensions.GetDirectoryName(Path.Combine(dest, file.Link)); 51 | var linkDestPath = Extensions.GetDirectoryName(destFilename); 52 | var relativeLinkPath = Extensions.GetRelativePath(linkDestPath, linkSrcPath); 53 | 54 | // try to delete output file, because we can't overwrite a link 55 | try 56 | { 57 | File.Delete(destFilename); 58 | } 59 | catch { 60 | // Ignore errors during file link deletion 61 | } 62 | 63 | var linkTo = Path.Combine(relativeLinkPath, Path.GetFileName(file.Link)); 64 | 65 | if (Extensions.GetRelativePath(dest, linkSrcPath).StartsWith("..")) 66 | { 67 | throw new InvalidOperationException( 68 | $"{fullPath}: file \"{file.Link}\" links out of the package to \"{linkSrcPath}\""); 69 | } 70 | 71 | // On Windows, creating symlinks requires additional permissions or enabling Developer Mode, 72 | // so just copy the contents of the file 73 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 74 | { 75 | var targetPath = Path.Combine(linkSrcPath, Path.GetFileName(file.Link)); 76 | if (Directory.Exists(targetPath)) 77 | { 78 | Directory.CreateDirectory(destFilename); 79 | Extensions.CopyDirectory(targetPath, destFilename); 80 | } 81 | else if (File.Exists(targetPath)) 82 | { 83 | Directory.CreateDirectory(Extensions.GetDirectoryName(destFilename)); 84 | File.Copy(targetPath, destFilename, true); 85 | } 86 | } 87 | else 88 | { 89 | // On Unix systems we use symlinks 90 | Directory.CreateDirectory(Extensions.GetDirectoryName(destFilename)); 91 | Extensions.CreateSymbolicLink(linkTo, destFilename); 92 | } 93 | } 94 | else if (file.IsFile) 95 | { 96 | // it's a file, try to extract it 97 | try 98 | { 99 | byte[] content; 100 | 101 | content = Disk.ReadFileSync(filesystem, filename, file); 102 | 103 | File.WriteAllBytes(destFilename, content); 104 | 105 | if (file.Executable == true && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 106 | { 107 | Extensions.SetUnixFilePermission(destFilename, "755"); 108 | } 109 | } 110 | catch (Exception e) 111 | { 112 | extractionErrors.Add(e); 113 | } 114 | } 115 | } 116 | catch (Exception ex) 117 | { 118 | extractionErrors.Add(ex); 119 | } 120 | } 121 | 122 | if (extractionErrors.Count > 0) 123 | { 124 | throw new AggregateException( 125 | "Unable to extract some files:\n\n" + 126 | string.Join("\n\n", extractionErrors.Select(e => e.ToString())), 127 | extractionErrors); 128 | } 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /AsarSharp/AsarFileSystem/Disk.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using AsarSharp.PickleTools; 5 | using AsarSharp.Utils; 6 | using Newtonsoft.Json; 7 | 8 | namespace AsarSharp.AsarFileSystem 9 | { 10 | public static class Disk 11 | { 12 | private static Dictionary _filesystemCache = new Dictionary(); 13 | 14 | public class ArchiveHeader 15 | { 16 | public FilesystemEntry Header { get; set; } 17 | public string HeaderString { get; set; } 18 | public int HeaderSize { get; set; } 19 | } 20 | 21 | public class FilesystemFilesAndLinks 22 | { 23 | public List Files { get; set; } = new List(); 24 | public List Links { get; set; } = new List(); 25 | } 26 | 27 | public class BasicFileInfo 28 | { 29 | public string Filename { get; set; } 30 | public bool Unpack { get; set; } 31 | } 32 | 33 | 34 | #region Reading 35 | 36 | public static ArchiveHeader ReadArchiveHeaderSync(string archivePath) 37 | { 38 | using (FileStream fs = File.OpenRead(archivePath)) 39 | { 40 | // read the size of the header (8 bytes) 41 | byte[] sizeBuf = new byte[8]; 42 | if (fs.Read(sizeBuf, 0, 8) != 8) 43 | { 44 | throw new Exception("Unable to read header size"); 45 | } 46 | 47 | var sizePickle = Pickle.CreateFromBuffer(sizeBuf); 48 | var size = sizePickle.CreateIterator().ReadUInt32(); 49 | 50 | // Read the header of the specified size 51 | var headerBuf = new byte[size]; 52 | if(fs.Read(headerBuf, 0, (int)size) != size) 53 | { 54 | throw new Exception("Unable to read header"); 55 | } 56 | 57 | var headerPickle = Pickle.CreateFromBuffer(headerBuf); 58 | var header = headerPickle.CreateIterator().ReadString(); 59 | 60 | var headerObj = JsonConvert.DeserializeObject(header); 61 | 62 | return new ArchiveHeader 63 | { 64 | Header = headerObj, 65 | HeaderString = header, 66 | HeaderSize = (int)size 67 | }; 68 | } 69 | } 70 | public static Filesystem ReadFilesystemSync(string archivePath) 71 | { 72 | if (!_filesystemCache.ContainsKey(archivePath) || _filesystemCache[archivePath] == null) 73 | { 74 | ArchiveHeader header = ReadArchiveHeaderSync(archivePath); 75 | Filesystem filesystem = new Filesystem(archivePath); 76 | filesystem.SetHeader(header.Header, header.HeaderSize); 77 | _filesystemCache[archivePath] = filesystem; 78 | } 79 | 80 | return _filesystemCache[archivePath]; 81 | } 82 | 83 | public static byte[] ReadFileSync(Filesystem filesystem, string filename, FilesystemEntry info) 84 | { 85 | if (!info.IsFile || !info.Size.HasValue) 86 | { 87 | throw new ArgumentException("Entry is not a file", nameof(info)); 88 | } 89 | 90 | long size = info.Size.Value; 91 | byte[] buffer = new byte[size]; 92 | 93 | if (size <= 0) 94 | { 95 | return buffer; 96 | } 97 | 98 | if (info.Unpacked == true) 99 | { 100 | // It's an unpacked file, read it directly 101 | string filePath = Path.Combine($"{filesystem.GetRootPath()}.unpacked", filename); 102 | return File.ReadAllBytes(filePath); 103 | } 104 | 105 | // Read from the ASAR archive 106 | using (FileStream fs = File.OpenRead(filesystem.GetRootPath())) 107 | { 108 | // Important: the offset must take into account the size of the Pickle header (8 bytes) 109 | // and the size of the header itself 110 | long offset = 8 + filesystem.GetHeaderSize() + long.Parse(info.Offset); 111 | fs.Position = offset; 112 | 113 | // Read the whole file at once 114 | int bytesRead = fs.Read(buffer, 0, (int)size); 115 | if (bytesRead != size) 116 | { 117 | throw new Exception($"Failed to read entire file, got {bytesRead} bytes instead of {size}"); 118 | } 119 | } 120 | 121 | 122 | return buffer; 123 | } 124 | 125 | #endregion 126 | 127 | public static bool UncacheFilesystem(string archivePath) 128 | { 129 | if (_filesystemCache.ContainsKey(archivePath)) 130 | { 131 | _filesystemCache.Remove(archivePath); 132 | return true; 133 | } 134 | 135 | return false; 136 | } 137 | 138 | public static void UncacheAll() 139 | { 140 | _filesystemCache.Clear(); 141 | } 142 | 143 | public static void CopyFile(string dest, string rootPath, string filename) 144 | { 145 | if(dest == null || rootPath == null || filename == null) 146 | throw new ArgumentNullException(); 147 | 148 | if (dest == rootPath) 149 | { 150 | return; 151 | } 152 | 153 | string sourcePath = Path.Combine(rootPath, filename); 154 | string destPath = Path.Combine(dest, filename); 155 | 156 | Directory.CreateDirectory(Path.GetDirectoryName(destPath) ?? throw new InvalidOperationException()); 157 | using (var sourceStream = new FileStream(sourcePath, FileMode.Open, FileAccess.Read)) 158 | using (var destinationStream = new FileStream(destPath, FileMode.Create, FileAccess.Write)) 159 | { 160 | sourceStream.CopyTo(destinationStream); 161 | } 162 | } 163 | 164 | 165 | public static void WriteFileSystem(string dest, Filesystem fileSystem, 166 | FilesystemFilesAndLinks lists, 167 | Dictionary metadata) 168 | { 169 | var fsHeader = fileSystem.GetHeader(); 170 | var headerPickle = Pickle.CreateEmpty(); 171 | var serializerSettings = new JsonSerializerSettings() 172 | { NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore } ; 173 | 174 | var headerJson = JsonConvert.SerializeObject(fsHeader,serializerSettings); 175 | headerPickle.WriteString(headerJson); 176 | var headerBuf = headerPickle.ToBuffer(); 177 | 178 | var sizePickle = Pickle.CreateEmpty(); 179 | sizePickle.WriteUInt32((uint)headerBuf.Length); 180 | var sizeBuf = sizePickle.ToBuffer(); 181 | 182 | using (FileStream fs = File.Create(dest)) 183 | { 184 | fs.Write(sizeBuf, 0, sizeBuf.Length); 185 | fs.Write(headerBuf, 0, headerBuf.Length); 186 | 187 | foreach (var file in lists.Files) 188 | { 189 | if (file.Unpack) 190 | { 191 | var filename = Extensions.GetRelativePath(fileSystem.GetRootPath(), file.Filename); 192 | CopyFile($"{dest}.unpacked", fileSystem.GetRootPath(), filename); 193 | continue; 194 | } 195 | using (var transformedFileStream = new FileStream(file.Filename, FileMode.Open, FileAccess.Read)) 196 | { 197 | transformedFileStream.CopyTo(fs); 198 | } 199 | } 200 | } 201 | } 202 | } 203 | } -------------------------------------------------------------------------------- /AsarSharp/AsarFileSystem/FileSystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using AsarSharp.Integrity; 5 | using AsarSharp.Utils; 6 | 7 | namespace AsarSharp.AsarFileSystem 8 | { 9 | public class Filesystem 10 | { 11 | private readonly string _src; 12 | private FilesystemEntry _header; 13 | private int _headerSize; 14 | private long _offset; 15 | 16 | private const uint UINT32_MAX = 0xFFFFFFFF; // 2^32 - 1 17 | 18 | public Filesystem(string src) 19 | { 20 | _src = Path.GetFullPath(src); 21 | _header = new FilesystemEntry { Files = new Dictionary(StringComparer.Ordinal) }; 22 | _headerSize = 0; 23 | _offset = 0; 24 | } 25 | 26 | public string GetRootPath() 27 | { 28 | return _src; 29 | } 30 | 31 | public FilesystemEntry GetHeader() 32 | { 33 | return _header; 34 | } 35 | 36 | public int GetHeaderSize() 37 | { 38 | return _headerSize; 39 | } 40 | 41 | public void SetHeader(FilesystemEntry header, int headerSize) 42 | { 43 | _header = header; 44 | _headerSize = headerSize; 45 | } 46 | 47 | public FilesystemEntry SearchNodeFromDirectory(string p) 48 | { 49 | FilesystemEntry json = _header; 50 | 51 | // Normalize path delimiters to system delimiters 52 | p = p.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar); 53 | 54 | string[] dirs = p.Split(Path.DirectorySeparatorChar); 55 | 56 | foreach (string dir in dirs) 57 | { 58 | if (dir == "." || string.IsNullOrEmpty(dir)) continue; 59 | 60 | if (json.IsDirectory) 61 | { 62 | if (!json.Files.ContainsKey(dir)) 63 | { 64 | json.Files[dir] = new FilesystemEntry { Files = new Dictionary(StringComparer.Ordinal) }; 65 | } 66 | json = json.Files[dir]; 67 | } 68 | else 69 | { 70 | throw new Exception($"Unexpected directory state while traversing: {p}"); 71 | } 72 | } 73 | 74 | return json; 75 | } 76 | 77 | public List ListFiles(bool isPack = false) 78 | { 79 | var files = new List(); 80 | 81 | FillFilesFromMetadata("/", _header); 82 | return files; 83 | 84 | void FillFilesFromMetadata(string basePath, FilesystemEntry metadata) 85 | { 86 | if (!metadata.IsDirectory) 87 | { 88 | return; 89 | } 90 | 91 | foreach (var entry in metadata.Files) 92 | { 93 | string childPath = entry.Key; 94 | FilesystemEntry childMetadata = entry.Value; 95 | string fullPath = Path.Combine(basePath, childPath).Replace('\\', '/'); 96 | 97 | string packState = 98 | childMetadata.Unpacked == true ? "unpack" : "pack "; 99 | 100 | files.Add(isPack ? $"{packState} : {fullPath}" : fullPath); 101 | FillFilesFromMetadata(fullPath, childMetadata); 102 | } 103 | } 104 | } 105 | 106 | public FilesystemEntry GetNode(string p, bool followLinks = true) 107 | { 108 | // Normalize path delimiters 109 | p = p.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar); 110 | 111 | FilesystemEntry node = SearchNodeFromDirectory(Extensions.GetDirectoryName(p)); 112 | string name = Path.GetFileName(p); 113 | 114 | // Process symbolic links 115 | if (node.IsLink && followLinks) 116 | { 117 | return GetNode(Path.Combine(node.Link, name)); 118 | } 119 | 120 | if (!string.IsNullOrEmpty(name)) 121 | { 122 | if (node.IsDirectory && node.Files.TryGetValue(name, out var entry)) 123 | { 124 | return entry; 125 | } 126 | return null; 127 | } 128 | 129 | return node; 130 | } 131 | 132 | public FilesystemEntry GetFile(string p, bool followLinks = true) 133 | { 134 | FilesystemEntry info = GetNode(p, followLinks); 135 | 136 | if (info == null) 137 | { 138 | throw new Exception($"\"{p}\" was not found in this archive"); 139 | } 140 | 141 | // If followLinks=false, do not allow symbolic links (TODO) 142 | if (info.IsLink && followLinks) 143 | { 144 | return GetFile(info.Link, followLinks); 145 | } 146 | 147 | return info; 148 | } 149 | 150 | public static string ReadLink(string path) 151 | { 152 | throw new NotImplementedException(); 153 | return Path.GetFileName(path); 154 | // TODO , NOT IMPLEMENTED 155 | } 156 | 157 | 158 | 159 | #region Writing 160 | 161 | public FilesystemEntry SearchNodeFromPath(string p) 162 | { 163 | p = Extensions.GetRelativePath(_src, p); 164 | 165 | if (string.IsNullOrEmpty(p)) 166 | { 167 | return _header; 168 | } 169 | 170 | var name = Path.GetFileName(p); 171 | var node = SearchNodeFromDirectory(Extensions.GetDirectoryName(p)); 172 | 173 | if (node.Files == null) 174 | { 175 | node.Files = new Dictionary(); 176 | } 177 | 178 | if (!node.Files.ContainsKey(name)) 179 | { 180 | node.Files[name] = new FilesystemEntry(); 181 | } 182 | 183 | return node.Files[name]; 184 | } 185 | 186 | public void InsertDirectory(string p, bool unpack) 187 | { 188 | FilesystemEntry node = SearchNodeFromPath(p); 189 | node.Files = node.Files ?? new Dictionary(); 190 | node.Unpacked = unpack; 191 | } 192 | 193 | public void InsertFile(string path, bool shouldUnpack, CrawledFileType file) 194 | { 195 | var dirName = Path.GetDirectoryName(path); 196 | var dirNode = SearchNodeFromPath(dirName); 197 | var node = SearchNodeFromPath(path); 198 | 199 | long size = 0; 200 | if (file.Stat is FileInfo fileInfo) 201 | { 202 | size = fileInfo.Length; 203 | } 204 | else 205 | { 206 | throw new Exception($"{path}: stat is not a file"); 207 | } 208 | 209 | if (shouldUnpack || dirNode.Unpacked == true) 210 | { 211 | node.Size = size; 212 | node.Unpacked = true; 213 | node.Integrity = IntegrityHelper.GetFileIntegrity(path); 214 | return; 215 | } 216 | 217 | // Check that the file size does not exceed UINT32_MAX 218 | if (size > UINT32_MAX) 219 | { 220 | throw new Exception($"{path}: file size cannot be larger than 4.2GB"); 221 | } 222 | 223 | node.Size = size; 224 | node.Offset = _offset.ToString(); 225 | node.Integrity = IntegrityHelper.GetFileIntegrity(path); 226 | if (!Extensions.IsWindowsPlatform() && (file.Stat.Attributes & FileAttributes.Hidden) != 0) 227 | { 228 | node.Executable = true; 229 | } 230 | _offset += size; 231 | } 232 | 233 | #endregion 234 | } 235 | } -------------------------------------------------------------------------------- /AsarSharp/AsarFileSystem/FileSystemCrawler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using AsarSharp.Utils; 6 | 7 | namespace AsarSharp.AsarFileSystem 8 | { 9 | public class CrawledFileType 10 | { 11 | public FileType Type { get; set; } 12 | public FileSystemInfo Stat { get; set; } 13 | public TransformedFile Transformed { get; set; } 14 | } 15 | 16 | public class TransformedFile 17 | { 18 | public string Path { get; set; } 19 | public FileSystemInfo Stat { get; set; } 20 | } 21 | 22 | public enum FileType 23 | { 24 | File, 25 | Directory, 26 | Link 27 | } 28 | 29 | public static class FileSystemCrawler 30 | { 31 | 32 | 33 | public static CrawledFileType DetermineFileType(string filename) 34 | { 35 | var fileInfo = new FileInfo(filename); 36 | if (fileInfo.Exists) 37 | { 38 | return new CrawledFileType { Type = FileType.File, Stat = fileInfo }; 39 | } 40 | 41 | var directoryInfo = new DirectoryInfo(filename); 42 | if (directoryInfo.Exists) 43 | { 44 | return new CrawledFileType { Type = FileType.Directory, Stat = directoryInfo }; 45 | } 46 | 47 | var linkInfo = new FileInfo(filename); 48 | if (linkInfo.Exists && (linkInfo.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint) 49 | { 50 | return new CrawledFileType { Type = FileType.Link, Stat = linkInfo }; 51 | } 52 | 53 | return null; 54 | } 55 | 56 | 57 | public static (List filenames, Dictionary metadata) CrawlFileSystem(string dir) 58 | { 59 | var metadata = new Dictionary(); 60 | var crawled = CrawlIterative(dir); 61 | var results = crawled.Select(filename => new { filename, type = DetermineFileType(filename) }).ToList(); 62 | 63 | var links = new List(); 64 | var filenames = new List(); 65 | 66 | foreach (var result in results.Where(result => result.type != null)) 67 | { 68 | metadata[result.filename] = result.type; 69 | if (result.type.Type == FileType.Link) 70 | { 71 | links.Add(result.filename); 72 | } 73 | filenames.Add(result.filename); 74 | } 75 | 76 | var filteredFilenames = new List(); 77 | 78 | foreach (var filename in filenames) 79 | { 80 | var exactLinkIndex = links.FindIndex(link => filename == link); 81 | var isValid = true; 82 | 83 | for (var i = 0; i < links.Count; i++) 84 | { 85 | if (i == exactLinkIndex) 86 | { 87 | continue; 88 | } 89 | 90 | var link = links[i]; 91 | var isFileWithinSymlinkDir = filename.StartsWith(link, StringComparison.OrdinalIgnoreCase); 92 | var relativePath = Extensions.GetRelativePath(link, Path.GetDirectoryName(filename) ?? string.Empty); 93 | 94 | if (isFileWithinSymlinkDir && !relativePath.StartsWith("..", StringComparison.Ordinal)) 95 | { 96 | isValid = false; 97 | break; 98 | } 99 | } 100 | 101 | if (isValid) 102 | { 103 | filteredFilenames.Add(filename); 104 | } 105 | } 106 | 107 | return (filteredFilenames, metadata); 108 | } 109 | 110 | 111 | // (File order is not important!!!) 112 | public static List CrawlIterative(string dir) 113 | { 114 | var result = new List(); 115 | var stack = new Stack(); 116 | 117 | 118 | string basePath = Extensions.GetBasePath(dir); 119 | 120 | if (!Directory.Exists(basePath)) 121 | return result; 122 | 123 | // Add only the base directory to the stack, but not to the result 124 | stack.Push(basePath); 125 | 126 | while (stack.Count > 0) 127 | { 128 | string currentDir = stack.Pop(); 129 | 130 | try 131 | { 132 | // Add all files from the current directory 133 | result.AddRange(Directory.GetFiles(currentDir, "*", SearchOption.TopDirectoryOnly)); 134 | 135 | // Add subdirectories to the results and to the stack 136 | foreach (var directory in Directory.GetDirectories(currentDir, "*", 137 | SearchOption.TopDirectoryOnly)) 138 | { 139 | // Add subdirectories to the result 140 | if (directory != basePath) // Do not add a base directory 141 | { 142 | result.Add(directory); 143 | } 144 | 145 | // Add to the stack for processing 146 | stack.Push(directory); 147 | } 148 | } 149 | catch (UnauthorizedAccessException) 150 | { 151 | // Skip directories to which there is no access 152 | continue; 153 | } 154 | } 155 | 156 | 157 | return result; 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /AsarSharp/AsarFileSystem/FilesystemEntry.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using AsarSharp.Integrity; 3 | using Newtonsoft.Json; 4 | 5 | namespace AsarSharp.AsarFileSystem 6 | { 7 | public class FilesystemEntry 8 | { 9 | [JsonProperty("files")] 10 | public Dictionary Files { get; set; } 11 | 12 | 13 | [JsonProperty("executable")] 14 | public bool? Executable { get; set; } 15 | 16 | [JsonProperty("size")] 17 | public long? Size { get; set; } 18 | 19 | [JsonProperty("offset")] 20 | public string Offset { get; set; } 21 | 22 | [JsonProperty("unpacked")] 23 | public bool? Unpacked { get; set; } 24 | 25 | [JsonProperty("integrity")] 26 | public IntegrityHelper.FileIntegrity Integrity { get; set; } 27 | 28 | [JsonProperty("link")] 29 | public string Link { get; set; } 30 | 31 | [JsonIgnore] 32 | public bool IsDirectory => Files != null; 33 | [JsonIgnore] 34 | public bool IsFile => Size.HasValue; 35 | 36 | [JsonIgnore] 37 | public bool IsLink => Link != null; 38 | 39 | public override string ToString() 40 | { 41 | return $"Offset: {Offset}, Size: {Size} Unpacked: {Unpacked}"; 42 | } 43 | 44 | public bool ShouldSerializeUnpacked() 45 | { 46 | return Unpacked == true; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /AsarSharp/AsarSharp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 5 | 6 | Debug 7 | AnyCPU 8 | {BEAA604A-402A-4387-8903-A53FC913A26E} 9 | Library 10 | Properties 11 | AsarSharp 12 | AsarSharp 13 | v4.8 14 | 512 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /AsarSharp/Integrity/IntegrityHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Security.Cryptography; 5 | using Newtonsoft.Json; 6 | 7 | namespace AsarSharp.Integrity 8 | { 9 | public static class IntegrityHelper 10 | { 11 | private const string ALGORITHM = "SHA256"; 12 | // 4MB default block size 13 | private const int BLOCK_SIZE = 4 * 1024 * 1024; 14 | 15 | public class FileIntegrity 16 | { 17 | [JsonProperty("algorithm")] 18 | public string Algorithm { get; set; } 19 | 20 | [JsonProperty("hash")] 21 | public string Hash { get; set; } 22 | 23 | [JsonProperty("blockSize")] 24 | public int BlockSize { get; set; } 25 | 26 | [JsonProperty("blocks")] 27 | public List Blocks { get; set; } 28 | } 29 | 30 | public static FileIntegrity GetFileIntegrity(string path) 31 | { 32 | using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) 33 | using(var fileHash = SHA256.Create()) 34 | { 35 | 36 | var blockHashes = new List(); 37 | var buffer = new byte[BLOCK_SIZE]; 38 | int bytesRead; 39 | 40 | while ((bytesRead = fileStream.Read(buffer, 0, BLOCK_SIZE)) > 0) 41 | { 42 | var block = new byte[bytesRead]; 43 | Array.Copy(buffer, block, bytesRead); 44 | blockHashes.Add(HashBlock(block)); 45 | fileHash.TransformBlock(block, 0, block.Length, null, 0); 46 | } 47 | 48 | fileHash.TransformFinalBlock(Array.Empty(), 0, 0); 49 | 50 | return new FileIntegrity 51 | { 52 | Algorithm = ALGORITHM, 53 | Hash = BitConverter.ToString(fileHash.Hash).Replace("-", "").ToLowerInvariant(), 54 | BlockSize = BLOCK_SIZE, 55 | Blocks = blockHashes, 56 | }; 57 | } 58 | } 59 | 60 | private static string HashBlock(byte[] block) 61 | { 62 | using (var sha256 = SHA256.Create()) 63 | { 64 | var hash = sha256.ComputeHash(block); 65 | return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); 66 | } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /AsarSharp/PickleTools/Pickle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace AsarSharp.PickleTools 5 | { 6 | public class Pickle 7 | { 8 | public const int SIZE_INT32 = 4; 9 | public const int SIZE_UINT32 = 4; 10 | public const int SIZE_INT64 = 8; 11 | public const int SIZE_UINT64 = 8; 12 | public const int SIZE_FLOAT = 4; 13 | public const int SIZE_DOUBLE = 8; 14 | 15 | // Size of memory allocation unit for payload 16 | public const int PAYLOAD_UNIT = 64; 17 | 18 | // Maximum value for read-only 19 | public const long CAPACITY_READ_ONLY = 9007199254740992; 20 | 21 | private byte[] _header; 22 | private int _headerSize; 23 | private long _capacityAfterHeader; 24 | private int _writeOffset; 25 | 26 | private Pickle(byte[] buffer = null) 27 | { 28 | if (buffer != null) 29 | { 30 | _header = buffer; 31 | _headerSize = buffer.Length - GetPayloadSize(); 32 | _capacityAfterHeader = CAPACITY_READ_ONLY; 33 | _writeOffset = 0; 34 | 35 | if (_headerSize > buffer.Length) 36 | { 37 | _headerSize = 0; 38 | } 39 | 40 | if (_headerSize != AlignInt(_headerSize, SIZE_UINT32)) 41 | { 42 | _headerSize = 0; 43 | } 44 | 45 | if (_headerSize == 0) 46 | { 47 | _header = new byte[0]; 48 | } 49 | } 50 | else 51 | { 52 | _header = new byte[0]; 53 | _headerSize = SIZE_UINT32; 54 | _capacityAfterHeader = 0; 55 | _writeOffset = 0; 56 | Resize(PAYLOAD_UNIT); 57 | SetPayloadSize(0); 58 | } 59 | } 60 | 61 | public static Pickle CreateEmpty() 62 | { 63 | return new Pickle(); 64 | } 65 | 66 | public static Pickle CreateFromBuffer(byte[] buffer) 67 | { 68 | return new Pickle(buffer); 69 | } 70 | 71 | public byte[] GetHeader() 72 | { 73 | return _header; 74 | } 75 | 76 | public int GetHeaderSize() 77 | { 78 | return _headerSize; 79 | } 80 | 81 | public PickleIterator CreateIterator() 82 | { 83 | return new PickleIterator(this); 84 | } 85 | 86 | /// 87 | /// Converts Pickle to a byte array 88 | /// 89 | public byte[] ToBuffer() 90 | { 91 | int resultSize = _headerSize + GetPayloadSize(); 92 | byte[] result = new byte[resultSize]; 93 | Array.Copy(_header, 0, result, 0, resultSize); 94 | return result; 95 | } 96 | 97 | 98 | public bool WriteBool(bool value) 99 | { 100 | return WriteInt(value ? 1 : 0); 101 | } 102 | 103 | public bool WriteInt(int value) 104 | { 105 | EnsureCapacity(SIZE_INT32); 106 | 107 | var dataLength = AlignInt(SIZE_INT32, SIZE_UINT32); 108 | var newSize = _writeOffset + dataLength; 109 | 110 | if (newSize > _capacityAfterHeader) 111 | { 112 | Resize(Math.Max((int)_capacityAfterHeader * 2, newSize)); 113 | } 114 | 115 | WriteInt32LE(value, _headerSize + _writeOffset); 116 | 117 | var endOffset = _headerSize + _writeOffset + SIZE_INT32; 118 | for (int i = endOffset; i < endOffset + dataLength - SIZE_INT32; i++) 119 | { 120 | _header[i] = 0; 121 | } 122 | 123 | SetPayloadSize(newSize); 124 | _writeOffset = newSize; 125 | return true; 126 | } 127 | 128 | 129 | public bool WriteUInt32(uint value) 130 | { 131 | EnsureCapacity(SIZE_UINT32); 132 | 133 | var dataLength = AlignInt(SIZE_UINT32, SIZE_UINT32); 134 | var newSize = _writeOffset + dataLength; 135 | 136 | if (newSize > _capacityAfterHeader) 137 | { 138 | Resize(Math.Max((int)_capacityAfterHeader * 2, newSize)); 139 | } 140 | 141 | WriteUInt32LE(value, _headerSize + _writeOffset); 142 | 143 | var endOffset = _headerSize + _writeOffset + SIZE_UINT32; 144 | for (int i = endOffset; i < endOffset + dataLength - SIZE_UINT32; i++) 145 | { 146 | _header[i] = 0; 147 | } 148 | 149 | SetPayloadSize(newSize); 150 | _writeOffset = newSize; 151 | return true; 152 | } 153 | 154 | public bool WriteInt64(long value) 155 | { 156 | EnsureCapacity(SIZE_INT64); 157 | 158 | var dataLength = AlignInt(SIZE_INT64, SIZE_UINT32); 159 | var newSize = _writeOffset + dataLength; 160 | 161 | if (newSize > _capacityAfterHeader) 162 | { 163 | Resize(Math.Max((int)_capacityAfterHeader * 2, newSize)); 164 | } 165 | 166 | WriteInt64LE(value, _headerSize + _writeOffset); 167 | 168 | var endOffset = _headerSize + _writeOffset + SIZE_INT64; 169 | for (int i = endOffset; i < endOffset + dataLength - SIZE_INT64; i++) 170 | { 171 | _header[i] = 0; 172 | } 173 | 174 | SetPayloadSize(newSize); 175 | _writeOffset = newSize; 176 | return true; 177 | } 178 | 179 | 180 | public bool WriteUInt64(ulong value) 181 | { 182 | EnsureCapacity(SIZE_UINT64); 183 | 184 | var dataLength = AlignInt(SIZE_UINT64, SIZE_UINT32); 185 | var newSize = _writeOffset + dataLength; 186 | 187 | if (newSize > _capacityAfterHeader) 188 | { 189 | Resize(Math.Max((int)_capacityAfterHeader * 2, newSize)); 190 | } 191 | 192 | WriteUInt64LE(value, _headerSize + _writeOffset); 193 | 194 | var endOffset = _headerSize + _writeOffset + SIZE_UINT64; 195 | for (int i = endOffset; i < endOffset + dataLength - SIZE_UINT64; i++) 196 | { 197 | _header[i] = 0; 198 | } 199 | 200 | SetPayloadSize(newSize); 201 | _writeOffset = newSize; 202 | return true; 203 | } 204 | 205 | public bool WriteFloat(float value) 206 | { 207 | EnsureCapacity(SIZE_FLOAT); 208 | 209 | var dataLength = AlignInt(SIZE_FLOAT, SIZE_UINT32); 210 | var newSize = _writeOffset + dataLength; 211 | 212 | if (newSize > _capacityAfterHeader) 213 | { 214 | Resize(Math.Max((int)_capacityAfterHeader * 2, newSize)); 215 | } 216 | 217 | byte[] bytes = BitConverter.GetBytes(value); 218 | if (!BitConverter.IsLittleEndian) 219 | { 220 | Array.Reverse(bytes); 221 | } 222 | 223 | Array.Copy(bytes, 0, _header, _headerSize + _writeOffset, SIZE_FLOAT); 224 | 225 | var endOffset = _headerSize + _writeOffset + SIZE_FLOAT; 226 | for (int i = endOffset; i < endOffset + dataLength - SIZE_FLOAT; i++) 227 | { 228 | _header[i] = 0; 229 | } 230 | 231 | SetPayloadSize(newSize); 232 | _writeOffset = newSize; 233 | return true; 234 | } 235 | 236 | public bool WriteDouble(double value) 237 | { 238 | EnsureCapacity(SIZE_DOUBLE); 239 | 240 | var dataLength = AlignInt(SIZE_DOUBLE, SIZE_UINT32); 241 | var newSize = _writeOffset + dataLength; 242 | 243 | if (newSize > _capacityAfterHeader) 244 | { 245 | Resize(Math.Max((int)_capacityAfterHeader * 2, newSize)); 246 | } 247 | 248 | byte[] bytes = BitConverter.GetBytes(value); 249 | if (!BitConverter.IsLittleEndian) 250 | { 251 | Array.Reverse(bytes); 252 | } 253 | 254 | Array.Copy(bytes, 0, _header, _headerSize + _writeOffset, SIZE_DOUBLE); 255 | 256 | var endOffset = _headerSize + _writeOffset + SIZE_DOUBLE; 257 | for (int i = endOffset; i < endOffset + dataLength - SIZE_DOUBLE; i++) 258 | { 259 | _header[i] = 0; 260 | } 261 | 262 | SetPayloadSize(newSize); 263 | _writeOffset = newSize; 264 | return true; 265 | } 266 | 267 | public bool WriteString(string value) 268 | { 269 | byte[] strBytes = Encoding.UTF8.GetBytes(value); 270 | int length = strBytes.Length; 271 | 272 | if (!WriteInt(length)) 273 | { 274 | return false; 275 | } 276 | 277 | var dataLength = AlignInt(length, SIZE_UINT32); 278 | var newSize = _writeOffset + dataLength; 279 | 280 | if (newSize > _capacityAfterHeader) 281 | { 282 | Resize(Math.Max((int)_capacityAfterHeader * 2, newSize)); 283 | } 284 | 285 | Array.Copy(strBytes, 0, _header, _headerSize + _writeOffset, length); 286 | 287 | var endOffset = _headerSize + _writeOffset + length; 288 | for (int i = endOffset; i < endOffset + dataLength - length; i++) 289 | { 290 | _header[i] = 0; 291 | } 292 | 293 | SetPayloadSize(newSize); 294 | _writeOffset = newSize; 295 | return true; 296 | } 297 | 298 | public void SetPayloadSize(int payloadSize) 299 | { 300 | WriteUInt32LE((uint)payloadSize, 0); 301 | } 302 | 303 | public int GetPayloadSize() 304 | { 305 | return (int)ReadUInt32LE(0); 306 | } 307 | 308 | private void Resize(int newCapacity) 309 | { 310 | newCapacity = AlignInt(newCapacity, PAYLOAD_UNIT); 311 | byte[] newHeader = new byte[_header.Length + newCapacity]; 312 | Array.Copy(_header, 0, newHeader, 0, _header.Length); 313 | _header = newHeader; 314 | _capacityAfterHeader = newCapacity; 315 | } 316 | 317 | public static int AlignInt(int i, int alignment) 318 | { 319 | return i + ((alignment - (i % alignment)) % alignment); 320 | } 321 | 322 | private void EnsureCapacity(int additionalSize) 323 | { 324 | var dataLength = AlignInt(additionalSize, SIZE_UINT32); 325 | var newSize = _writeOffset + dataLength; 326 | 327 | if (newSize > _capacityAfterHeader) 328 | { 329 | Resize(Math.Max((int)_capacityAfterHeader * 2, newSize)); 330 | } 331 | } 332 | 333 | #region Auxiliary methods for reading/writing values in Little Endian 334 | 335 | private uint ReadUInt32LE(int offset) 336 | { 337 | if (BitConverter.IsLittleEndian) 338 | { 339 | return BitConverter.ToUInt32(_header, offset); 340 | } 341 | else 342 | { 343 | return (uint)(_header[offset] | 344 | (_header[offset + 1] << 8) | 345 | (_header[offset + 2] << 16) | 346 | (_header[offset + 3] << 24)); 347 | } 348 | } 349 | 350 | private void WriteInt32LE(int value, int offset) 351 | { 352 | if (BitConverter.IsLittleEndian) 353 | { 354 | byte[] bytes = BitConverter.GetBytes(value); 355 | Array.Copy(bytes, 0, _header, offset, 4); 356 | } 357 | else 358 | { 359 | _header[offset] = (byte)value; 360 | _header[offset + 1] = (byte)(value >> 8); 361 | _header[offset + 2] = (byte)(value >> 16); 362 | _header[offset + 3] = (byte)(value >> 24); 363 | } 364 | } 365 | 366 | private void WriteUInt32LE(uint value, int offset) 367 | { 368 | if (BitConverter.IsLittleEndian) 369 | { 370 | byte[] bytes = BitConverter.GetBytes(value); 371 | Array.Copy(bytes, 0, _header, offset, 4); 372 | } 373 | else 374 | { 375 | _header[offset] = (byte)value; 376 | _header[offset + 1] = (byte)(value >> 8); 377 | _header[offset + 2] = (byte)(value >> 16); 378 | _header[offset + 3] = (byte)(value >> 24); 379 | } 380 | } 381 | 382 | private void WriteInt64LE(long value, int offset) 383 | { 384 | if (BitConverter.IsLittleEndian) 385 | { 386 | byte[] bytes = BitConverter.GetBytes(value); 387 | Array.Copy(bytes, 0, _header, offset, 8); 388 | } 389 | else 390 | { 391 | _header[offset] = (byte)value; 392 | _header[offset + 1] = (byte)(value >> 8); 393 | _header[offset + 2] = (byte)(value >> 16); 394 | _header[offset + 3] = (byte)(value >> 24); 395 | _header[offset + 4] = (byte)(value >> 32); 396 | _header[offset + 5] = (byte)(value >> 40); 397 | _header[offset + 6] = (byte)(value >> 48); 398 | _header[offset + 7] = (byte)(value >> 56); 399 | } 400 | } 401 | 402 | private void WriteUInt64LE(ulong value, int offset) 403 | { 404 | if (BitConverter.IsLittleEndian) 405 | { 406 | byte[] bytes = BitConverter.GetBytes(value); 407 | Array.Copy(bytes, 0, _header, offset, 8); 408 | } 409 | else 410 | { 411 | _header[offset] = (byte)value; 412 | _header[offset + 1] = (byte)(value >> 8); 413 | _header[offset + 2] = (byte)(value >> 16); 414 | _header[offset + 3] = (byte)(value >> 24); 415 | _header[offset + 4] = (byte)(value >> 32); 416 | _header[offset + 5] = (byte)(value >> 40); 417 | _header[offset + 6] = (byte)(value >> 48); 418 | _header[offset + 7] = (byte)(value >> 56); 419 | } 420 | } 421 | 422 | 423 | #endregion 424 | } 425 | } -------------------------------------------------------------------------------- /AsarSharp/PickleTools/PickleIterator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace AsarSharp.PickleTools 5 | { 6 | public class PickleIterator 7 | { 8 | private readonly byte[] _payload; 9 | private readonly int _payloadOffset; 10 | private int _readIndex; 11 | private readonly int _endIndex; 12 | 13 | public PickleIterator(Pickle pickle) 14 | { 15 | _payload = pickle.GetHeader(); 16 | _payloadOffset = pickle.GetHeaderSize(); 17 | _readIndex = 0; 18 | _endIndex = pickle.GetPayloadSize(); 19 | } 20 | 21 | public bool ReadBool() 22 | { 23 | return ReadInt() != 0; 24 | } 25 | 26 | public int ReadInt() 27 | { 28 | return ReadBytes(Pickle.SIZE_INT32, BitConverter.ToInt32); 29 | } 30 | 31 | public uint ReadUInt32() 32 | { 33 | return ReadBytes(Pickle.SIZE_UINT32, BitConverter.ToUInt32); 34 | } 35 | 36 | public long ReadInt64() 37 | { 38 | return ReadBytes(Pickle.SIZE_INT64, BitConverter.ToInt64); 39 | } 40 | 41 | public ulong ReadUInt64() 42 | { 43 | return ReadBytes(Pickle.SIZE_UINT64, BitConverter.ToUInt64); 44 | } 45 | 46 | public float ReadFloat() 47 | { 48 | return ReadBytes(Pickle.SIZE_FLOAT, BitConverter.ToSingle); 49 | } 50 | 51 | public double ReadDouble() 52 | { 53 | return ReadBytes(Pickle.SIZE_DOUBLE, BitConverter.ToDouble); 54 | } 55 | 56 | public string ReadString() 57 | { 58 | int length = ReadInt(); 59 | return Encoding.UTF8.GetString(ReadBytes(length)); 60 | } 61 | 62 | private T ReadBytes(int length, Func converter) 63 | { 64 | int readPayloadOffset = GetReadPayloadOffsetAndAdvance(length); 65 | return converter(_payload, readPayloadOffset); 66 | } 67 | 68 | private byte[] ReadBytes(int length) 69 | { 70 | int readPayloadOffset = GetReadPayloadOffsetAndAdvance(length); 71 | byte[] result = new byte[length]; 72 | Array.Copy(_payload, readPayloadOffset, result, 0, length); 73 | return result; 74 | } 75 | 76 | private int GetReadPayloadOffsetAndAdvance(int length) 77 | { 78 | if (length > _endIndex - _readIndex) 79 | { 80 | _readIndex = _endIndex; 81 | throw new InvalidOperationException($"Failed to read data with length of {length}"); 82 | } 83 | int readPayloadOffset = _payloadOffset + _readIndex; 84 | Advance(length); 85 | return readPayloadOffset; 86 | } 87 | 88 | private void Advance(int size) 89 | { 90 | int alignedSize = Pickle.AlignInt(size, Pickle.SIZE_UINT32); 91 | if (_endIndex - _readIndex < alignedSize) 92 | { 93 | _readIndex = _endIndex; 94 | } 95 | else 96 | { 97 | _readIndex += alignedSize; 98 | } 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /AsarSharp/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("AsarSharp")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("AsarSharp")] 12 | [assembly: AssemblyCopyright("Copyright © 2025")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("BEAA604A-402A-4387-8903-A53FC913A26E")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /AsarSharp/Utils/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace AsarSharp.Utils 6 | { 7 | internal static class Extensions 8 | { 9 | public static string GetRelativePath(string relativeTo, string path) 10 | { 11 | if (string.IsNullOrEmpty(relativeTo)) 12 | throw new ArgumentNullException(nameof(relativeTo)); 13 | if (string.IsNullOrEmpty(path)) 14 | throw new ArgumentNullException(nameof(path)); 15 | 16 | var fullRelativeTo = Path.GetFullPath(relativeTo); 17 | var fullPath = Path.GetFullPath(path); 18 | 19 | if (string.Equals(fullRelativeTo, fullPath, StringComparison.OrdinalIgnoreCase)) 20 | return ""; 21 | 22 | var relativeToUri = new Uri(fullRelativeTo.EndsWith(Path.DirectorySeparatorChar.ToString()) 23 | ? fullRelativeTo 24 | : fullRelativeTo + Path.DirectorySeparatorChar); 25 | var pathUri = new Uri(fullPath.EndsWith(Path.DirectorySeparatorChar.ToString()) && !File.Exists(fullPath) 26 | ? fullPath 27 | : fullPath + (Directory.Exists(fullPath) ? Path.DirectorySeparatorChar.ToString() : "")); 28 | 29 | var relativeUri = relativeToUri.MakeRelativeUri(pathUri); 30 | var relativePath = Uri.UnescapeDataString(relativeUri.ToString()) 31 | .Replace('/', Path.DirectorySeparatorChar); 32 | 33 | return relativePath.TrimEnd(Path.DirectorySeparatorChar); 34 | } 35 | 36 | public static string GetDirectoryName(string path) 37 | { 38 | if (string.IsNullOrEmpty(path)) 39 | return "."; 40 | 41 | string result = Path.GetDirectoryName(path); 42 | 43 | // If the result is an empty string, return “.” as in Node.js 44 | if (string.IsNullOrEmpty(result)) 45 | return "."; 46 | 47 | return result; 48 | } 49 | 50 | public static void CopyDirectory(string sourceDir, string destinationDir) 51 | { 52 | // Create the destination directory 53 | Directory.CreateDirectory(destinationDir); 54 | 55 | // Get all files in the source directory 56 | foreach (var file in Directory.GetFiles(sourceDir)) 57 | { 58 | var destFile = Path.Combine(destinationDir, Path.GetFileName(file)); 59 | File.Copy(file, destFile, true); 60 | } 61 | 62 | // Recursively copy all subdirectories 63 | foreach (var dir in Directory.GetDirectories(sourceDir)) 64 | { 65 | var destDir = Path.Combine(destinationDir, Path.GetFileName(dir)); 66 | CopyDirectory(dir, destDir); 67 | } 68 | } 69 | 70 | public static string GetBasePath(string dir) 71 | { 72 | // Look for the last path delimiter before any pattern 73 | int wildcardIndex = dir.IndexOfAny(new[] { '*', '?' }); 74 | if (wildcardIndex == -1) 75 | { 76 | return dir; 77 | } 78 | 79 | int lastSeparatorIndex = dir.LastIndexOf(Path.DirectorySeparatorChar, wildcardIndex); 80 | if (lastSeparatorIndex == -1) 81 | { 82 | return "."; 83 | } 84 | 85 | return dir.Substring(0, lastSeparatorIndex); 86 | } 87 | 88 | public static void SetUnixFilePermission(string filePath, string permission) 89 | { 90 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 91 | return; 92 | 93 | // Use chmod 94 | var process = new System.Diagnostics.Process 95 | { 96 | StartInfo = new System.Diagnostics.ProcessStartInfo 97 | { 98 | FileName = "chmod", 99 | Arguments = $"{permission} \"{filePath}\"", 100 | UseShellExecute = false, 101 | RedirectStandardOutput = true, 102 | CreateNoWindow = true 103 | } 104 | }; 105 | process.Start(); 106 | process.WaitForExit(); 107 | } 108 | 109 | 110 | public static void CreateSymbolicLink(string linkTarget, string linkPath) 111 | { 112 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 113 | { 114 | // On Windows, creating symlinks requires special privileges, 115 | // so on many systems it simply won't work without administrator privileges 116 | NativeMethods.CreateSymbolicLink(linkPath, linkTarget, 117 | Directory.Exists(linkTarget) 118 | ? NativeMethods.SymLinkFlag.Directory 119 | : NativeMethods.SymLinkFlag.File); 120 | return; 121 | } 122 | 123 | // In Unix systems we use the corresponding system call 124 | var process = new System.Diagnostics.Process 125 | { 126 | StartInfo = new System.Diagnostics.ProcessStartInfo 127 | { 128 | FileName = "ln", 129 | Arguments = $"-s \"{linkTarget}\" \"{linkPath}\"", 130 | UseShellExecute = false, 131 | RedirectStandardOutput = true, 132 | CreateNoWindow = true 133 | } 134 | }; 135 | process.Start(); 136 | process.WaitForExit(); 137 | } 138 | 139 | 140 | public static bool IsWindowsPlatform() 141 | { 142 | return Environment.OSVersion.Platform == PlatformID.Win32NT; 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /AsarSharp/Utils/NativeMethods.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace AsarSharp.Utils 4 | { 5 | internal static class NativeMethods 6 | { 7 | [DllImport("kernel32.dll", SetLastError = true)] 8 | public static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, SymLinkFlag dwFlags); 9 | 10 | public enum SymLinkFlag 11 | { 12 | File = 0, 13 | Directory = 1, 14 | AllowUnprivilegedCreate = 2 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /AsarSharp/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2024 k1tbyte 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ![logo](./assets/icon.svg) 4 | 5 | --- 6 |

WeMod Patcher

7 |
8 | 9 |

WeMod patcher allows you to get some WeMod Pro features absolutely free. This script patches WeMod, thereby removing the daily usage limit (2h), etc.

10 | 11 | ## 👾 No malware? Is it safe for me? 12 | 13 | Yes, this is safe. This script is open-source, and you can check the code yourself. I have no intention of harming you or your computer. The script does absolutely nothing with your computer and does not require Internet access. It only affects WeMod files. 14 | 15 | ## 💻 Does this script only work with older versions of WeMod like other unlockers? 16 | 17 | With this patch you will be able to use the latest version together with Pro. 18 | 19 | ## 💫 What features will be available? 20 | 21 | ✅ Unlimited usage time
22 | ✅ Disabling automatic updates (optional)
23 | ✅ Automatic patching of new WeMod versions
24 | ✅ AI Game guides
25 | ✅ Saving mods
26 | ✅ Exclusive to pro subscription customization for hacks
27 | ✅ Hotkeys (hotkey functionality is broken after static patching for unknown reason)
28 | ❌ Connect phone
29 | 30 | 31 | ## 👀 How to use? 32 | 33 | 1. Go to [Releases](https://github.com/k1tbyte/Wemod-Patcher/releases) page. 34 | 2. Download latest version 35 | 3. Run and click the patch 36 | 37 | --- 38 | 39 | ## ❓ Q&A 40 | 41 | - I applied the patch but when I inject I get stuck on 'Loading mods...'. 42 | - Just close WeMod and try again 43 | - During the game, some hacks are enabled without my input 44 | - This is a bug after the static patch, you have to turn off hotkeys in WeMod settings 45 | - VirusTotal claims that this program is a malware/trojan. 46 | - 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. 47 | - Does this application transfer any data to the Internet from my computer? 48 | - 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. 49 | - What makes this application better than other patchers? 50 | - All actions related to patches are performed on your computer. No files of unknown origin will be downloaded. 51 | 52 | --- 53 | 54 | ## 🖼️ Screenshots 55 | ![1](./assets/screenshots/app1.png) 56 | ![2](./assets/screenshots/app2.png) 57 | --- 58 | 59 | ## 📜 License 60 | This project is licensed under the Apache-2.0 - see the [LICENSE](LICENSE.md) file for details. 61 | 62 | --- 63 | ## ❤️ Support 64 | [![ko-fi](https://www.ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/kitbyte) 65 | 66 | --- 67 | 68 | [![Star History Chart](https://api.star-history.com/svg?repos=k1tbyte/Wemod-Patcher&type=Date)](https://www.star-history.com/#k1tbyte/Wemod-Patcher&Date) -------------------------------------------------------------------------------- /WeModPatcher/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /WeModPatcher/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | pack://application:,,,/Style/#Inter 18pt 18pt 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /WeModPatcher/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using System.Windows; 4 | using WeModPatcher.Core; 5 | using WeModPatcher.View.MainWindow; 6 | using MessageBox = System.Windows.Forms.MessageBox; 7 | 8 | namespace WeModPatcher 9 | { 10 | /// 11 | /// Interaction logic for App.xaml 12 | /// 13 | public partial class App 14 | { 15 | protected override void OnStartup(StartupEventArgs e) 16 | { 17 | this.MainWindow.Show(); 18 | } 19 | 20 | public new static void Shutdown() 21 | { 22 | Current.Dispatcher.Invoke(() => Current.Shutdown()); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /WeModPatcher/Constants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using WeModPatcher.Models; 4 | 5 | namespace WeModPatcher 6 | { 7 | public static class Constants 8 | { 9 | public const string RepoName = "Wemod-Patcher"; 10 | public const string Owner = "k1tbyte"; 11 | public static readonly string RepositoryUrl = $"https://github.com/{Owner}/{RepoName}"; 12 | public static readonly Version Version; 13 | 14 | // cmp dword ptr [rdx], 0 15 | // jnz loc_XXXXXXXX 16 | // mov rsi, rdx 17 | public static Signature ExePatchSignature = new Signature( 18 | "83 3A 00 0F ?? ?? 01 00 00 48 89 D6 48 B8", 19 | 4, 20 | new byte[]{ 0x84, 0x17 }, 21 | new byte[]{ 0x85, 0x22 } 22 | ); 23 | 24 | /*// ... 25 | // test eax, eax (0x85 for r/m16/32/64) 26 | // jnz short loc_1403A4DD2 (Integrity check failed) 27 | // call near ptr funk_1445527E0 28 | // ... 29 | private const string PatchSignature = "E8 ?? ?? ?? ?? ?? C0 75 ?? F6 C3 01 74 ?? 48 89 F9 E8 ?? ?? ?? ??"; 30 | private static readonly byte[] PatchBytes = { 0x31 }; 31 | private const int PatchOffset = 0x5;*/ 32 | 33 | static Constants() 34 | { 35 | Version = Assembly.GetExecutingAssembly().GetName().Version; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /WeModPatcher/Converters/BaseBooleanConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Windows.Data; 5 | 6 | namespace WeModPatcher.Converters 7 | { 8 | public abstract class BaseBooleanConverter : IValueConverter 9 | { 10 | protected BaseBooleanConverter(T trueValue, T falseValue) 11 | { 12 | True = trueValue; 13 | False = falseValue; 14 | } 15 | 16 | protected T True { get; set; } 17 | protected T False { get; set; } 18 | 19 | public virtual object Convert(object value, Type targetType, object parameter, CultureInfo culture) 20 | { 21 | switch (value) 22 | { 23 | case null: 24 | return False; 25 | case bool booleanValue: 26 | return booleanValue ? True : False; 27 | } 28 | 29 | if (!(value is int intValue)) 30 | { 31 | return True; 32 | } 33 | 34 | switch (parameter) 35 | { 36 | case null: 37 | return intValue == 0 ? False : True; 38 | case int param: 39 | return intValue > param ? True : False; 40 | default: 41 | //Because object not null 42 | return True; 43 | } 44 | } 45 | 46 | public virtual object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 47 | { 48 | return value is T t && EqualityComparer.Default.Equals(t, True); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /WeModPatcher/Converters/ToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace WeModPatcher.Converters 4 | { 5 | internal sealed class ToVisibilityConverter : BaseBooleanConverter 6 | { 7 | public ToVisibilityConverter() : 8 | base(Visibility.Visible, Visibility.Collapsed) 9 | { } 10 | } 11 | 12 | internal sealed class ToVisibilityInvertedConverter : BaseBooleanConverter 13 | { 14 | public ToVisibilityInvertedConverter() : 15 | base(Visibility.Collapsed, Visibility.Visible) 16 | { } 17 | } 18 | } -------------------------------------------------------------------------------- /WeModPatcher/Core/RuntimePatcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Runtime.InteropServices; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using WeModPatcher.Models; 9 | using WeModPatcher.Utils; 10 | using WeModPatcher.Utils.Win32; 11 | using WeModPatcher.View.MainWindow; 12 | 13 | namespace WeModPatcher.Core 14 | { 15 | 16 | public class RuntimePatcher 17 | { 18 | private readonly string _exePath; 19 | 20 | public RuntimePatcher(string exePath) 21 | { 22 | _exePath = exePath; 23 | } 24 | 25 | 26 | public void StartProcess() 27 | { 28 | if(string.IsNullOrEmpty(_exePath)) 29 | { 30 | throw new Exception("Path is not specified"); 31 | } 32 | 33 | KillWeMod(); 34 | var startupInfo = new Imports.StartupInfo { cb = Marshal.SizeOf(typeof(Imports.StartupInfo)) }; 35 | if(!Imports.CreateProcessA(_exePath, 36 | null, 37 | IntPtr.Zero, 38 | IntPtr.Zero, 39 | false, Imports.DEBUG_PROCESS, IntPtr.Zero, 40 | null, ref startupInfo, out var processInfo)) 41 | { 42 | throw new Exception("Failed to create process, error code: " + Marshal.GetLastWin32Error()); 43 | } 44 | 45 | var debugEvent = new Imports.DEBUG_EVENT(); 46 | var processIds = new Dictionary(); 47 | while (Imports.WaitForDebugEvent(ref debugEvent, uint.MaxValue)) 48 | { 49 | uint continueStatus = Imports.DBG_CONTINUE; 50 | var code = debugEvent.dwDebugEventCode; 51 | // Console.WriteLine("Debug event code: " + code); 52 | if (code == Imports.CREATE_PROCESS_DEBUG_EVENT) 53 | { 54 | // Console.WriteLine("Spawning process: " + debugEvent.dwProcessId); 55 | processIds.Add(debugEvent.dwProcessId, false); 56 | } 57 | else if (code == Imports.EXIT_PROCESS_DEBUG_EVENT) 58 | { 59 | processIds.Remove(debugEvent.dwProcessId); 60 | 61 | if(processIds.Count == 0) 62 | { 63 | break; 64 | } 65 | } 66 | else if (code == Imports.EXCEPTION_DEBUG_EVENT) 67 | { 68 | // pass the exception to the process 69 | continueStatus = Imports.DBG_EXCEPTION_NOT_HANDLED; 70 | 71 | var exceptionInfo = Imports.MapUnmanagedStructure(debugEvent.Union); 72 | // Console.WriteLine("Exception code: " + exceptionInfo.ExceptionRecord.ExceptionCode); 73 | 74 | if (exceptionInfo.ExceptionRecord.ExceptionCode == Imports.EXCEPTION_BREAKPOINT && 75 | processIds.TryGetValue(debugEvent.dwProcessId, out var wasPatched) && !wasPatched) 76 | { 77 | var process = Process.GetProcessById((int)debugEvent.dwProcessId); 78 | // Console.WriteLine("Scanning process: " + process.ProcessName + " " + process.Id); 79 | var address = MemoryUtils.ScanVirtualMemory( 80 | process.Handle, 81 | process.Modules[0].BaseAddress, 82 | process.Modules[0].ModuleMemorySize, 83 | Constants.ExePatchSignature.Sequence, Constants.ExePatchSignature.Mask 84 | ); 85 | 86 | if (address != IntPtr.Zero) 87 | { 88 | processIds[debugEvent.dwProcessId] = MemoryUtils.SafeWriteVirtualMemory( 89 | process.Handle, 90 | address + Constants.ExePatchSignature.Offset, 91 | Constants.ExePatchSignature.PatchBytes 92 | ); 93 | 94 | /*byte[] patchedBytes = new byte[32]; 95 | if (Imports.ReadProcessMemory(process.Handle, address, patchedBytes, patchedBytes.Length, out int bytesRead)) 96 | { 97 | Console.WriteLine("Bytes after patching: "); 98 | for (int i = 0; i < bytesRead; i++) 99 | { 100 | Console.Write($"{patchedBytes[i]:X2} "); 101 | } 102 | Console.WriteLine(); 103 | }*/ 104 | } 105 | } 106 | } 107 | 108 | Imports.ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, continueStatus); 109 | } 110 | 111 | foreach (var entry in processIds) 112 | { 113 | Imports.DebugActiveProcessStop(entry.Key); 114 | } 115 | 116 | Imports.CloseHandle(processInfo.hProcess); 117 | } 118 | 119 | public static void Patch(PatchConfig config, Action logger) 120 | { 121 | if (config.Path == null) 122 | { 123 | throw new Exception("Path is not specified"); 124 | } 125 | 126 | var parent = Directory.GetParent(config.Path)?.FullName ?? config.Path; 127 | var latestPath = Extensions.FindLatestWeMod(parent) ?? config.Path; 128 | 129 | if (!Extensions.CheckWeModPath(latestPath)) 130 | { 131 | throw new Exception("Invalid WeMod path"); 132 | } 133 | 134 | if(!File.Exists(Path.Combine(latestPath, "resources", "app.asar.backup"))) 135 | { 136 | config.PatchMethod = EPatchProcessMethod.None; 137 | new StaticPatcher(latestPath, logger, config).Patch(); 138 | } 139 | 140 | new RuntimePatcher(Path.Combine(latestPath, "WeMod.exe")) 141 | .StartProcess(); 142 | } 143 | 144 | 145 | 146 | public static void KillWeMod() 147 | { 148 | Process[] processes = Process.GetProcessesByName("WeMod"); 149 | for (int i = 0; processes.Length > i || i < 5; i++) 150 | { 151 | foreach (var process in processes) 152 | { 153 | try 154 | { 155 | process.Kill(); 156 | } 157 | catch 158 | { 159 | // ignored 160 | } 161 | } 162 | 163 | processes = Process.GetProcessesByName("WeMod"); 164 | Thread.Sleep(250); 165 | } 166 | 167 | if (processes.Length > 0) 168 | { 169 | throw new Exception("Failed to kill WeMod"); 170 | } 171 | } 172 | } 173 | } -------------------------------------------------------------------------------- /WeModPatcher/Core/StaticPatcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text.RegularExpressions; 7 | using System.Windows.Forms; 8 | using AsarSharp; 9 | using Newtonsoft.Json; 10 | using WeModPatcher.Models; 11 | using WeModPatcher.Utils; 12 | using WeModPatcher.View.MainWindow; 13 | using Application = System.Windows.Application; 14 | 15 | namespace WeModPatcher.Core 16 | { 17 | public class StaticPatcher 18 | { 19 | private class PatchEntry 20 | { 21 | public Regex Target { get; set; } 22 | public string Patch { get; set; } 23 | public bool Applied { get; set; } 24 | public bool SingleMatch { get; set; } = true; 25 | public bool DynamicFieldResolve { get; set; } 26 | } 27 | 28 | private static readonly Dictionary Patches = new Dictionary() 29 | { 30 | { 31 | EPatchType.ActivatePro, 32 | new PatchEntry 33 | { 34 | DynamicFieldResolve = true, 35 | Target = new Regex(@"getUserAccount\(\)\{.*?return\s+this\.#\w+\.fetch\(\{.*?\}\)\}", RegexOptions.Singleline), 36 | Patch = "getUserAccount(){return this.#.fetch({endpoint:\"/v3/account\",method:\"GET\",name:\"/v3/account\",collectMetrics:0}).then(response=>{response.subscription={period:\"yearly\",state:\"active\"};response.flags=78;return response;})}" 37 | } 38 | }, 39 | { 40 | EPatchType.DisableUpdates, 41 | new PatchEntry 42 | { 43 | Target = new Regex(@"registerHandler\(""ACTION_CHECK_FOR_UPDATE"".*?\)\)\)\)", RegexOptions.Singleline), 44 | Patch = "registerHandler(\"ACTION_CHECK_FOR_UPDATE\",(e=>expectUpdateFeedUrl(e,(e=>null)))" 45 | } 46 | } 47 | }; 48 | 49 | private readonly string _weModRootFolder; 50 | private readonly Action _logger; 51 | private readonly PatchConfig _config; 52 | private readonly string _asarPath; 53 | private readonly string _backupPath; 54 | private readonly string _unpackedPath; 55 | private int _sumOfPatches = 0; 56 | private readonly string _exePath; 57 | 58 | public StaticPatcher(string weModRootFolder, Action logger, PatchConfig config) 59 | { 60 | _weModRootFolder = weModRootFolder; 61 | _logger = logger; 62 | _config = config; 63 | 64 | _asarPath = Path.Combine(weModRootFolder, "resources", "app.asar"); 65 | _unpackedPath = Path.Combine(weModRootFolder, "resources", "app.asar.unpacked"); 66 | _backupPath = Path.Combine(weModRootFolder, "resources", "app.asar.backup"); 67 | _exePath = Path.Combine(_weModRootFolder, "WeMod.exe"); 68 | } 69 | 70 | private static string GetFetchFieldName(string targetFunction) 71 | { 72 | var fetchMatch = Regex.Match(targetFunction, @"return\s+this\.#(\w+)\.fetch"); 73 | return fetchMatch.Success ? fetchMatch.Groups[1].Value : null; 74 | } 75 | 76 | private void ApplyJsPatch(string fileName, string js, PatchEntry patch, EPatchType patchType) 77 | { 78 | if (patch.Applied) 79 | { 80 | return; 81 | } 82 | 83 | var matches = patch.Target.Matches(js); 84 | if (matches.Count == 0) 85 | { 86 | return; 87 | } 88 | 89 | if(matches.Count > 1 && patch.SingleMatch) 90 | { 91 | throw new Exception( 92 | $"[PATCHER] [{patchType}] Patch failed. Multiple target functions found. Looks like the version is not supported"); 93 | } 94 | 95 | if (patch.DynamicFieldResolve) 96 | { 97 | string fetchFieldName = GetFetchFieldName(matches[0].Value); 98 | if (string.IsNullOrEmpty(fetchFieldName)) 99 | { 100 | throw new Exception($"[PATCHER] [{patchType}] Fetch field name not found"); 101 | } 102 | 103 | patch.Patch = patch.Patch.Replace("", fetchFieldName); 104 | } 105 | 106 | _logger($"[PATCHER] [{patchType}] Found target function in: " + Path.GetFileName(fileName), ELogType.Info); 107 | 108 | 109 | File.WriteAllText(fileName, patch.Target.Replace(js, patch.Patch)); 110 | _logger($"[PATCHER] [{patchType}] Patch applied", ELogType.Success); 111 | patch.Applied = true; 112 | _sumOfPatches -= (int)patchType; 113 | } 114 | 115 | private void PatchAsar() 116 | { 117 | var items = Directory.EnumerateFiles(_unpackedPath) 118 | .Where(file => !Directory.Exists(file) && Regex.IsMatch(Path.GetFileName(file), @"^app-\w+|index\.js")) 119 | .ToList(); 120 | 121 | if (!items.Any()) 122 | { 123 | throw new Exception("[PATCHER] No app bundle found"); 124 | } 125 | 126 | var requestedPatches = _config.PatchTypes.ToList(); 127 | requestedPatches.ForEach(patch => _sumOfPatches += (int)patch); 128 | foreach (var item in items) 129 | { 130 | if (_sumOfPatches <= 0) 131 | { 132 | break; 133 | } 134 | 135 | string data = File.ReadAllText(item); 136 | foreach (var entry in requestedPatches) 137 | { 138 | ApplyJsPatch(item, data, Patches[entry], entry); 139 | } 140 | } 141 | } 142 | 143 | private void PatchPe() 144 | { 145 | _logger("[PATCHER] Patching PE...", ELogType.Info); 146 | var patchResult = MemoryUtils.PatchFile(_exePath,Constants.ExePatchSignature, Constants.ExePatchSignature.PatchBytes); 147 | if(patchResult == -1) 148 | { 149 | _logger("[PATCHER] Failed to patch PE", ELogType.Error); 150 | return; 151 | } 152 | _logger(patchResult == 0 ? "[PATCHER] PE already patched!" : "[PATCHER] PE patched successfully!", ELogType.Success); 153 | } 154 | 155 | private void CreateShortcut() 156 | { 157 | // invoke file dialog save file 158 | 159 | var fileDialog = new SaveFileDialog() 160 | { 161 | CheckPathExists = true, 162 | AddExtension = true, 163 | SupportMultiDottedExtensions = false, 164 | FileName = "WeMod", 165 | }; 166 | 167 | if(fileDialog.ShowDialog() != DialogResult.OK) 168 | { 169 | return; 170 | } 171 | 172 | _config.Path = _weModRootFolder; 173 | 174 | var json = JsonConvert.SerializeObject(_config, Formatting.None); 175 | Utils.Win32.Shortcut.CreateShortcut( 176 | fileName: fileDialog.FileName + ".lnk", 177 | targetPath: Assembly.GetExecutingAssembly().Location, 178 | arguments: Extensions.Base64Encode(json), 179 | workingDirectory: Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), 180 | description: null, 181 | iconPath: _exePath 182 | ); 183 | 184 | _logger("[PATCHER] The shortcut has been created, now you should only run WeMod through this shortcut", ELogType.Success); 185 | } 186 | 187 | public void Patch() 188 | { 189 | RuntimePatcher.KillWeMod(); 190 | if (!File.Exists(_backupPath)) 191 | { 192 | _logger("[PATCHER] Creating backup...", ELogType.Info); 193 | File.Copy(_asarPath, _backupPath); 194 | } 195 | else 196 | { 197 | _logger("[PATCHER] Backup already exists", ELogType.Warn); 198 | } 199 | 200 | if(!File.Exists(_asarPath)) 201 | { 202 | throw new Exception("app.asar not found"); 203 | } 204 | 205 | try 206 | { 207 | _logger("[PATCHER] Extracting app.asar...", ELogType.Info); 208 | AsarExtractor.ExtractAll(_asarPath, _unpackedPath); 209 | } 210 | catch (Exception e) 211 | { 212 | throw new Exception($"[PATCHER] Failed to unpack app.asar: {e.Message}"); 213 | } 214 | 215 | PatchAsar(); 216 | 217 | try 218 | { 219 | new AsarCreator(_unpackedPath, _asarPath, new CreateOptions 220 | { 221 | Unpack = new Regex(@"^static\\unpacked.*$") 222 | }).CreatePackageWithOptions(); 223 | } 224 | catch (Exception e) 225 | { 226 | throw new Exception($"[PATCHER] Failed to pack app.asar: {e.Message}"); 227 | } 228 | 229 | if (_config.PatchMethod == EPatchProcessMethod.Static) 230 | { 231 | PatchPe(); 232 | } 233 | else if(_config.PatchMethod == EPatchProcessMethod.Runtime) 234 | { 235 | Application.Current.Dispatcher.Invoke(CreateShortcut); 236 | } 237 | 238 | _logger("[PATCHER] Done!", ELogType.Success); 239 | } 240 | } 241 | } -------------------------------------------------------------------------------- /WeModPatcher/Models/PatchConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace WeModPatcher.Models 4 | { 5 | 6 | public enum EPatchType 7 | { 8 | ActivatePro = 1, 9 | DisableUpdates = 2, 10 | DisableTelemetry = 4 11 | } 12 | 13 | public enum EPatchProcessMethod 14 | { 15 | None = 0, 16 | Runtime = 1, 17 | Static = 2 18 | } 19 | 20 | public sealed class PatchConfig 21 | { 22 | public HashSet PatchTypes { get; set; } 23 | public EPatchProcessMethod PatchMethod { get; set; } 24 | public string Path { get; set; } 25 | } 26 | } -------------------------------------------------------------------------------- /WeModPatcher/Models/Signature.cs: -------------------------------------------------------------------------------- 1 | using WeModPatcher.Utils; 2 | 3 | namespace WeModPatcher.Models 4 | { 5 | public sealed class Signature 6 | { 7 | public readonly byte[] OriginalBytes; 8 | public readonly byte[] PatchBytes; 9 | public readonly byte[] Sequence; 10 | public readonly byte[] Mask; 11 | public readonly int Offset; 12 | 13 | public int Length => Sequence.Length; 14 | 15 | public static implicit operator byte[](Signature signature) => signature.Sequence; 16 | 17 | public Signature(string signature, int offset, byte[] patchBytes, byte[] originalBytes) 18 | { 19 | MemoryUtils.ParseSignature(signature, out Sequence, out Mask); 20 | PatchBytes = patchBytes; 21 | OriginalBytes = originalBytes; 22 | Offset = offset; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /WeModPatcher/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Diagnostics; 5 | using System.Threading.Tasks; 6 | using System.Windows.Forms; 7 | using Newtonsoft.Json; 8 | using WeModPatcher.Core; 9 | using WeModPatcher.Models; 10 | using WeModPatcher.Utils; 11 | using WeModPatcher.View.MainWindow; 12 | 13 | namespace WeModPatcher 14 | { 15 | public static class Program 16 | { 17 | [STAThread] 18 | public static void Main(string[] args) 19 | { 20 | AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; 21 | TaskScheduler.UnobservedTaskException += OnUnobservedTaskException; 22 | 23 | List logEntries = new List(); 24 | if (args.Length > 0) 25 | { 26 | try 27 | { 28 | var patchConfig = JsonConvert.DeserializeObject(Extensions.Base64Decode(args[0])); 29 | RuntimePatcher.Patch(patchConfig, (message, type) => 30 | { 31 | logEntries.Add(new LogEntry 32 | { 33 | Message = message, 34 | LogType = type 35 | }); 36 | }); 37 | Environment.Exit(0); 38 | } 39 | catch (Exception e) 40 | { 41 | logEntries.Add(new LogEntry 42 | { 43 | Message = "Runtime patching failed: " + e.Message, 44 | LogType = ELogType.Error 45 | }); 46 | } 47 | 48 | } 49 | 50 | var application = new App(); 51 | application.InitializeComponent(); 52 | application.MainWindow = new MainWindow(); 53 | foreach (var logEntry in logEntries) 54 | { 55 | MainWindow.Instance.ViewModel.LogList.Add(logEntry); 56 | } 57 | application.Run(); 58 | } 59 | 60 | 61 | private static void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) 62 | { 63 | MessageBox.Show(e.Exception.ToString()); 64 | Environment.Exit(1); 65 | } 66 | 67 | private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e) 68 | { 69 | MessageBox.Show(e.ExceptionObject.ToString()); 70 | Environment.Exit(1); 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /WeModPatcher/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Windows; 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [assembly: AssemblyTitle("WeModPatcher")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("")] 14 | [assembly: AssemblyProduct("WeModPatcher")] 15 | [assembly: AssemblyCopyright("Copyright © 2025")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | //In order to begin building localizable applications, set 25 | //CultureYouAreCodingWith in your .csproj file 26 | //inside a . For example, if you are using US english 27 | //in your source files, set the to en-US. Then uncomment 28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 29 | //the line below to match the UICulture setting in the project file. 30 | 31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 32 | 33 | 34 | [assembly: ThemeInfo( 35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 36 | //(used if a resource is not found in the page, 37 | // or application resource dictionaries) 38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 39 | //(used if a resource is not found in the page, 40 | // app, or any theme specific resource dictionaries) 41 | )] 42 | 43 | 44 | // Version information for an assembly consists of the following four values: 45 | // 46 | // Major Version 47 | // Minor Version 48 | // Build Number 49 | // Revision 50 | // 51 | // You can specify all the values or you can default the Build and Revision Numbers 52 | // by using the '*' as shown below: 53 | // [assembly: AssemblyVersion("1.0.*")] 54 | [assembly: AssemblyVersion("1.0.2.0")] 55 | [assembly: AssemblyFileVersion("1.0.2.0")] -------------------------------------------------------------------------------- /WeModPatcher/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace WeModPatcher.Properties 12 | { 13 | /// 14 | /// A strongly-typed resource class, for looking up localized strings, etc. 15 | /// 16 | // This class was auto-generated by the StronglyTypedResourceBuilder 17 | // class via a tool like ResGen or Visual Studio. 18 | // To add or remove a member, edit your .ResX file then rerun ResGen 19 | // with the /str option, or rebuild your VS project. 20 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", 21 | "4.0.0.0")] 22 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 23 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 24 | internal class Resources 25 | { 26 | private static global::System.Resources.ResourceManager resourceMan; 27 | 28 | private static global::System.Globalization.CultureInfo resourceCulture; 29 | 30 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", 31 | "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() 33 | { 34 | } 35 | 36 | /// 37 | /// Returns the cached ResourceManager instance used by this class. 38 | /// 39 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState 40 | .Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get 44 | { 45 | if ((resourceMan == null)) 46 | { 47 | global::System.Resources.ResourceManager temp = 48 | new global::System.Resources.ResourceManager("WeModPatcher.Properties.Resources", 49 | typeof(Resources).Assembly); 50 | resourceMan = temp; 51 | } 52 | 53 | return resourceMan; 54 | } 55 | } 56 | 57 | /// 58 | /// Overrides the current thread's CurrentUICulture property for all 59 | /// resource lookups using this strongly typed resource class. 60 | /// 61 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState 62 | .Advanced)] 63 | internal static global::System.Globalization.CultureInfo Culture 64 | { 65 | get { return resourceCulture; } 66 | set { resourceCulture = value; } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /WeModPatcher/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /WeModPatcher/ReactiveUICore/AsyncRelayCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using System.Windows.Input; 5 | 6 | namespace WeModPatcher.ReactiveUICore 7 | { 8 | public sealed class AsyncRelayCommand : ICommand 9 | { 10 | private readonly Func _execute; 11 | private readonly Func _canExecute; 12 | 13 | private long _isExecuting; 14 | 15 | public AsyncRelayCommand(Func execute, Func canExecute = null) 16 | { 17 | this._execute = execute; 18 | this._canExecute = canExecute ?? (o => true); 19 | } 20 | 21 | public event EventHandler CanExecuteChanged 22 | { 23 | add => CommandManager.RequerySuggested += value; 24 | remove => CommandManager.RequerySuggested -= value; 25 | } 26 | 27 | private static void RaiseCanExecuteChanged() => CommandManager.InvalidateRequerySuggested(); 28 | 29 | public bool CanExecute(object parameter) => Interlocked.Read(ref _isExecuting) == 0 && _canExecute(parameter); 30 | 31 | public async void Execute(object parameter) 32 | { 33 | Interlocked.Exchange(ref _isExecuting, 1); 34 | RaiseCanExecuteChanged(); 35 | 36 | try 37 | { 38 | await _execute(parameter); 39 | } 40 | finally 41 | { 42 | Interlocked.Exchange(ref _isExecuting, 0); 43 | RaiseCanExecuteChanged(); 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /WeModPatcher/ReactiveUICore/ObservableObject.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace WeModPatcher.ReactiveUICore 5 | { 6 | public class ObservableObject : INotifyPropertyChanged 7 | { 8 | public event PropertyChangedEventHandler PropertyChanged; 9 | 10 | protected void OnPropertyChanged([CallerMemberName] string name = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); 11 | 12 | protected virtual bool SetProperty(ref T field, T value, [CallerMemberName] string propertyName = null) 13 | { 14 | if (Equals(field, value)) return false; 15 | field = value; 16 | OnPropertyChanged(propertyName); 17 | return true; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /WeModPatcher/ReactiveUICore/RelayCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Input; 3 | 4 | namespace WeModPatcher.ReactiveUICore 5 | { 6 | public sealed class RelayCommand : ICommand 7 | { 8 | private readonly Action _execute; 9 | private readonly Func _canExecute; 10 | 11 | public event EventHandler CanExecuteChanged 12 | { 13 | add => CommandManager.RequerySuggested += value; 14 | remove => CommandManager.RequerySuggested -= value; 15 | } 16 | 17 | public RelayCommand(Action execute, Func canExecute = null) 18 | { 19 | _execute = execute; 20 | _canExecute = canExecute; 21 | } 22 | 23 | public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter); 24 | public void Execute(object parameter) => _execute(parameter); 25 | } 26 | } -------------------------------------------------------------------------------- /WeModPatcher/Style/ColorScheme.xaml: -------------------------------------------------------------------------------- 1 |  3 | #09090b 4 | #FAFAFA 5 | #27272A 6 | #FAFAFA 7 | #09090b 8 | #FAFAFA 9 | #FAFAFA 10 | #18181B 11 | #27272A 12 | #FAFAFA 13 | #18181a 14 | #A1A1AA 15 | #27272A 16 | #FAFAFA 17 | Red 18 | #FAFAFA 19 | #27272A 20 | #27272A 21 | #D4D4D8 22 | -------------------------------------------------------------------------------- /WeModPatcher/Style/Icons.xaml: -------------------------------------------------------------------------------- 1 |  3 | 4 | M13.46,12L19,17.54V19H17.54L12,13.46L6.46,19H5V17.54L10.54,12L5,6.46V5H6.46L12,10.54L17.54,5H19V6.46L13.46,12Z 5 | 6 | 7 | 8 | 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 9 | 10 | 11 | 12 | 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 13 | 14 | 15 | 16 | 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 17 | 18 | 19 | 20 | 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 21 | 22 | 23 | 24 | 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 25 | 26 | 27 | 28 | M5.05 11.94l5-5v3.99H19l-.03 2.01H10.05v4Z 29 | 30 | 31 | 34 | -------------------------------------------------------------------------------- /WeModPatcher/Style/Inter_18pt-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k1tbyte/Wemod-Patcher/b283c63fd72988dbdc9581a4b4f61e06a83c166e/WeModPatcher/Style/Inter_18pt-Regular.ttf -------------------------------------------------------------------------------- /WeModPatcher/Style/Styles.xaml: -------------------------------------------------------------------------------- 1 |  3 | 4 | 5 | 6 | 35 | 36 | 50 | 51 | 68 | 69 | 70 | 71 | 106 | 107 | 138 | 139 | 147 | 148 | 156 | 157 | 158 | 192 | 193 | 194 | 245 | -------------------------------------------------------------------------------- /WeModPatcher/Utils/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace WeModPatcher.Utils 7 | { 8 | public static class Extensions 9 | { 10 | public static bool CheckWeModPath(string root) 11 | { 12 | try 13 | { 14 | return File.Exists(Path.Combine(root, "WeMod.exe")) && 15 | File.Exists(Path.Combine(root, "resources", "app.asar")); 16 | } 17 | catch 18 | { 19 | return false; 20 | } 21 | } 22 | 23 | public static string FindWeModDirectory() 24 | { 25 | string localAppDataPath = Environment.GetEnvironmentVariable("LOCALAPPDATA"); 26 | 27 | string defaultDir = Path.Combine(localAppDataPath ?? "", "WeMod"); 28 | 29 | if (!Directory.Exists(defaultDir)) 30 | { 31 | return null; 32 | } 33 | 34 | return FindLatestWeMod(defaultDir); 35 | } 36 | 37 | public static string Base64Decode(string base64EncodedData) 38 | { 39 | var base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData); 40 | return System.Text.Encoding.UTF8.GetString(base64EncodedBytes); 41 | } 42 | 43 | public static string Base64Encode(string plainText) 44 | { 45 | var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText); 46 | return System.Convert.ToBase64String(plainTextBytes); 47 | } 48 | 49 | public static string FindLatestWeMod(string root) 50 | { 51 | var appFolders = Directory.EnumerateDirectories(root) 52 | .Select(folderPath => new DirectoryInfo(folderPath)) 53 | .Where(dirInfo => Regex.IsMatch(dirInfo.Name, @"^app-\w+")) 54 | .Select(dirInfo => new 55 | { 56 | Name = dirInfo.Name, 57 | Path = dirInfo.FullName, 58 | LastModified = dirInfo.LastWriteTime 59 | }) 60 | .OrderByDescending(item => item.LastModified) 61 | .ToList(); 62 | 63 | return ( 64 | from folder 65 | in appFolders 66 | where CheckWeModPath(folder.Path) 67 | select folder.Path 68 | ).FirstOrDefault(); 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /WeModPatcher/Utils/MemoryUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using WeModPatcher.Models; 9 | using WeModPatcher.Utils.Win32; 10 | 11 | namespace WeModPatcher.Utils 12 | { 13 | public class MemoryUtils 14 | { 15 | public static int ScanMemoryBlock(byte[] buffer, int bufferLength, byte[] pattern, byte[] mask) 16 | { 17 | var patternLength = pattern.Length; 18 | if (bufferLength < patternLength) 19 | { 20 | return -1; 21 | } 22 | 23 | // Make a length of length outside the first cycle for optimization 24 | var searchEnd = bufferLength - patternLength; 25 | 26 | // first pass - use the first non-empty byte of the mask for a quick check 27 | var firstValidIndex = -1; 28 | for (var i = 0; i < patternLength; i++) 29 | { 30 | if (mask[i] == 1) 31 | { 32 | firstValidIndex = i; 33 | break; 34 | } 35 | } 36 | 37 | if (firstValidIndex == -1) 38 | { 39 | return 0; 40 | } 41 | 42 | var firstByte = pattern[firstValidIndex]; 43 | 44 | for (var i = 0; i <= searchEnd; i++) 45 | { 46 | // quick check by the first byte before full comparison 47 | if (buffer[i + firstValidIndex] != firstByte) 48 | continue; 49 | 50 | var found = true; 51 | 52 | // check only those positions where mask = 1 53 | for (var j = 0; j < patternLength; j++) 54 | { 55 | if (mask[j] == 0 || buffer[i + j] == pattern[j]) 56 | { 57 | continue; 58 | } 59 | 60 | found = false; 61 | break; 62 | } 63 | 64 | if (found) 65 | { 66 | return i; 67 | } 68 | } 69 | 70 | return -1; 71 | } 72 | 73 | public static void ParseSignature(string signatureStr, out byte[] pattern, out byte[] mask) 74 | { 75 | var parts = signatureStr.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); 76 | var length = parts.Length; 77 | 78 | pattern = new byte[length]; 79 | mask = new byte[length]; 80 | 81 | for (var i = 0; i < length; i++) 82 | { 83 | if (parts[i] == "??" || parts[i] == "?") 84 | { 85 | pattern[i] = 0; 86 | // wildcard byte 87 | mask[i] = 0; 88 | continue; 89 | } 90 | 91 | pattern[i] = Convert.ToByte(parts[i], 16); 92 | mask[i] = 1; 93 | } 94 | } 95 | 96 | public static bool SafeWriteVirtualMemory(IntPtr hProcess, IntPtr address, byte[] bytes) 97 | { 98 | if (!Imports.VirtualProtectEx(hProcess, address, (IntPtr)1, 0x40, out uint oldProtect)) 99 | { 100 | return false; 101 | } 102 | 103 | bool result = Imports.WriteProcessMemory(hProcess, address, bytes, bytes.Length, out _); 104 | 105 | // Restore the previous access rights 106 | Imports.VirtualProtectEx(hProcess, address, (IntPtr)1, oldProtect, out _); 107 | return result; 108 | } 109 | 110 | public static IntPtr ScanVirtualMemory(IntPtr hProcess, IntPtr startAddress, int searchSize, byte[] signature, byte[] mask) 111 | { 112 | const int BUFFER_SIZE = 4096; 113 | byte[] buffer = new byte[BUFFER_SIZE]; 114 | 115 | // We can't copy all the crap of the process into a byte array at once. Don't try this 116 | for (long currentAddress = startAddress.ToInt64(); 117 | currentAddress < startAddress.ToInt64() + searchSize; 118 | currentAddress += BUFFER_SIZE - signature.Length) 119 | { 120 | if (!Imports.ReadProcessMemory(hProcess, new IntPtr(currentAddress), buffer, BUFFER_SIZE, out int bytesRead) || bytesRead == 0) 121 | { 122 | // Read error or end of memory, throw mb? 123 | continue; 124 | } 125 | 126 | var i = ScanMemoryBlock(buffer, bytesRead, signature, mask); 127 | if (i != -1) 128 | { 129 | return new IntPtr(currentAddress + i); 130 | } 131 | } 132 | 133 | return IntPtr.Zero; 134 | } 135 | 136 | public static int PatchFile(string filePath, Signature signature, byte[] patchBytes) 137 | { 138 | const int bufferSize = 8192; 139 | var buffer = new byte[bufferSize + signature.Length - 1]; 140 | 141 | using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)) 142 | { 143 | int filePosition = 0; 144 | while (true) 145 | { 146 | int bytesRead = fileStream.Read(buffer, 0, bufferSize); 147 | if (bytesRead == 0) break; 148 | 149 | int matchIndex = ScanMemoryBlock(buffer, bytesRead, signature, signature.Mask); 150 | if (matchIndex != -1) 151 | { 152 | int functionStartPosition = filePosition + matchIndex; 153 | 154 | var checkBuffer = new byte[patchBytes.Length]; 155 | fileStream.Seek(functionStartPosition + signature.Offset, SeekOrigin.Begin); 156 | fileStream.Read(checkBuffer, 0, patchBytes.Length); 157 | 158 | if (checkBuffer.SequenceEqual(patchBytes)) 159 | { 160 | return 0; // Memory already patched 161 | } 162 | 163 | // Go to patch position 164 | fileStream.Seek(functionStartPosition + signature.Offset, SeekOrigin.Begin); 165 | fileStream.Write(patchBytes, 0, patchBytes.Length); 166 | 167 | return functionStartPosition; // Return the address of the function start by signature 168 | } 169 | 170 | filePosition += bytesRead; 171 | Array.Copy(buffer, bufferSize, buffer, 0, signature.Length - 1); 172 | } 173 | } 174 | 175 | return -1; 176 | } 177 | } 178 | } -------------------------------------------------------------------------------- /WeModPatcher/Utils/Updater.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Threading.Tasks; 7 | using System.Net.Http; 8 | using System.Windows; 9 | using Newtonsoft.Json; 10 | 11 | namespace WeModPatcher.Utils 12 | { 13 | public class GitHubRelease 14 | { 15 | public class AssetsType 16 | { 17 | public string Name { get; set; } 18 | 19 | [JsonProperty("browser_download_url")] 20 | public string Url { get; set; } 21 | } 22 | 23 | [JsonProperty("tag_name")] 24 | public string TagName { get; set; } 25 | 26 | [JsonProperty("assets")] 27 | public AssetsType[] Assets { get; set; } 28 | 29 | } 30 | 31 | public class Updater 32 | { 33 | private GitHubRelease _release = null; 34 | private static readonly HttpClient _httpClient = new HttpClient() 35 | { 36 | DefaultRequestHeaders = 37 | { 38 | { "User-Agent", "GitHub-Updater" } 39 | } 40 | }; 41 | 42 | private static readonly string ApiUrl = $"https://api.github.com/repos/{Constants.Owner}/{Constants.RepoName}/releases/latest"; 43 | public async Task CheckForUpdates() 44 | { 45 | try 46 | { 47 | var currentVersion = Assembly.GetExecutingAssembly().GetName().Version; 48 | var response = await _httpClient.GetAsync(ApiUrl); 49 | response.EnsureSuccessStatusCode(); 50 | _release = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); 51 | 52 | if (_release == null) 53 | { 54 | return false; 55 | } 56 | 57 | var latestVersion = new Version(_release.TagName); 58 | 59 | return latestVersion > currentVersion; 60 | } 61 | catch (Exception e) 62 | { 63 | return false; 64 | } 65 | } 66 | 67 | public async Task Update() 68 | { 69 | if (_release == null) 70 | { 71 | throw new Exception("No release found"); 72 | } 73 | 74 | var asset = _release.Assets.FirstOrDefault(o => o.Name.EndsWith(".exe")); 75 | if(asset == null) 76 | { 77 | throw new Exception("No asset found"); 78 | } 79 | 80 | // download to temp 81 | var downloadPath = Path.Combine(Path.GetTempPath(), asset.Name); 82 | 83 | using(var response = await _httpClient.GetAsync(asset.Url)) 84 | using(var fileStream = File.Create(downloadPath)) 85 | { 86 | response.EnsureSuccessStatusCode(); 87 | await response.Content.CopyToAsync(fileStream); 88 | } 89 | 90 | ApplyUpdate(downloadPath); 91 | } 92 | 93 | 94 | 95 | private static void ApplyUpdate(string filePath) 96 | { 97 | try 98 | { 99 | var currentExecutable = Assembly.GetExecutingAssembly().Location; 100 | 101 | var psCommand = $"Start-Sleep -Seconds 2; " + 102 | $"Copy-Item -Path '{filePath}' -Destination '{currentExecutable}' -Force; " + 103 | $"Remove-Item -Path '{filePath}' -Force; " + 104 | $"Start-Sleep -Seconds 1; " + 105 | $"Start-Process -FilePath '{currentExecutable}';"; 106 | 107 | var startInfo = new ProcessStartInfo 108 | { 109 | FileName = "powershell.exe", 110 | Arguments = $"-WindowStyle Hidden -ExecutionPolicy Bypass -Command \"{psCommand}\"", 111 | UseShellExecute = true, 112 | CreateNoWindow = true, 113 | WindowStyle = ProcessWindowStyle.Hidden 114 | }; 115 | 116 | Process.Start(startInfo); 117 | 118 | Task.Delay(500).ContinueWith(_ => 119 | { 120 | App.Shutdown(); 121 | }); 122 | } 123 | catch (Exception ex) 124 | { 125 | throw new Exception($"Update failed: {ex.Message}"); 126 | } 127 | } 128 | 129 | } 130 | } -------------------------------------------------------------------------------- /WeModPatcher/Utils/Win32/Imports.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Text; 4 | 5 | namespace WeModPatcher.Utils.Win32 6 | { 7 | public static class Imports 8 | { 9 | [DllImport("kernel32.dll", SetLastError = true)] 10 | public static extern bool ReadProcessMemory( 11 | IntPtr hProcess, 12 | IntPtr lpBaseAddress, 13 | [Out] byte[] lpBuffer, 14 | int dwSize, 15 | out int lpNumberOfBytesRead); 16 | 17 | [DllImport("kernel32.dll", SetLastError = true)] 18 | public static extern bool WriteProcessMemory( 19 | IntPtr hProcess, 20 | IntPtr lpBaseAddress, 21 | byte[] lpBuffer, 22 | int nSize, 23 | out IntPtr lpNumberOfBytesWritten); 24 | 25 | [DllImport("kernel32.dll", SetLastError = true)] 26 | public static extern bool VirtualProtectEx( 27 | IntPtr hProcess, 28 | IntPtr lpAddress, 29 | IntPtr dwSize, 30 | uint flNewProtect, 31 | out uint lpflOldProtect); 32 | 33 | [DllImport("kernel32.dll", SetLastError = true)] 34 | public static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds); 35 | 36 | [DllImport("kernel32.dll", SetLastError = true)] 37 | public static extern bool WaitForDebugEvent(ref DEBUG_EVENT lpDebugEvent, uint dwMilliseconds); 38 | 39 | [DllImport("kernel32.dll", SetLastError = true)] 40 | public static extern bool CloseHandle(IntPtr hObject); 41 | 42 | [DllImport("kernel32.dll", SetLastError = true)] 43 | public static extern uint ResumeThread(IntPtr hThread); 44 | 45 | [DllImport("kernel32.dll", SetLastError = true)] 46 | public static extern bool DebugActiveProcessStop(uint dwProcessId); 47 | 48 | [DllImport("kernel32.dll", SetLastError = true)] 49 | public static extern bool DebugActiveProcess(int dwProcessId); 50 | 51 | [DllImport("psapi.dll", SetLastError = true)] 52 | public static extern bool EnumProcessModules( 53 | IntPtr hProcess, 54 | IntPtr lphModule, 55 | uint cb, 56 | out uint lpcbNeeded); 57 | 58 | [DllImport("psapi.dll", SetLastError = true, CharSet = CharSet.Unicode)] 59 | public static extern int GetModuleFileNameEx( 60 | IntPtr hProcess, 61 | IntPtr hModule, 62 | StringBuilder lpFilename, 63 | int nSize); 64 | 65 | [DllImport("psapi.dll", SetLastError = true)] 66 | public static extern bool GetModuleInformation(IntPtr hProcess, IntPtr hModule, out MODULEINFO lpmodinfo, uint cb); 67 | 68 | 69 | [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)] 70 | public static extern bool CreateProcessA 71 | ( 72 | String lpApplicationName, 73 | String lpCommandLine, 74 | IntPtr lpProcessAttributes, 75 | IntPtr lpThreadAttributes, 76 | Boolean bInheritHandles, 77 | uint dwCreationFlags, 78 | IntPtr lpEnvironment, 79 | String lpCurrentDirectory, 80 | [In] ref StartupInfo lpStartupInfo, 81 | out ProcessInformation lpProcessInformation 82 | ); 83 | 84 | [DllImport("kernel32.dll", SetLastError = true)] 85 | public static extern bool ContinueDebugEvent(uint dwProcessId, uint dwThreadId, uint dwContinueStatus); 86 | 87 | [StructLayout(LayoutKind.Sequential)] 88 | public struct StartupInfo 89 | { 90 | public Int32 cb ; 91 | public IntPtr lpReserved ; 92 | public IntPtr lpDesktop ; 93 | public IntPtr lpTitle ; 94 | public Int32 dwX ; 95 | public Int32 dwY ; 96 | public Int32 dwXSize ; 97 | public Int32 dwYSize ; 98 | public Int32 dwXCountChars ; 99 | public Int32 dwYCountChars ; 100 | public Int32 dwFillAttribute ; 101 | public Int32 dwFlags ; 102 | public Int16 wShowWindow ; 103 | public Int16 cbReserved2 ; 104 | public IntPtr lpReserved2 ; 105 | public IntPtr hStdInput ; 106 | public IntPtr hStdOutput ; 107 | public IntPtr hStdError ; 108 | } 109 | 110 | [StructLayout(LayoutKind.Sequential)] 111 | public struct ProcessInformation 112 | { 113 | public IntPtr hProcess; 114 | public IntPtr hThread; 115 | public Int32 dwProcessId; 116 | public Int32 dwThreadId; 117 | } 118 | 119 | #region Debug event structures 120 | 121 | [StructLayout(LayoutKind.Explicit)] 122 | public struct DEBUG_EVENT 123 | { 124 | [FieldOffset(0)] 125 | public uint dwDebugEventCode; 126 | [FieldOffset(4)] 127 | public uint dwProcessId; 128 | [FieldOffset(8)] 129 | public uint dwThreadId; 130 | [FieldOffset(16)] 131 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 160)] 132 | public byte[] Union; 133 | } 134 | 135 | [StructLayout(LayoutKind.Sequential, Pack = 8)] 136 | public struct EXCEPTION_DEBUG_INFO 137 | { 138 | public EXCEPTION_RECORD ExceptionRecord; 139 | public uint dwFirstChance; 140 | } 141 | 142 | [StructLayout(LayoutKind.Sequential, Pack = 8)] 143 | public struct EXCEPTION_RECORD 144 | { 145 | public uint ExceptionCode; 146 | public uint ExceptionFlags; 147 | public IntPtr pExceptionRecord; 148 | public IntPtr ExceptionAddress; 149 | public uint NumberParameters; 150 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 15)] 151 | public IntPtr[] ExceptionInformation; 152 | } 153 | 154 | [StructLayout(LayoutKind.Sequential, Pack = 8)] 155 | public struct CREATE_THREAD_DEBUG_INFO 156 | { 157 | public IntPtr hThread; 158 | public IntPtr lpThreadLocalBase; 159 | public IntPtr lpStartAddress; 160 | } 161 | 162 | [StructLayout(LayoutKind.Sequential, Pack = 8)] 163 | public struct CREATE_PROCESS_DEBUG_INFO 164 | { 165 | public IntPtr hFile; 166 | public IntPtr hProcess; 167 | public IntPtr hThread; 168 | public IntPtr lpBaseOfImage; 169 | public uint dwDebugInfoFileOffset; 170 | public uint nDebugInfoSize; 171 | public IntPtr lpThreadLocalBase; 172 | public IntPtr lpStartAddress; 173 | public IntPtr lpImageName; 174 | public ushort fUnicode; 175 | } 176 | 177 | [StructLayout(LayoutKind.Sequential, Pack = 8)] 178 | public struct MODULEINFO 179 | { 180 | public IntPtr lpBaseOfDll; 181 | public uint SizeOfImage; 182 | public IntPtr EntryPoint; 183 | } 184 | 185 | [StructLayout(LayoutKind.Sequential, Pack = 8)] 186 | public struct EXIT_THREAD_DEBUG_INFO 187 | { 188 | public uint dwExitCode; 189 | } 190 | 191 | [StructLayout(LayoutKind.Sequential)] 192 | public struct EXIT_PROCESS_DEBUG_INFO 193 | { 194 | public uint dwExitCode; 195 | } 196 | 197 | [StructLayout(LayoutKind.Sequential)] 198 | public struct LOAD_DLL_DEBUG_INFO 199 | { 200 | public IntPtr hFile; 201 | public IntPtr lpBaseOfDll; 202 | public uint dwDebugInfoFileOffset; 203 | public uint nDebugInfoSize; 204 | public IntPtr lpImageName; 205 | public ushort fUnicode; 206 | } 207 | 208 | [StructLayout(LayoutKind.Sequential)] 209 | public struct UNLOAD_DLL_DEBUG_INFO 210 | { 211 | public IntPtr lpBaseOfDll; 212 | } 213 | 214 | [StructLayout(LayoutKind.Sequential)] 215 | public struct OUTPUT_DEBUG_STRING_INFO 216 | { 217 | public IntPtr lpDebugStringData; 218 | public ushort fUnicode; 219 | public ushort nDebugStringLength; 220 | } 221 | 222 | [StructLayout(LayoutKind.Sequential)] 223 | public struct RIP_INFO 224 | { 225 | public uint dwError; 226 | public uint dwType; 227 | } 228 | 229 | 230 | public static T MapUnmanagedStructure(byte[] debugInfo) 231 | { 232 | GCHandle handle = GCHandle.Alloc(debugInfo, GCHandleType.Pinned); 233 | try 234 | { 235 | return Marshal.PtrToStructure(handle.AddrOfPinnedObject()); 236 | } 237 | finally 238 | { 239 | handle.Free(); 240 | } 241 | } 242 | 243 | #endregion 244 | 245 | // Determining constants for debugging 246 | public const uint INFINITE = 0xFFFFFFFF; 247 | public const uint DEBUG_PROCESS = 0x00000001; 248 | public const uint DBG_CONTINUE = 0x00010002; 249 | public const uint CREATE_PROCESS_DEBUG_EVENT = 3; 250 | public const uint EXIT_PROCESS_DEBUG_EVENT = 5; 251 | public const uint EXCEPTION_DEBUG_EVENT = 1; 252 | public const uint LOAD_DLL_DEBUG_EVENT = 6; 253 | public const uint OUTPUT_DEBUG_STRING_EVENT = 8; 254 | public const uint EXCEPTION_BREAKPOINT = 0x80000003; 255 | public const uint DBG_EXCEPTION_NOT_HANDLED = 0x80010001; 256 | 257 | // Constants for VirtualProtectex 258 | public const uint PAGE_EXECUTE_READWRITE = 0x40; 259 | } 260 | } -------------------------------------------------------------------------------- /WeModPatcher/Utils/Win32/Shortcut.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace WeModPatcher.Utils.Win32 5 | { 6 | public class Shortcut 7 | { 8 | public class ShortcutParams 9 | { 10 | public string FileName { get; set; } 11 | public string TargetPath { get; set; } 12 | public string Arguments { get; set; } 13 | public string WorkingDirectory { get; set; } 14 | public string Description { get; set; } 15 | public string Hotkey { get; set; } 16 | public string IconPath { get; set; } 17 | }; 18 | 19 | private static readonly Type m_type = Type.GetTypeFromProgID("WScript.Shell"); 20 | private static readonly object m_shell = Activator.CreateInstance(m_type); 21 | 22 | [ComImport, TypeLibType(0x1040), Guid("F935DC23-1CF0-11D0-ADB9-00C04FD58A0B")] 23 | private interface IWshShortcut 24 | { 25 | [DispId(0)] 26 | string FullName { [return: MarshalAs(UnmanagedType.BStr)][DispId(0)] get; } 27 | [DispId(0x3e8)] 28 | string Arguments { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3e8)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3e8)] set; } 29 | [DispId(0x3e9)] 30 | string Description { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3e9)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3e9)] set; } 31 | [DispId(0x3ea)] 32 | string Hotkey { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3ea)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3ea)] set; } 33 | [DispId(0x3eb)] 34 | string IconLocation { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3eb)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3eb)] set; } 35 | [DispId(0x3ec)] 36 | string RelativePath { [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3ec)] set; } 37 | [DispId(0x3ed)] 38 | string TargetPath { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3ed)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3ed)] set; } 39 | [DispId(0x3ee)] 40 | int WindowStyle { [DispId(0x3ee)] get; [param: In][DispId(0x3ee)] set; } 41 | [DispId(0x3ef)] 42 | string WorkingDirectory { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3ef)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3ef)] set; } 43 | [TypeLibFunc((short)0x40), DispId(0x7d0)] 44 | void Load([In, MarshalAs(UnmanagedType.BStr)] string PathLink); 45 | [DispId(0x7d1)] 46 | void Save(); 47 | } 48 | 49 | public static void CreateShortcut(string fileName, string targetPath, string arguments, string workingDirectory, string description, string iconPath) 50 | { 51 | IWshShortcut shortcut = (IWshShortcut)m_type.InvokeMember("CreateShortcut", System.Reflection.BindingFlags.InvokeMethod, null, m_shell, new object[] { fileName }); 52 | shortcut.Description = description; 53 | shortcut.TargetPath = targetPath; 54 | shortcut.WorkingDirectory = workingDirectory; 55 | shortcut.Arguments = arguments; 56 | if (!string.IsNullOrEmpty(iconPath)) 57 | shortcut.IconLocation = iconPath; 58 | shortcut.Save(); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /WeModPatcher/View/Controls/InfoItem.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /WeModPatcher/View/Controls/InfoItem.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | using System.Windows.Media; 4 | 5 | namespace WeModPatcher.View.Controls 6 | { 7 | public partial class InfoItem : UserControl 8 | { 9 | public static readonly DependencyProperty IconDataProperty = 10 | DependencyProperty.Register(nameof(IconData), typeof(Geometry), typeof(InfoItem)); 11 | 12 | public static readonly DependencyProperty IconColorProperty = 13 | DependencyProperty.Register(nameof(IconColor), typeof(Brush), typeof(InfoItem)); 14 | 15 | public static readonly DependencyProperty TextProperty = 16 | DependencyProperty.Register(nameof(Text), typeof(string), typeof(InfoItem)); 17 | 18 | public Geometry IconData 19 | { 20 | get => (Geometry)GetValue(IconDataProperty); 21 | set => SetValue(IconDataProperty, value); 22 | } 23 | 24 | public Brush IconColor 25 | { 26 | get => (Brush)GetValue(IconColorProperty); 27 | set => SetValue(IconColorProperty, value); 28 | } 29 | 30 | public string Text 31 | { 32 | get => (string)GetValue(TextProperty); 33 | set => SetValue(TextProperty, value); 34 | } 35 | 36 | public InfoItem() 37 | { 38 | InitializeComponent(); 39 | this.DataContext = this; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /WeModPatcher/View/Controls/PopupHost.xaml: -------------------------------------------------------------------------------- 1 |  9 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 39 | 40 | 41 | 44 | 45 | 46 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /WeModPatcher/View/Controls/PopupHost.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | using System.Windows.Input; 6 | using System.Windows.Media.Animation; 7 | 8 | namespace WeModPatcher.View.Controls 9 | { 10 | public partial class PopupHost : Grid 11 | { 12 | internal Action Closed; 13 | 14 | public static readonly DependencyProperty PopupContentProperty = 15 | DependencyProperty.Register("PopupContent", typeof(object), typeof(PopupHost), new PropertyMetadata(null)); 16 | 17 | internal readonly SemaphoreSlim OpenedSemaphore = new SemaphoreSlim(1, 1); 18 | 19 | private DoubleAnimation OpeningAnimation; 20 | private DoubleAnimation ClosingAnimation; 21 | 22 | 23 | public bool IsOpen 24 | { 25 | get => this.Visibility == Visibility.Visible; 26 | set 27 | { 28 | if (value) 29 | { 30 | if(OpenedSemaphore.CurrentCount == 0) 31 | return; 32 | 33 | 34 | Visibility = Visibility.Visible; 35 | cancel.Focus(); 36 | PopupPresenter.BeginAnimation(OpacityProperty, OpeningAnimation); 37 | OpenedSemaphore.Wait(); 38 | } 39 | else 40 | { 41 | PopupPresenter.BeginAnimation(OpacityProperty, ClosingAnimation); 42 | } 43 | 44 | } 45 | } 46 | 47 | public object PopupContent 48 | { 49 | get => GetValue(PopupContentProperty); 50 | set => SetValue(PopupContentProperty, value); 51 | } 52 | 53 | private void HidePopup(object sender, EventArgs e) 54 | { 55 | if (OpenedSemaphore.CurrentCount == 1) 56 | return; 57 | 58 | IsOpen = false; 59 | } 60 | 61 | private void OnClosing(object sender, EventArgs e) 62 | { 63 | if (PopupContent == null) 64 | return; 65 | 66 | Visibility = Visibility.Collapsed; 67 | Closed?.Invoke(); 68 | if (PopupContent is IDisposable disposable) 69 | { 70 | disposable.Dispose(); 71 | } 72 | PopupContent = null; 73 | Closed = null; 74 | OpenedSemaphore.Release(); 75 | } 76 | 77 | public PopupHost() 78 | { 79 | InitializeComponent(); 80 | 81 | PreviewKeyDown += (sender, e) => 82 | { 83 | if (e.Key != Key.Escape) 84 | return; 85 | 86 | HidePopup(null, null); 87 | e.Handled = true; 88 | }; 89 | 90 | OpeningAnimation = new DoubleAnimation(0, 1, new Duration(TimeSpan.FromSeconds(0.4))) 91 | { 92 | EasingFunction = App.Current.FindResource("BaseAnimationFunction") as IEasingFunction 93 | }; 94 | OpeningAnimation.Freeze(); 95 | 96 | ClosingAnimation = new DoubleAnimation(1, 0, new Duration(TimeSpan.FromSeconds(0.2))); 97 | ClosingAnimation.Completed += OnClosing; 98 | ClosingAnimation.Freeze(); 99 | 100 | this.Splash.DataContext = this; 101 | this.PopupPresenter.DataContext = this; 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /WeModPatcher/View/MainWindow/Logs.cs: -------------------------------------------------------------------------------- 1 | namespace WeModPatcher.View.MainWindow 2 | { 3 | public enum ELogType 4 | { 5 | Info, 6 | Warn, 7 | Error, 8 | Success 9 | } 10 | public class LogEntry 11 | { 12 | public ELogType LogType { get; set; } 13 | public string Message { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /WeModPatcher/View/MainWindow/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  18 | 21 | 22 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | 33 | 34 | 38 | 39 | WeMod Patcher 40 | 41 | 42 | 45 | v 1.0.0 46 | 47 | 48 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 105 | 113 | 114 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 162 | 163 | 167 | 168 |