gkms-localify-ios/ScriptsAndBinary/Geode.js

159 lines
6.0 KiB
JavaScript

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:(?<tid>[0-9a-f]+);/.exec(brkResponse);
let tid = tidMatch ? tidMatch.groups['tid'] : null;
let pcMatch = /20:(?<reg>[0-9a-f]{16});/.exec(brkResponse);
let pc = pcMatch ? pcMatch.groups['reg'] : null;
let x0Match = /00:(?<reg>[0-9a-f]{16});/.exec(brkResponse);
let x0 = x0Match ? x0Match.groups['reg'] : null;
let x1Match = /01:(?<reg>[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:(?<reg>[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)},<size>`
// M (write) = `M${curPointer.toString(16)},<size>:<your hex instructions goes here>`
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}`);