From 371977168cae8c712d0ff6924316d38dcc6d9bdf Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 26 Feb 2026 17:45:53 +0200 Subject: [PATCH] Files Upload --- LICENSE | 22 ++ README.md | 76 ++++- index.js | 736 ++++++++++++++++++++++++++++++++++++++++++++++ inject.js | 168 +++++++++++ package-lock.json | 353 ++++++++++++++++++++++ package.json | 23 ++ 6 files changed, 1377 insertions(+), 1 deletion(-) create mode 100644 LICENSE create mode 100644 index.js create mode 100644 inject.js create mode 100644 package-lock.json create mode 100644 package.json diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6e8e821 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2025 xenos1337 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/README.md b/README.md index 9401178..fec325d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,76 @@ -# httptoolkit-patcher +# HTTP Toolkit Patcher + +A minimal, cross-platform patcher for HTTP Toolkit that removes subscription requirements. + +![WindowsTerminal_BsiVsdyUoj](https://github.com/user-attachments/assets/20a226f3-4620-4a0e-b1df-8cc55609203c) + +## Why? + +I don't feel like paying a **monthly subscription** for an HTTP proxy/interceptor. A lifetime license? Sure. But subscription-based for a dev tool? No thanks. + +## How It Works + +The patcher intercepts HTTP Toolkit's authentication functions: +- `isPaidUser` +- `isLoggedIn` +- `userHasSubscription` +- `userEmail` +- `mightBePaidUser` +- `isPastDueUser` + +By hooking these functions, we bypass the subscription checks entirely. + +## Installation + +1. Install Node.js (if not already installed) +2. Install dependencies: +```bash +npm install +``` + +## Usage + +**Patch HTTP Toolkit:** +```bash +npm start +``` + +**Unpatch/Restore:** +```bash +npm run unpatch +``` + +**Show help:** +```bash +npm start help +``` + +That's it. The patcher handles everything automatically and will request elevated permissions if needed. + +## Technical Details + +1. Finds HTTP Toolkit installation +2. Kills running processes +3. Requests elevation if needed +4. Backs up `app.asar` +5. Extracts and patches `preload.js` +6. Repackages and launches + +## Troubleshooting + +**Permission errors?** The patcher will automatically request elevated permissions (admin/sudo). + +**Already patched?** The patcher will ask if you want to repatch. + +**Want to restore?** Run `npm run unpatch` to restore from backup. + +**Anything else?** Open an issue on the [GitHub repository](https://github.com/xenos1337/httptoolkit-patcher/issues). + +## Disclaimer + +This tool is provided as-is. Use at your own risk. For educational purposes only. + +## License + +MIT License - see [LICENSE](LICENSE) file. diff --git a/index.js b/index.js new file mode 100644 index 0000000..a80b11e --- /dev/null +++ b/index.js @@ -0,0 +1,736 @@ +// @ts-check +import { spawn, execSync } from "child_process"; +import asar from "@electron/asar"; +import chalk from "chalk"; +import path from "path"; +import fs from "fs"; +import readline from "readline"; +import https from "https"; +import { fileURLToPath } from "url"; + +const isWin = process.platform === "win32"; +const isMac = process.platform === "darwin"; +const isLinux = process.platform === "linux"; + +// Handle both ESM and pkg-bundled scenarios +const __filename = (() => { + // Check if running as pkg bundle + // @ts-ignore - pkg adds this property at runtime + if (process.pkg) { + return process.execPath; + } + // ESM: use import.meta.url + 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) { + try { + execSync("net session", { stdio: "ignore" }); + return true; + } catch { + return false; + } + } else { + return process.getuid && process.getuid() === 0; + } +} + +// Helper function to prompt user for input +function prompt(question) { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + return new Promise(resolve => { + rl.question(question, answer => { + rl.close(); + resolve(answer); + }); + }); +} + +/** + * Fetch JSON from a URL + * @param {string} url + * @returns {Promise>} + */ +function fetchJson(url) { + return new Promise((resolve, reject) => { + const options = { + headers: { + "User-Agent": "httptoolkit-patcher", + Accept: "application/vnd.github.v3+json", + }, + }; + https + .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", () => { + 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; + if (!fs.lstatSync(dirPath).isDirectory()) return fs.rmSync(dirPath, { force: true }); + for (const entry of fs.readdirSync(dirPath)) { + const entryPath = path.join(dirPath, entry); + if (fs.lstatSync(entryPath).isDirectory()) rm(entryPath); + else fs.rmSync(entryPath, { force: true }); + } + fs.rmdirSync(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"), 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"))) { + return p; + } + } + + console.log(chalk.yellowBright`[!] HTTP Toolkit not found in default locations`); + const userPath = await prompt("Please enter the path to HTTP Toolkit executable/app: "); + + if (!userPath) { + console.error(chalk.redBright`[-] No path provided`); + process.exit(1); + } + + // Extract resources path from user input + let resourcesPath = userPath.trim().replace(/['"]/g, ""); + + if (resourcesPath.endsWith(".exe") || resourcesPath.endsWith(".app")) resourcesPath = path.dirname(resourcesPath); + if (!resourcesPath.endsWith("resources")) resourcesPath = path.join(resourcesPath, "resources"); + + if (!fs.existsSync(path.join(resourcesPath, "app.asar"))) { + console.error(chalk.redBright`[-] app.asar not found at ${resourcesPath}`); + process.exit(1); + } + + return resourcesPath; +} + +// Request elevated permissions +async function requestElevation() { + console.log(chalk.yellowBright`[!] Requesting elevated permissions...`); + + // @ts-ignore - pkg adds this property at runtime + const isBundled = process.pkg; + + if (isWin) { + // Windows: Use PowerShell to run as administrator + let script; + if (isBundled) { + script = `Start-Process -FilePath "${__filename}" -Verb RunAs`; + } else { + script = `Start-Process -FilePath "node" -ArgumentList "${__filename}" -Verb RunAs`; + } + + try { + console.log(chalk.greenBright`[+] Spawning PowerShell with script: ${script}`); + execSync(`powershell -Command "${script}"`, { stdio: "inherit" }); + console.log(chalk.blueBright`[+] Restarting with administrator privileges...`); + process.exit(0); + } catch (e) { + console.error(chalk.redBright`[-] Failed to elevate permissions: ${e.message}`); + console.error(chalk.redBright`[-] Please run as administrator manually`); + process.exit(1); + } + } else if (isLinux) { + // Linux: Cannot auto-elevate with sudo, show instructions instead + console.log(chalk.yellowBright`[!] Elevated permissions are required for patching on Linux`); + console.log(chalk.yellowBright`[!] Please re-run this script with sudo:`); + if (isBundled) { + console.log(chalk.blueBright` sudo ${__filename}`); + } else { + console.log(chalk.blueBright` sudo node ${__filename}`); + } + process.exit(1); + } else { + // macOS: Try to elevate with sudo + console.log(chalk.blueBright`[+] Restarting with sudo...`); + try { + let child; + if (isBundled) { + child = spawn("sudo", [__filename], { + stdio: "inherit", + }); + } else { + child = spawn("sudo", ["node", __filename], { + stdio: "inherit", + }); + } + child.on("exit", code => process.exit(code || 0)); + } catch (e) { + console.error(chalk.redBright`[-] Failed to elevate permissions: ${e.message}`); + console.error(chalk.redBright`[-] Please run with sudo manually`); + process.exit(1); + } + } +} + +// Check if we have write permissions +function checkPermissions(filePath) { + try { + // Check write access to the file/directory + fs.accessSync(filePath, fs.constants.W_OK); + + // Check if we can create directories + const testDirPath = path.join(path.dirname(filePath), `.test_${Date.now()}`); + try { + fs.mkdirSync(testDirPath, { recursive: true }); + fs.rmdirSync(testDirPath); + } catch (dirError) { + console.error(chalk.redBright`[-] Cannot create directories in ${path.dirname(filePath)}: ${dirError.message}`); + return false; + } + + console.log(chalk.greenBright`[+] Permissions check passed for ${filePath}`); + return true; + } catch (e) { + console.error(chalk.redBright`[-] Permissions check failed for ${filePath}: ${e.message}`); + return false; + } +} + +// Kill HTTP Toolkit processes +async function killHttpToolkit() { + console.log(chalk.yellowBright`[+] Checking for running HTTP Toolkit processes...`); + + try { + if (isWin) { + // Windows: Use tasklist to find and taskkill to terminate + const output = execSync('tasklist /FI "IMAGENAME eq HTTP Toolkit.exe" /FO CSV /NH', { encoding: "utf-8" }); + if (output.includes("HTTP Toolkit.exe")) { + console.log(chalk.yellowBright`[!] HTTP Toolkit is running, attempting to close it...`); + execSync('taskkill /F /IM "HTTP Toolkit.exe" /T', { stdio: "ignore" }); + console.log(chalk.greenBright`[+] HTTP Toolkit processes terminated`); + // Wait a moment for the process to fully close + await new Promise(resolve => setTimeout(resolve, 2000)); + } else { + console.log(chalk.greenBright`[+] HTTP Toolkit is not running`); + } + } else if (isMac) { + // macOS: Use pgrep and pkill + try { + execSync('pgrep -f "HTTP Toolkit"', { stdio: "ignore" }); + console.log(chalk.yellowBright`[!] HTTP Toolkit is running, attempting to close it...`); + execSync('pkill -f "HTTP Toolkit"', { stdio: "ignore" }); + console.log(chalk.greenBright`[+] HTTP Toolkit processes terminated`); + await new Promise(resolve => setTimeout(resolve, 2000)); + } catch (e) { + console.log(chalk.greenBright`[+] HTTP Toolkit is not running`); + } + } else { + // Linux: Use pgrep and pkill + try { + execSync('pgrep -f "HTTP Toolkit"', { stdio: "ignore" }); + console.log(chalk.yellowBright`[!] HTTP Toolkit is running, attempting to close it...`); + execSync('pkill -f "HTTP Toolkit"', { stdio: "ignore" }); + console.log(chalk.greenBright`[+] HTTP Toolkit processes terminated`); + await new Promise(resolve => setTimeout(resolve, 2000)); + } catch (e) { + console.log(chalk.greenBright`[+] HTTP Toolkit is not running`); + } + } + } catch (e) { + console.log(chalk.yellowBright`[!] Could not check/kill processes: ${e.message}`); + console.log(chalk.yellowBright`[!] If HTTP Toolkit is running, please close it manually`); + } +} + +// Resolve the base installation directory (without the trailing resources folder) +function getBinaryBasePath(resourcesPath) { + const normalized = resourcesPath.replace(/[\\/]+$/, ""); + if (normalized.toLowerCase().endsWith("resources")) { + return path.dirname(normalized); + } + return normalized; +} + +// Determine the executable path for HTTP Toolkit based on platform +function getExecutablePath(resourcesPath) { + const basePath = getBinaryBasePath(resourcesPath); + const candidates = isWin ? [path.join(basePath, "HTTP Toolkit.exe"), path.join(basePath, "httptoolkit.exe")] : isMac ? [path.join(basePath, "MacOS", "HTTP Toolkit"), path.join(basePath, "MacOS", "HTTP Toolkit Preview")] : [path.join(basePath, "httptoolkit"), path.join(basePath, "HTTP Toolkit")]; + + for (const candidate of candidates) { + if (fs.existsSync(candidate)) { + return candidate; + } + } + + throw new Error(`Could not locate HTTP Toolkit executable near ${resourcesPath}`); +} + +// Extract hashes from integrity check output +function extractIntegrityHashes(output) { + const regex = /Integrity check failed for asar archive[\s\S]*?\(\s*([0-9a-f]{64})\s*vs\s*([0-9a-f]{64})\s*\)/i; + const match = output.match(regex); + if (!match) return null; + return { + originalHash: match[1], + newHash: match[2], + }; +} + +// Launch the app once to grab integrity hashes from its crash output +async function captureIntegrityHashes(executablePath) { + return new Promise((resolve, reject) => { + let output = ""; + let finished = false; + const child = spawn(executablePath, { + cwd: path.dirname(executablePath), + stdio: ["ignore", "pipe", "pipe"], + windowsHide: true, + }); + + const timeout = setTimeout(() => { + if (!finished) { + finished = true; + child.kill(); + reject(new Error("Timed out waiting for integrity check output")); + } + }, 20000); + + const handleData = data => { + output += data.toString(); + const hashes = extractIntegrityHashes(output); + if (hashes && !finished) { + finished = true; + clearTimeout(timeout); + child.kill(); + resolve(hashes); + } + }; + + child.stdout.on("data", handleData); + child.stderr.on("data", handleData); + + child.on("error", err => { + if (!finished) { + finished = true; + clearTimeout(timeout); + reject(err); + } + }); + + child.on("exit", () => { + if (!finished) { + clearTimeout(timeout); + const hashes = extractIntegrityHashes(output); + if (hashes) { + resolve(hashes); + } else { + reject(new Error("Could not find integrity hashes in HTTP Toolkit output")); + } + } + }); + }); +} + +// Replace all occurrences of the original hash with the new hash inside the binary +function patchExecutableHash(executablePath, originalHash, newHash) { + if (originalHash.length !== newHash.length) { + throw new Error("Hash lengths do not match; cannot safely patch binary"); + } + + const binary = fs.readFileSync(executablePath); + const originalBuf = Buffer.from(originalHash, "utf-8"); + const newBuf = Buffer.from(newHash, "utf-8"); + + let occurrences = 0; + let idx = binary.indexOf(originalBuf); + while (idx !== -1) { + newBuf.copy(binary, idx); + occurrences += 1; + idx = binary.indexOf(originalBuf, idx + originalBuf.length); + } + + if (occurrences === 0) { + throw new Error("Original hash not found in binary"); + } + + fs.writeFileSync(executablePath, binary); + return occurrences; +} + +// Unpatch/restore function +async function unpatchApp() { + console.log(chalk.blueBright`[+] HTTP Toolkit Unpatcher Started`); + + const appPath = await findAppPath(); + console.log(chalk.greenBright`[+] HTTP Toolkit found at ${appPath}`); + + await killHttpToolkit(); + + const asarPath = path.join(appPath, "app.asar"); + const backupPath = path.join(appPath, "app.asar.bak"); + const extractPath = path.join(appPath, "app.asar_extracted"); + + // Check if we have write permissions on both the directory and the file + const hasPermissions = checkPermissions(appPath) && checkPermissions(asarPath); + + if (!hasPermissions) { + console.log(chalk.yellowBright`[!] No write permissions for ${appPath}`); + + if (isElevated()) { + console.error(chalk.redBright`[-] Still no permissions even with elevated privileges`); + process.exit(1); + } + + console.log(chalk.yellowBright`[!] Administrator/sudo privileges required for unpatching`); + await requestElevation(); + } + + if (!fs.existsSync(backupPath)) { + console.error(chalk.redBright`[-] Backup file not found at ${backupPath}`); + console.error(chalk.redBright`[-] Cannot unpatch without backup file`); + process.exit(1); + } + + console.log(chalk.yellowBright`[+] Restoring from backup...`); + try { + fs.copyFileSync(backupPath, asarPath); + console.log(chalk.greenBright`[+] Restored app.asar from backup`); + } catch (e) { + console.error(chalk.redBright`[-] Failed to restore backup: ${e.message}`); + process.exit(1); + } + + if (fs.existsSync(extractPath)) { + console.log(chalk.yellowBright`[+] Removing extracted files...`); + rm(extractPath); + console.log(chalk.greenBright`[+] Cleaned up extracted files`); + } + + const removeBackup = await prompt("Do you want to remove the backup file? (y/n): "); + if (removeBackup.toLowerCase() === "y" || removeBackup.toLowerCase() === "yes") { + fs.rmSync(backupPath, { force: true }); + console.log(chalk.greenBright`[+] Backup file removed`); + } + + console.log(chalk.greenBright`[+] Successfully unpatched!`); +} + +// Main patching function +async function patchApp() { + console.log(chalk.blueBright`[+] HTTP Toolkit Patcher Started`); + + const appPath = await findAppPath(); + console.log(chalk.greenBright`[+] HTTP Toolkit found at ${appPath}`); + + await killHttpToolkit(); + + const asarPath = path.join(appPath, "app.asar"); + + // Check if we have write permissions on both the directory and the file + const hasPermissions = checkPermissions(appPath) && checkPermissions(asarPath); + + if (!hasPermissions) { + console.log(chalk.yellowBright`[!] No write permissions for ${appPath}`); + + if (isElevated()) { + console.error(chalk.redBright`[-] Still no permissions even with elevated privileges`); + process.exit(1); + } + + console.log(chalk.yellowBright`[!] Administrator/sudo privileges required for patching`); + await requestElevation(); + } + + const backupPath = path.join(appPath, "app.asar.bak"); + if (!fs.existsSync(backupPath)) { + console.log(chalk.yellowBright`[+] Creating backup...`); + fs.copyFileSync(asarPath, backupPath); + console.log(chalk.greenBright`[+] Backup created at ${backupPath}`); + } + + const extractPath = path.join(appPath, "app.asar_extracted"); + console.log(chalk.yellowBright`[+] Extracting app.asar...`); + rm(extractPath); + asar.extractAll(asarPath, extractPath); + console.log(chalk.greenBright`[+] Extracted to ${extractPath}`); + + const preloadPath = path.join(extractPath, "build", "preload.cjs"); + if (!fs.existsSync(preloadPath)) { + 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.cjs`); + + 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`); + + let preloadContent = fs.readFileSync(preloadPath, "utf-8"); + + const electronVarName = preloadContent.includes("electron_1") ? "electron_1" : "electron"; + console.log(chalk.greenBright`[+] Detected electron variable: ${electronVarName}`); + + const preloadPatchCode = ` +(function loadInjectScript() { + const injectCode = ${JSON.stringify(injectCode)}; + + function injectViaWebFrame() { + try { + const { webFrame } = ${electronVarName}; + 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 }); + } + } +})(); +`; + const isPreloadPatched = preloadContent.includes("loadInjectScript"); + + 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") { + console.log(chalk.blueBright`[+] Patching cancelled`); + rm(extractPath); + process.exit(0); + } + + // Remove existing patches + console.log(chalk.yellowBright`[+] Replacing existing patches...`); + + // Remove preload patch + const preloadPatchRegex = /\n?\(function loadInjectScript\(\) \{[\s\S]*?\}\)\(\);/; + preloadContent = preloadContent.replace(preloadPatchRegex, ""); + } + + console.log(chalk.yellowBright`[+] Applying preload patch...`); + const preloadLines = preloadContent.split("\n"); + let preloadInsertIndex = -1; + + for (let i = 0; i < preloadLines.length; i++) { + const line = preloadLines[i]; + if (line.includes('require("electron")') || line.includes("require('electron')") || line.includes("electron_1")) { + preloadInsertIndex = i + 1; + break; + } + } + + if (preloadInsertIndex === -1) { + console.error(chalk.redBright`[-] Could not find insertion point (electron import) 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`); + + console.log(chalk.yellowBright`[+] Repackaging app.asar...`); + await asar.createPackage(extractPath, asarPath); + console.log(chalk.greenBright`[+] app.asar repackaged successfully`); + + let executablePath; + try { + executablePath = getExecutablePath(appPath); + } catch (e) { + rm(extractPath); + console.error(chalk.redBright`[-] ${e.message}`); + process.exit(1); + } + + console.log(chalk.yellowBright`[+] Launching HTTP Toolkit to read integrity hashes...`); + let hashes; + try { + hashes = await captureIntegrityHashes(executablePath); + } catch (e) { + rm(extractPath); + console.error(chalk.redBright`[-] Failed to capture integrity hashes: ${e.message}`); + process.exit(1); + } + + console.log(chalk.greenBright`[+] Integrity hashes captured`); + console.log(chalk.white` Original hash: ${hashes.originalHash}`); + console.log(chalk.white` New asar hash: ${hashes.newHash}`); + + try { + const replacements = patchExecutableHash(executablePath, hashes.originalHash, hashes.newHash); + console.log(chalk.greenBright`[+] Patched binary hash (${replacements} replacement${replacements === 1 ? "" : "s"})`); + } catch (e) { + rm(extractPath); + console.error(chalk.redBright`[-] Failed to patch binary hash: ${e.message}`); + process.exit(1); + } + + console.log(chalk.yellowBright`[+] Cleaning up temporary files...`); + rm(extractPath); + console.log(chalk.greenBright`[+] Successfully patched!`); + + console.log(chalk.blueBright`[+] Opening HTTP Toolkit...`); + try { + const child = spawn(executablePath, { + stdio: "ignore", + shell: false, + detached: true, + }); + child.unref(); + console.log(chalk.greenBright`[+] HTTP Toolkit launched successfully`); + } catch (e) { + console.error(chalk.yellowBright`[!] Could not auto-start HTTP Toolkit: ${e.message}`); + console.log(chalk.blueBright`[+] Please start HTTP Toolkit manually`); + } +} + +const args = process.argv.slice(2); +const command = args[0]; + +const commandName = (() => { + // @ts-ignore - pkg adds this property at runtime + if (process.pkg) { + return path.basename(__filename); + } else { + return `node ${process.argv[1]}`; + } +})(); + +// 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") { + console.log(chalk.blueBright`HTTP Toolkit Patcher`); + console.log(chalk.white`\nUsage:`); + console.log(chalk.white` ${commandName} [command]`); + console.log(chalk.white`\nCommands:`); + console.log(chalk.white` patch ${chalk.gray`- Patch HTTP Toolkit (default)`}`); + console.log(chalk.white` unpatch ${chalk.gray`- Restore original HTTP Toolkit from backup`}`); + console.log(chalk.white` restore ${chalk.gray`- Alias for unpatch`}`); + console.log(chalk.white` help ${chalk.gray`- Show this help message`}`); + process.exit(0); + } else if (!command || command === "patch") { + // Ask for confirmation before patching + const answer = await prompt("Do you want to patch HTTP Toolkit? [Y/n]: "); + if (answer.toLowerCase() === "n" || answer.toLowerCase() === "no") { + console.log(chalk.blueBright`[+] Patching cancelled`); + process.exit(0); + } + await patchApp(); + } else { + console.error(chalk.redBright`[-] Unknown command: ${command}`); + console.log(chalk.yellowBright`[!] Use 'help' to see available commands`); + process.exit(1); + } + } catch (error) { + console.error(chalk.redBright`[-] Error: ${error.message}`); + process.exit(1); + } +})(); diff --git a/inject.js b/inject.js new file mode 100644 index 0000000..a6f0016 --- /dev/null +++ b/inject.js @@ -0,0 +1,168 @@ +(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); + }; + + // 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 { + 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(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) {} + } + + // 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"); + } + }, 100); + + console.log("[PAGE-INJECT] Hooks installed successfully"); +})(); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b10a288 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,353 @@ +{ + "name": "httptoolkit-patcher", + "version": "2.0.10", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "httptoolkit-patcher", + "version": "2.0.10", + "license": "MIT", + "dependencies": { + "@electron/asar": "^4.0.1", + "chalk": "4" + }, + "bin": { + "httptoolkit-patcher": "index.js" + }, + "devDependencies": { + "@types/node": "^20.14.9" + } + }, + "node_modules/@electron/asar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-4.0.1.tgz", + "integrity": "sha512-F4Ykm1jiBGY1WV/o8Q8oFW8Nq0u+S2/vPujzNJtdSJ6C4LHC4CiGLn7c17s7SolZ23gcvCebMncmZtNc+MkxPQ==", + "license": "MIT", + "dependencies": { + "commander": "^13.1.0", + "glob": "^11.0.1", + "minimatch": "^10.0.1" + }, + "bin": { + "asar": "bin/asar.mjs" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/node": { + "version": "20.19.23", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.23.tgz", + "integrity": "sha512-yIdlVVVHXpmqRhtyovZAcSy0MiPcYWGkoO4CGe/+jpP0hmNuihm4XhHbADpK++MsiLHP5MVlv+bcgdF99kSiFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/balanced-match": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz", + "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", + "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "BlueOak-1.0.0", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^9.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/minimatch": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", + "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..865ddf4 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "httptoolkit-patcher", + "version": "2.0.10", + "description": "HTTP Toolkit Pro Patcher", + "main": "index.js", + "type": "module", + "bin": "index.js", + "author": "xenos1337", + "scripts": { + "start": "node index.js", + "patch": "node index.js patch", + "unpatch": "node index.js unpatch", + "restore": "node index.js restore" + }, + "license": "MIT", + "dependencies": { + "@electron/asar": "^4.0.1", + "chalk": "4" + }, + "devDependencies": { + "@types/node": "^20.14.9" + } +}