8 Commits
6 changed files with 400 additions and 2450 deletions
-120
View File
@@ -1,120 +0,0 @@
name: Release Build
on:
push:
tags:
- 'v*'
workflow_dispatch:
permissions:
contents: write
jobs:
build:
name: Build ${{ matrix.os }}
runs-on: ${{ matrix.runner }}
strategy:
matrix:
include:
- os: linux
runner: ubuntu-latest
targets: node18-linux-x64,node18-linux-arm64
- os: macos
runner: macos-latest
targets: node18-macos-x64,node18-macos-arm64
- os: windows
runner: windows-latest
targets: node18-win-x64
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Create dist directory
run: mkdir -p dist
- name: Build for ${{ matrix.os }}
run: |
npm run build
npx pkg dist/bundle.cjs --targets ${{ matrix.targets }} --compress GZip --out-path dist/
- name: List built files (debug - Unix)
if: matrix.os != 'windows'
run: ls -lah dist/
- name: List built files (debug - Windows)
if: matrix.os == 'windows'
shell: pwsh
run: Get-ChildItem -Path dist/ | Format-Table -AutoSize
- name: Rename executables (Linux)
if: matrix.os == 'linux'
run: |
[ -f dist/bundle-x64 ] && mv dist/bundle-x64 dist/httptoolkit-patcher-linux-x64
[ -f dist/bundle-arm64 ] && mv dist/bundle-arm64 dist/httptoolkit-patcher-linux-arm64
rm -f dist/bundle dist/bundle.cjs 2>/dev/null || true
- name: Rename executables (macOS)
if: matrix.os == 'macos'
run: |
[ -f dist/bundle-x64 ] && mv dist/bundle-x64 dist/httptoolkit-patcher-macos-x64
[ -f dist/bundle-arm64 ] && mv dist/bundle-arm64 dist/httptoolkit-patcher-macos-arm64
rm -f dist/bundle dist/bundle.cjs 2>/dev/null || true
- name: Rename executables (Windows)
if: matrix.os == 'windows'
shell: pwsh
run: |
if (Test-Path dist/bundle.exe) { Move-Item -Force dist/bundle.exe dist/httptoolkit-patcher.exe }
if (Test-Path dist/bundle-x64.exe) { Move-Item -Force dist/bundle-x64.exe dist/httptoolkit-patcher-win-x64.exe }
Remove-Item -Force dist/bundle.cjs -ErrorAction SilentlyContinue
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: binaries-${{ matrix.os }}
path: dist/*
release:
name: Create Release
needs: build
runs-on: ubuntu-latest
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
pattern: binaries-*
path: artifacts
merge-multiple: true
- name: Prepare release files
run: |
mkdir -p release
cp artifacts/* release/ 2>/dev/null || find artifacts -type f -exec cp {} release/ \;
ls -lah release/
- name: Extract version from tag
id: get_version
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: release/*
draft: false
prerelease: false
generate_release_notes: true
name: Release ${{ steps.get_version.outputs.VERSION }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+8 -67
View File
@@ -20,70 +20,32 @@ The patcher intercepts HTTP Toolkit's authentication functions:
By hooking these functions, we bypass the subscription checks entirely. By hooking these functions, we bypass the subscription checks entirely.
### Can They Fix It?
Yes, but they most likely won't. Fixing this would require changing their entire codebase architecture. And if they do? I'll just update the patcher.
## Installation ## Installation
1. Install dependencies: 1. Install Node.js (if not already installed)
2. Install dependencies:
```bash ```bash
npm install npm install
``` ```
## Building
Build standalone executables for all platforms and architectures:
```bash
npm run build
```
This creates self-contained executables in the `dist/` directory:
**Windows:**
- `httptoolkit-patcher-win-x64.exe` (64-bit)
**Linux:**
- `httptoolkit-patcher-linux-x64` (x86_64/AMD64)
- `httptoolkit-patcher-linux-arm64` (ARM64/AArch64)
**macOS:**
- `httptoolkit-patcher-macos-x64` (Intel)
- `httptoolkit-patcher-macos-arm64` (Apple Silicon M Chip)
**Note:** These are standalone executables created with `pkg` that include Node.js runtime and all dependencies. No need to install Node.js separately!
## Usage ## Usage
### From Source **Patch HTTP Toolkit:**
**Patch:**
```bash ```bash
npm start npm start
``` ```
**Unpatch:** **Unpatch/Restore:**
```bash ```bash
npm run unpatch npm run unpatch
``` ```
### Using Prebuilt Executables **Show help:**
Download the appropriate executable for your platform from [Releases](https://github.com/xenos1337/httptoolkit-patcher/releases), then:
**Windows:**
```cmd
httptoolkit-patcher-win-x64.exe
```
**Linux/macOS:**
```bash ```bash
chmod +x httptoolkit-patcher-linux-x64 # or your architecture npm start help
./httptoolkit-patcher-linux-x64
``` ```
That's it. The patcher handles everything automatically. That's it. The patcher handles everything automatically and will request elevated permissions if needed.
## Technical Details ## Technical Details
@@ -96,7 +58,7 @@ That's it. The patcher handles everything automatically.
## Troubleshooting ## Troubleshooting
**Permission errors?** Run as admin/sudo or let the patcher request elevation. **Permission errors?** The patcher will automatically request elevated permissions (admin/sudo).
**Already patched?** The patcher will ask if you want to repatch. **Already patched?** The patcher will ask if you want to repatch.
@@ -104,27 +66,6 @@ That's it. The patcher handles everything automatically.
**Anything else?** Open an issue on the [GitHub repository](https://github.com/xenos1337/httptoolkit-patcher/issues). **Anything else?** Open an issue on the [GitHub repository](https://github.com/xenos1337/httptoolkit-patcher/issues).
## GitHub Release Workflow
This project includes an automated GitHub Actions workflow that builds and releases the patcher for all platforms.
### Creating a Release
1. Create and push a version tag:
```bash
git tag v2.0.1
git push origin v2.0.1
```
2. The GitHub Actions workflow will automatically:
- Build for all platforms and architectures (Windows x64, Linux x64/ARM64, macOS Intel/Apple Silicon)
- Create standalone native executables using pkg
- Create a GitHub release with all binaries as downloadable artifacts
### Manual Workflow Trigger
You can also manually trigger the release workflow from the GitHub Actions tab.
## Disclaimer ## Disclaimer
This tool is provided as-is. Use at your own risk. For educational purposes only. This tool is provided as-is. Use at your own risk. For educational purposes only.
+237 -96
View File
@@ -15,19 +15,28 @@ const isLinux = process.platform === "linux";
// Handle both ESM and pkg-bundled scenarios // Handle both ESM and pkg-bundled scenarios
const __filename = (() => { const __filename = (() => {
// Check if running as pkg bundle // Check if running as pkg bundle
// @ts-ignore - pkg adds this property at runtime
if (process.pkg) { if (process.pkg) {
return process.execPath; return process.execPath;
} }
// Check if import.meta.url exists (ESM) // ESM: use import.meta.url
if (typeof import.meta !== 'undefined' && import.meta.url) { return fileURLToPath(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";
} }
// Fallback to __filename if in CommonJS
return typeof __filename !== 'undefined' ? __filename : process.argv[1];
})(); })();
// Check if running with elevated privileges // Check if running with elevated privileges
const isElevated = () => { function isElevated() {
if (isWin) { if (isWin) {
try { try {
execSync("net session", { stdio: "ignore" }); execSync("net session", { stdio: "ignore" });
@@ -38,10 +47,10 @@ const isElevated = () => {
} else { } else {
return process.getuid && process.getuid() === 0; return process.getuid && process.getuid() === 0;
} }
}; }
// Helper function to prompt user for input // Helper function to prompt user for input
const prompt = question => { function prompt(question) {
const rl = readline.createInterface({ const rl = readline.createInterface({
input: process.stdin, input: process.stdin,
output: process.stdout, output: process.stdout,
@@ -52,23 +61,83 @@ const prompt = question => {
resolve(answer); resolve(answer);
}); });
}); });
}; }
// Helper function to fetch content from URL /**
const fetchUrl = url => { * Fetch JSON from a URL
* @param {string} url
* @returns {Promise<Array<{name: string}>>}
*/
function fetchJson(url) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const options = {
headers: {
"User-Agent": "httptoolkit-patcher",
"Accept": "application/vnd.github.v3+json",
},
};
https https
.get(url, res => { .get(url, options, res => {
if (res.statusCode !== 200) {
reject(new Error(`HTTP ${res.statusCode}`));
return;
}
let data = ""; let data = "";
res.on("data", chunk => (data += chunk)); 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); .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 // Helper function to remove directory recursively
const rm = dirPath => { function rm(dirPath) {
if (!fs.existsSync(dirPath)) return; if (!fs.existsSync(dirPath)) return;
if (!fs.lstatSync(dirPath).isDirectory()) return fs.rmSync(dirPath, { force: true }); if (!fs.lstatSync(dirPath).isDirectory()) return fs.rmSync(dirPath, { force: true });
for (const entry of fs.readdirSync(dirPath)) { for (const entry of fs.readdirSync(dirPath)) {
@@ -77,11 +146,11 @@ const rm = dirPath => {
else fs.rmSync(entryPath, { force: true }); else fs.rmSync(entryPath, { force: true });
} }
fs.rmdirSync(dirPath); fs.rmdirSync(dirPath);
}; }
// Find HTTP Toolkit installation path // Find HTTP Toolkit installation path
const findAppPath = async () => { 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) { for (const p of possiblePaths) {
if (fs.existsSync(path.join(p, "app.asar"))) { if (fs.existsSync(path.join(p, "app.asar"))) {
@@ -109,20 +178,27 @@ const findAppPath = async () => {
} }
return resourcesPath; return resourcesPath;
}; }
// Request elevated permissions // Request elevated permissions
const requestElevation = async () => { async function requestElevation() {
console.log(chalk.yellowBright`[!] Requesting elevated permissions...`); console.log(chalk.yellowBright`[!] Requesting elevated permissions...`);
// @ts-ignore - pkg adds this property at runtime
const isBundled = process.pkg;
if (isWin) { if (isWin) {
// Windows: Use PowerShell to run as administrator // Windows: Use PowerShell to run as administrator
const script = `Start-Process -FilePath "node" -ArgumentList '"${__filename}"' -Verb RunAs`; let script;
if (isBundled) {
script = `Start-Process -FilePath "${__filename}" -Verb RunAs`;
} else {
script = `Start-Process -FilePath "node" -ArgumentList "${__filename}" -Verb RunAs`;
}
try { try {
spawn("powershell", ["-Command", script], { console.log(chalk.greenBright`[+] Spawning PowerShell with script: ${script}`);
stdio: "inherit", execSync(`powershell -Command "${script}"`, { stdio: "inherit" });
shell: true,
});
console.log(chalk.blueBright`[+] Restarting with administrator privileges...`); console.log(chalk.blueBright`[+] Restarting with administrator privileges...`);
process.exit(0); process.exit(0);
} catch (e) { } catch (e) {
@@ -134,15 +210,26 @@ const requestElevation = async () => {
// Linux: Cannot auto-elevate with sudo, show instructions instead // 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`[!] Elevated permissions are required for patching on Linux`);
console.log(chalk.yellowBright`[!] Please re-run this script with sudo:`); console.log(chalk.yellowBright`[!] Please re-run this script with sudo:`);
console.log(chalk.blueBright` sudo node ${__filename}`); if (isBundled) {
console.log(chalk.blueBright` sudo ${__filename}`);
} else {
console.log(chalk.blueBright` sudo node ${__filename}`);
}
process.exit(1); process.exit(1);
} else { } else {
// macOS: Try to elevate with sudo // macOS: Try to elevate with sudo
console.log(chalk.blueBright`[+] Restarting with sudo...`); console.log(chalk.blueBright`[+] Restarting with sudo...`);
try { try {
const child = spawn("sudo", ["node", __filename], { let child;
stdio: "inherit", if (isBundled) {
}); child = spawn("sudo", [__filename], {
stdio: "inherit",
});
} else {
child = spawn("sudo", ["node", __filename], {
stdio: "inherit",
});
}
child.on("exit", code => process.exit(code || 0)); child.on("exit", code => process.exit(code || 0));
} catch (e) { } catch (e) {
console.error(chalk.redBright`[-] Failed to elevate permissions: ${e.message}`); console.error(chalk.redBright`[-] Failed to elevate permissions: ${e.message}`);
@@ -150,20 +237,34 @@ const requestElevation = async () => {
process.exit(1); process.exit(1);
} }
} }
}; }
// Check if we have write permissions // Check if we have write permissions
const checkPermissions = filePath => { function checkPermissions(filePath) {
try { try {
// Check write access to the file/directory
fs.accessSync(filePath, fs.constants.W_OK); 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; return true;
} catch (e) { } catch (e) {
console.error(chalk.redBright`[-] Permissions check failed for ${filePath}: ${e.message}`);
return false; return false;
} }
}; }
// Kill HTTP Toolkit processes // Kill HTTP Toolkit processes
const killHttpToolkit = async () => { async function killHttpToolkit() {
console.log(chalk.yellowBright`[+] Checking for running HTTP Toolkit processes...`); console.log(chalk.yellowBright`[+] Checking for running HTTP Toolkit processes...`);
try { try {
@@ -206,10 +307,10 @@ const killHttpToolkit = async () => {
console.log(chalk.yellowBright`[!] Could not check/kill processes: ${e.message}`); console.log(chalk.yellowBright`[!] Could not check/kill processes: ${e.message}`);
console.log(chalk.yellowBright`[!] If HTTP Toolkit is running, please close it manually`); console.log(chalk.yellowBright`[!] If HTTP Toolkit is running, please close it manually`);
} }
}; }
// Unpatch/restore function // Unpatch/restore function
const unpatchApp = async () => { async function unpatchApp() {
console.log(chalk.blueBright`[+] HTTP Toolkit Unpatcher Started`); console.log(chalk.blueBright`[+] HTTP Toolkit Unpatcher Started`);
// Step 1: Find app path // Step 1: Find app path
@@ -236,13 +337,7 @@ const unpatchApp = async () => {
} }
console.log(chalk.yellowBright`[!] Administrator/sudo privileges required for unpatching`); console.log(chalk.yellowBright`[!] Administrator/sudo privileges required for unpatching`);
const answer = await prompt("Do you want to request elevated permissions? [Y/n]: "); await requestElevation();
if (!answer || answer.toLowerCase() === "y" || answer.toLowerCase() === "yes") {
await requestElevation();
} else {
console.log(chalk.redBright`[-] Cannot proceed without write permissions`);
process.exit(1);
}
} }
// Step 4: Check if backup exists // Step 4: Check if backup exists
@@ -277,10 +372,10 @@ const unpatchApp = async () => {
} }
console.log(chalk.greenBright`[+] Successfully unpatched!`); console.log(chalk.greenBright`[+] Successfully unpatched!`);
}; }
// Main patching function // Main patching function
const patchApp = async () => { async function patchApp() {
console.log(chalk.blueBright`[+] HTTP Toolkit Patcher Started`); console.log(chalk.blueBright`[+] HTTP Toolkit Patcher Started`);
// Step 1: Find app path // Step 1: Find app path
@@ -305,13 +400,7 @@ const patchApp = async () => {
} }
console.log(chalk.yellowBright`[!] Administrator/sudo privileges required for patching`); console.log(chalk.yellowBright`[!] Administrator/sudo privileges required for patching`);
const answer = await prompt("Do you want to request elevated permissions? [Y/n]: "); await requestElevation();
if (!answer || answer.toLowerCase() === "y" || answer.toLowerCase() === "yes") {
await requestElevation();
} else {
console.log(chalk.redBright`[-] Cannot proceed without write permissions`);
process.exit(1);
}
} }
// Step 4: Backup app.asar // Step 4: Backup app.asar
@@ -329,30 +418,71 @@ const patchApp = async () => {
asar.extractAll(asarPath, extractPath); asar.extractAll(asarPath, extractPath);
console.log(chalk.greenBright`[+] Extracted to ${extractPath}`); console.log(chalk.greenBright`[+] Extracted to ${extractPath}`);
// Step 6: Check if preload.js exists // Step 6: Check if preload.cjs exists
const preloadPath = path.join(extractPath, "build", "preload.js"); const preloadPath = path.join(extractPath, "build", "preload.cjs");
if (!fs.existsSync(preloadPath)) { if (!fs.existsSync(preloadPath)) {
console.error(chalk.redBright`[-] preload.js not found at ${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); rm(extractPath);
process.exit(1); process.exit(1);
} }
console.log(chalk.greenBright`[+] Found preload.cjs`);
// Step 7: Fetch inject.js from GitHub // Step 7: Read inject.js from local file (same directory as this script)
console.log(chalk.yellowBright`[+] Fetching inject code from GitHub...`); console.log(chalk.yellowBright`[+] Reading inject code from local file...`);
const injectCode = await fetchUrl("https://raw.githubusercontent.com/xenos1337/httptoolkit-patcher/refs/heads/master/inject.js"); const injectJsPath = path.join(path.dirname(__filename), "inject.js");
if (!injectCode || !injectCode.includes("injectPageContextHooks")) { if (!fs.existsSync(injectJsPath)) {
console.error(chalk.redBright`[-] Failed to fetch inject.js from GitHub`); console.error(chalk.redBright`[-] inject.js not found at ${injectJsPath}`);
rm(extractPath); rm(extractPath);
process.exit(1); 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.js 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"); let preloadContent = fs.readFileSync(preloadPath, "utf-8");
const isPatched = preloadContent.includes("injectPageContextHooks"); const isPreloadPatched = preloadContent.includes("loadInjectScript");
if (isPatched) { if (isPreloadPatched) {
console.log(chalk.yellowBright`[!] File already patched`); console.log(chalk.yellowBright`[!] Files already patched`);
const answer = await prompt("Do you want to repatch? (y/n): "); const answer = await prompt("Do you want to repatch? (y/n): ");
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") { if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
@@ -361,36 +491,39 @@ const patchApp = async () => {
process.exit(0); process.exit(0);
} }
// Replace the existing injectPageContextHooks function // Remove existing patches
console.log(chalk.yellowBright`[+] Replacing existing patch...`); console.log(chalk.yellowBright`[+] Replacing existing patches...`);
const functionRegex = /\(function injectPageContextHooks\(\) \{[\s\S]*?\}\)\(\);/;
preloadContent = preloadContent.replace(functionRegex, injectCode); // Remove preload patch
} else { const preloadPatchRegex = /\n?\(function loadInjectScript\(\) \{[\s\S]*?\}\)\(\);/;
// Find line with electron_1 and insert inject code below it preloadContent = preloadContent.replace(preloadPatchRegex, "");
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 preload.js`);
rm(extractPath);
process.exit(1);
}
lines.splice(insertIndex, 0, injectCode);
preloadContent = lines.join("\n");
} }
// Step 9: Write patched preload.js // 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"); fs.writeFileSync(preloadPath, preloadContent, "utf-8");
console.log(chalk.greenBright`[+] preload.js patched successfully`); console.log(chalk.greenBright`[+] ${path.basename(preloadPath)} patched successfully`);
// Step 10: Repackage app.asar // Step 10: Repackage app.asar
console.log(chalk.yellowBright`[+] Repackaging app.asar...`); console.log(chalk.yellowBright`[+] Repackaging app.asar...`);
@@ -417,25 +550,33 @@ const patchApp = async () => {
shell: true, shell: true,
detached: true, detached: true,
}); });
child.unref(); // Completely detach the child process child.unref();
console.log(chalk.greenBright`[+] HTTP Toolkit launched successfully`); console.log(chalk.greenBright`[+] HTTP Toolkit launched successfully`);
} }
} catch (e) { } catch (e) {
console.error(chalk.yellowBright`[!] Could not auto-start HTTP Toolkit: ${e.message}`); console.error(chalk.yellowBright`[!] Could not auto-start HTTP Toolkit: ${e.message}`);
console.log(chalk.blueBright`[+] Please start HTTP Toolkit manually`); console.log(chalk.blueBright`[+] Please start HTTP Toolkit manually`);
} }
}; }
// Parse command line arguments
const args = process.argv.slice(2); const args = process.argv.slice(2);
const command = args[0]; const command = args[0];
// Get the command name for display const commandName = (() => {
const commandName = process.pkg ? path.basename(__filename) : 'node index.js'; // @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 // Run the appropriate command
(async () => { (async () => {
try { try {
// Check for updates from GitHub
await checkForUpdates();
if (command === "unpatch" || command === "restore") { if (command === "unpatch" || command === "restore") {
await unpatchApp(); await unpatchApp();
} else if (command === "help" || command === "-h" || command === "--help") { } else if (command === "help" || command === "-h" || command === "--help") {
+150 -188
View File
@@ -1,206 +1,168 @@
(function injectPageContextHooks() { (function () {
const injectionCode = ` console.log("[PAGE-INJECT] Installing hooks in page context");
(function() {
console.log("[PAGE-INJECT] Installing hooks in page context"); const propertyHooks = {
isPaidUser: true,
const propertyHooks = { isLoggedIn: true,
isPaidUser: true, userHasSubscription: true,
isLoggedIn: true, userEmail: "hi@httptoolkit.com",
userHasSubscription: true, mightBePaidUser: true,
userEmail: "hi@httptoolkit.com", isPastDueUser: false,
mightBePaidUser: true, isStatusUnexpired: true,
isPastDueUser: false, userSubscription: {
userSubscription: { state: "fulfilled",
status: "active", status: "active",
plan: "pro", plan: "pro",
sku: "sku", sku: "sku",
tierCode: "pro", tierCode: "pro",
interval: "monthly", interval: "monthly",
quantity: 1, quantity: 1,
expiry: new Date("2999-01-01"), expiry: new Date(new Date().setFullYear(new Date().getFullYear() + 10)),
updateBillingDetailsUrl: "https://httptoolkit.com/", updateBillingDetailsUrl: "https://httptoolkit.com/",
cancelSubscriptionUrl: "https://httptoolkit.com/", cancelSubscriptionUrl: "https://httptoolkit.com/",
lastReceiptUrl: "https://httptoolkit.com/", lastReceiptUrl: "https://httptoolkit.com/",
canManageSubscription: true 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(); // Periodically scan and patch existing objects
function scanAndPatch() {
// Override Object.defineProperty to intercept all property definitions // Search through window and common store locations
const originalDefineProperty = Object.defineProperty; const searchPaths = [window, window.accountStore, window.stores && window.stores.accountStore, window.appState && window.appState.accountStore];
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 searchPaths.forEach((obj, idx) => {
const originalDefineProperties = Object.defineProperties; if (!obj || hookedObjects.has(obj)) return;
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 try {
function scanAndPatch() { Object.keys(propertyHooks).forEach(prop => {
// 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 { try {
Object.keys(propertyHooks).forEach(prop => { const desc = Object.getOwnPropertyDescriptor(obj, prop);
try { if (desc && desc.configurable) {
const desc = Object.getOwnPropertyDescriptor(obj, prop); console.log("[PAGE-INJECT] Found " + prop + " on object #" + idx + ", patching...");
if (desc && desc.configurable) {
console.log("[PAGE-INJECT] Found " + prop + " on object #" + idx + ", patching..."); if (desc.get) {
const originalGetter = desc.get;
if (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; const originalGetter = desc.get;
Object.defineProperty(obj, prop, { Object.defineProperty(store, prop, {
get: function() { get: function () {
const originalValue = originalGetter.call(this); 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]; return propertyHooks[prop];
}, },
set: desc.set, set: desc.set,
configurable: true, 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) {}
} catch (e) { });
// Ignore individual property errors hookedObjects.add(store);
} }
});
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) {} } catch (e) {}
} }
} 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);
}
} }
// Try to inject immediately if DOM is ready, otherwise wait // Run initial scan
if (document.documentElement) { scanAndPatch();
injectScript();
} else { // Scan periodically for late-initialized stores
// Wait for document to be created let scanCount = 0;
const observer = new MutationObserver(() => { const scanInterval = setInterval(() => {
if (document.documentElement) { scanCount++;
observer.disconnect(); scanAndPatch();
injectScript();
} if (scanCount >= 50) {
}); clearInterval(scanInterval);
observer.observe(document, { childList: true, subtree: true }); console.log("[PAGE-INJECT] Stopped periodic scanning after 10 attempts");
// Also try on DOMContentLoaded as backup
if (document.addEventListener) {
document.addEventListener('DOMContentLoaded', injectScript, { once: true });
} }
} }, 100);
})();
console.log("[PAGE-INJECT] Hooks installed successfully");
})();
+1 -1963
View File
File diff suppressed because it is too large Load Diff
+4 -16
View File
@@ -1,26 +1,16 @@
{ {
"name": "httptoolkit-patcher", "name": "httptoolkit-patcher",
"version": "2.0.5", "version": "2.0.8",
"description": "HTTP Toolkit Pro Patcher", "description": "HTTP Toolkit Pro Patcher",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
"bin": "index.js", "bin": "index.js",
"author": "xenos1337",
"scripts": { "scripts": {
"start": "node index.js", "start": "node index.js",
"patch": "node index.js patch", "patch": "node index.js patch",
"unpatch": "node index.js unpatch", "unpatch": "node index.js unpatch",
"restore": "node index.js restore", "restore": "node index.js restore"
"build": "esbuild index.js --bundle --platform=node --format=cjs --target=node18 --outfile=dist/bundle.cjs --external:@electron/asar --external:chalk"
},
"pkg": {
"targets": [
"node18-win-x64",
"node18-linux-x64",
"node18-linux-arm64",
"node18-macos-x64",
"node18-macos-arm64"
],
"outputPath": "dist"
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -28,8 +18,6 @@
"chalk": "4" "chalk": "4"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.14.9", "@types/node": "^20.14.9"
"esbuild": "^0.25.11",
"pkg": "^5.8.1"
} }
} }