''s
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -4,3 +4,7 @@ Dockerfile
|
|||||||
houdini/
|
houdini/
|
||||||
magisk/
|
magisk/
|
||||||
mindthegapps/
|
mindthegapps/
|
||||||
|
offline/downloads/*
|
||||||
|
!offline/downloads/.gitkeep
|
||||||
|
offline/images/*
|
||||||
|
!offline/images/.gitkeep
|
||||||
|
|||||||
98
README.md
98
README.md
@@ -1,48 +1,74 @@
|
|||||||
# redroid-android13
|
# 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`
|
- `redroid/redroid:13.0.0`
|
||||||
- 集成 `MindTheGapps`
|
- `MindTheGapps`
|
||||||
- 集成 `libhoudini`
|
- `libhoudini`
|
||||||
- 集成 `Kitsune Mask / Magisk Delta`
|
- `Kitsune Mask / Magisk Delta`
|
||||||
- 启动后伪装成 `Pixel 7 Pro`
|
- `Pixel 7 Pro` device profile
|
||||||
- 修复 `Magisk Installed N/A` 和设置页不可用的问题
|
- the `post-fs-data` fix that keeps `Magisk Installed` and the settings page healthy
|
||||||
|
|
||||||
## 保留的文件
|
## Included files
|
||||||
|
|
||||||
- `redroid.py`
|
- `redroid.py`
|
||||||
- `run_redroid.py`
|
- `run_redroid.py`
|
||||||
- `bootstrap_redroid.py`
|
- `bootstrap_redroid.py`
|
||||||
|
- `verify_offline_bundle.py`
|
||||||
|
- `offline/README.md`
|
||||||
|
- `offline/manifest.json`
|
||||||
- `stuff/mindthegapps.py`
|
- `stuff/mindthegapps.py`
|
||||||
- `stuff/houdini.py`
|
- `stuff/houdini.py`
|
||||||
- `stuff/houdini_hack.py`
|
- `stuff/houdini_hack.py`
|
||||||
- `stuff/magisk.py`
|
- `stuff/magisk.py`
|
||||||
- `assets/redroid_extreme_post_fs_data.sh`
|
- `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
|
```bash
|
||||||
python -m pip install -r requirements.txt
|
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
|
```bash
|
||||||
python redroid.py -mtg -i -m
|
python redroid.py -mtg -i -m
|
||||||
```
|
```
|
||||||
|
|
||||||
这会生成并构建:
|
Expected image tag:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
redroid/redroid:13.0.0_mindthegapps_houdini_magisk
|
redroid/redroid:13.0.0_mindthegapps_houdini_magisk
|
||||||
```
|
```
|
||||||
|
|
||||||
## 启动容器
|
## Launch the container
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python run_redroid.py \
|
python run_redroid.py \
|
||||||
@@ -54,7 +80,7 @@ python run_redroid.py \
|
|||||||
--replace
|
--replace
|
||||||
```
|
```
|
||||||
|
|
||||||
## 启动后做 5570 硬化
|
## Apply the 5570 hardening
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python bootstrap_redroid.py \
|
python bootstrap_redroid.py \
|
||||||
@@ -64,39 +90,39 @@ python bootstrap_redroid.py \
|
|||||||
--install-extreme-script
|
--install-extreme-script
|
||||||
```
|
```
|
||||||
|
|
||||||
这一步会做 5 件事:
|
This post-boot step:
|
||||||
|
|
||||||
- 跳过 SetupWizard
|
- disables SetupWizard
|
||||||
- 标记系统已完成初始化
|
- marks the device provisioned
|
||||||
- 开启高精度定位模式
|
- enables high accuracy location mode
|
||||||
- 允许 `com.android.shell` 使用 mock location
|
- allows `com.android.shell` to use mock location
|
||||||
- 应用 `Pixel 7 Pro / user / release-keys / locked / green` 画像
|
- 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,应用命名空间可见
|
- `vendor/bin/su` stays visible to app namespaces after reboot
|
||||||
- `Pixel 7 Pro` 属性在每次开机时重新注入
|
- `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.product.model=Pixel 7 Pro`
|
||||||
- `ro.build.type=user`
|
- `ro.build.type=user`
|
||||||
- `ro.build.tags=release-keys`
|
- `ro.build.tags=release-keys`
|
||||||
- `ro.boot.vbmeta.device_state=locked`
|
- `ro.boot.vbmeta.device_state=locked`
|
||||||
- `ro.boot.verifiedbootstate=green`
|
- `ro.boot.verifiedbootstate=green`
|
||||||
- 应用 UID 下 `su -V` 返回 `27001`
|
- app UID `su -V = 27001`
|
||||||
- 应用 UID 下 `su -mm -c id` 能拿到 `uid=0(root)`
|
- app UID `su -mm -c id = uid=0(root)`
|
||||||
- `Kitsune` 首页的 `Installed` 正常显示
|
- `Kitsune` home page shows a normal `Installed` state
|
||||||
- `Kitsune` 设置页正常打开,`SuList / MagiskHide` 可配置
|
- `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
|
- camera HAL
|
||||||
- TikTok Live 专用补丁
|
- TikTok Live specific patches
|
||||||
|
|
||||||
后续如果要继续推进直播链路,直接在这个仓库往上叠加即可。
|
|
||||||
|
|||||||
31
offline/README.md
Normal file
31
offline/README.md
Normal file
@@ -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.
|
||||||
1
offline/downloads/.gitkeep
Normal file
1
offline/downloads/.gitkeep
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
53
offline/manifest.json
Normal file
53
offline/manifest.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
28
redroid.py
28
redroid.py
@@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from stuff.houdini import Houdini
|
from stuff.houdini import Houdini
|
||||||
@@ -13,6 +14,31 @@ import tools.helper as helper
|
|||||||
SUPPORTED_ANDROID_VERSIONS = ["13.0.0"]
|
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):
|
def build_layers(args):
|
||||||
dockerfile = "FROM redroid/redroid:{}-latest\n".format(args.android)
|
dockerfile = "FROM redroid/redroid:{}-latest\n".format(args.android)
|
||||||
tags = [args.android]
|
tags = [args.android]
|
||||||
@@ -103,6 +129,7 @@ def main():
|
|||||||
)
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
base_image_source = ensure_base_image(args.container, args.android)
|
||||||
dockerfile, tags = build_layers(args)
|
dockerfile, tags = build_layers(args)
|
||||||
|
|
||||||
print("\nDockerfile\n{}".format(dockerfile))
|
print("\nDockerfile\n{}".format(dockerfile))
|
||||||
@@ -115,6 +142,7 @@ def main():
|
|||||||
"Successfully built {}".format(image_name),
|
"Successfully built {}".format(image_name),
|
||||||
helper.bcolors.GREEN,
|
helper.bcolors.GREEN,
|
||||||
)
|
)
|
||||||
|
print("Base image source: {}".format(base_image_source))
|
||||||
|
|
||||||
if args.houdini:
|
if args.houdini:
|
||||||
print_recommended_flow(image_name)
|
print_recommended_flow(image_name)
|
||||||
|
|||||||
@@ -174,6 +174,15 @@ on property:init.svc.zygote=stopped
|
|||||||
self._validate_archive()
|
self._validate_archive()
|
||||||
return
|
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:
|
if self.custom_url:
|
||||||
self.dl_link = self.custom_url
|
self.dl_link = self.custom_url
|
||||||
self.release_version = "url:" + os.path.basename(self.custom_url)
|
self.release_version = "url:" + os.path.basename(self.custom_url)
|
||||||
|
|||||||
@@ -1,39 +1,29 @@
|
|||||||
|
import hashlib
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
import platform
|
import platform
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from tqdm import tqdm
|
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():
|
def get_download_dir():
|
||||||
download_loc = ""
|
return ensure_dir(DOWNLOAD_ROOT)
|
||||||
current_user = (
|
|
||||||
os.environ.get("SUDO_USER")
|
|
||||||
or os.environ.get("USER")
|
def get_image_dir():
|
||||||
or os.environ.get("USERNAME")
|
return ensure_dir(IMAGE_ROOT)
|
||||||
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
|
|
||||||
|
|
||||||
def run(args):
|
def run(args):
|
||||||
result = subprocess.run(args=args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
result = subprocess.run(args=args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
|
|||||||
47
verify_offline_bundle.py
Normal file
47
verify_offline_bundle.py
Normal file
@@ -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()
|
||||||
Reference in New Issue
Block a user