OpenMiInk/scripts/adbme.ts
Hellings b8c8ff98e2 Hi
2026-10-16 09:05:56 -04:00

126 lines
4.3 KiB
TypeScript

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 <adb-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 <package>");
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}`);
}