From 08b532236d1610d5887b20f34e3bad5b6c08c9ca Mon Sep 17 00:00:00 2001 From: eric Date: Sat, 4 Apr 2026 00:03:05 -0500 Subject: [PATCH] ''s --- .gitignore | 4 ++ README.md | 98 ++++++++++++++++++++++++-------------- offline/README.md | 31 ++++++++++++ offline/downloads/.gitkeep | 1 + offline/manifest.json | 53 +++++++++++++++++++++ redroid.py | 28 +++++++++++ stuff/magisk.py | 9 ++++ tools/helper.py | 48 ++++++++----------- verify_offline_bundle.py | 47 ++++++++++++++++++ 9 files changed, 254 insertions(+), 65 deletions(-) create mode 100644 offline/README.md create mode 100644 offline/downloads/.gitkeep create mode 100644 offline/manifest.json create mode 100644 verify_offline_bundle.py diff --git a/.gitignore b/.gitignore index f53fd02..ee24008 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,7 @@ Dockerfile houdini/ magisk/ mindthegapps/ +offline/downloads/* +!offline/downloads/.gitkeep +offline/images/* +!offline/images/.gitkeep diff --git a/README.md b/README.md index a4aeac8..fbe13b1 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,74 @@ # redroid-android13 -这个仓库只保留了 `5570` 那套优化过的 Android 13 redroid 最小集,不再带 `gapps/litegapps/ndk/widevine` 这些无关入口。 +This repo is the minimal Android 13 redroid bundle that matches the optimized `5570` instance. -目标很明确: +It keeps only the parts that were actually used: -- 基于 `redroid/redroid:13.0.0` -- 集成 `MindTheGapps` -- 集成 `libhoudini` -- 集成 `Kitsune Mask / Magisk Delta` -- 启动后伪装成 `Pixel 7 Pro` -- 修复 `Magisk Installed N/A` 和设置页不可用的问题 +- `redroid/redroid:13.0.0` +- `MindTheGapps` +- `libhoudini` +- `Kitsune Mask / Magisk Delta` +- `Pixel 7 Pro` device profile +- the `post-fs-data` fix that keeps `Magisk Installed` and the settings page healthy -## 保留的文件 +## Included files - `redroid.py` - `run_redroid.py` - `bootstrap_redroid.py` +- `verify_offline_bundle.py` +- `offline/README.md` +- `offline/manifest.json` - `stuff/mindthegapps.py` - `stuff/houdini.py` - `stuff/houdini_hack.py` - `stuff/magisk.py` - `assets/redroid_extreme_post_fs_data.sh` +- local `offline/downloads/` +- local `offline/images/` -## 依赖 +## Offline archive + +The repo tracks the offline bundle metadata under [offline](C:/Users/admin/Desktop/redroid-android13/offline). The large zip/apk/tar payloads are intentionally kept out of Git and should be stored locally under `offline/downloads/` and `offline/images/`. + +Use [offline/manifest.json](C:/Users/admin/Desktop/redroid-android13/offline/manifest.json) for checksums and source metadata, and [offline/README.md](C:/Users/admin/Desktop/redroid-android13/offline/README.md) for the expected local layout. + +Expected local files: + +- `offline/downloads/mindthegapps.zip` +- `offline/downloads/libhoudini.zip` +- `offline/downloads/libhoudini_hack.zip` +- `offline/downloads/magisk.apk` +- `offline/downloads/magisk.version` +- `offline/images/redroid-redroid-13.0.0-latest.tar` + +`redroid.py` now auto-loads the base image tar when `redroid/redroid:13.0.0-latest` is not already present in the local container runtime. + +## Dependencies ```bash python -m pip install -r requirements.txt ``` -容器侧默认按 `docker` 写,`podman` 也能用。 +## Verify the offline bundle -## 构建 5570 对应镜像 +```bash +python verify_offline_bundle.py +``` + +## Build the 5570 image ```bash python redroid.py -mtg -i -m ``` -这会生成并构建: +Expected image tag: ```bash redroid/redroid:13.0.0_mindthegapps_houdini_magisk ``` -## 启动容器 +## Launch the container ```bash python run_redroid.py \ @@ -54,7 +80,7 @@ python run_redroid.py \ --replace ``` -## 启动后做 5570 硬化 +## Apply the 5570 hardening ```bash python bootstrap_redroid.py \ @@ -64,39 +90,39 @@ python bootstrap_redroid.py \ --install-extreme-script ``` -这一步会做 5 件事: +This post-boot step: -- 跳过 SetupWizard -- 标记系统已完成初始化 -- 开启高精度定位模式 -- 允许 `com.android.shell` 使用 mock location -- 应用 `Pixel 7 Pro / user / release-keys / locked / green` 画像 +- disables SetupWizard +- marks the device provisioned +- enables high accuracy location mode +- allows `com.android.shell` to use mock location +- applies the `Pixel 7 Pro / user / release-keys / locked / green` identity -如果带上 `--install-extreme-script`,还会把 [`assets/redroid_extreme_post_fs_data.sh`](C:\Users\admin\Desktop\redroid-android13\assets\redroid_extreme_post_fs_data.sh) 安装到 `/data/adb/post-fs-data.d/20-redroid-extreme.sh`,让下面两件事在重启后继续成立: +With `--install-extreme-script`, the validated script [assets/redroid_extreme_post_fs_data.sh](C:/Users/admin/Desktop/redroid-android13/assets/redroid_extreme_post_fs_data.sh) is copied to `/data/adb/post-fs-data.d/20-redroid-extreme.sh` so that: -- `vendor/bin/su` 指向 Magisk applet,应用命名空间可见 -- `Pixel 7 Pro` 属性在每次开机时重新注入 +- `vendor/bin/su` stays visible to app namespaces after reboot +- `Pixel 7 Pro` properties are re-applied on every boot -## 已验证结果 +## Verified 5570 state -这套仓库对应的 `5570` 实例已经验证过: +The matching `5570` instance was already validated with: - `ro.product.model=Pixel 7 Pro` - `ro.build.type=user` - `ro.build.tags=release-keys` - `ro.boot.vbmeta.device_state=locked` - `ro.boot.verifiedbootstate=green` -- 应用 UID 下 `su -V` 返回 `27001` -- 应用 UID 下 `su -mm -c id` 能拿到 `uid=0(root)` -- `Kitsune` 首页的 `Installed` 正常显示 -- `Kitsune` 设置页正常打开,`SuList / MagiskHide` 可配置 +- app UID `su -V = 27001` +- app UID `su -mm -c id = uid=0(root)` +- `Kitsune` home page shows a normal `Installed` state +- `Kitsune` settings page opens normally and `SuList / MagiskHide` is configurable -## 当前范围 +## Current scope -这个仓库只负责把 `5570` 的系统环境复现出来,不包含: +This repo only reproduces the system environment for the optimized `5570` build. -- 虚拟摄像头 +It does not yet include: + +- virtual camera - camera HAL -- TikTok Live 专用补丁 - -后续如果要继续推进直播链路,直接在这个仓库往上叠加即可。 +- TikTok Live specific patches diff --git a/offline/README.md b/offline/README.md new file mode 100644 index 0000000..044c7b2 --- /dev/null +++ b/offline/README.md @@ -0,0 +1,31 @@ +# Offline Bundle + +This directory describes the local offline bundle required by the current Android 13 `5570` build path. + +The large bundle payloads are intentionally not tracked in Git. Keep them in the local `downloads/` and `images/` directories and use `manifest.json` plus `verify_offline_bundle.py` to validate what is on disk. + +## Layout + +- `downloads/` + - `mindthegapps.zip` + - `libhoudini.zip` + - `libhoudini_hack.zip` + - `magisk.apk` + - `magisk.version` +- `images/` + - `redroid-redroid-13.0.0-latest.tar` +- `manifest.json` + +## Verification + +```bash +python verify_offline_bundle.py +``` + +## Offline build flow + +1. Make sure your container runtime can load local image archives. +2. Populate `downloads/` and `images/` with the files listed in `manifest.json`. +3. Run `python redroid.py -mtg -i -m`. +4. If the base image tag is missing, `redroid.py` will automatically load `images/redroid-redroid-13.0.0-latest.tar`. +5. The build modules read the zip/apk files from `downloads/` first and do not need the network when those files are present and valid. diff --git a/offline/downloads/.gitkeep b/offline/downloads/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/offline/downloads/.gitkeep @@ -0,0 +1 @@ + diff --git a/offline/manifest.json b/offline/manifest.json new file mode 100644 index 0000000..b840a57 --- /dev/null +++ b/offline/manifest.json @@ -0,0 +1,53 @@ +{ + "generated_at": "2026-04-03", + "artifacts": [ + { + "name": "mindthegapps.zip", + "relative_path": "offline/downloads/mindthegapps.zip", + "size": 197927264, + "md5": "eee87a540b6e778f3a114fff29e133aa", + "sha256": "9fcb1531cb72df521bd49af5443f7a1b269ebb9c06d8f7dfa37b975da9ec0af1", + "source": "https://github.com/s1204IT/MindTheGappsBuilder/releases/download/20240226/MindTheGapps-13.0.0-x86_64-20240226.zip" + }, + { + "name": "libhoudini.zip", + "relative_path": "offline/downloads/libhoudini.zip", + "size": 75100163, + "md5": "cb7ffac26d47ec7c89df43818e126b47", + "sha256": "7c78abbcced1f7c5c1cc794a0afb4010a2e5bb0bb18eadd6de3af2fe13e9dc25", + "source": "https://github.com/rote66/vendor_intel_proprietary_houdini/archive/debc3dc91cf12b5c5b8a1c546a5b0b7bf7f838a8.zip" + }, + { + "name": "libhoudini_hack.zip", + "relative_path": "offline/downloads/libhoudini_hack.zip", + "size": 88394, + "md5": "8f71a58f3e54eca879a2f7de64dbed58", + "sha256": "5c904d6db5e15adc37ffadde5f21437ccabf86c1e94a3a0e4f77371a90776983", + "source": "https://github.com/rote66/redroid_libhoudini_hack/archive/a2194c5e294cbbfdfe87e51eb9eddb4c3621d8c3.zip" + }, + { + "name": "magisk.apk", + "relative_path": "offline/downloads/magisk.apk", + "size": 21099562, + "md5": "bb730e78db0bb9dde2fccfa7bfb1b35c", + "sha256": "af43697f67a229cf824494686e8ee07cb5668b57c9671a555e2a869c5bb64649", + "source": "Pulled from the validated 5570 instance: /vendor/etc/magisk/magisk.apk" + }, + { + "name": "magisk.version", + "relative_path": "offline/downloads/magisk.version", + "size": 10, + "md5": "57de9facce3180844a0460e9680c3836", + "sha256": "e82ddd5337dd7a7da9f2215c7272f5f26a4fd3b50d9676dea23d6d69a62c46ee", + "source": "Version name from the validated 5570 instance: b149cd26" + }, + { + "name": "redroid-redroid-13.0.0-latest.tar", + "relative_path": "offline/images/redroid-redroid-13.0.0-latest.tar", + "size": 2028835328, + "md5": "a2c44b2d90fb3445be9c2d6df867f960", + "sha256": "4474a84a78c62a793ca24c20b7159dc678196bd88a1ef4d9511b83e7090d1179", + "source": "docker save redroid/redroid:13.0.0-latest" + } + ] +} diff --git a/redroid.py b/redroid.py index e956da6..63e8188 100644 --- a/redroid.py +++ b/redroid.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import argparse +import os import subprocess from stuff.houdini import Houdini @@ -13,6 +14,31 @@ import tools.helper as helper SUPPORTED_ANDROID_VERSIONS = ["13.0.0"] +def ensure_base_image(container_runtime, android_version): + base_image = "redroid/redroid:{}-latest".format(android_version) + inspect = subprocess.run( + [container_runtime, "image", "inspect", base_image], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + ) + if inspect.returncode == 0: + return "present" + + tar_name = "redroid-redroid-{}-latest.tar".format(android_version) + tar_path = os.path.join(helper.get_image_dir(), tar_name) + if not os.path.isfile(tar_path): + raise FileNotFoundError( + "Base image {} is missing and offline archive {} was not found".format( + base_image, + tar_path, + ) + ) + + subprocess.run([container_runtime, "load", "-i", tar_path], check=True) + return tar_path + + def build_layers(args): dockerfile = "FROM redroid/redroid:{}-latest\n".format(args.android) tags = [args.android] @@ -103,6 +129,7 @@ def main(): ) args = parser.parse_args() + base_image_source = ensure_base_image(args.container, args.android) dockerfile, tags = build_layers(args) print("\nDockerfile\n{}".format(dockerfile)) @@ -115,6 +142,7 @@ def main(): "Successfully built {}".format(image_name), helper.bcolors.GREEN, ) + print("Base image source: {}".format(base_image_source)) if args.houdini: print_recommended_flow(image_name) diff --git a/stuff/magisk.py b/stuff/magisk.py index 43c55a1..3f84fa8 100644 --- a/stuff/magisk.py +++ b/stuff/magisk.py @@ -174,6 +174,15 @@ on property:init.svc.zygote=stopped self._validate_archive() return + if not self.custom_url and os.path.isfile(self.dl_file_name): + self.release_version = self._cached_version() or "bundled" + print_color( + "Using bundled Kitsune Mask {} ...".format(self.release_version), + bcolors.GREEN, + ) + self._validate_archive() + return + if self.custom_url: self.dl_link = self.custom_url self.release_version = "url:" + os.path.basename(self.custom_url) diff --git a/tools/helper.py b/tools/helper.py index fc28033..7db8049 100644 --- a/tools/helper.py +++ b/tools/helper.py @@ -1,39 +1,29 @@ +import hashlib import os +from pathlib import Path import platform import subprocess + import requests from tqdm import tqdm -import hashlib + + +REPO_ROOT = Path(__file__).resolve().parents[1] +OFFLINE_ROOT = REPO_ROOT / "offline" +DOWNLOAD_ROOT = OFFLINE_ROOT / "downloads" +IMAGE_ROOT = OFFLINE_ROOT / "images" + + +def ensure_dir(path): + path.mkdir(parents=True, exist_ok=True) + return str(path) def get_download_dir(): - download_loc = "" - current_user = ( - os.environ.get("SUDO_USER") - or os.environ.get("USER") - or os.environ.get("USERNAME") - or "user" - ) - if os.environ.get("XDG_CACHE_HOME", None) is None: - if os.name == "nt": - download_loc = os.path.join( - os.environ.get("LOCALAPPDATA", os.path.expanduser("~")), - "redroid", - "downloads", - ) - else: - download_loc = os.path.join( - '/', - "home", - current_user, - ".cache", - "redroid", - "downloads", - ) - else: - download_loc = os.path.join(os.environ["XDG_CACHE_HOME"], "redroid", "downloads") - if not os.path.exists(download_loc): - os.makedirs(download_loc) - return download_loc + return ensure_dir(DOWNLOAD_ROOT) + + +def get_image_dir(): + return ensure_dir(IMAGE_ROOT) def run(args): result = subprocess.run(args=args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) diff --git a/verify_offline_bundle.py b/verify_offline_bundle.py new file mode 100644 index 0000000..41d0efa --- /dev/null +++ b/verify_offline_bundle.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 + +import hashlib +import json +from pathlib import Path +import sys + + +ROOT = Path(__file__).resolve().parent +MANIFEST = ROOT / "offline" / "manifest.json" + + +def digest(path, algorithm): + hasher = hashlib.new(algorithm) + with path.open("rb") as handle: + for chunk in iter(lambda: handle.read(1024 * 1024), b""): + hasher.update(chunk) + return hasher.hexdigest() + + +def main(): + manifest = json.loads(MANIFEST.read_text(encoding="utf-8")) + failures = [] + for entry in manifest["artifacts"]: + path = ROOT / entry["relative_path"] + if not path.is_file(): + failures.append("{} missing".format(entry["relative_path"])) + continue + if path.stat().st_size != entry["size"]: + failures.append("{} size mismatch".format(entry["relative_path"])) + continue + sha256 = digest(path, "sha256") + if sha256 != entry["sha256"]: + failures.append("{} sha256 mismatch".format(entry["relative_path"])) + continue + print("OK {}".format(entry["relative_path"])) + + if failures: + for item in failures: + print("FAIL {}".format(item)) + sys.exit(1) + + print("Offline bundle is complete.") + + +if __name__ == "__main__": + main()