From fe7cae9dd92ec1be39a78439088dd5101ab0bde5 Mon Sep 17 00:00:00 2001 From: xenos <66328734+xenos1337@users.noreply.github.com> Date: Sat, 29 Nov 2025 00:04:31 +0100 Subject: [PATCH] refactor(patcher): use local inject.js and simplify injection mechanism --- index.js | 152 +++++++++++++----------- inject.js | 338 ++++++++++++++++++++++++------------------------------ 2 files changed, 238 insertions(+), 252 deletions(-) diff --git a/index.js b/index.js index 0005cea..898f1c1 100644 --- a/index.js +++ b/index.js @@ -51,19 +51,6 @@ function prompt(question) { }); } -// Helper function to fetch content from URL -function fetchUrl(url) { - return new Promise((resolve, reject) => { - https - .get(url, res => { - let data = ""; - res.on("data", chunk => (data += chunk)); - res.on("end", () => resolve(data)); - }) - .on("error", reject); - }); -} - // Helper function to remove directory recursively function rm(dirPath) { if (!fs.existsSync(dirPath)) return; @@ -78,7 +65,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 +333,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")}`); - 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`); + 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`[+] Inject code fetched successfully`); + console.log(chalk.greenBright`[+] Found preload.cjs`); - // Step 8: Read preload file and check if already patched + // 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); + } + 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`); + + 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 +406,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; - - for (let i = 0; i < lines.length; i++) { - if (lines[i].includes("electron_1")) { - insertIndex = i + 1; - break; - } - } - - if (insertIndex === -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"); + // Remove existing patches + console.log(chalk.yellowBright`[+] Replacing existing patches...`); + + // Remove preload patch + const preloadPatchRegex = /\n?\(function loadInjectScript\(\) \{[\s\S]*?\}\)\(\);/; + preloadContent = preloadContent.replace(preloadPatchRegex, ""); } - // Step 9: Write patched preload file + // 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 (preloadInsertIndex === -1) { + console.error(chalk.redBright`[-] Could not find insertion point (electron_1) in ${path.basename(preloadPath)}`); + rm(extractPath); + process.exit(1); + } + + preloadLines.splice(preloadInsertIndex, 0, preloadPatchCode); + preloadContent = preloadLines.join("\n"); + + // 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); diff --git a/inject.js b/inject.js index ed74295..a6f0016 100644 --- a/inject.js +++ b/inject.js @@ -1,206 +1,168 @@ -(function injectPageContextHooks() { - const injectionCode = ` - (function() { - console.log("[PAGE-INJECT] Installing hooks in page context"); - - const propertyHooks = { - isPaidUser: true, - isLoggedIn: true, - userHasSubscription: true, - userEmail: "hi@httptoolkit.com", - mightBePaidUser: true, - isPastDueUser: false, - userSubscription: { - status: "active", - plan: "pro", - sku: "sku", - tierCode: "pro", - interval: "monthly", - quantity: 1, - expiry: new Date("2999-01-01"), - updateBillingDetailsUrl: "https://httptoolkit.com/", - cancelSubscriptionUrl: "https://httptoolkit.com/", - lastReceiptUrl: "https://httptoolkit.com/", - canManageSubscription: true +(function () { + console.log("[PAGE-INJECT] Installing hooks in page context"); + + const propertyHooks = { + isPaidUser: true, + isLoggedIn: true, + userHasSubscription: true, + userEmail: "hi@httptoolkit.com", + mightBePaidUser: true, + isPastDueUser: false, + isStatusUnexpired: true, + userSubscription: { + state: "fulfilled", + status: "active", + plan: "pro", + sku: "sku", + tierCode: "pro", + interval: "monthly", + quantity: 1, + expiry: new Date(new Date().setFullYear(new Date().getFullYear() + 10)), + updateBillingDetailsUrl: "https://httptoolkit.com/", + cancelSubscriptionUrl: "https://httptoolkit.com/", + lastReceiptUrl: "https://httptoolkit.com/", + canManageSubscription: true, + }, + }; + + const hookedObjects = new WeakSet(); + + // Override Object.defineProperty to intercept all property definitions + const originalDefineProperty = Object.defineProperty; + Object.defineProperty = function (target, prop, descriptor) { + // Intercept our target properties + if (prop in propertyHooks) { + console.log("[PAGE-INJECT] Intercepting defineProperty for: " + prop); + + if (descriptor && descriptor.get) { + const originalGetter = descriptor.get; + descriptor.get = function () { + const originalValue = originalGetter.call(this); + console.log("[PAGE-INJECT] " + prop + " getter called, original=" + originalValue + ", returning=" + JSON.stringify(propertyHooks[prop])); + return propertyHooks[prop]; + }; + } else if (descriptor && descriptor.value !== undefined) { + console.log("[PAGE-INJECT] " + prop + " value being defined, overriding to " + JSON.stringify(propertyHooks[prop])); + descriptor.value = propertyHooks[prop]; + } + } + + return originalDefineProperty.call(this, target, prop, descriptor); + }; + + // Hook Object.defineProperties too + const originalDefineProperties = Object.defineProperties; + Object.defineProperties = function (target, props) { + for (let prop in props) { + if (prop in propertyHooks) { + console.log("[PAGE-INJECT] Intercepting defineProperties for: " + prop); + if (props[prop].get) { + const originalGetter = props[prop].get; + props[prop].get = function () { + const originalValue = originalGetter.call(this); + console.log("[PAGE-INJECT] " + prop + " getter called, original=" + originalValue + ", returning=" + JSON.stringify(propertyHooks[prop])); + return propertyHooks[prop]; + }; + } else if (props[prop].value !== undefined) { + props[prop].value = propertyHooks[prop]; } - }; + } + } + return originalDefineProperties.call(this, target, props); + }; - const hookedObjects = new WeakSet(); - - // Override Object.defineProperty to intercept all property definitions - const originalDefineProperty = Object.defineProperty; - Object.defineProperty = function(target, prop, descriptor) { - // Intercept our target properties - if (prop in propertyHooks) { - console.log("[PAGE-INJECT] Intercepting defineProperty for: " + prop); - - if (descriptor && descriptor.get) { - const originalGetter = descriptor.get; - descriptor.get = function() { - const originalValue = originalGetter.call(this); - console.log("[PAGE-INJECT] " + prop + " getter called, original=" + originalValue + ", returning=" + JSON.stringify(propertyHooks[prop])); - return propertyHooks[prop]; - }; - } else if (descriptor && descriptor.value !== undefined) { - console.log("[PAGE-INJECT] " + prop + " value being defined, overriding to " + JSON.stringify(propertyHooks[prop])); - descriptor.value = propertyHooks[prop]; - } - } - - return originalDefineProperty.call(this, target, prop, descriptor); - }; + // Periodically scan and patch existing objects + function scanAndPatch() { + // Search through window and common store locations + const searchPaths = [window, window.accountStore, window.stores && window.stores.accountStore, window.appState && window.appState.accountStore]; - // Hook Object.defineProperties too - const originalDefineProperties = Object.defineProperties; - Object.defineProperties = function(target, props) { - for (let prop in props) { - if (prop in propertyHooks) { - console.log("[PAGE-INJECT] Intercepting defineProperties for: " + prop); - if (props[prop].get) { - const originalGetter = props[prop].get; - props[prop].get = function() { - const originalValue = originalGetter.call(this); - console.log("[PAGE-INJECT] " + prop + " getter called, original=" + originalValue + ", returning=" + JSON.stringify(propertyHooks[prop])); - return propertyHooks[prop]; - }; - } else if (props[prop].value !== undefined) { - props[prop].value = propertyHooks[prop]; - } - } - } - return originalDefineProperties.call(this, target, props); - }; + searchPaths.forEach((obj, idx) => { + if (!obj || hookedObjects.has(obj)) return; - // Periodically scan and patch existing objects - function scanAndPatch() { - // Search through window and common store locations - const searchPaths = [ - window, - window.accountStore, - window.stores && window.stores.accountStore, - window.appState && window.appState.accountStore, - ]; - - searchPaths.forEach((obj, idx) => { - if (!obj || hookedObjects.has(obj)) return; - + try { + Object.keys(propertyHooks).forEach(prop => { try { - Object.keys(propertyHooks).forEach(prop => { - try { - const desc = Object.getOwnPropertyDescriptor(obj, prop); - if (desc && desc.configurable) { - console.log("[PAGE-INJECT] Found " + prop + " on object #" + idx + ", patching..."); - - if (desc.get) { + const desc = Object.getOwnPropertyDescriptor(obj, prop); + if (desc && desc.configurable) { + console.log("[PAGE-INJECT] Found " + prop + " on object #" + idx + ", patching..."); + + if (desc.get) { + const originalGetter = desc.get; + Object.defineProperty(obj, prop, { + get: function () { + const originalValue = originalGetter.call(this); + console.log("[PAGE-INJECT] " + prop + " getter intercepted, original=" + originalValue + ", returning=" + JSON.stringify(propertyHooks[prop])); + return propertyHooks[prop]; + }, + set: desc.set, + configurable: true, + enumerable: desc.enumerable, + }); + } else if (desc.writable) { + obj[prop] = propertyHooks[prop]; + console.log("[PAGE-INJECT] " + prop + " value set to " + JSON.stringify(propertyHooks[prop])); + } + } + } catch (e) { + // Ignore individual property errors + } + }); + + hookedObjects.add(obj); + } catch (e) { + // Ignore object access errors + } + }); + + // Also try to find accountStore by scanning window properties + try { + for (let key in window) { + try { + const obj = window[key]; + if (obj && typeof obj === "object" && "accountStore" in obj) { + console.log("[PAGE-INJECT] Found accountStore in window." + key); + const store = obj.accountStore; + if (store && !hookedObjects.has(store)) { + Object.keys(propertyHooks).forEach(prop => { + try { + const desc = Object.getOwnPropertyDescriptor(store, prop); + if (desc && desc.configurable && desc.get) { const originalGetter = desc.get; - Object.defineProperty(obj, prop, { - get: function() { + Object.defineProperty(store, prop, { + get: function () { const originalValue = originalGetter.call(this); - console.log("[PAGE-INJECT] " + prop + " getter intercepted, original=" + originalValue + ", returning=" + JSON.stringify(propertyHooks[prop])); + console.log("[PAGE-INJECT] accountStore." + prop + " intercepted, original=" + originalValue + ", returning=" + JSON.stringify(propertyHooks[prop])); return propertyHooks[prop]; }, set: desc.set, configurable: true, - enumerable: desc.enumerable + enumerable: desc.enumerable, }); - } else if (desc.writable) { - obj[prop] = propertyHooks[prop]; - console.log("[PAGE-INJECT] " + prop + " value set to " + JSON.stringify(propertyHooks[prop])); } - } - } catch (e) { - // Ignore individual property errors - } - }); - - hookedObjects.add(obj); - } catch (e) { - // Ignore object access errors - } - }); - - // Also try to find accountStore by scanning window properties - try { - for (let key in window) { - try { - const obj = window[key]; - if (obj && typeof obj === 'object' && 'accountStore' in obj) { - console.log("[PAGE-INJECT] Found accountStore in window." + key); - const store = obj.accountStore; - if (store && !hookedObjects.has(store)) { - Object.keys(propertyHooks).forEach(prop => { - try { - const desc = Object.getOwnPropertyDescriptor(store, prop); - if (desc && desc.configurable && desc.get) { - const originalGetter = desc.get; - Object.defineProperty(store, prop, { - get: function() { - const originalValue = originalGetter.call(this); - console.log("[PAGE-INJECT] accountStore." + prop + " intercepted, original=" + originalValue + ", returning=" + JSON.stringify(propertyHooks[prop])); - return propertyHooks[prop]; - }, - set: desc.set, - configurable: true, - enumerable: desc.enumerable - }); - } - } catch (e) {} - }); - hookedObjects.add(store); - } - } - } catch (e) {} + } catch (e) {} + }); + hookedObjects.add(store); + } } } catch (e) {} } - - // Run initial scan - scanAndPatch(); - - // Scan periodically for late-initialized stores - let scanCount = 0; - const scanInterval = setInterval(() => { - scanCount++; - scanAndPatch(); - - if (scanCount >= 10) { - clearInterval(scanInterval); - console.log("[PAGE-INJECT] Stopped periodic scanning after 10 attempts"); - } - }, 100); - - console.log("[PAGE-INJECT] Hooks installed successfully"); - })(); - `; - - function injectScript() { - try { - const script = document.createElement('script'); - script.textContent = injectionCode; - (document.head || document.documentElement).appendChild(script); - script.remove(); // Clean up the script tag - console.log("[PRELOAD] Injected page context hooks"); - } catch (e) { - console.error("[PRELOAD] Failed to inject hooks:", e); - } + } catch (e) {} } - - // Try to inject immediately if DOM is ready, otherwise wait - if (document.documentElement) { - injectScript(); - } else { - // Wait for document to be created - const observer = new MutationObserver(() => { - if (document.documentElement) { - observer.disconnect(); - injectScript(); - } - }); - observer.observe(document, { childList: true, subtree: true }); - - // Also try on DOMContentLoaded as backup - if (document.addEventListener) { - document.addEventListener('DOMContentLoaded', injectScript, { once: true }); + + // Run initial scan + scanAndPatch(); + + // Scan periodically for late-initialized stores + let scanCount = 0; + const scanInterval = setInterval(() => { + scanCount++; + scanAndPatch(); + + if (scanCount >= 50) { + clearInterval(scanInterval); + console.log("[PAGE-INJECT] Stopped periodic scanning after 10 attempts"); } - } -})(); \ No newline at end of file + }, 100); + + console.log("[PAGE-INJECT] Hooks installed successfully"); +})();