function littleEndianHexStringToNumber(hexStr) { const bytes = []; for (let i = 0; i < hexStr.length; i += 2) { bytes.push(parseInt(hexStr.substr(i, 2), 16)); } let num = 0n; for (let i = 4; i >= 0; i--) { num = (num << 8n) | BigInt(bytes[i]); } return num; } function numberToLittleEndianHexString(num) { const bytes = []; for (let i = 0; i < 5; i++) { bytes.push(Number(num & 0xFFn)); num >>= 8n; } while (bytes.length < 8) { bytes.push(0); } return bytes.map(b => b.toString(16).padStart(2, '0')).join(''); } function littleEndianHexToU32(hexStr) { return parseInt(hexStr.match(/../g).reverse().join(''), 16); } function extractBrkImmediate(u32) { return (u32 >> 5) & 0xFFFF; } let pid = get_pid(); log(`[GakumasBk] pid = ${pid}`); let attachResponse = send_command(`vAttach;${pid.toString(16)}`); log(`[GakumasBk] attach_response = ${attachResponse}`); let validBreakpoints = 0; let totalBreakpoints = 0; let invalidBreakpoints = 0; while (invalidBreakpoints < 10) { totalBreakpoints++; log(`[GakumasBk] Handling breakpoint ${totalBreakpoints} (valid: ${validBreakpoints})`); let brkResponse = send_command(`c`); log(`[GakumasBk] brkResponse = ${brkResponse}`); let tidMatch = /T[0-9a-f]+thread:(?[0-9a-f]+);/.exec(brkResponse); let tid = tidMatch ? tidMatch.groups['tid'] : null; let pcMatch = /20:(?[0-9a-f]{16});/.exec(brkResponse); let pc = pcMatch ? pcMatch.groups['reg'] : null; let x0Match = /00:(?[0-9a-f]{16});/.exec(brkResponse); let x0 = x0Match ? x0Match.groups['reg'] : null; let x1Match = /01:(?[0-9a-f]{16});/.exec(brkResponse); let x1 = x1Match ? x1Match.groups['reg'] : null; if (!tid || !pc || !x0) { log(`[GakumasBk] Failed to extract registers: tid=${tid}, pc=${pc}, x0=${x0}, x1=${x1}`); invalidBreakpoints++; continue; } const pcNum = littleEndianHexStringToNumber(pc); const x0Num = littleEndianHexStringToNumber(x0); const x1Num = x1 ? littleEndianHexStringToNumber(x1) : 0n; log(`[GakumasBk] tid = ${tid}, pc = ${pcNum.toString(16)}, x0 = ${x0Num.toString(16)}, x1 = ${x1Num.toString(16)}`); let instructionResponse = send_command(`m${pcNum.toString(16)},4`); log(`[GakumasBk] instruction at pc: ${instructionResponse}`); let instrU32 = littleEndianHexToU32(instructionResponse); let brkImmediate = extractBrkImmediate(instrU32); log(`[GakumasBk] BRK immediate: 0x${brkImmediate.toString(16)} (${brkImmediate})`); if (brkImmediate !== 0x69 && brkImmediate !== 0x70 && brkImmediate !== 0x71) { log(`[GakumasBk] Skipping: BRK immediate not 0x69 or 0x70 or 0x71 (was 0x${brkImmediate.toString(16)})`); invalidBreakpoints++; continue; } invalidBreakpoints = 0; if (brkImmediate === 0x69) { // usual jit mapping log(`[GakumasBk] Received command to process JIT mapping (0x69)`); let jitPageAddress = x0Num; let size = x1Num > 0n ? x1Num : 0x10000n; // allocate 64 KB if x1 somehow is 0 log(`[GakumasBk] Got RX page address: 0x${jitPageAddress.toString(16)}, preparing region with 0x${size.toString(16)} bytes!`); let prepareJITPageResponse = prepare_memory_region(Number(jitPageAddress), Number(size)); // Unsure if this is specific to iOS26 but this func doesnt take in a BigInt, resulting in an error unless converted to a number. I'm not sure how other scripts don't have this problem. log(`[GakumasBk] prepareJITPageResponse = ${prepareJITPageResponse}`); let pcPlus4 = numberToLittleEndianHexString(pcNum + 4n); let pcPlus4Response = send_command(`P20=${pcPlus4};thread:${tid};`); log(`[GakumasBk] pcPlus4Response = ${pcPlus4Response}`); validBreakpoints++; } else if (brkImmediate === 0x70) { // patching instructs log(`[GakumasBk] Received command to patch instructions (0x70)`); let x2Match = /02:(?[0-9a-f]{16});/.exec(brkResponse); let x2 = x2Match ? x2Match.groups['reg'] : null; if (!x1 || !x2) { log(`[GakumasBk] Missing x1 or x2 for function patching`); continue; } let destAddr = x0Num; let srcAddr = x1Num; let size = x2 ? littleEndianHexStringToNumber(x2) : 10n; log(`[GakumasBk] Patching: dest=0x${destAddr.toString(16)}, src=0x${srcAddr.toString(16)}, size=0x${size.toString(16)}`); // Unsure exactly why, but anything over 4 MB freezes the app for some reason, so we will set a soft limit if (size > 0x400000n) { log(`[GakumasBk] Size too large (0x${size.toString(16)}), skipping`); let pcPlus4 = numberToLittleEndianHexString(pcNum + 4n); let pcPlus4Response = send_command(`P20=${pcPlus4};thread:${tid};`); log(`[GakumasBk] pcPlus4Response = ${pcPlus4Response}`); validBreakpoints++; continue; } try { // m (read) = `m${curPointer.toString(16)},` // M (write) = `M${curPointer.toString(16)},:` const CHUNK_SIZE = 0x4000n; // 16 KB for (let i = 0n; i < size; i += CHUNK_SIZE) { let chunkSize = i + CHUNK_SIZE <= size ? CHUNK_SIZE : size - i; let readAddr = srcAddr + i; let writeAddr = destAddr + i; let readRes = send_command(`m${readAddr.toString(16)},${chunkSize.toString(16)}`); if (readRes && readRes.length > 0) { let writeResponse = send_command(`M${writeAddr.toString(16)},${chunkSize.toString(16)}:${readRes}`); if (writeResponse !== "OK") { log(`[GakumasBk] Write failed at offset ${i.toString(16)}`); break; } } if (Number(i / CHUNK_SIZE) % 10 === 0) { log(`[GakumasBk] Progress: 0x${i.toString(16)}/0x${size.toString(16)}`); } } log(`[GakumasBk] Memory write completed!`); } catch (e) { log(`[GakumasBk] Memory write failed: ${e}`); } let pcPlus4 = numberToLittleEndianHexString(pcNum + 4n); let pcPlus4Response = send_command(`P20=${pcPlus4};thread:${tid};`); log(`[GakumasBk] pcPlus4Response = ${pcPlus4Response}`); validBreakpoints++; } else if (brkImmediate === 0x71) { // detach, might be unnecessary break; } log(`[GakumasBk] Completed breakpoint ${validBreakpoints}`); } log(`[GakumasBk] Stopping script (Received 0x71 or too many invalid breakpoints)`); let detachResponse = send_command(`D`); log(`[GakumasBk] detachResponse = ${detachResponse}`);