import { spawnSync } from "node:child_process"; import path from "node:path"; const root = path.resolve(import.meta.dir, ".."); const adb = process.env.ADB ?? "adb"; const args = process.argv.slice(2); const command = args[0] ?? "help"; function option(name: string): string | undefined { const index = args.indexOf(name); return index >= 0 ? args[index + 1] : undefined; } function options(name: string): string[] { const values: string[] = []; for (let index = 0; index < args.length; index += 1) { const value = args[index + 1]; if (args[index] === name && value) values.push(value); } return values; } function requireSerial(): string { const serial = option("--serial"); if (!serial) throw new Error("Specify exactly one target with --serial "); return serial; } function run(serial: string, commandArgs: string[]): void { const result = spawnSync(adb, ["-s", serial, ...commandArgs], { stdio: "inherit", }); if (result.error) throw result.error; if (result.status !== 0) throw new Error(`adb ${commandArgs.join(" ")} failed`); } function confirm(flag: string): void { if (!args.includes(flag)) throw new Error(`Refusing this device-changing operation without ${flag}`); } function replacePackages( serial: string, launcher: string, note: string, uninstall: string[], ): void { if (uninstall.length !== 2) { throw new Error("Provide exactly two explicit --uninstall package names"); } for (const packageName of uninstall) run(serial, ["uninstall", packageName]); run(serial, ["install", "-r", launcher]); run(serial, ["install", "-r", note]); } function packageFromApk(file: string): string { const result = spawnSync("apkanalyzer", ["manifest", "print", file], { encoding: "utf8", }); if (result.error) throw result.error; const match = result.stdout.match(/\bpackage="([^"]+)"/); if (result.status !== 0 || !match) throw new Error(`Could not infer the package name from ${file}`); return match[1]!; } if (command === "help") { console.log(`ADBMe guarded deployment commands bun run adbme status --serial SERIAL bun run adbme grant-settings --serial SERIAL --app rip.crit.ms.adbme --confirm-permissions bun run adbme set-language --serial SERIAL --confirm-system-changes bun run adbme patch-and-replace --serial SERIAL --confirm-replace bun run adbme replace --serial SERIAL --uninstall OLD_LAUNCHER --uninstall OLD_NOTE --launcher APK --note APK --confirm-replace CHANGE_CONFIGURATION cannot be granted to an ordinary APK on stock Android. It requires a privileged or OEM-signed app.`); } else if (command === "status") { run(requireSerial(), ["get-state"]); } else if (command === "grant-settings") { const serial = requireSerial(); const app = option("--app"); if (!app) throw new Error("Specify the installed companion app with --app "); confirm("--confirm-permissions"); run(serial, ["shell", "appops", "set", app, "WRITE_SETTINGS", "allow"]); console.log( "[ok] WRITE_SETTINGS app-op enabled. CHANGE_CONFIGURATION was not attempted because stock Android rejects it for ordinary apps.", ); } else if (command === "set-language") { const serial = requireSerial(); confirm("--confirm-system-changes"); run(serial, ["shell", "cmd", "locale", "set", "en-US"]); } else if (command === "replace") { const serial = requireSerial(); const launcher = option("--launcher"); const note = option("--note"); const uninstall = options("--uninstall"); if (!launcher || !note) throw new Error("Provide --launcher APK and --note APK"); confirm("--confirm-replace"); replacePackages(serial, launcher, note, uninstall); } else if (command === "patch-and-replace") { const serial = requireSerial(); confirm("--confirm-replace"); const patched = spawnSync("bun", ["run", "patch-apks"], { cwd: root, stdio: "inherit", }); if (patched.error) throw patched.error; if (patched.status !== 0) throw new Error("APK patching failed"); const oldLauncher = packageFromApk( path.join(root, "inputs", "MoanLauncher.apk"), ); const oldNote = packageFromApk(path.join(root, "inputs", "MoanNote.apk")); console.log(`[ok] Inferred original packages: ${oldLauncher}, ${oldNote}`); replacePackages( serial, path.join(root, "outputs", "MoanLauncher_patched.apk"), path.join(root, "outputs", "MoanNote_patched.apk"), [oldLauncher, oldNote], ); } else { throw new Error(`Unknown ADBMe command: ${command}`); }