StikDebug/StikJIT/Scripts/manic.js
Stephen f3d2dec2e5
Semi-Rewrite – Major Refactor & Codebase Cleanup (#328)
* Change default UPLOAD_IPA value to 'false'

* Part 1 of the great debloat

* Remove Mini Tools, adopt native alerts in StikJITApp, and update HomeView UI

* Refactor views to use native SwiftUI alerts instead of CustomErrorView

* Update ProfileView to use minimal List UI style with native alerts

* Fix up homeview

* Squash: Redesign HomeView, remove Feature Flags, enable all tabs, refactor alerts, and updates

- Redesign HomeView to a simpler style with 'Enable JIT' action.
- Remove 'Help & Info' and 'Quick Launch' from Home.
- Fix DDI false positive by starting heartbeat on HomeView appear.
- Restore robust DDI checks and update app name to 'StikDebug'.
- Remove FeatureFlags and Beta tags; enable 'profiles', 'processes', 'location' tabs by default.
- Remove 'Devices' tab; add 'Target Device IP' field in Settings.
- Hardcode 'Settings' tab to 4th position.
- Refactor 'SettingsView' tab customization to be reorder-only.
- Remove 'Appearance' theme section from Settings.
- Refactor custom error cards to use native SwiftUI/UIKit alerts across the app (HomeView, ConsoleLogsView, ProfileView, etc).
- Restore JS callback support and script execution logic in HomeView.
- Add necessary Color extensions.
- Update project version to 3.0.0.

* Clean up: Remove In-App Purchases, update checker, and unused IPA installer code

* delete more bloat

* fix build issue

* Remove widget to save on App IDs

* Attempt heartbeat restart when Enable JIT button is pressed

* Get rid of PIP

* Update Tab Naming

* remove more bloat

* final changes for today

* Remove unused packages

* UX Improvements

* revert bundle id

* Update README.md

* remove old idevice

* add updated idevice

* Fix ddi false positive errors and add templates for GitHub stuff

* Remove more unused stuff.

* Fix links and update README content

* Update compatibility table in README.md

* Improve performance of console logs

* Remove old code

* Change default UPLOAD_IPA value to 'true'

* remove the welcome sheet

* import support for .mobiledevicepair

* Add back LC JIT stuff

* Fix heartbeat issues with LC JIT

* update the build yml for nightly stuff

* Update README.md

* Update README.md

* Update GitHub Actions workflow for IPA build

* Update GitHub Actions workflow for nightly builds

* Add URL scheme to kill tasks stikjit://kill-process?pid=1234

* fix build issues

* Added App Intents

* Unify the error handling style across the app
2026-02-25 13:44:00 -05:00

176 lines
6.6 KiB
JavaScript

// manic.js - iOS 26 TXM JIT Support Script for Manic EMU Emulator
// Optimized for Manic EMU using Geode.js's infinite loop mode
// Features:
// 1. Only handles brk #0x69 (JIT memory mapping requests)
// 2. Keeps StikDebug connection alive indefinitely
// 3. Supports JIT memory requests for multiple code blocks
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;
}
// Check if the instruction is a BRK instruction
// BRK instruction format: 0xD4200000 | (imm16 << 5)
// The upper 16 bits should be 0xD420
function isBrkInstruction(u32) {
return (u32 >>> 16) === 0xD420;
}
// Format size into a human-readable format
function formatSize(size) {
if (size >= 1024 * 1024) {
return `${(size / (1024 * 1024)).toFixed(2)} MB`;
} else if (size >= 1024) {
return `${(size / 1024).toFixed(2)} KB`;
}
return `${size} bytes`;
}
log(`[Manic EMU] ========================================`);
log(`[Manic EMU] Manic EMU iOS 26 TXM JIT Support Script`);
log(`[Manic EMU] ========================================`);
let pid = get_pid();
log(`[Manic EMU] pid = ${pid}`);
let attachResponse = send_command(`vAttach;${pid.toString(16)}`);
log(`[Manic EMU] attach_response = ${attachResponse}`);
let validBreakpoints = 0;
let totalBreakpoints = 0;
let totalMemoryMapped = 0n;
// Track processed memory areas for debugging.
let mappedRegions = [];
// Infinite Loop - StikDebug Must Stay Connected in TXM Mode
// The Manic EMU emulator dynamically creates multiple code blocks, each of which needs to be marked as executable by StikDebug.
log(`[Manic EMU] Starting infinite loop - StikDebug will stay connected`);
log(`[Manic EMU] Waiting for JIT memory requests (brk #0x69)...`);
while (true) {
totalBreakpoints++;
let brkResponse = send_command(`c`);
// Check Exception Types (metype)
// metype:6 = EXC_BREAKPOINT
let metypeMatch = /metype:(\d+)/.exec(brkResponse);
let metype = metypeMatch ? parseInt(metypeMatch[1]) : -1;
// If it's not a breakpoint exception, just continue (let the program handle it).
if (metype !== 6 && metype !== -1) {
log(`[Manic EMU] Non-breakpoint exception (metype=${metype}), continuing...`);
continue;
}
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(`[Manic EMU] Failed to extract registers, continuing...`);
continue;
}
const pcNum = littleEndianHexStringToNumber(pc);
const x0Num = littleEndianHexStringToNumber(x0);
const x1Num = x1 ? littleEndianHexStringToNumber(x1) : 0n;
let instructionResponse = send_command(`m${pcNum.toString(16)},4`);
let instrU32 = littleEndianHexToU32(instructionResponse);
// Check if it's a BRK instruction.
if (!isBrkInstruction(instrU32)) {
// Not a BRK instruction, skip it.
log(`[Manic EMU] Not a BRK instruction at PC=0x${pcNum.toString(16)}, skipping...`);
continue;
}
let brkImmediate = extractBrkImmediate(instrU32);
// Only handle brk #0x69 (JIT memory mapping request).
if (brkImmediate === 0x69) {
validBreakpoints++;
let jitPageAddress = x0Num;
// If x1 is 0, use the default size of 64KB (0x10000).
// Manic EMU's CodeBlock typically requests large memory chunks (tens of MB for CPU JIT)
// but also asks for smaller ones (like 4KB/16KB for SpinLock and Shader JIT).
let size = x1Num > 0n ? x1Num : 0x10000n;
log(`[Manic EMU] ----------------------------------------`);
log(`[Manic EMU] JIT Request #${validBreakpoints}`);
log(`[Manic EMU] Address: 0x${jitPageAddress.toString(16)}`);
log(`[Manic EMU] Size: 0x${size.toString(16)} (${formatSize(Number(size))})`);
// Call prepare_memory_region to mark the memory as executable.
let prepareJITPageResponse = prepare_memory_region(Number(jitPageAddress), Number(size));
log(`[Manic EMU] prepare_memory_region result: ${prepareJITPageResponse}`);
// Log statistics
totalMemoryMapped += size;
mappedRegions.push({
address: `0x${jitPageAddress.toString(16)}`,
size: Number(size),
index: validBreakpoints
});
log(`[Manic EMU] Total JIT memory mapped: ${formatSize(Number(totalMemoryMapped))}`);
// Set PC+4 to continue program execution
let pcPlus4 = numberToLittleEndianHexString(pcNum + 4n);
send_command(`P20=${pcPlus4};thread:${tid};`);
log(`[Manic EMU] Resumed execution at PC=0x${(pcNum + 4n).toString(16)}`);
log(`[Manic EMU] ----------------------------------------`);
} else if (brkImmediate === 0x70 || brkImmediate === 0x71) {
// brk #0x70 and brk #0x71 are breakpoints used by some debuggers.
// Skip directly to PC+4 and continue execution.
log(`[Manic EMU] Debug breakpoint brk #0x${brkImmediate.toString(16)} at PC=0x${pcNum.toString(16)}, skipping...`);
let pcPlus4 = numberToLittleEndianHexString(pcNum + 4n);
send_command(`P20=${pcPlus4};thread:${tid};`);
} else {
// Other BRK instructions, skip PC+4 and continue execution.
log(`[Manic EMU] Unknown brk #0x${brkImmediate.toString(16)} at PC=0x${pcNum.toString(16)}, skipping...`);
let pcPlus4 = numberToLittleEndianHexString(pcNum + 4n);
send_command(`P20=${pcPlus4};thread:${tid};`);
}
}
// This line of code will never run because it's an infinite loop
// log(`[Manic EMU] Script ended`);