Add an option to set how the app is started

This commit is contained in:
Samuel Elliott 2022-09-14 17:07:39 +01:00
parent 56a991b03b
commit 31926d83f4
No known key found for this signature in database
GPG Key ID: 8420C7CDE43DC4D6

View File

@ -38,6 +38,10 @@ export function builder(yargs: Argv<ParentArguments>) {
describe: 'Path to the frida-server executable on the device',
type: 'string',
default: '/data/local/tmp/frida-server',
}).option('start-method', {
describe: 'Method to ensure the app is running (one of "spawn", "none", "activity", "service")',
type: 'string',
default: 'service',
}).option('strict-validate', {
describe: 'Validate data exactly matches the format that would be generated by Nintendo\'s Android app',
type: 'boolean',
@ -55,6 +59,21 @@ export function builder(yargs: Argv<ParentArguments>) {
type Arguments = YargsArguments<ReturnType<typeof builder>>;
enum StartMethod {
/** Spawn the app process with Frida, even if the app is already running (recommended) */
SPAWN,
/** Start the app's main activity using the am command */
ACTIVITY,
/**
* Start a background service using the am command (default)
* This tricks Android into not killing the app for some reason and allows the process to be started
* in the background.
*/
SERVICE,
/** Do not attempt to start the app - if it is not already running the server will fail */
NONE,
}
interface PackageInfo {
name: string;
version: string;
@ -101,12 +120,18 @@ interface FResult {
}
export async function handler(argv: ArgumentsCamelCase<Arguments>) {
const start_method =
argv.startMethod === 'spawn' ? StartMethod.SPAWN :
argv.startMethod === 'activity' ? StartMethod.ACTIVITY :
argv.startMethod === 'service' ? StartMethod.SERVICE :
StartMethod.NONE;
await mkdirp(script_dir);
const storage = await initStorage(argv.dataPath);
await setup(argv);
await setup(argv, start_method);
let {session, script} = await attach(argv);
let {session, script} = await attach(argv, start_method);
let ready: Promise<void> | null = null;
let api: {
@ -144,7 +169,7 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
debug('Attempting to reconnect to the device');
ready = attach(argv).then(async a => {
ready = attach(argv, start_method).then(async a => {
ready = null;
session = a.session;
script = a.script;
@ -406,12 +431,14 @@ export async function handler(argv: ArgumentsCamelCase<Arguments>) {
const frida_script = `
const perform = callback => new Promise((rs, rj) => {
Java.scheduleOnMainThread(() => {
try {
rs(callback());
} catch (err) {
rj(err);
}
Java.perform(() => {
Java.scheduleOnMainThread(() => {
try {
rs(callback());
} catch (err) {
rj(err);
}
});
});
});
@ -520,6 +547,7 @@ rpc.exports = {
const setup_script = (options: {
frida_server_path: string;
start_method: StartMethod;
}) => `#!/system/bin/sh
# Ensure frida-server is running
@ -534,10 +562,16 @@ fi
sleep 1
${(options.start_method === StartMethod.ACTIVITY ? `
# Ensure the app is running
echo "Starting com.nintendo.znca in foreground"
am start-activity com.nintendo.znca/com.nintendo.coral.ui.boot.BootActivity
` : options.start_method === StartMethod.SERVICE ? `
# Ensure the app is running
echo "Starting com.nintendo.znca"
am start-foreground-service com.nintendo.znca/com.google.firebase.messaging.FirebaseMessagingService
am start-service com.nintendo.znca/com.google.firebase.messaging.FirebaseMessagingService
` : '').trim()}
if [ "$?" != "0" ]; then
echo "Failed to start com.nintendo.znca"
@ -554,7 +588,7 @@ echo "Releasing wake lock"
echo androidzncaapiserver > /sys/power/wake_unlock
`;
async function setup(argv: ArgumentsCamelCase<Arguments>) {
async function setup(argv: ArgumentsCamelCase<Arguments>, start_method: StartMethod) {
debug('Connecting to device %s', argv.device);
let co = execFileSync('adb', [
'connect',
@ -583,11 +617,12 @@ async function setup(argv: ArgumentsCamelCase<Arguments>) {
await pushScript(argv.device, setup_script({
frida_server_path: argv.fridaServerPath,
start_method,
}), '/data/local/tmp/android-znca-api-server-setup.sh');
await pushScript(argv.device, shutdown_script, '/data/local/tmp/android-znca-api-server-shutdown.sh');
}
async function attach(argv: ArgumentsCamelCase<Arguments>) {
async function attach(argv: ArgumentsCamelCase<Arguments>, start_method: StartMethod) {
const frida = await import('frida');
type Session = import('frida').Session;
@ -602,11 +637,17 @@ async function attach(argv: ArgumentsCamelCase<Arguments>) {
let session: Session;
try {
const process = await device.getProcess('Nintendo Switch Online');
const process = start_method === StartMethod.SPAWN ?
{pid: await device.spawn('com.nintendo.znca')} :
await device.getProcess('Nintendo Switch Online');
debug('process', process);
session = await device.attach(process.pid);
if (start_method === StartMethod.SPAWN) {
await device.resume(session.pid);
}
} catch (err) {
debug('Could not attach to process', err);
throw new Error('Failed to attach to process');