mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-18 02:01:20 +02:00
New ui/frame_quest.py (CLI: devices | list | import), GET/POST /api/quest and an Import from my Quest panel on the Android page. It finds a Quest on this computer's adb (never Frame Control's own tunnelled serials), lists third-party apps with split, OBB and Android/data details, pulls the APKs into a per-user cache and checks them before copying anything else: Meta's Platform SDK licence check (libovrplatformloader.so) and 32-bit-only builds are refused, VrApi-only builds flagged. It then installs through the normal APK path, adds OBBs with the existing helper and, if asked, Android/data files, and always removes the cache. No licence-check bypass is bundled, called or linked. Tests use a fake adb (tests/fakeadb.py). Docs: docs/quest-import.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
101 lines
3.6 KiB
Python
101 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
|
"""A stand-in for Android's adb, for tests/test_quest.py: just enough of `devices -l`,
|
|
`shell`, and `pull` for frame_quest.py, run as a real subprocess.
|
|
|
|
$FAKEADB_STATE is a JSON file:
|
|
devices [{"serial", "state", "extra" (the rest of the `devices -l` line), "props" {getprop name: value}}]
|
|
root a folder standing in for each device's file system (remote /x/y is root/x/y)
|
|
packages {package: [APK paths, as `pm path` prints them]}
|
|
fail_pull a remote path whose pull fails
|
|
Every call is appended to $FAKEADB_LOG as a JSON line. Shell commands are the simple
|
|
`; `-separated ones frame_quest sends, each answered here: no real shell runs.
|
|
"""
|
|
import json
|
|
import os
|
|
import shutil
|
|
import sys
|
|
|
|
args = sys.argv[1:]
|
|
with open(os.environ['FAKEADB_LOG'], 'a') as f:
|
|
f.write(json.dumps(args) + '\n')
|
|
with open(os.environ['FAKEADB_STATE']) as f:
|
|
state = json.load(f)
|
|
root = state.get('root', '')
|
|
serial = None
|
|
if args[:1] == ['-s']:
|
|
serial, args = args[1], args[2:]
|
|
|
|
|
|
def fail(message, code=1):
|
|
sys.stderr.write(message + '\n')
|
|
sys.exit(code)
|
|
|
|
|
|
def local(remote):
|
|
return os.path.join(root, remote.lstrip('/'))
|
|
|
|
|
|
def size_kb(path):
|
|
total = 0
|
|
for dirpath, _, files in os.walk(path):
|
|
total += sum(os.path.getsize(os.path.join(dirpath, n)) for n in files)
|
|
return max(4, total // 1024)
|
|
|
|
|
|
if args == ['devices', '-l']:
|
|
print('List of devices attached')
|
|
for d in state.get('devices', []):
|
|
print(f"{d['serial']:<22} {d['state']} {d.get('extra', '')}".rstrip())
|
|
sys.exit(0)
|
|
|
|
device = next((d for d in state.get('devices', []) if d['serial'] == serial), None)
|
|
if serial is None or device is None:
|
|
fail(f"adb: device '{serial}' not found")
|
|
if device['state'] == 'unauthorized':
|
|
fail('adb: device unauthorized.\nThis adb server\'s $ADB_VENDOR_KEYS is not set')
|
|
if device['state'] != 'device':
|
|
fail(f"adb: device {device['state']}")
|
|
|
|
if args[:1] == ['shell']:
|
|
out = []
|
|
for command in ' '.join(args[1:]).split('; '):
|
|
command = command.replace(' 2>/dev/null', '').strip()
|
|
words = command.split()
|
|
if not words or command == 'true':
|
|
continue
|
|
if words[0] == 'getprop':
|
|
out.append(device.get('props', {}).get(words[1], ''))
|
|
elif words[0] == 'echo':
|
|
out.append(' '.join(words[1:]))
|
|
elif command == 'pm list packages -3':
|
|
out += ['package:' + p for p in state.get('packages', {})]
|
|
elif words[:2] == ['pm', 'path']:
|
|
out += ['package:' + p for p in state.get('packages', {}).get(words[2], [])]
|
|
elif words[:2] == ['du', '-sk']:
|
|
if os.path.isdir(local(words[2])):
|
|
out.append(f'{size_kb(local(words[2]))}\t{words[2]}')
|
|
elif words[:3] == ['stat', '-c', '%s']:
|
|
out += [str(os.path.getsize(local(p.strip("'")))) for p in words[3:]]
|
|
else:
|
|
fail(f'/system/bin/sh: {words[0]}: inaccessible or not found', 127)
|
|
print('\n'.join(out))
|
|
sys.exit(0)
|
|
|
|
if args[:1] == ['pull'] and len(args) == 3:
|
|
remote, dest = args[1], args[2]
|
|
if remote == state.get('fail_pull'):
|
|
fail(f"adb: error: failed to copy '{remote}' to '{dest}': remote Connection reset")
|
|
src = local(remote)
|
|
if not os.path.exists(src):
|
|
fail(f"adb: error: failed to stat remote object '{remote}': No such file or directory")
|
|
if os.path.isdir(dest):
|
|
dest = os.path.join(dest, os.path.basename(remote.rstrip('/')))
|
|
if os.path.isdir(src):
|
|
shutil.copytree(src, dest)
|
|
else:
|
|
shutil.copyfile(src, dest)
|
|
print(f'{remote}: 1 file pulled.')
|
|
sys.exit(0)
|
|
|
|
fail('adb: unknown command ' + ' '.join(args))
|