2025-10-23 20:31:50 +02:00
// @ts-check
import { spawn , execSync } from "child_process" ;
2026-03-21 17:08:12 +07:00
import * as asar from "@electron/asar" ;
2025-10-23 20:31:50 +02:00
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
2025-11-24 05:47:59 +01:00
// @ts-ignore - pkg adds this property at runtime
2025-10-23 20:31:50 +02:00
if ( process . pkg ) {
return process . execPath ;
}
2025-11-24 05:47:59 +01:00
// ESM: use import.meta.url
return fileURLToPath ( import . meta . url );
2025-10-23 20:31:50 +02:00
})();
2025-11-29 00:06:55 +01:00
// 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" ;
}
})();
2025-10-23 20:31:50 +02:00
// Check if running with elevated privileges
2025-11-24 05:47:59 +01:00
function isElevated () {
2025-10-23 20:31:50 +02:00
if ( isWin ) {
try {
execSync ( "net session" , { stdio : "ignore" });
return true ;
} catch {
return false ;
}
} else {
return process . getuid && process . getuid () === 0 ;
}
2025-11-24 05:47:59 +01:00
}
2025-10-23 20:31:50 +02:00
// Helper function to prompt user for input
2025-11-24 05:47:59 +01:00
function prompt ( question ) {
2025-10-23 20:31:50 +02:00
const rl = readline . createInterface ({
input : process . stdin ,
output : process . stdout ,
});
return new Promise ( resolve => {
rl . question ( question , answer => {
rl . close ();
resolve ( answer );
});
});
2025-11-24 05:47:59 +01:00
}
2025-10-23 20:31:50 +02:00
2025-11-29 00:06:24 +01:00
/**
* Fetch JSON from a URL
* @param {string} url
* @returns {Promise<Array<{name: string}>>}
*/
function fetchJson ( url ) {
return new Promise (( resolve , reject ) => {
const options = {
headers : {
"User-Agent" : "httptoolkit-patcher" ,
2025-12-18 17:38:56 +01:00
Accept : "application/vnd.github.v3+json" ,
2025-11-29 00:06:24 +01:00
},
};
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 );
2025-12-18 17:38:56 +01:00
2025-11-29 00:06:24 +01:00
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` );
2025-12-18 17:38:56 +01:00
2025-11-29 00:06:24 +01:00
if ( ! tags || tags . length === 0 ) {
return ; // No tags found, skip update check
}
2025-12-18 17:38:56 +01:00
2025-11-29 00:06:24 +01:00
// Tags are returned in order, first one is the latest
const latestTag = tags [ 0 ]. name ;
const latestVersion = latestTag . replace ( /^v/ , "" );
2025-12-18 17:38:56 +01:00
2025-11-29 00:06:24 +01:00
if ( compareVersions ( latestVersion , LOCAL_VERSION ) > 0 ) {
2026-03-21 17:08:12 +07:00
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' ));
2025-11-29 00:06:24 +01:00
}
} catch ( e ) {
// Silently ignore update check errors (network issues, etc.)
}
}
2025-10-23 20:31:50 +02:00
// Helper function to remove directory recursively
2025-11-24 05:47:59 +01:00
function rm ( dirPath ) {
2025-10-23 20:31:50 +02:00
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 );
2025-11-24 05:47:59 +01:00
}
2025-10-23 20:31:50 +02:00
// Find HTTP Toolkit installation path
2025-11-24 05:47:59 +01:00
async function findAppPath () {
2025-12-18 17:38:56 +01:00
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" ];
2025-10-23 20:31:50 +02:00
for ( const p of possiblePaths ) {
if ( fs . existsSync ( path . join ( p , "app.asar" ))) {
return p ;
}
}
2026-03-21 17:08:12 +07:00
console . log ( chalk . yellowBright ( '[!] HTTP Toolkit not found in default locations' ));
2025-10-23 20:31:50 +02:00
const userPath = await prompt ( "Please enter the path to HTTP Toolkit executable/app: " );
if ( ! userPath ) {
2026-03-21 17:08:12 +07:00
console . error ( chalk . redBright ( '[-] No path provided' ));
2025-10-23 20:31:50 +02:00
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" ))) {
2026-03-21 17:08:12 +07:00
console . error ( chalk . redBright ( `[-] app.asar not found at ${ resourcesPath } ` ));
2025-10-23 20:31:50 +02:00
process . exit ( 1 );
}
return resourcesPath ;
2025-11-24 05:47:59 +01:00
}
2025-10-23 20:31:50 +02:00
// Request elevated permissions
2025-11-24 05:47:59 +01:00
async function requestElevation () {
2026-03-21 17:08:12 +07:00
console . log ( chalk . yellowBright ( '[!] Requesting elevated permissions...' ));
2025-10-23 20:31:50 +02:00
2025-11-24 05:47:59 +01:00
// @ts-ignore - pkg adds this property at runtime
const isBundled = process . pkg ;
2025-10-23 20:31:50 +02:00
if ( isWin ) {
// Windows: Use PowerShell to run as administrator
2025-11-24 05:47:59 +01:00
let script ;
if ( isBundled ) {
script = `Start-Process -FilePath " ${ __filename } " -Verb RunAs` ;
} else {
script = `Start-Process -FilePath "node" -ArgumentList " ${ __filename } " -Verb RunAs` ;
}
2025-10-23 20:31:50 +02:00
try {
2026-03-21 17:08:12 +07:00
console . log ( chalk . greenBright ( `[+] Spawning PowerShell with script: ${ script } ` ));
2025-11-24 05:47:59 +01:00
execSync ( `powershell -Command " ${ script } "` , { stdio : "inherit" });
2026-03-21 17:08:12 +07:00
console . log ( chalk . blueBright ( '[+] Restarting with administrator privileges...' ));
2025-10-23 20:31:50 +02:00
process . exit ( 0 );
} catch ( e ) {
2026-03-21 17:08:12 +07:00
console . error ( chalk . redBright ( `[-] Failed to elevate permissions: ${ e . message } ` ));
console . error ( chalk . redBright ( '[-] Please run as administrator manually' ));
2025-10-23 20:31:50 +02:00
process . exit ( 1 );
}
2025-11-12 19:01:34 +01:00
} else if ( isLinux ) {
// Linux: Cannot auto-elevate with sudo, show instructions instead
2026-03-21 17:08:12 +07:00
console . log ( chalk . yellowBright ( '[!] Elevated permissions are required for patching on Linux' ));
console . log ( chalk . yellowBright ( '[!] Please re-run this script with sudo:' ));
2025-11-24 05:47:59 +01:00
if ( isBundled ) {
2026-03-21 17:08:12 +07:00
console . log ( chalk . blueBright ( ` sudo ${ __filename } ` ));
2025-11-24 05:47:59 +01:00
} else {
2026-03-21 17:08:12 +07:00
console . log ( chalk . blueBright ( ` sudo node ${ __filename } ` ));
2025-11-24 05:47:59 +01:00
}
2025-11-12 19:01:34 +01:00
process . exit ( 1 );
2025-10-23 20:31:50 +02:00
} else {
2025-11-12 19:01:34 +01:00
// macOS: Try to elevate with sudo
2026-03-21 17:08:12 +07:00
console . log ( chalk . blueBright ( '[+] Restarting with sudo...' ));
2025-10-23 20:31:50 +02:00
try {
2025-11-24 05:47:59 +01:00
let child ;
if ( isBundled ) {
child = spawn ( "sudo" , [ __filename ], {
stdio : "inherit" ,
});
} else {
child = spawn ( "sudo" , [ "node" , __filename ], {
stdio : "inherit" ,
});
}
2025-10-23 20:31:50 +02:00
child . on ( "exit" , code => process . exit ( code || 0 ));
} catch ( e ) {
2026-03-21 17:08:12 +07:00
console . error ( chalk . redBright ( `[-] Failed to elevate permissions: ${ e . message } ` ));
console . error ( chalk . redBright ( '[-] Please run with sudo manually' ));
2025-10-23 20:31:50 +02:00
process . exit ( 1 );
}
}
2025-11-24 05:47:59 +01:00
}
2025-10-23 20:31:50 +02:00
// Check if we have write permissions
2025-11-24 05:47:59 +01:00
function checkPermissions ( filePath ) {
2025-10-23 20:31:50 +02:00
try {
2025-11-24 05:47:59 +01:00
// Check write access to the file/directory
2025-10-23 20:31:50 +02:00
fs . accessSync ( filePath , fs . constants . W_OK );
2025-12-18 17:38:56 +01:00
2025-11-24 05:47:59 +01:00
// 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 ) {
2026-03-21 17:08:12 +07:00
console . error ( chalk . redBright ( `[-] Cannot create directories in ${ path . dirname ( filePath ) } : ${ dirError . message } ` ));
2025-11-24 05:47:59 +01:00
return false ;
}
2025-12-18 17:38:56 +01:00
2026-03-21 17:08:12 +07:00
console . log ( chalk . greenBright ( `[+] Permissions check passed for ${ filePath } ` ));
2025-10-23 20:31:50 +02:00
return true ;
} catch ( e ) {
2026-03-21 17:08:12 +07:00
console . error ( chalk . redBright ( `[-] Permissions check failed for ${ filePath } : ${ e . message } ` ));
2025-10-23 20:31:50 +02:00
return false ;
}
2025-11-24 05:47:59 +01:00
}
2025-10-23 20:31:50 +02:00
// Kill HTTP Toolkit processes
2025-11-24 05:47:59 +01:00
async function killHttpToolkit () {
2026-03-21 17:08:12 +07:00
console . log ( chalk . yellowBright ( '[+] Checking for running HTTP Toolkit processes...' ));
2025-10-23 20:31:50 +02:00
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" )) {
2026-03-21 17:08:12 +07:00
console . log ( chalk . yellowBright ( '[!] HTTP Toolkit is running, attempting to close it...' ));
2025-10-23 20:31:50 +02:00
execSync ( 'taskkill /F /IM "HTTP Toolkit.exe" /T' , { stdio : "ignore" });
2026-03-21 17:08:12 +07:00
console . log ( chalk . greenBright ( '[+] HTTP Toolkit processes terminated' ));
2025-10-23 20:31:50 +02:00
// Wait a moment for the process to fully close
await new Promise ( resolve => setTimeout ( resolve , 2000 ));
} else {
2026-03-21 17:08:12 +07:00
console . log ( chalk . greenBright ( '[+] HTTP Toolkit is not running' ));
2025-10-23 20:31:50 +02:00
}
} else if ( isMac ) {
// macOS: Use pgrep and pkill
try {
execSync ( 'pgrep -f "HTTP Toolkit"' , { stdio : "ignore" });
2026-03-21 17:08:12 +07:00
console . log ( chalk . yellowBright ( '[!] HTTP Toolkit is running, attempting to close it...' ));
2025-10-23 20:31:50 +02:00
execSync ( 'pkill -f "HTTP Toolkit"' , { stdio : "ignore" });
2026-03-21 17:08:12 +07:00
console . log ( chalk . greenBright ( '[+] HTTP Toolkit processes terminated' ));
2025-10-23 20:31:50 +02:00
await new Promise ( resolve => setTimeout ( resolve , 2000 ));
} catch ( e ) {
2026-03-21 17:08:12 +07:00
console . log ( chalk . greenBright ( '[+] HTTP Toolkit is not running' ));
2025-10-23 20:31:50 +02:00
}
} else {
// Linux: Use pgrep and pkill
try {
execSync ( 'pgrep -f "HTTP Toolkit"' , { stdio : "ignore" });
2026-03-21 17:08:12 +07:00
console . log ( chalk . yellowBright ( '[!] HTTP Toolkit is running, attempting to close it...' ));
2025-10-23 20:31:50 +02:00
execSync ( 'pkill -f "HTTP Toolkit"' , { stdio : "ignore" });
2026-03-21 17:08:12 +07:00
console . log ( chalk . greenBright ( '[+] HTTP Toolkit processes terminated' ));
2025-10-23 20:31:50 +02:00
await new Promise ( resolve => setTimeout ( resolve , 2000 ));
} catch ( e ) {
2026-03-21 17:08:12 +07:00
console . log ( chalk . greenBright ( '[+] HTTP Toolkit is not running' ));
2025-10-23 20:31:50 +02:00
}
}
} catch ( e ) {
2026-03-21 17:08:12 +07:00
console . log ( chalk . yellowBright ( `[!] Could not check/kill processes: ${ e . message } ` ));
console . log ( chalk . yellowBright ( '[!] If HTTP Toolkit is running, please close it manually' ));
2025-10-23 20:31:50 +02:00
}
2025-11-24 05:47:59 +01:00
}
2025-10-23 20:31:50 +02:00
2025-12-18 17:38:56 +01:00
// 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" ));
}
2026-07-11 00:19:02 +02:00
}, 30000 );
2025-12-18 17:38:56 +01:00
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 ;
}
2026-03-21 17:08:12 +07:00
// Replace the asar integrity hash inside the macOS Info.plist
function patchInfoPlistHash ( resourcesPath , originalHash , newHash ) {
const basePath = getBinaryBasePath ( resourcesPath );
const plistPath = path . join ( basePath , "Info.plist" );
if ( ! fs . existsSync ( plistPath )) {
throw new Error ( `Info.plist not found at ${ plistPath } ` );
}
let content = fs . readFileSync ( plistPath , "utf-8" );
if ( ! content . includes ( originalHash )) {
throw new Error ( "Original hash not found in Info.plist" );
}
content = content . replaceAll ( originalHash , newHash );
fs . writeFileSync ( plistPath , content , "utf-8" );
}
2025-10-23 20:31:50 +02:00
// Unpatch/restore function
2025-11-24 05:47:59 +01:00
async function unpatchApp () {
2026-03-21 17:08:12 +07:00
console . log ( chalk . blueBright ( '[+] HTTP Toolkit Unpatcher Started' ));
2025-10-23 20:31:50 +02:00
const appPath = await findAppPath ();
2026-03-21 17:08:12 +07:00
console . log ( chalk . greenBright ( `[+] HTTP Toolkit found at ${ appPath } ` ));
2025-10-23 20:31:50 +02:00
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 ) {
2026-03-21 17:08:12 +07:00
console . log ( chalk . yellowBright ( `[!] No write permissions for ${ appPath } ` ));
2025-10-23 20:31:50 +02:00
if ( isElevated ()) {
2026-03-21 17:08:12 +07:00
console . error ( chalk . redBright ( '[-] Still no permissions even with elevated privileges' ));
2025-10-23 20:31:50 +02:00
process . exit ( 1 );
}
2026-03-21 17:08:12 +07:00
console . log ( chalk . yellowBright ( '[!] Administrator/sudo privileges required for unpatching' ));
2025-11-24 05:47:59 +01:00
await requestElevation ();
2025-10-23 20:31:50 +02:00
}
if ( ! fs . existsSync ( backupPath )) {
2026-03-21 17:08:12 +07:00
console . error ( chalk . redBright ( `[-] Backup file not found at ${ backupPath } ` ));
console . error ( chalk . redBright ( '[-] Cannot unpatch without backup file' ));
2025-10-23 20:31:50 +02:00
process . exit ( 1 );
}
2026-03-21 17:08:12 +07:00
console . log ( chalk . yellowBright ( '[+] Restoring from backup...' ));
2025-10-23 20:31:50 +02:00
try {
fs . copyFileSync ( backupPath , asarPath );
2026-03-21 17:08:12 +07:00
console . log ( chalk . greenBright ( '[+] Restored app.asar from backup' ));
2025-10-23 20:31:50 +02:00
} catch ( e ) {
2026-03-21 17:08:12 +07:00
console . error ( chalk . redBright ( `[-] Failed to restore backup: ${ e . message } ` ));
2025-10-23 20:31:50 +02:00
process . exit ( 1 );
}
if ( fs . existsSync ( extractPath )) {
2026-03-21 17:08:12 +07:00
console . log ( chalk . yellowBright ( '[+] Removing extracted files...' ));
2025-10-23 20:31:50 +02:00
rm ( extractPath );
2026-03-21 17:08:12 +07:00
console . log ( chalk . greenBright ( '[+] Cleaned up extracted files' ));
2025-10-23 20:31:50 +02:00
}
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 });
2026-03-21 17:08:12 +07:00
console . log ( chalk . greenBright ( '[+] Backup file removed' ));
2025-10-23 20:31:50 +02:00
}
2026-03-21 17:08:12 +07:00
console . log ( chalk . greenBright ( '[+] Successfully unpatched!' ));
2025-11-24 05:47:59 +01:00
}
2025-10-23 20:31:50 +02:00
// Main patching function
2025-11-24 05:47:59 +01:00
async function patchApp () {
2026-03-21 17:08:12 +07:00
console . log ( chalk . blueBright ( '[+] HTTP Toolkit Patcher Started' ));
2025-10-23 20:31:50 +02:00
const appPath = await findAppPath ();
2026-03-21 17:08:12 +07:00
console . log ( chalk . greenBright ( `[+] HTTP Toolkit found at ${ appPath } ` ));
2025-10-23 20:31:50 +02:00
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 ) {
2026-03-21 17:08:12 +07:00
console . log ( chalk . yellowBright ( `[!] No write permissions for ${ appPath } ` ));
2025-10-23 20:31:50 +02:00
if ( isElevated ()) {
2026-03-21 17:08:12 +07:00
console . error ( chalk . redBright ( '[-] Still no permissions even with elevated privileges' ));
2025-10-23 20:31:50 +02:00
process . exit ( 1 );
}
2026-03-21 17:08:12 +07:00
console . log ( chalk . yellowBright ( '[!] Administrator/sudo privileges required for patching' ));
2025-11-24 05:47:59 +01:00
await requestElevation ();
2025-10-23 20:31:50 +02:00
}
const backupPath = path . join ( appPath , "app.asar.bak" );
if ( ! fs . existsSync ( backupPath )) {
2026-03-21 17:08:12 +07:00
console . log ( chalk . yellowBright ( '[+] Creating backup...' ));
2025-10-23 20:31:50 +02:00
fs . copyFileSync ( asarPath , backupPath );
2026-03-21 17:08:12 +07:00
console . log ( chalk . greenBright ( `[+] Backup created at ${ backupPath } ` ));
2025-10-23 20:31:50 +02:00
}
const extractPath = path . join ( appPath , "app.asar_extracted" );
2026-03-21 17:08:12 +07:00
console . log ( chalk . yellowBright ( '[+] Extracting app.asar...' ));
2025-10-23 20:31:50 +02:00
rm ( extractPath );
asar . extractAll ( asarPath , extractPath );
2026-03-21 17:08:12 +07:00
console . log ( chalk . greenBright ( `[+] Extracted to ${ extractPath } ` ));
2025-10-23 20:31:50 +02:00
2025-11-29 00:04:31 +01:00
const preloadPath = path . join ( extractPath , "build" , "preload.cjs" );
2025-10-23 20:31:50 +02:00
if ( ! fs . existsSync ( preloadPath )) {
2026-03-21 17:08:12 +07:00
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/' ));
2025-11-29 00:04:31 +01:00
rm ( extractPath );
process . exit ( 1 );
2025-10-23 20:31:50 +02:00
}
2026-03-21 17:08:12 +07:00
console . log ( chalk . greenBright ( '[+] Found preload.cjs' ));
2025-10-23 20:31:50 +02:00
2026-03-21 17:08:12 +07:00
console . log ( chalk . yellowBright ( '[+] Reading inject code from local file...' ));
2025-11-29 00:04:31 +01:00
const injectJsPath = path . join ( path . dirname ( __filename ), "inject.js" );
if ( ! fs . existsSync ( injectJsPath )) {
2026-03-21 17:08:12 +07:00
console . error ( chalk . redBright ( `[-] inject.js not found at ${ injectJsPath } ` ));
2025-10-23 20:31:50 +02:00
rm ( extractPath );
process . exit ( 1 );
}
2025-11-29 00:04:31 +01:00
const injectCode = fs . readFileSync ( injectJsPath , "utf-8" );
if ( ! injectCode || ! injectCode . includes ( "PAGE-INJECT" )) {
2026-03-21 17:08:12 +07:00
console . error ( chalk . redBright ( '[-] Invalid inject.js file' ));
2025-11-29 00:04:31 +01:00
rm ( extractPath );
process . exit ( 1 );
}
2026-03-21 17:08:12 +07:00
console . log ( chalk . greenBright ( '[+] Inject code loaded successfully' ));
2025-11-29 00:04:31 +01:00
2025-12-11 21:31:42 +02:00
let preloadContent = fs . readFileSync ( preloadPath , "utf-8" );
const electronVarName = preloadContent . includes ( "electron_1" ) ? "electron_1" : "electron" ;
2026-03-21 17:08:12 +07:00
console . log ( chalk . greenBright ( `[+] Detected electron variable: ${ electronVarName } ` ));
2025-12-11 21:31:42 +02:00
2025-11-29 00:04:31 +01:00
const preloadPatchCode = `
(function loadInjectScript() {
const injectCode = ${ JSON . stringify ( injectCode ) } ;
2026-03-21 17:08:12 +07:00
2025-11-29 00:04:31 +01:00
function injectViaWebFrame() {
try {
2025-12-11 21:31:42 +02:00
const { webFrame } = ${ electronVarName } ;
2025-11-29 00:04:31 +01:00
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;
}
2026-03-21 17:08:12 +07:00
2025-11-29 00:04:31 +01:00
if (!injectViaWebFrame()) {
const tryInject = () => {
if (!injectViaWebFrame()) {
console.error("[PRELOAD] All injection methods failed");
}
};
2026-03-21 17:08:12 +07:00
2025-11-29 00:04:31 +01:00
if (document.readyState === 'complete' || document.readyState === 'interactive') {
tryInject();
} else {
document.addEventListener('DOMContentLoaded', tryInject, { once: true });
}
}
})();
` ;
const isPreloadPatched = preloadContent . includes ( "loadInjectScript" );
2025-10-23 20:31:50 +02:00
2025-11-29 00:04:31 +01:00
if ( isPreloadPatched ) {
2026-03-21 17:08:12 +07:00
console . log ( chalk . yellowBright ( '[!] Files already patched' ));
2025-10-23 20:31:50 +02:00
const answer = await prompt ( "Do you want to repatch? (y/n): " );
if ( answer . toLowerCase () !== "y" && answer . toLowerCase () !== "yes" ) {
2026-03-21 17:08:12 +07:00
console . log ( chalk . blueBright ( '[+] Patching cancelled' ));
2025-10-23 20:31:50 +02:00
rm ( extractPath );
process . exit ( 0 );
}
2025-11-29 00:04:31 +01:00
// Remove existing patches
2026-03-21 17:08:12 +07:00
console . log ( chalk . yellowBright ( '[+] Replacing existing patches...' ));
2025-12-18 17:38:56 +01:00
2025-11-29 00:04:31 +01:00
// Remove preload patch
const preloadPatchRegex = /\n?\(function loadInjectScript\(\) \{[\s\S]*?\}\)\(\);/ ;
preloadContent = preloadContent . replace ( preloadPatchRegex , "" );
}
2025-10-23 20:31:50 +02:00
2026-03-21 17:08:12 +07:00
console . log ( chalk . yellowBright ( '[+] Applying preload patch...' ));
2025-11-29 00:04:31 +01:00
const preloadLines = preloadContent . split ( "\n" );
let preloadInsertIndex = - 1 ;
for ( let i = 0 ; i < preloadLines . length ; i ++ ) {
2025-12-11 21:31:42 +02:00
const line = preloadLines [ i ];
2025-12-18 17:38:56 +01:00
if ( line . includes ( 'require("electron")' ) || line . includes ( "require('electron')" ) || line . includes ( "electron_1" )) {
2025-11-29 00:04:31 +01:00
preloadInsertIndex = i + 1 ;
break ;
2025-10-23 20:31:50 +02:00
}
2025-11-29 00:04:31 +01:00
}
2025-10-23 20:31:50 +02:00
2025-11-29 00:04:31 +01:00
if ( preloadInsertIndex === - 1 ) {
2026-03-21 17:08:12 +07:00
console . error ( chalk . redBright ( `[-] Could not find insertion point (electron import) in ${ path . basename ( preloadPath ) } ` ));
2025-11-29 00:04:31 +01:00
rm ( extractPath );
process . exit ( 1 );
2025-10-23 20:31:50 +02:00
}
2025-11-29 00:04:31 +01:00
preloadLines . splice ( preloadInsertIndex , 0 , preloadPatchCode );
preloadContent = preloadLines . join ( "\n" );
// Write patched preload file
2025-10-23 20:31:50 +02:00
fs . writeFileSync ( preloadPath , preloadContent , "utf-8" );
2026-03-21 17:08:12 +07:00
console . log ( chalk . greenBright ( `[+] ${ path . basename ( preloadPath ) } patched successfully` ));
2025-10-23 20:31:50 +02:00
2026-03-21 17:08:12 +07:00
console . log ( chalk . yellowBright ( '[+] Repackaging app.asar...' ));
2025-10-23 20:31:50 +02:00
await asar . createPackage ( extractPath , asarPath );
2026-03-21 17:08:12 +07:00
console . log ( chalk . greenBright ( '[+] app.asar repackaged successfully' ));
2025-10-23 20:31:50 +02:00
2025-12-18 17:38:56 +01:00
let executablePath ;
try {
executablePath = getExecutablePath ( appPath );
} catch ( e ) {
rm ( extractPath );
2026-03-21 17:08:12 +07:00
console . error ( chalk . redBright ( `[-] ${ e . message } ` ));
2025-12-18 17:38:56 +01:00
process . exit ( 1 );
}
2026-07-11 00:19:02 +02:00
// macOS: Sign the patched app
if ( isMac ) {
const appBundlePath = path . resolve ( appPath , '..' , '..' );
console . log ( chalk . yellowBright ( '[+] Signing HTTP Toolkit with ad-hoc signature...' ));
try {
execSync ( `codesign --deep --force --sign - " ${ appBundlePath } "` , { stdio : "pipe" });
console . log ( chalk . greenBright ( '[+] HTTP Toolkit signed successfully' ));
console . log ( chalk . yellowBright ( ` Make sure to whitelist the app in privacy and security settings!` ));
} catch ( e ) {
rm ( extractPath );
console . error ( chalk . redBright ( `[!] Failed to sign HTTP Toolkit: ${ e . message } ` ));
process . exit ( 1 );
}
try {
execSync ( `xattr -d com.apple.quarantine " ${ appBundlePath } " 2>/dev/null` , { stdio : 'pipe' });
} catch ( e ) {
console . log ( chalk . redBright ( '[!] Failed to remove the quarantine attribute' ));
}
}
2026-03-21 17:08:12 +07:00
console . log ( chalk . yellowBright ( '[+] Launching HTTP Toolkit to read integrity hashes...' ));
2025-12-18 17:38:56 +01:00
let hashes ;
try {
hashes = await captureIntegrityHashes ( executablePath );
} catch ( e ) {
rm ( extractPath );
2026-03-21 17:08:12 +07:00
console . error ( chalk . redBright ( `[-] Failed to capture integrity hashes: ${ e . message } ` ));
2025-12-18 17:38:56 +01:00
process . exit ( 1 );
}
2026-03-21 17:08:12 +07:00
console . log ( chalk . greenBright ( '[+] Integrity hashes captured' ));
console . log ( chalk . white ( ` Original hash: ${ hashes . originalHash } ` ));
console . log ( chalk . white ( ` New asar hash: ${ hashes . newHash } ` ));
2025-12-18 17:38:56 +01:00
try {
2026-03-21 17:08:12 +07:00
if ( isMac ) {
patchInfoPlistHash ( appPath , hashes . originalHash , hashes . newHash );
console . log ( chalk . greenBright ( '[+] Patched Info.plist hash' ));
} else {
const replacements = patchExecutableHash ( executablePath , hashes . originalHash , hashes . newHash );
console . log ( chalk . greenBright ( `[+] Patched binary hash ( ${ replacements } replacement ${ replacements === 1 ? "" : "s" } )` ));
}
2025-12-18 17:38:56 +01:00
} catch ( e ) {
rm ( extractPath );
2026-03-21 17:08:12 +07:00
console . error ( chalk . redBright ( `[-] Failed to patch hash: ${ e . message } ` ));
2025-12-18 17:38:56 +01:00
process . exit ( 1 );
}
2026-03-21 17:08:12 +07:00
console . log ( chalk . yellowBright ( '[+] Cleaning up temporary files...' ));
2025-10-23 20:31:50 +02:00
rm ( extractPath );
2026-03-21 17:08:12 +07:00
console . log ( chalk . greenBright ( '[+] Successfully patched!' ));
2025-10-23 20:31:50 +02:00
2026-03-21 17:08:12 +07:00
console . log ( chalk . blueBright ( '[+] Opening HTTP Toolkit...' ));
2025-10-23 20:31:50 +02:00
try {
2025-12-18 17:38:56 +01:00
const child = spawn ( executablePath , {
stdio : "ignore" ,
shell : false ,
detached : true ,
});
child . unref ();
2026-03-21 17:08:12 +07:00
console . log ( chalk . greenBright ( '[+] HTTP Toolkit launched successfully' ));
2025-10-23 20:31:50 +02:00
} catch ( e ) {
2026-03-21 17:08:12 +07:00
console . error ( chalk . yellowBright ( `[!] Could not auto-start HTTP Toolkit: ${ e . message } ` ));
console . log ( chalk . blueBright ( '[+] Please start HTTP Toolkit manually' ));
2025-10-23 20:31:50 +02:00
}
2025-11-24 05:47:59 +01:00
}
2025-10-23 20:31:50 +02:00
const args = process . argv . slice ( 2 );
const command = args [ 0 ];
2025-11-24 05:47:59 +01:00
const commandName = (() => {
// @ts-ignore - pkg adds this property at runtime
if ( process . pkg ) {
return path . basename ( __filename );
} else {
return `node ${ process . argv [ 1 ] } ` ;
}
})();
2025-10-23 20:31:50 +02:00
// Run the appropriate command
( async () => {
try {
2025-11-29 00:06:24 +01:00
// Check for updates from GitHub
await checkForUpdates ();
2026-03-21 17:08:12 +07:00
2025-10-23 20:31:50 +02:00
if ( command === "unpatch" || command === "restore" ) {
await unpatchApp ();
} else if ( command === "help" || command === "-h" || command === "--help" ) {
2026-03-21 17:08:12 +07:00
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' ) } ` ));
2025-10-23 20:31:50 +02:00
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" ) {
2026-03-21 17:08:12 +07:00
console . log ( chalk . blueBright ( '[+] Patching cancelled' ));
2025-10-23 20:31:50 +02:00
process . exit ( 0 );
}
await patchApp ();
} else {
2026-03-21 17:08:12 +07:00
console . error ( chalk . redBright ( `[-] Unknown command: ${ command } ` ));
console . log ( chalk . yellowBright ( '[!] Use \'help\' to see available commands' ));
2025-10-23 20:31:50 +02:00
process . exit ( 1 );
}
} catch ( error ) {
2026-03-21 17:08:12 +07:00
console . error ( chalk . redBright ( `[-] Error: ${ error . message } ` ));
2025-10-23 20:31:50 +02:00
process . exit ( 1 );
}
})();