mirror of
https://github.com/luanslimadev/Wand-Enhancer.git
synced 2026-08-28 17:01:05 +00:00
Moved to electron. Beta desktop app 0.0.1
This commit is contained in:
@@ -12,7 +12,7 @@ Yes, this is safe. This script is open-source, and you can check the code yourse
|
|||||||
|
|
||||||
With this patch you will be able to use the latest version together with Pro.
|
With this patch you will be able to use the latest version together with Pro.
|
||||||
|
|
||||||
## 👀 What features will be available?
|
## 💫 What features will be available?
|
||||||
|
|
||||||
✅ Unlimited usage time <br/>
|
✅ Unlimited usage time <br/>
|
||||||
✅ No ads <br/>
|
✅ No ads <br/>
|
||||||
@@ -21,21 +21,25 @@ With this patch you will be able to use the latest version together with Pro.
|
|||||||
❌ Connect phone <br/>
|
❌ Connect phone <br/>
|
||||||
❌ Hotkeys (hotkey functionality breaks after patch for unknown reason)
|
❌ Hotkeys (hotkey functionality breaks after patch for unknown reason)
|
||||||
|
|
||||||
## ❓ How to use?
|
## 👀 How to use?
|
||||||
|
|
||||||
1. **Clone the Repository (or just download source code)**
|
1. Go to [Releases](https://github.com/k1tbyte/Wemod-Patcher/releases) page.
|
||||||
``` bash
|
2. Download latest version
|
||||||
git clone https://github.com/k1tbyte/Wemod-patcher.git
|
3. Run and click the patch
|
||||||
cd Wemod-patcher
|
|
||||||
```
|
|
||||||
2. **Run the script**
|
|
||||||
``` bash
|
|
||||||
bun install/npm install/yarn install/whatever you use
|
|
||||||
node ./unlocker.js
|
|
||||||
```
|
|
||||||
3. **Follow the instructions**
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## ❓ Q&A
|
||||||
|
|
||||||
|
- I applied the patch but when I inject I get stuck on 'Loading mods...'.
|
||||||
|
- Just close WeMod and try again
|
||||||
|
- During the game, some hacks are enabled without my input
|
||||||
|
- This is a bug after the patch, you have to turn off hotkeys in WeMod settings
|
||||||
|
- Why is the patch executable file size so large? It seems to me that you want to harm my system.
|
||||||
|
- The thing is that the application is written in Electron, so it also puts chromium, nodejs and some libraries in the exe. Maybe Electron is a temporary solution and in the future I will consider another option
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 📜 License
|
## 📜 License
|
||||||
This project is licensed under the Apache-2.0 - see the [LICENSE](LICENSE.txt) file for details.
|
This project is licensed under the Apache-2.0 - see the [LICENSE](LICENSE.txt) file for details.
|
||||||
|
|
||||||
|
|||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 103 KiB |
+13
@@ -28,6 +28,19 @@
|
|||||||
<div class="log-section">
|
<div class="log-section">
|
||||||
<div class="log-entry info">Waiting for action...</div>
|
<div class="log-entry info">Waiting for action...</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<footer class="footer">
|
||||||
|
<div>
|
||||||
|
<span id="version-label"></span>
|
||||||
|
<button id="updateBtn" class="hidden">
|
||||||
|
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<svg id="sourceBtn" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="40" height="40" viewBox="0 0 24 24">
|
||||||
|
<path fill="#ffff" d="M10.9,2.1c-4.6,0.5-8.3,4.2-8.8,8.7c-0.5,4.7,2.2,8.9,6.3,10.5C8.7,21.4,9,21.2,9,20.8v-1.6c0,0-0.4,0.1-0.9,0.1 c-1.4,0-2-1.2-2.1-1.9c-0.1-0.4-0.3-0.7-0.6-1C5.1,16.3,5,16.3,5,16.2C5,16,5.3,16,5.4,16c0.6,0,1.1,0.7,1.3,1c0.5,0.8,1.1,1,1.4,1 c0.4,0,0.7-0.1,0.9-0.2c0.1-0.7,0.4-1.4,1-1.8c-2.3-0.5-4-1.8-4-4c0-1.1,0.5-2.2,1.2-3C7.1,8.8,7,8.3,7,7.6C7,7.2,7,6.6,7.3,6 c0,0,1.4,0,2.8,1.3C10.6,7.1,11.3,7,12,7s1.4,0.1,2,0.3C15.3,6,16.8,6,16.8,6C17,6.6,17,7.2,17,7.6c0,0.8-0.1,1.2-0.2,1.4 c0.7,0.8,1.2,1.8,1.2,3c0,2.2-1.7,3.5-4,4c0.6,0.5,1,1.4,1,2.3v2.6c0,0.3,0.3,0.6,0.7,0.5c3.7-1.5,6.3-5.1,6.3-9.3 C22,6.1,16.9,1.4,10.9,2.1z"></path>
|
||||||
|
</svg>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="renderer.js"></script>
|
<script src="renderer.js"></script>
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
const { app, BrowserWindow, ipcMain, dialog } = require('electron');
|
const { app, BrowserWindow, ipcMain, dialog } = require('electron');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
const Unlocker = require("./unlocker");
|
||||||
|
const GitHubUpdater = require("./updater");
|
||||||
|
|
||||||
|
const packageJsonPath = path.join(__dirname, 'package.json');
|
||||||
|
const packageData = require(packageJsonPath)
|
||||||
|
const updater = new GitHubUpdater(packageData.author, packageData.name);
|
||||||
|
|
||||||
|
|
||||||
function createWindow() {
|
function createWindow() {
|
||||||
const win = new BrowserWindow({
|
const win = new BrowserWindow({
|
||||||
@@ -20,6 +27,12 @@ function createWindow() {
|
|||||||
const checkWeModPath = (root) => fs.existsSync(path.join(root, 'WeMod.exe')) &&
|
const checkWeModPath = (root) => fs.existsSync(path.join(root, 'WeMod.exe')) &&
|
||||||
fs.existsSync(path.join(root, 'resources/app.asar'))
|
fs.existsSync(path.join(root, 'resources/app.asar'))
|
||||||
|
|
||||||
|
function log(message, type = 'info') {
|
||||||
|
BrowserWindow.getAllWindows().forEach(win => {
|
||||||
|
win.webContents.send('log', { message, type });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
app.whenReady().then(createWindow);
|
app.whenReady().then(createWindow);
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
app.on('window-all-closed', () => {
|
||||||
@@ -44,6 +57,11 @@ ipcMain.handle('select-file', async () => {
|
|||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('apply-patch', async (event, path) => {
|
||||||
|
const unlocker = new Unlocker(path, (e) => log(e))
|
||||||
|
await unlocker.start()
|
||||||
|
})
|
||||||
|
|
||||||
ipcMain.handle('resolve-default-path', async () => {
|
ipcMain.handle('resolve-default-path', async () => {
|
||||||
const defaultDir = path.join(process.env.LOCALAPPDATA || path.join(process.env.HOME || process.env.USERPROFILE, 'AppData', 'Local'), 'WeMod');
|
const defaultDir = path.join(process.env.LOCALAPPDATA || path.join(process.env.HOME || process.env.USERPROFILE, 'AppData', 'Local'), 'WeMod');
|
||||||
|
|
||||||
@@ -89,4 +107,28 @@ ipcMain.handle('start-patch', async (event, filePath) => {
|
|||||||
message: error.message
|
message: error.message
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("get-current-version", () => {
|
||||||
|
return packageData.version
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
ipcMain.handle("check-updates", async () => {
|
||||||
|
return await updater.checkForUpdates()
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('open-link', () => {
|
||||||
|
require('electron').shell.openExternal("https://github.com/k1tbyte/Wemod-Patcher")
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on("apply-update", async (event, source) => {
|
||||||
|
try {
|
||||||
|
log("Downloading update ...")
|
||||||
|
const path = await updater.downloadUpdate(source)
|
||||||
|
log("Preparation")
|
||||||
|
updater.applyUpdate(path)
|
||||||
|
} catch (err) {
|
||||||
|
log(err, "error")
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -55,6 +55,13 @@ async function patchBySignature(filePath, functionSignature, patchBytes, patchOf
|
|||||||
if (matchIndex !== -1) {
|
if (matchIndex !== -1) {
|
||||||
const functionStartPosition = filePosition + matchIndex;
|
const functionStartPosition = filePosition + matchIndex;
|
||||||
|
|
||||||
|
const checkBuffer = Buffer.alloc(patchBytes.length);
|
||||||
|
await fileHandle.read(checkBuffer, 0, patchBytes.length, functionStartPosition + patchOffset);
|
||||||
|
|
||||||
|
if (Buffer.compare(checkBuffer, Buffer.from(patchBytes)) === 0) {
|
||||||
|
return 0; // Memory already patched
|
||||||
|
}
|
||||||
|
|
||||||
// Go to patch position
|
// Go to patch position
|
||||||
await fileHandle.write(Buffer.from(patchBytes), 0, patchBytes.length, functionStartPosition + patchOffset);
|
await fileHandle.write(Buffer.from(patchBytes), 0, patchBytes.length, functionStartPosition + patchOffset);
|
||||||
|
|
||||||
|
|||||||
+11
-3
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "wemod-patcher",
|
"name": "wemod-patcher",
|
||||||
"version": "1.0.0",
|
"version": "0.0.1",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron .",
|
"start": "electron .",
|
||||||
"build": "electron-builder --win portable"
|
"build": "electron-builder --win portable"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "kitbyte",
|
"author": "k1tbyte",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -23,14 +23,22 @@
|
|||||||
"directories": {
|
"directories": {
|
||||||
"output": "dist"
|
"output": "dist"
|
||||||
},
|
},
|
||||||
"asar": true,
|
|
||||||
"win": {
|
"win": {
|
||||||
|
"icon": "./appicon.ico",
|
||||||
"target": [{
|
"target": [{
|
||||||
"target": "portable",
|
"target": "portable",
|
||||||
"arch": ["x64"]
|
"arch": ["x64"]
|
||||||
}],
|
}],
|
||||||
"artifactName": "${productName}.exe"
|
"artifactName": "${productName}.exe"
|
||||||
},
|
},
|
||||||
|
"nsis": {
|
||||||
|
"oneClick": false,
|
||||||
|
"perMachine": false,
|
||||||
|
"allowToChangeInstallationDirectory": true,
|
||||||
|
"installerIcon": "./appicon.ico",
|
||||||
|
"uninstallerIcon": "./appicon.ico",
|
||||||
|
"installerHeaderIcon": "./appicon.ico"
|
||||||
|
},
|
||||||
"compression": "maximum",
|
"compression": "maximum",
|
||||||
"removePackageScripts": true,
|
"removePackageScripts": true,
|
||||||
"removePackageKeywords": true,
|
"removePackageKeywords": true,
|
||||||
|
|||||||
+31
-2
@@ -5,13 +5,22 @@ class PatcherUI {
|
|||||||
this.selectedPath = '';
|
this.selectedPath = '';
|
||||||
this.initializeElements();
|
this.initializeElements();
|
||||||
this.bindEvents();
|
this.bindEvents();
|
||||||
|
|
||||||
|
ipcRenderer.on('log', (event, { message, type }) => {
|
||||||
|
this.addLog(message, type);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeElements() {
|
initializeElements() {
|
||||||
this.filePathInput = document.getElementById('file-path');
|
this.filePathInput = document.getElementById('file-path');
|
||||||
this.browseBtn = document.getElementById('browse-btn');
|
this.browseBtn = document.getElementById('browse-btn');
|
||||||
this.patchBtn = document.getElementById('patch-btn');
|
this.patchBtn = document.getElementById('patch-btn');
|
||||||
|
this.updateBtn = document.getElementById('updateBtn');
|
||||||
|
this.versionLabel = document.getElementById('version-label');
|
||||||
this.logSection = document.querySelector('.log-section');
|
this.logSection = document.querySelector('.log-section');
|
||||||
|
document.getElementById(`sourceBtn`).addEventListener('click', () => {
|
||||||
|
ipcRenderer.send("open-link")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
bindEvents() {
|
bindEvents() {
|
||||||
@@ -58,8 +67,11 @@ class PatcherUI {
|
|||||||
this.addLog('Starting patch process...', 'info');
|
this.addLog('Starting patch process...', 'info');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await ipcRenderer.invoke('start-patch', this.selectedPath);
|
await ipcRenderer.invoke(
|
||||||
this.addLog(result.message, result.success ? 'success' : 'error');
|
'apply-patch',
|
||||||
|
this.selectedPath
|
||||||
|
);
|
||||||
|
this.addLog("Success", 'success');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.addLog('Patch failed: ' + error.message, 'error');
|
this.addLog('Patch failed: ' + error.message, 'error');
|
||||||
} finally {
|
} finally {
|
||||||
@@ -68,12 +80,29 @@ class PatcherUI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resolveDefault() {
|
resolveDefault() {
|
||||||
|
|
||||||
|
ipcRenderer.invoke("get-current-version").then((v) =>
|
||||||
|
this.versionLabel.textContent = `Current version: ${v}`);
|
||||||
|
|
||||||
ipcRenderer.invoke("resolve-default-path").then(path => {
|
ipcRenderer.invoke("resolve-default-path").then(path => {
|
||||||
this.addLog(path ? 'The WeMod folder has been found!' :
|
this.addLog(path ? 'The WeMod folder has been found!' :
|
||||||
"WeMod folder was not found. You need to specify the path manually", path ? "success" : "warning"
|
"WeMod folder was not found. You need to specify the path manually", path ? "success" : "warning"
|
||||||
);
|
);
|
||||||
this.setPath(path)
|
this.setPath(path)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipcRenderer.invoke("check-updates").then(result => {
|
||||||
|
if(!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateBtn.className = ""
|
||||||
|
this.updateBtn.textContent = `Update to ${result.version}`
|
||||||
|
this.updateBtn.addEventListener('click', () => {
|
||||||
|
this.updateBtn.className = "hidden"
|
||||||
|
ipcRenderer.send("apply-update", result);
|
||||||
|
});
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+38
-2
@@ -15,7 +15,7 @@ body {
|
|||||||
|
|
||||||
.container {
|
.container {
|
||||||
background: #21223d;
|
background: #21223d;
|
||||||
padding: 24px;
|
padding: 24px 24px 15px;
|
||||||
border: 1px solid #1e2039;
|
border: 1px solid #1e2039;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -29,7 +29,6 @@ body {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
@@ -55,6 +54,7 @@ body {
|
|||||||
.path-section {
|
.path-section {
|
||||||
background: #26254b;
|
background: #26254b;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
margin-top: 20px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
border: 1px solid #1e2039;
|
border: 1px solid #1e2039;
|
||||||
}
|
}
|
||||||
@@ -97,6 +97,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.patch-btn {
|
.patch-btn {
|
||||||
|
margin: 15px 0;
|
||||||
background: #6d67fd;
|
background: #6d67fd;
|
||||||
color: #f8f6f6;
|
color: #f8f6f6;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -149,4 +150,39 @@ body {
|
|||||||
|
|
||||||
.info {
|
.info {
|
||||||
color: #6d67fd;
|
color: #6d67fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sourceBtn {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
margin-top: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer span {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#updateBtn {
|
||||||
|
background: springgreen;
|
||||||
|
color: #1e2039;
|
||||||
|
font-weight: bold;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-left: 10px;
|
||||||
|
padding: 10px 25px;
|
||||||
|
transition: all 0.1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#updateBtn:hover {
|
||||||
|
scale: 105%
|
||||||
}
|
}
|
||||||
+80
-140
@@ -1,13 +1,13 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const readline = require('readline');
|
|
||||||
const asar = require('asar');
|
const asar = require('asar');
|
||||||
|
const { execSync } = require("child_process");
|
||||||
const patchBySignature = require("./memoryScanner");
|
const patchBySignature = require("./memoryScanner");
|
||||||
|
|
||||||
|
|
||||||
const regex = /getUserAccount\(\)\{.*?return\s+this\.#\w+\.fetch\(\{.*?}\).*?}/g;
|
const regex = /getUserAccount\(\)\{.*?return\s+this\.#\w+\.fetch\(\{.*?}\).*?}/g;
|
||||||
const asarPatch = "getUserAccount(){return this.#<fetch_field_name>.fetch({endpoint:\"/v3/account\",method:\"GET\",name:\"/v3/account\",collectMetrics:0}).then(response=>{response.subscription={period:\"yearly\",state:\"active\"};response.flags=78;return response;})}"
|
const asarPatch = "getUserAccount(){return this.#<fetch_field_name>.fetch({endpoint:\"/v3/account\",method:\"GET\",name:\"/v3/account\",collectMetrics:0}).then(response=>{response.subscription={period:\"yearly\",state:\"active\"};response.flags=78;return response;})}"
|
||||||
const signature = "E8 ?? ?? ?? ?? 85 C0 75 ?? F6 C3 01 74 ?? 48 89 F9 E8 ?? ?? ?? ??"
|
const signature = "E8 ?? ?? ?? ?? ?? C0 75 ?? F6 C3 01 74 ?? 48 89 F9 E8 ?? ?? ?? ??"
|
||||||
const patchBytes = [0x31]
|
const patchBytes = [0x31]
|
||||||
const patchOffset = 0x5
|
const patchOffset = 0x5
|
||||||
// ...
|
// ...
|
||||||
@@ -16,157 +16,97 @@ const patchOffset = 0x5
|
|||||||
// call near ptr funk_1445527E0
|
// call near ptr funk_1445527E0
|
||||||
// ...
|
// ...
|
||||||
|
|
||||||
console.log("WeMod unlocker by K1tbyte")
|
class Unlocker {
|
||||||
let defaultDir = path.join(process.env.LOCALAPPDATA || path.join(process.env.HOME || process.env.USERPROFILE, 'AppData', 'Local'), 'WeMod');
|
|
||||||
let appDir = null
|
|
||||||
|
|
||||||
const rl = readline.createInterface({
|
constructor(appDir, logger) {
|
||||||
input: process.stdin,
|
this.appDir = appDir;
|
||||||
output: process.stdout
|
this.logger = logger;
|
||||||
});
|
|
||||||
|
|
||||||
const read = (title, onSubmit, onExit) => {
|
|
||||||
rl.question(title, (answer) => {
|
|
||||||
try {
|
|
||||||
onSubmit(answer)
|
|
||||||
} finally {
|
|
||||||
rl.close()
|
|
||||||
onExit?.()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkWeModPaths = (dir) => {
|
|
||||||
return fs.existsSync(dir) && fs.existsSync(path.join(dir, 'WeMod.exe')) && fs.existsSync(path.join(dir, 'resources'))
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFetchFieldName(code) {
|
|
||||||
const match = code.match(/this\.#([a-zA-Z_$][0-9a-zA-Z_$]*)\.fetch/);
|
|
||||||
return match ? match[1] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const patchAsar = unpackedPath => {
|
|
||||||
let items = fs.readdirSync(unpackedPath,{ withFileTypes: true })
|
|
||||||
items = items.filter(item => !item.isDirectory() && /^app-\w+/.test(item.name));
|
|
||||||
if(items.length === 0) {
|
|
||||||
console.log(" - No app bundle found")
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let asarPatchApplied = false;
|
#getFetchFieldName(code) {
|
||||||
for (const item of items) {
|
const match = code.match(/this\.#([a-zA-Z_$][0-9a-zA-Z_$]*)\.fetch/);
|
||||||
const data = fs.readFileSync(path.join(unpackedPath, item.name), { encoding: 'utf8'})
|
return match ? match[1] : null;
|
||||||
|
}
|
||||||
|
|
||||||
const matches = data.match(regex)
|
#patchAsar (unpackedPath) {
|
||||||
if(!matches) {
|
let items = fs.readdirSync(unpackedPath,{ withFileTypes: true })
|
||||||
continue;
|
items = items.filter(item => !item.isDirectory() && /^app-\w+/.test(item.name));
|
||||||
}
|
if(items.length === 0) {
|
||||||
if(matches.length > 1) {
|
throw new Error(" - No app bundle found");
|
||||||
console.error(" - Multiple target functions found. Looks like the version is not supported")
|
|
||||||
throw new Error("Multiple target functions found");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchFieldName = getFetchFieldName(matches[0]);
|
let asarPatchApplied = false;
|
||||||
if(!fetchFieldName) {
|
for (const item of items) {
|
||||||
console.error(" - Fetch field name not found")
|
const data = fs.readFileSync(path.join(unpackedPath, item.name), { encoding: 'utf8'})
|
||||||
throw new Error("Fetch field name not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
const patch = asarPatch.replace(/<fetch_field_name>/g, fetchFieldName)
|
const matches = data.match(regex)
|
||||||
|
if(!matches) {
|
||||||
console.log(" - Found target function in: ", item.name)
|
continue;
|
||||||
console.log(" - Patching asar...")
|
}
|
||||||
fs.writeFileSync(path.join(unpackedPath, item.name), data.replace(regex, patch), {encoding: 'utf8'})
|
if(matches.length > 1) {
|
||||||
console.log(" - Patch applied")
|
throw new Error(" - Multiple target functions found. Looks like the version is not supported");
|
||||||
asarPatchApplied = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!asarPatchApplied) {
|
|
||||||
throw new Error("Failed to apply patch");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const patchPE = async () => {
|
|
||||||
console.log(" - Patching PE...")
|
|
||||||
const pePath = path.join(appDir, 'WeMod.exe')
|
|
||||||
const procStart = await patchBySignature(pePath, signature, patchBytes, patchOffset)
|
|
||||||
if(procStart === -1) {
|
|
||||||
console.log(" - Signature not found or already patched")
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log(" - Patch saved")
|
|
||||||
}
|
|
||||||
|
|
||||||
const start = async () => {
|
|
||||||
console.log(" - Extracting asar...")
|
|
||||||
try {
|
|
||||||
const asarPath = path.join(appDir, 'resources', 'app.asar')
|
|
||||||
asar.extractAll(asarPath, path.join(appDir, 'resources', 'app.asar.unpacked'))
|
|
||||||
} catch(e) {
|
|
||||||
console.error("Failed to extract asar", e)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const unpackedPath = path.join(appDir, 'resources', 'app.asar.unpacked')
|
|
||||||
|
|
||||||
fs.renameSync(path.join(appDir, 'resources', 'app.asar'), path.join(appDir, 'resources', 'app.asar.backup'))
|
|
||||||
console.log(" - Backup saved")
|
|
||||||
patchAsar(unpackedPath)
|
|
||||||
|
|
||||||
await asar.createPackageWithOptions(unpackedPath, path.join(appDir, 'resources', 'app.asar'),
|
|
||||||
{ unpack: path.join(unpackedPath,"static/unpacked/**") })
|
|
||||||
console.log(" - Patch saved")
|
|
||||||
|
|
||||||
await patchPE()
|
|
||||||
console.log("Done!")
|
|
||||||
process.exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
const prepare = async () => {
|
|
||||||
console.log(" - Path: ", appDir || "Not found")
|
|
||||||
|
|
||||||
if (!appDir) {
|
|
||||||
read("WeMod directory not found. Enter the path manually: ", (answer) => {
|
|
||||||
if (checkWeModPaths(answer)) {
|
|
||||||
appDir = answer;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
console.log("Invalid path")
|
|
||||||
}, prepare);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
read("Continue? (y/n): ", (answer) => {
|
const fetchFieldName = this.#getFetchFieldName(matches[0]);
|
||||||
if(answer.toLowerCase() === 'y') {
|
if(!fetchFieldName) {
|
||||||
start()
|
throw new Error(" - Fetch field name not found");
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if(fs.existsSync(defaultDir)) {
|
const patch = asarPatch.replace(/<fetch_field_name>/g, fetchFieldName)
|
||||||
const items = fs.readdirSync(defaultDir, { withFileTypes: true });
|
|
||||||
const appFolders = items
|
|
||||||
.filter(item => item.isDirectory() && /^app-\w+/.test(item.name))
|
|
||||||
.map(item => {
|
|
||||||
const folderPath = path.join(defaultDir, item.name);
|
|
||||||
const stats = fs.statSync(folderPath);
|
|
||||||
return {
|
|
||||||
name: item.name,
|
|
||||||
path: folderPath,
|
|
||||||
mtime: stats.mtime,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
appFolders.sort((a, b) => b.mtime - a.mtime);
|
this.logger(" - Found target function in: " + item.name)
|
||||||
for(const folder of appFolders) {
|
this.logger(" - Patching asar...")
|
||||||
if(checkWeModPaths(folder.path)) {
|
fs.writeFileSync(path.join(unpackedPath, item.name), data.replace(regex, patch), {encoding: 'utf8'})
|
||||||
appDir = folder.path;
|
this.logger(" - Patch applied")
|
||||||
|
asarPatchApplied = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!asarPatchApplied) {
|
||||||
|
throw new Error("Failed to apply patch");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async #patchPE () {
|
||||||
|
this.logger(" - Patching PE...")
|
||||||
|
const pePath = path.join(this.appDir, 'WeMod.exe')
|
||||||
|
const procStart = await patchBySignature(pePath, signature, patchBytes, patchOffset)
|
||||||
|
if(procStart === -1) {
|
||||||
|
throw new Error(" - Signature not found or already patched")
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger(procStart === 0 ? " - PE already patched" : " - Patch saved")
|
||||||
|
}
|
||||||
|
|
||||||
|
async start () {
|
||||||
|
this.logger(" - Extracting asar...")
|
||||||
|
const asarPath = path.join(this.appDir, 'resources', 'app.asar')
|
||||||
|
const unpackedPath = path.join(this.appDir, 'resources', 'app.asar.unpacked')
|
||||||
|
const backupPath = path.join(this.appDir, 'resources', 'app.asar.backup')
|
||||||
|
|
||||||
|
if(fs.existsSync(backupPath)) {
|
||||||
|
this.logger(" - Backup already exists")
|
||||||
|
} else {
|
||||||
|
execSync(`copy "${asarPath}" "${backupPath}"`, { encoding: "utf-8" });
|
||||||
|
this.logger(" - Backup saved")
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
asar.extractAll(asarPath, unpackedPath)
|
||||||
|
} catch(e) {
|
||||||
|
throw new Error("Failed to extract asar: " + e)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#patchAsar(unpackedPath)
|
||||||
|
|
||||||
|
await asar.createPackageWithOptions(unpackedPath, asarPath,
|
||||||
|
{ unpack: path.join(unpackedPath,"static/unpacked/**") })
|
||||||
|
this.logger(" - Patch saved")
|
||||||
|
|
||||||
|
await this.#patchPE()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(async () => {
|
module.exports = Unlocker;
|
||||||
await prepare();
|
|
||||||
})();
|
|
||||||
|
|||||||
+105
@@ -0,0 +1,105 @@
|
|||||||
|
const { app } = require("electron");
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const { exec } = require("child_process");
|
||||||
|
|
||||||
|
class GitHubUpdater {
|
||||||
|
constructor(owner, repo, currentVersion) {
|
||||||
|
this.owner = owner;
|
||||||
|
this.repo = repo;
|
||||||
|
this.currentVersion = currentVersion || app.getVersion();
|
||||||
|
this.apiUrl = `https://api.github.com/repos/${owner}/${repo}/releases/latest`;
|
||||||
|
this.downloadDir = path.join(app.getPath("temp"), "update");
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkForUpdates() {
|
||||||
|
const response = await fetch(this.apiUrl, {
|
||||||
|
headers: { "User-Agent": "GitHub-Updater" },
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`An error occurred while checking for an update: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const release = await response.json();
|
||||||
|
const latestVersion = release.tag_name;
|
||||||
|
const assets = release.assets;
|
||||||
|
|
||||||
|
if (this.currentVersion === latestVersion) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const asset = assets.find(a => a.name.endsWith(".exe"));
|
||||||
|
if (!asset) {
|
||||||
|
throw new Error("Unable to find files to update");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: latestVersion,
|
||||||
|
url: asset.browser_download_url,
|
||||||
|
name: asset.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async downloadUpdate(updateInfo) {
|
||||||
|
const response = await fetch(updateInfo.url);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Error downloading file: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(this.downloadDir)) {
|
||||||
|
fs.mkdirSync(this.downloadDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = path.join(this.downloadDir, updateInfo.name);
|
||||||
|
const fileStream = fs.createWriteStream(filePath);
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const downloadStream = response.body.getReader();
|
||||||
|
|
||||||
|
const pump = async () => {
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await downloadStream.read();
|
||||||
|
if (done) {
|
||||||
|
fileStream.end();
|
||||||
|
resolve();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
fileStream.write(value);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fileStream.on("error", (error) => {
|
||||||
|
reject(new Error(`File write error: ${error.message}`));
|
||||||
|
});
|
||||||
|
|
||||||
|
pump();
|
||||||
|
});
|
||||||
|
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
async applyUpdate(filePath) {
|
||||||
|
try {
|
||||||
|
const currentExecutable = process.env.PORTABLE_EXECUTABLE_FILE;
|
||||||
|
const updateScript = `Start-Sleep -Seconds 3; Copy-Item -Path '${filePath}' -Destination '${currentExecutable}' -Force; Remove-Item -Path '${filePath}' -Force; Start-Sleep -Seconds 2; Start-Process -FilePath '${currentExecutable}';`;
|
||||||
|
|
||||||
|
exec(`start /b "" powershell -WindowStyle Hidden -Command "${updateScript}"`, {
|
||||||
|
windowsHide: true,
|
||||||
|
stdio: 'ignore'
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
app.quit();
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Update failed: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = GitHubUpdater;
|
||||||
Reference in New Issue
Block a user