325 lines
10 KiB
Python
325 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
import os
|
|
import re
|
|
import shlex
|
|
import subprocess
|
|
|
|
from tools.helper import bcolors, print_color
|
|
|
|
|
|
COMMON_BRIDGE_PROPS = [
|
|
"ro.product.cpu.abilist=x86_64,arm64-v8a,x86,armeabi-v7a,armeabi",
|
|
"ro.product.cpu.abilist64=x86_64,arm64-v8a",
|
|
"ro.product.cpu.abilist32=x86,armeabi-v7a,armeabi",
|
|
"ro.dalvik.vm.isa.arm=x86",
|
|
"ro.dalvik.vm.isa.arm64=x86_64",
|
|
"ro.enable.native.bridge.exec=1",
|
|
"ro.enable.native.bridge.exec64=1",
|
|
"ro.vendor.enable.native.bridge.exec=1",
|
|
"ro.vendor.enable.native.bridge.exec64=1",
|
|
]
|
|
|
|
DEVICE_PROFILES = {
|
|
"pixel-3-xl": [
|
|
"ro.product.model=Pixel 3 XL",
|
|
"ro.product.brand=google",
|
|
"ro.product.name=crosshatch",
|
|
"ro.product.device=crosshatch",
|
|
"ro.product.manufacturer=Google",
|
|
"ro.product.system.model=Pixel 3 XL",
|
|
"ro.product.system.brand=google",
|
|
"ro.product.system.name=crosshatch",
|
|
"ro.product.system.device=crosshatch",
|
|
"ro.product.system.manufacturer=Google",
|
|
"ro.product.vendor.model=Pixel 3 XL",
|
|
"ro.product.vendor.brand=google",
|
|
"ro.product.vendor.name=crosshatch",
|
|
"ro.product.vendor.device=crosshatch",
|
|
"ro.product.vendor.manufacturer=Google",
|
|
"ro.product.product.model=Pixel 3 XL",
|
|
"ro.product.product.brand=google",
|
|
"ro.product.product.name=crosshatch",
|
|
"ro.product.product.device=crosshatch",
|
|
"ro.product.product.manufacturer=Google",
|
|
"ro.build.fingerprint=google/crosshatch/crosshatch:11/RQ3A.211001.001/7641976:user/release-keys",
|
|
"ro.system.build.fingerprint=google/crosshatch/crosshatch:11/RQ3A.211001.001/7641976:user/release-keys",
|
|
"ro.vendor.build.fingerprint=google/crosshatch/crosshatch:11/RQ3A.211001.001/7641976:user/release-keys",
|
|
"ro.product.build.fingerprint=google/crosshatch/crosshatch:11/RQ3A.211001.001/7641976:user/release-keys",
|
|
"ro.build.type=user",
|
|
"ro.build.tags=release-keys",
|
|
"ro.secure=1",
|
|
"ro.debuggable=0",
|
|
"ro.boot.verifiedbootstate=green",
|
|
"ro.boot.vbmeta.device_state=locked",
|
|
"ro.boot.flash.locked=1",
|
|
"ro.boot.veritymode=enforcing",
|
|
],
|
|
"pixel-7-pro": [
|
|
"ro.product.model=Pixel 7 Pro",
|
|
"ro.product.brand=google",
|
|
"ro.product.name=cheetah",
|
|
"ro.product.device=cheetah",
|
|
"ro.product.manufacturer=Google",
|
|
"ro.product.system.model=Pixel 7 Pro",
|
|
"ro.product.system.brand=google",
|
|
"ro.product.system.name=cheetah",
|
|
"ro.product.system.device=cheetah",
|
|
"ro.product.system.manufacturer=Google",
|
|
"ro.product.vendor.model=Pixel 7 Pro",
|
|
"ro.product.vendor.brand=google",
|
|
"ro.product.vendor.name=cheetah",
|
|
"ro.product.vendor.device=cheetah",
|
|
"ro.product.vendor.manufacturer=Google",
|
|
"ro.product.product.model=Pixel 7 Pro",
|
|
"ro.product.product.brand=google",
|
|
"ro.product.product.name=cheetah",
|
|
"ro.product.product.device=cheetah",
|
|
"ro.product.product.manufacturer=Google",
|
|
"ro.build.fingerprint=google/cheetah/cheetah:13/TQ3A.230805.001/2023080800:user/release-keys",
|
|
"ro.system.build.fingerprint=google/cheetah/cheetah:13/TQ3A.230805.001/2023080800:user/release-keys",
|
|
"ro.vendor.build.fingerprint=google/cheetah/cheetah:13/TQ3A.230805.001/2023080800:user/release-keys",
|
|
"ro.product.build.fingerprint=google/cheetah/cheetah:13/TQ3A.230805.001/2023080800:user/release-keys",
|
|
"ro.build.type=user",
|
|
"ro.build.tags=release-keys",
|
|
"ro.secure=1",
|
|
"ro.debuggable=0",
|
|
"ro.boot.verifiedbootstate=green",
|
|
"ro.boot.vbmeta.device_state=locked",
|
|
"ro.boot.flash.locked=1",
|
|
"ro.boot.veritymode=enforcing",
|
|
]
|
|
}
|
|
|
|
LAUNCH_PRESETS = {
|
|
"redroid-pro": {
|
|
"props": [
|
|
"androidboot.redroid_width=720",
|
|
"androidboot.redroid_height=1280",
|
|
"androidboot.redroid_dpi=320",
|
|
"androidboot.redroid_fps=30",
|
|
"androidboot.redroid_vm_heap=256m",
|
|
"ro.config.low_ram=true",
|
|
"config.disable_consumerir=true",
|
|
],
|
|
"device_profile": "pixel-3-xl",
|
|
"memory": "4g",
|
|
}
|
|
}
|
|
|
|
|
|
def infer_bridge(image, requested):
|
|
if requested != "auto":
|
|
return requested
|
|
if "houdini" in image:
|
|
return "libhoudini"
|
|
if "ndk" in image:
|
|
return "libndk"
|
|
return "none"
|
|
|
|
|
|
def bridge_props(mode):
|
|
if mode == "none":
|
|
return []
|
|
props = list(COMMON_BRIDGE_PROPS)
|
|
if mode == "libhoudini":
|
|
props.append("ro.dalvik.vm.native.bridge=libhoudini.so")
|
|
elif mode == "libnb":
|
|
props.append("ro.dalvik.vm.native.bridge=libnb.so")
|
|
elif mode == "libndk":
|
|
props.append("ro.dalvik.vm.native.bridge=libndk_translation.so")
|
|
props.append("ro.ndk_translation.version=0.2.3")
|
|
else:
|
|
raise ValueError("Unsupported bridge mode: {}".format(mode))
|
|
return props
|
|
|
|
|
|
def device_profile_props(profile):
|
|
if profile == "none":
|
|
return []
|
|
try:
|
|
return list(DEVICE_PROFILES[profile])
|
|
except KeyError as exc:
|
|
raise ValueError("Unsupported device profile: {}".format(profile)) from exc
|
|
|
|
|
|
def preset_props(preset):
|
|
if preset == "none":
|
|
return []
|
|
try:
|
|
return list(LAUNCH_PRESETS[preset]["props"])
|
|
except KeyError as exc:
|
|
raise ValueError("Unsupported preset: {}".format(preset)) from exc
|
|
|
|
|
|
def resolve_device_profile(preset, requested):
|
|
if requested != "none":
|
|
return requested
|
|
if preset == "none":
|
|
return "none"
|
|
return LAUNCH_PRESETS[preset].get("device_profile", "none")
|
|
|
|
|
|
def resolve_memory(preset, requested):
|
|
if requested:
|
|
return requested
|
|
if preset == "none":
|
|
return None
|
|
return LAUNCH_PRESETS[preset].get("memory")
|
|
|
|
|
|
def default_name(image):
|
|
name = image.split("/")[-1].replace(":", "_")
|
|
name = re.sub(r"[^a-zA-Z0-9_.-]+", "-", name).strip("-")
|
|
return name or "redroid"
|
|
|
|
|
|
def quote_cmd(args):
|
|
return " ".join(shlex.quote(part) for part in args)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Launch a redroid container with a tested native bridge configuration."
|
|
)
|
|
parser.add_argument(
|
|
"--image",
|
|
required=True,
|
|
help="Image name, for example redroid/redroid:13.0.0_mindthegapps_houdini_magisk",
|
|
)
|
|
parser.add_argument(
|
|
"-c",
|
|
"--container",
|
|
default="docker",
|
|
choices=["docker", "podman"],
|
|
help="Container runtime",
|
|
)
|
|
parser.add_argument(
|
|
"--name",
|
|
help="Container name. Defaults to a slug derived from the image tag.",
|
|
)
|
|
parser.add_argument(
|
|
"--data-dir",
|
|
default="~/data-redroid",
|
|
help="Host directory mapped to /data",
|
|
)
|
|
parser.add_argument(
|
|
"--port",
|
|
type=int,
|
|
default=5555,
|
|
help="Host TCP port mapped to guest ADB 5555",
|
|
)
|
|
parser.add_argument(
|
|
"--bridge",
|
|
choices=["auto", "none", "libhoudini", "libnb", "libndk"],
|
|
default="auto",
|
|
help="Native bridge runtime property to inject",
|
|
)
|
|
parser.add_argument(
|
|
"--preset",
|
|
choices=["none"] + sorted(LAUNCH_PRESETS.keys()),
|
|
default="none",
|
|
help="Inject a tested launch preset",
|
|
)
|
|
parser.add_argument(
|
|
"--gpu-mode",
|
|
default="guest",
|
|
help="Value for androidboot.redroid_gpu_mode",
|
|
)
|
|
parser.add_argument(
|
|
"--device-profile",
|
|
choices=["none"] + sorted(DEVICE_PROFILES.keys()),
|
|
default="none",
|
|
help="Inject a tested device identity profile",
|
|
)
|
|
parser.add_argument(
|
|
"--memory",
|
|
help="Optional container memory limit, for example 4g",
|
|
)
|
|
parser.add_argument(
|
|
"--replace",
|
|
action="store_true",
|
|
help="Remove an existing container with the same name before running",
|
|
)
|
|
parser.add_argument(
|
|
"--keep-container",
|
|
action="store_true",
|
|
help="Do not pass --rm to the runtime",
|
|
)
|
|
parser.add_argument(
|
|
"--dry-run",
|
|
action="store_true",
|
|
help="Print the generated command without executing it",
|
|
)
|
|
parser.add_argument(
|
|
"--extra-prop",
|
|
action="append",
|
|
default=[],
|
|
help="Additional redroid property, repeatable",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
image = args.image
|
|
name = args.name or default_name(image)
|
|
data_dir = os.path.abspath(os.path.expanduser(args.data_dir))
|
|
bridge = infer_bridge(image, args.bridge)
|
|
preset = args.preset
|
|
device_profile = resolve_device_profile(preset, args.device_profile)
|
|
memory = resolve_memory(preset, args.memory)
|
|
|
|
os.makedirs(data_dir, exist_ok=True)
|
|
|
|
cmd = [
|
|
args.container,
|
|
"run",
|
|
"-d",
|
|
"--privileged",
|
|
"--name",
|
|
name,
|
|
"-v",
|
|
"{}:/data".format(data_dir),
|
|
"-p",
|
|
"{}:5555".format(args.port),
|
|
]
|
|
if memory:
|
|
cmd.extend(["--memory", memory])
|
|
if not args.keep_container:
|
|
cmd.append("--rm")
|
|
cmd.extend(
|
|
[
|
|
image,
|
|
"androidboot.redroid_gpu_mode={}".format(args.gpu_mode),
|
|
]
|
|
)
|
|
cmd.extend(bridge_props(bridge))
|
|
cmd.extend(preset_props(preset))
|
|
cmd.extend(device_profile_props(device_profile))
|
|
cmd.extend(args.extra_prop)
|
|
|
|
if args.replace and not args.dry_run:
|
|
subprocess.run(
|
|
[args.container, "rm", "-f", name],
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
check=False,
|
|
)
|
|
|
|
print_color("Selected native bridge: {}".format(bridge), bcolors.GREEN)
|
|
if preset != "none":
|
|
print_color("Selected launch preset: {}".format(preset), bcolors.GREEN)
|
|
if device_profile != "none":
|
|
print_color("Selected device profile: {}".format(device_profile), bcolors.GREEN)
|
|
print("Command:\n{}\n".format(quote_cmd(cmd)))
|
|
|
|
if args.dry_run:
|
|
return
|
|
|
|
subprocess.run(cmd, check=True)
|
|
print_color("Container started as {}".format(name), bcolors.GREEN)
|
|
print("ADB: adb connect 127.0.0.1:{}".format(args.port))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|