|
|
|
@@ -23,6 +23,18 @@ const __filename = (() => {
|
|
|
|
|
return fileURLToPath(import.meta.url);
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
// Version info - read from package.json
|
|
|
|
|
const GITHUB_REPO = "xenos1337/httptoolkit-patcher";
|
|
|
|
|
const LOCAL_VERSION = (() => {
|
|
|
|
|
try {
|
|
|
|
|
const packageJsonPath = path.join(path.dirname(__filename), "package.json");
|
|
|
|
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
|
|
|
return packageJson.version || "0.0.0";
|
|
|
|
|
} catch {
|
|
|
|
|
return "0.0.0";
|
|
|
|
|
}
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
// Check if running with elevated privileges
|
|
|
|
|
function isElevated() {
|
|
|
|
|
if (isWin) {
|
|
|
|
@@ -51,19 +63,79 @@ function prompt(question) {
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper function to fetch content from URL
|
|
|
|
|
function fetchUrl(url) {
|
|
|
|
|
/**
|
|
|
|
|
* Fetch JSON from a URL
|
|
|
|
|
* @param {string} url
|
|
|
|
|
* @returns {Promise<Array<{name: string}>>}
|
|
|
|
|
*/
|
|
|
|
|
function fetchJson(url) {
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
const options = {
|
|
|
|
|
headers: {
|
|
|
|
|
"User-Agent": "httptoolkit-patcher",
|
|
|
|
|
"Accept": "application/vnd.github.v3+json",
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
https
|
|
|
|
|
.get(url, res => {
|
|
|
|
|
.get(url, options, res => {
|
|
|
|
|
if (res.statusCode !== 200) {
|
|
|
|
|
reject(new Error(`HTTP ${res.statusCode}`));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let data = "";
|
|
|
|
|
res.on("data", chunk => (data += chunk));
|
|
|
|
|
res.on("end", () => resolve(data));
|
|
|
|
|
res.on("end", () => {
|
|
|
|
|
try {
|
|
|
|
|
resolve(JSON.parse(data));
|
|
|
|
|
} catch (e) {
|
|
|
|
|
reject(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
.on("error", reject);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function compareVersions(v1, v2) {
|
|
|
|
|
const normalize = (/** @type {string} */ v) => v.replace(/^v/, "").split(".").map(Number);
|
|
|
|
|
const parts1 = normalize(v1);
|
|
|
|
|
const parts2 = normalize(v2);
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
|
|
|
|
const num1 = parts1[i] || 0;
|
|
|
|
|
const num2 = parts2[i] || 0;
|
|
|
|
|
if (num1 > num2) return 1;
|
|
|
|
|
if (num1 < num2) return -1;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check for updates from GitHub
|
|
|
|
|
*/
|
|
|
|
|
async function checkForUpdates() {
|
|
|
|
|
try {
|
|
|
|
|
const tags = await fetchJson(`https://api.github.com/repos/${GITHUB_REPO}/tags`);
|
|
|
|
|
|
|
|
|
|
if (!tags || tags.length === 0) {
|
|
|
|
|
return; // No tags found, skip update check
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Tags are returned in order, first one is the latest
|
|
|
|
|
const latestTag = tags[0].name;
|
|
|
|
|
const latestVersion = latestTag.replace(/^v/, "");
|
|
|
|
|
|
|
|
|
|
if (compareVersions(latestVersion, LOCAL_VERSION) > 0) {
|
|
|
|
|
console.log(chalk.yellowBright`\n╔════════════════════════════════════════════════════════════╗`);
|
|
|
|
|
console.log(chalk.yellowBright`║` + chalk.white` A new version is available: ` + chalk.greenBright`v${latestVersion}` + chalk.white` (current: ` + chalk.gray`v${LOCAL_VERSION}` + chalk.white`) ` + chalk.yellowBright` ║`);
|
|
|
|
|
console.log(chalk.yellowBright`║` + chalk.white` Update: ` + chalk.cyanBright`https://github.com/${GITHUB_REPO}` + chalk.white` ` + chalk.yellowBright`║`);
|
|
|
|
|
console.log(chalk.yellowBright`╚════════════════════════════════════════════════════════════╝\n`);
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// Silently ignore update check errors (network issues, etc.)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper function to remove directory recursively
|
|
|
|
|
function rm(dirPath) {
|
|
|
|
|
if (!fs.existsSync(dirPath)) return;
|
|
|
|
@@ -78,7 +150,7 @@ function rm(dirPath) {
|
|
|
|
|
|
|
|
|
|
// Find HTTP Toolkit installation path
|
|
|
|
|
async function findAppPath() {
|
|
|
|
|
const possiblePaths = isWin ? [path.join("C:", "Program Files", "HTTP Toolkit", "resources"), path.join("C:", "Program Files (x86)", "HTTP Toolkit", "resources")] : isMac ? ["/Applications/HTTP Toolkit.app/Contents/Resources"] : ["/opt/HTTP Toolkit/resources", "/opt/httptoolkit/resources"];
|
|
|
|
|
const possiblePaths = isWin ? [path.join("C:", "Program Files", "HTTP Toolkit", "resources"), path.join("C:", "Program Files (x86)", "HTTP Toolkit", "resources"), path.join(process.env.LOCALAPPDATA || path.join(process.env.USERPROFILE || "", "AppData", "Local"), "Programs", "HTTP Toolkit", "resources")] : isMac ? ["/Applications/HTTP Toolkit.app/Contents/Resources"] : ["/opt/HTTP Toolkit/resources", "/opt/httptoolkit/resources"];
|
|
|
|
|
|
|
|
|
|
for (const p of possiblePaths) {
|
|
|
|
|
if (fs.existsSync(path.join(p, "app.asar"))) {
|
|
|
|
@@ -346,37 +418,71 @@ async function patchApp() {
|
|
|
|
|
asar.extractAll(asarPath, extractPath);
|
|
|
|
|
console.log(chalk.greenBright`[+] Extracted to ${extractPath}`);
|
|
|
|
|
|
|
|
|
|
// Step 6: Check if preload.cjs or preload.js exists
|
|
|
|
|
let preloadPath = path.join(extractPath, "build", "preload.cjs");
|
|
|
|
|
// Step 6: Check if preload.cjs exists
|
|
|
|
|
const preloadPath = path.join(extractPath, "build", "preload.cjs");
|
|
|
|
|
if (!fs.existsSync(preloadPath)) {
|
|
|
|
|
console.log(chalk.yellowBright`[!] preload.cjs not found, checking for preload.js...`);
|
|
|
|
|
preloadPath = path.join(extractPath, "build", "preload.js");
|
|
|
|
|
if (!fs.existsSync(preloadPath)) {
|
|
|
|
|
console.error(chalk.redBright`[-] Neither preload.cjs nor preload.js found in ${path.join(extractPath, "build")}`);
|
|
|
|
|
console.error(chalk.redBright`[-] preload.cjs not found in ${path.join(extractPath, "build")}`);
|
|
|
|
|
console.error(chalk.yellowBright`[!] Please download the latest version of HTTP Toolkit from https://httptoolkit.com/`);
|
|
|
|
|
rm(extractPath);
|
|
|
|
|
process.exit(1);
|
|
|
|
|
}
|
|
|
|
|
console.log(chalk.greenBright`[+] Found preload.js`);
|
|
|
|
|
} else {
|
|
|
|
|
console.log(chalk.greenBright`[+] Found preload.cjs`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Step 7: Fetch inject.js from GitHub
|
|
|
|
|
console.log(chalk.yellowBright`[+] Fetching inject code from GitHub...`);
|
|
|
|
|
const injectCode = await fetchUrl("https://raw.githubusercontent.com/xenos1337/httptoolkit-patcher/refs/heads/master/inject.js");
|
|
|
|
|
if (!injectCode || !injectCode.includes("injectPageContextHooks")) {
|
|
|
|
|
console.error(chalk.redBright`[-] Failed to fetch inject.js from GitHub`);
|
|
|
|
|
// Step 7: Read inject.js from local file (same directory as this script)
|
|
|
|
|
console.log(chalk.yellowBright`[+] Reading inject code from local file...`);
|
|
|
|
|
const injectJsPath = path.join(path.dirname(__filename), "inject.js");
|
|
|
|
|
if (!fs.existsSync(injectJsPath)) {
|
|
|
|
|
console.error(chalk.redBright`[-] inject.js not found at ${injectJsPath}`);
|
|
|
|
|
rm(extractPath);
|
|
|
|
|
process.exit(1);
|
|
|
|
|
}
|
|
|
|
|
console.log(chalk.greenBright`[+] Inject code fetched successfully`);
|
|
|
|
|
const injectCode = fs.readFileSync(injectJsPath, "utf-8");
|
|
|
|
|
if (!injectCode || !injectCode.includes("PAGE-INJECT")) {
|
|
|
|
|
console.error(chalk.redBright`[-] Invalid inject.js file`);
|
|
|
|
|
rm(extractPath);
|
|
|
|
|
process.exit(1);
|
|
|
|
|
}
|
|
|
|
|
console.log(chalk.greenBright`[+] Inject code loaded successfully`);
|
|
|
|
|
|
|
|
|
|
// Step 8: Read preload file and check if already patched
|
|
|
|
|
const preloadPatchCode = `
|
|
|
|
|
(function loadInjectScript() {
|
|
|
|
|
const injectCode = ${JSON.stringify(injectCode)};
|
|
|
|
|
|
|
|
|
|
function injectViaWebFrame() {
|
|
|
|
|
try {
|
|
|
|
|
const { webFrame } = electron_1;
|
|
|
|
|
if (webFrame && webFrame.executeJavaScript) {
|
|
|
|
|
webFrame.executeJavaScript(injectCode).then(() => console.log("[PRELOAD] Injected via webFrame.executeJavaScript")).catch(err => console.error("[PRELOAD] webFrame injection failed:", err));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error("[PRELOAD] webFrame not available:", e);
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!injectViaWebFrame()) {
|
|
|
|
|
const tryInject = () => {
|
|
|
|
|
if (!injectViaWebFrame()) {
|
|
|
|
|
console.error("[PRELOAD] All injection methods failed");
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
|
|
|
|
tryInject();
|
|
|
|
|
} else {
|
|
|
|
|
document.addEventListener('DOMContentLoaded', tryInject, { once: true });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})();
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
// Step 8: Read files and check if already patched
|
|
|
|
|
let preloadContent = fs.readFileSync(preloadPath, "utf-8");
|
|
|
|
|
const isPatched = preloadContent.includes("injectPageContextHooks");
|
|
|
|
|
const isPreloadPatched = preloadContent.includes("loadInjectScript");
|
|
|
|
|
|
|
|
|
|
if (isPatched) {
|
|
|
|
|
console.log(chalk.yellowBright`[!] File already patched`);
|
|
|
|
|
if (isPreloadPatched) {
|
|
|
|
|
console.log(chalk.yellowBright`[!] Files already patched`);
|
|
|
|
|
const answer = await prompt("Do you want to repatch? (y/n): ");
|
|
|
|
|
|
|
|
|
|
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
|
|
|
@@ -385,37 +491,40 @@ async function patchApp() {
|
|
|
|
|
process.exit(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Replace the existing injectPageContextHooks function
|
|
|
|
|
console.log(chalk.yellowBright`[+] Replacing existing patch...`);
|
|
|
|
|
const functionRegex = /\(function injectPageContextHooks\(\) \{[\s\S]*?\}\)\(\);/;
|
|
|
|
|
preloadContent = preloadContent.replace(functionRegex, injectCode);
|
|
|
|
|
} else {
|
|
|
|
|
// Find line with electron_1 and insert inject code below it
|
|
|
|
|
console.log(chalk.yellowBright`[+] Applying patch...`);
|
|
|
|
|
const lines = preloadContent.split("\n");
|
|
|
|
|
let insertIndex = -1;
|
|
|
|
|
// Remove existing patches
|
|
|
|
|
console.log(chalk.yellowBright`[+] Replacing existing patches...`);
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
|
|
|
if (lines[i].includes("electron_1")) {
|
|
|
|
|
insertIndex = i + 1;
|
|
|
|
|
// Remove preload patch
|
|
|
|
|
const preloadPatchRegex = /\n?\(function loadInjectScript\(\) \{[\s\S]*?\}\)\(\);/;
|
|
|
|
|
preloadContent = preloadContent.replace(preloadPatchRegex, "");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Step 9: Patch preload file - find line with electron_1 and insert patch code below it
|
|
|
|
|
console.log(chalk.yellowBright`[+] Applying preload patch...`);
|
|
|
|
|
const preloadLines = preloadContent.split("\n");
|
|
|
|
|
let preloadInsertIndex = -1;
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < preloadLines.length; i++) {
|
|
|
|
|
if (preloadLines[i].includes("electron_1")) {
|
|
|
|
|
preloadInsertIndex = i + 1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (insertIndex === -1) {
|
|
|
|
|
if (preloadInsertIndex === -1) {
|
|
|
|
|
console.error(chalk.redBright`[-] Could not find insertion point (electron_1) in ${path.basename(preloadPath)}`);
|
|
|
|
|
rm(extractPath);
|
|
|
|
|
process.exit(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lines.splice(insertIndex, 0, injectCode);
|
|
|
|
|
preloadContent = lines.join("\n");
|
|
|
|
|
}
|
|
|
|
|
preloadLines.splice(preloadInsertIndex, 0, preloadPatchCode);
|
|
|
|
|
preloadContent = preloadLines.join("\n");
|
|
|
|
|
|
|
|
|
|
// Step 9: Write patched preload file
|
|
|
|
|
// Write patched preload file
|
|
|
|
|
fs.writeFileSync(preloadPath, preloadContent, "utf-8");
|
|
|
|
|
console.log(chalk.greenBright`[+] ${path.basename(preloadPath)} patched successfully`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Step 10: Repackage app.asar
|
|
|
|
|
console.log(chalk.yellowBright`[+] Repackaging app.asar...`);
|
|
|
|
|
await asar.createPackage(extractPath, asarPath);
|
|
|
|
@@ -465,6 +574,9 @@ const commandName = (() => {
|
|
|
|
|
// Run the appropriate command
|
|
|
|
|
(async () => {
|
|
|
|
|
try {
|
|
|
|
|
// Check for updates from GitHub
|
|
|
|
|
await checkForUpdates();
|
|
|
|
|
|
|
|
|
|
if (command === "unpatch" || command === "restore") {
|
|
|
|
|
await unpatchApp();
|
|
|
|
|
} else if (command === "help" || command === "-h" || command === "--help") {
|
|
|
|
|