This commit is contained in:
eric
2026-04-03 23:22:27 -05:00
parent a7f39a399f
commit a57303c579
14 changed files with 1555 additions and 63 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
__pycache__/
*.pyc
Dockerfile
houdini/
magisk/
mindthegapps/

7
LICENSE Normal file
View File

@@ -0,0 +1,7 @@
Copyright © 2023 Rikka
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

136
README.md
View File

@@ -1,92 +1,102 @@
# redroid-android13
这个仓库只保留了 `5570` 那套优化过的 Android 13 redroid 最小集,不再带 `gapps/litegapps/ndk/widevine` 这些无关入口。
目标很明确:
## Getting started
- 基于 `redroid/redroid:13.0.0`
- 集成 `MindTheGapps`
- 集成 `libhoudini`
- 集成 `Kitsune Mask / Magisk Delta`
- 启动后伪装成 `Pixel 7 Pro`
- 修复 `Magisk Installed N/A` 和设置页不可用的问题
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
## 保留的文件
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
- `redroid.py`
- `run_redroid.py`
- `bootstrap_redroid.py`
- `stuff/mindthegapps.py`
- `stuff/houdini.py`
- `stuff/houdini_hack.py`
- `stuff/magisk.py`
- `assets/redroid_extreme_post_fs_data.sh`
## Add your files
## 依赖
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
```
cd existing_repo
git remote add origin http://gitlab.yidooplanet.com/gitlab-instance-0a899031/redroid-android13.git
git branch -M main
git push -uf origin main
```bash
python -m pip install -r requirements.txt
```
## Integrate with your tools
容器侧默认按 `docker` 写,`podman` 也能用。
- [ ] [Set up project integrations](http://gitlab.yidooplanet.com/gitlab-instance-0a899031/redroid-android13/-/settings/integrations)
## 构建 5570 对应镜像
## Collaborate with your team
```bash
python redroid.py -mtg -i -m
```
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
这会生成并构建:
## Test and Deploy
```bash
redroid/redroid:13.0.0_mindthegapps_houdini_magisk
```
Use the built-in continuous integration in GitLab.
## 启动容器
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
```bash
python run_redroid.py \
--image redroid/redroid:13.0.0_mindthegapps_houdini_magisk \
--bridge libhoudini \
--device-profile pixel-7-pro \
--data-dir ~/data-redroid13 \
--port 5570 \
--replace
```
***
## 启动后做 5570 硬化
# Editing this README
```bash
python bootstrap_redroid.py \
--serial 127.0.0.1:5570 \
--device-profile pixel-7-pro \
--proxy-host auto \
--install-extreme-script
```
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
这一步会做 5 件事:
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
- 跳过 SetupWizard
- 标记系统已完成初始化
- 开启高精度定位模式
- 允许 `com.android.shell` 使用 mock location
- 应用 `Pixel 7 Pro / user / release-keys / locked / green` 画像
## Name
Choose a self-explaining name for your project.
如果带上 `--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`,让下面两件事在重启后继续成立:
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
- `vendor/bin/su` 指向 Magisk applet应用命名空间可见
- `Pixel 7 Pro` 属性在每次开机时重新注入
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
## 已验证结果
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
这套仓库对应的 `5570` 实例已经验证过:
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
- `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` 可配置
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
## 当前范围
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
这个仓库只负责把 `5570` 的系统环境复现出来,不包含:
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
- 虚拟摄像头
- camera HAL
- TikTok Live 专用补丁
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
## License
For open source projects, say how it is licensed.
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
后续如果要继续推进直播链路,直接在这个仓库往上叠加即可。

View File

@@ -0,0 +1,51 @@
#!/system/bin/sh
MAGISKTMP=/debug_ramdisk
RESETPROP="$MAGISKTMP/resetprop"
if [ ! -x "$MAGISKTMP/magisk" ]; then
exit 0
fi
# Make Magisk's applets discoverable from app namespaces before zygote forks.
ln -sf "$MAGISKTMP/magisk" /vendor/bin/su
ln -sf "$MAGISKTMP/magisk" /vendor/bin/magisk
ln -sf "$MAGISKTMP/magisk" /vendor/bin/resetprop
ln -sf "$MAGISKTMP/magiskpolicy" /vendor/bin/magiskpolicy
if [ ! -x "$RESETPROP" ]; then
exit 0
fi
"$RESETPROP" ro.product.model "Pixel 7 Pro"
"$RESETPROP" ro.product.brand google
"$RESETPROP" ro.product.name cheetah
"$RESETPROP" ro.product.device cheetah
"$RESETPROP" ro.product.manufacturer Google
"$RESETPROP" ro.product.system.model "Pixel 7 Pro"
"$RESETPROP" ro.product.system.brand google
"$RESETPROP" ro.product.system.name cheetah
"$RESETPROP" ro.product.system.device cheetah
"$RESETPROP" ro.product.system.manufacturer Google
"$RESETPROP" ro.product.vendor.model "Pixel 7 Pro"
"$RESETPROP" ro.product.vendor.brand google
"$RESETPROP" ro.product.vendor.name cheetah
"$RESETPROP" ro.product.vendor.device cheetah
"$RESETPROP" ro.product.vendor.manufacturer Google
"$RESETPROP" ro.product.product.model "Pixel 7 Pro"
"$RESETPROP" ro.product.product.brand google
"$RESETPROP" ro.product.product.name cheetah
"$RESETPROP" ro.product.product.device cheetah
"$RESETPROP" ro.product.product.manufacturer Google
"$RESETPROP" ro.build.fingerprint google/cheetah/cheetah:13/TQ3A.230805.001/2023080800:user/release-keys
"$RESETPROP" ro.system.build.fingerprint google/cheetah/cheetah:13/TQ3A.230805.001/2023080800:user/release-keys
"$RESETPROP" ro.vendor.build.fingerprint google/cheetah/cheetah:13/TQ3A.230805.001/2023080800:user/release-keys
"$RESETPROP" ro.product.build.fingerprint google/cheetah/cheetah:13/TQ3A.230805.001/2023080800:user/release-keys
"$RESETPROP" ro.build.type user
"$RESETPROP" ro.build.tags release-keys
"$RESETPROP" ro.secure 1
"$RESETPROP" ro.debuggable 0
"$RESETPROP" ro.boot.verifiedbootstate green
"$RESETPROP" ro.boot.vbmeta.device_state locked
"$RESETPROP" ro.boot.flash.locked 1
"$RESETPROP" ro.boot.veritymode enforcing

311
bootstrap_redroid.py Normal file
View File

@@ -0,0 +1,311 @@
#!/usr/bin/env python3
import argparse
import os
import platform
import shlex
import subprocess
import time
from run_redroid import DEVICE_PROFILES
def run(cmd, check=True, capture_output=False):
return subprocess.run(
cmd,
check=check,
text=True,
capture_output=capture_output,
)
def adb(serial, args, check=True, capture_output=False):
return run(
["adb", "-s", serial] + args,
check=check,
capture_output=capture_output,
)
class Backend:
def __init__(self, serial="", container_name="", container_runtime="docker"):
self.serial = serial
self.container_name = container_name
self.container_runtime = container_runtime
self._root_checked = False
self._root_available = False
@property
def mode(self):
return "container" if self.container_name else "adb"
def connect(self):
if self.mode == "adb" and ":" in self.serial:
run(["adb", "connect", self.serial], check=False)
def has_root(self):
if self.mode == "container":
return True
if not self._root_checked:
result = adb(
self.serial,
["shell", "su", "-mm", "-c", "id"],
check=False,
capture_output=True,
)
self._root_available = result.returncode == 0 and "uid=0(" in result.stdout
self._root_checked = True
return self._root_available
def exec_shell(self, command, check=True, capture_output=False, root=False):
if self.mode == "container":
return run(
[
self.container_runtime,
"exec",
self.container_name,
"/system/bin/sh",
"-c",
command,
],
check=check,
capture_output=capture_output,
)
if root and self.has_root():
return adb(
self.serial,
["shell", "su", "-mm", "-c", command],
check=check,
capture_output=capture_output,
)
return adb(
self.serial,
["shell", command],
check=check,
capture_output=capture_output,
)
def wait_for_boot(self, timeout):
deadline = time.time() + timeout
if self.mode == "adb":
run(["adb", "-s", self.serial, "wait-for-device"])
while time.time() < deadline:
boot = self.exec_shell(
"getprop sys.boot_completed",
check=False,
capture_output=True,
).stdout.strip()
if boot == "1":
return
time.sleep(2)
raise TimeoutError("Timed out waiting for Android boot completion")
def try_shell(backend, command, root=False):
backend.exec_shell(command, check=False, root=root)
def put_setting(backend, namespace, key, value):
try_shell(backend, "settings put {} {} {}".format(namespace, key, value))
def delete_setting(backend, namespace, key):
try_shell(backend, "settings delete {} {}".format(namespace, key))
def detect_gateway_ip():
system = platform.system().lower()
if system == "linux":
result = run(
["sh", "-lc", "ip route show default 2>/dev/null | awk 'NR==1 {print $3}'"],
capture_output=True,
)
gateway = result.stdout.strip()
if gateway:
return gateway
elif system == "windows":
result = run(
[
"powershell",
"-NoProfile",
"-Command",
"(Get-NetRoute -DestinationPrefix '0.0.0.0/0' | "
"Sort-Object RouteMetric | Select-Object -First 1).NextHop",
],
capture_output=True,
)
gateway = result.stdout.strip()
if gateway:
return gateway
return ""
def configure_proxy(backend, proxy_host, proxy_port):
if proxy_host == "none":
delete_setting(backend, "global", "http_proxy")
return "disabled"
host = proxy_host
if proxy_host == "auto":
host = detect_gateway_ip()
if not host:
return "unresolved"
put_setting(backend, "global", "http_proxy", "{}:{}".format(host, proxy_port))
return "{}:{}".format(host, proxy_port)
def shell_quote(value):
return shlex.quote(value)
def apply_device_profile(backend, profile):
if profile == "none":
return "skipped"
props = DEVICE_PROFILES[profile]
resetprop = backend.exec_shell(
"if [ -x /debug_ramdisk/resetprop ]; then echo /debug_ramdisk/resetprop; "
"elif command -v resetprop >/dev/null 2>&1; then command -v resetprop; fi",
check=False,
capture_output=True,
).stdout.strip()
tool = resetprop or "setprop"
for item in props:
key, value = item.split("=", 1)
try_shell(
backend,
"{} {} {}".format(
tool,
shell_quote(key),
shell_quote(value),
),
root=True,
)
return "{} via {}".format(profile, tool)
def asset_path(*parts):
return os.path.join(os.path.dirname(os.path.abspath(__file__)), *parts)
def install_extreme_script(backend):
if backend.mode != "container" and not backend.has_root():
return "root unavailable"
local_script = asset_path("assets", "redroid_extreme_post_fs_data.sh")
if not os.path.isfile(local_script):
return "missing asset"
tmp_path = "/data/local/tmp/20-redroid-extreme.sh"
final_path = "/data/adb/post-fs-data.d/20-redroid-extreme.sh"
if backend.mode == "container":
run(
[
backend.container_runtime,
"cp",
local_script,
"{}:{}".format(backend.container_name, tmp_path),
]
)
else:
run(["adb", "-s", backend.serial, "push", local_script, tmp_path])
try_shell(
backend,
"mkdir -p /data/adb/post-fs-data.d && "
"cp {} {} && "
"chown 0:0 {} && "
"chmod 0755 {} && "
"rm -f {}".format(tmp_path, final_path, final_path, final_path, tmp_path),
root=True,
)
return final_path
def main():
parser = argparse.ArgumentParser(
description="Apply post-boot hardening to a redroid instance."
)
parser.add_argument(
"--serial",
default="127.0.0.1:5555",
help="ADB serial, for example 127.0.0.1:5555",
)
parser.add_argument(
"--container-name",
help="Operate on a running redroid container directly instead of using ADB",
)
parser.add_argument(
"--container-runtime",
default="docker",
choices=["docker", "podman"],
help="Container runtime when --container-name is used",
)
parser.add_argument(
"--timeout",
type=int,
default=300,
help="Seconds to wait for Android to finish booting",
)
parser.add_argument(
"--proxy-host",
default="auto",
help="Proxy host, auto, or none",
)
parser.add_argument(
"--proxy-port",
type=int,
default=20171,
help="Proxy port when proxy is enabled",
)
parser.add_argument(
"--device-profile",
choices=["none"] + sorted(DEVICE_PROFILES.keys()),
default="none",
help="Apply a runtime device profile through resetprop when available",
)
parser.add_argument(
"--install-extreme-script",
action="store_true",
help="Install the validated 5570 post-fs-data script into /data/adb/post-fs-data.d",
)
args = parser.parse_args()
backend = Backend(
serial=args.serial,
container_name=args.container_name or "",
container_runtime=args.container_runtime,
)
backend.connect()
backend.wait_for_boot(args.timeout)
put_setting(backend, "global", "device_provisioned", "1")
put_setting(backend, "secure", "user_setup_complete", "1")
put_setting(backend, "secure", "location_mode", "3")
put_setting(backend, "global", "package_verifier_enable", "0")
put_setting(backend, "global", "verifier_verify_adb_installs", "0")
try_shell(backend, "pm disable-user --user 0 com.android.setupwizard")
try_shell(backend, "pm disable-user --user 0 com.google.android.setupwizard")
try_shell(backend, "appops set com.android.shell android:mock_location allow")
proxy_state = configure_proxy(backend, args.proxy_host, args.proxy_port)
profile_state = apply_device_profile(backend, args.device_profile)
extreme_script_state = "skipped"
if args.install_extreme_script:
extreme_script_state = install_extreme_script(backend)
if backend.mode == "container":
print("Container: {}".format(backend.container_name))
else:
print("ADB serial: {}".format(backend.serial))
print("Boot status: completed")
print("SetupWizard: disabled")
print("Provisioned: yes")
print("Mock location for shell: allowed")
print("HTTP proxy: {}".format(proxy_state))
print("Device profile: {}".format(profile_state))
print("Extreme script: {}".format(extreme_script_state))
if __name__ == "__main__":
main()

124
redroid.py Normal file
View File

@@ -0,0 +1,124 @@
#!/usr/bin/env python3
import argparse
import subprocess
from stuff.houdini import Houdini
from stuff.houdini_hack import Houdini_Hack
from stuff.magisk import Magisk
from stuff.mindthegapps import MindTheGapps
import tools.helper as helper
SUPPORTED_ANDROID_VERSIONS = ["13.0.0"]
def build_layers(args):
dockerfile = "FROM redroid/redroid:{}-latest\n".format(args.android)
tags = [args.android]
if args.mindthegapps:
MindTheGapps(args.android).install()
dockerfile += "COPY mindthegapps /\n"
tags.append("mindthegapps")
if args.houdini:
arch = helper.host()[0]
if arch not in {"x86", "x86_64"}:
raise ValueError("Houdini only makes sense on x86/x86_64 hosts")
Houdini(args.android).install()
Houdini_Hack(args.android).install()
dockerfile += "COPY houdini /\n"
tags.append("houdini")
if args.magisk:
Magisk().install()
dockerfile += "COPY magisk /\n"
tags.append("magisk")
return dockerfile, tags
def print_recommended_flow(image_name):
print("\nRecommended 5570 launch")
print(
"python run_redroid.py "
"--image {} "
"--bridge libhoudini "
"--device-profile pixel-7-pro "
"--data-dir ~/data-redroid13 "
"--port 5570 "
"--replace\n".format(image_name)
)
print("Recommended post-boot hardening")
print(
"python bootstrap_redroid.py "
"--serial 127.0.0.1:5570 "
"--device-profile pixel-7-pro "
"--proxy-host auto "
"--install-extreme-script\n"
)
def main():
parser = argparse.ArgumentParser(
description="Build the minimal Android 13 redroid image used for the optimized 5570 setup."
)
parser.add_argument(
"-a",
"--android-version",
dest="android",
default="13.0.0",
choices=SUPPORTED_ANDROID_VERSIONS,
help="Android base image to build from",
)
parser.add_argument(
"-mtg",
"--install-mindthegapps",
dest="mindthegapps",
action="store_true",
help="Install MindTheGapps into the image",
)
parser.add_argument(
"-i",
"--install-houdini",
dest="houdini",
action="store_true",
help="Install libhoudini and the Android 13 compatibility hack",
)
parser.add_argument(
"-m",
"--install-magisk",
dest="magisk",
action="store_true",
help="Install Kitsune Mask / Magisk Delta bootless payload",
)
parser.add_argument(
"-c",
"--container",
dest="container",
default="docker",
choices=["docker", "podman"],
help="Container runtime",
)
args = parser.parse_args()
dockerfile, tags = build_layers(args)
print("\nDockerfile\n{}".format(dockerfile))
with open("./Dockerfile", "w", encoding="utf-8") as handle:
handle.write(dockerfile)
image_name = "redroid/redroid:" + "_".join(tags)
subprocess.run([args.container, "build", "-t", image_name, "."], check=True)
helper.print_color(
"Successfully built {}".format(image_name),
helper.bcolors.GREEN,
)
if args.houdini:
print_recommended_flow(image_name)
if __name__ == "__main__":
main()

2
requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
requests==2.28.1
tqdm==4.64.1

324
run_redroid.py Normal file
View File

@@ -0,0 +1,324 @@
#!/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()

34
stuff/general.py Normal file
View File

@@ -0,0 +1,34 @@
import os
import zipfile
import hashlib
from tools.helper import bcolors, download_file, print_color
class General:
def download(self):
loc_md5 = ""
if os.path.isfile(self.dl_file_name):
with open(self.dl_file_name,"rb") as f:
bytes = f.read()
loc_md5 = hashlib.md5(bytes).hexdigest()
while not os.path.isfile(self.dl_file_name) or loc_md5 != self.act_md5:
if os.path.isfile(self.dl_file_name):
os.remove(self.dl_file_name)
print_color("md5 mismatches, redownloading now ....",bcolors.YELLOW)
loc_md5 = download_file(self.dl_link, self.dl_file_name)
def extract(self):
print_color("Extracting archive...", bcolors.GREEN)
print(self.dl_file_name)
print(self.extract_to)
with zipfile.ZipFile(self.dl_file_name) as z:
z.extractall(self.extract_to)
def copy(self):
pass
def install(self):
# pass
self.download()
self.extract()
self.copy()

96
stuff/houdini.py Normal file
View File

@@ -0,0 +1,96 @@
import os
import re
import shutil
from stuff.general import General
from tools.helper import bcolors, get_download_dir, print_color, run
class Houdini(General):
download_loc = get_download_dir()
copy_dir = "./houdini"
init_rc_component = """
on early-init
mount binfmt_misc binfmt_misc /proc/sys/fs/binfmt_misc
on property:ro.enable.native.bridge.exec=1
copy /vendor/etc/binfmt_misc/arm_exe /proc/sys/fs/binfmt_misc/register
copy /vendor/etc/binfmt_misc/arm_dyn /proc/sys/fs/binfmt_misc/register
on property:ro.enable.native.bridge.exec64=1
copy /vendor/etc/binfmt_misc/arm64_exe /proc/sys/fs/binfmt_misc/register
copy /vendor/etc/binfmt_misc/arm64_dyn /proc/sys/fs/binfmt_misc/register
on property:sys.boot_completed=1
exec -- /system/bin/sh -c "echo ':arm_exe:M::\\\\x7f\\\\x45\\\\x4c\\\\x46\\\\x01\\\\x01\\\\x01\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x02\\\\x00\\\\x28::/system/bin/houdini:P' >> /proc/sys/fs/binfmt_misc/register"
exec -- /system/bin/sh -c "echo ':arm_dyn:M::\\\\x7f\\\\x45\\\\x4c\\\\x46\\\\x01\\\\x01\\\\x01\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x03\\\\x00\\\\x28::/system/bin/houdini:P' >> /proc/sys/fs/binfmt_misc/register"
exec -- /system/bin/sh -c "echo ':arm64_exe:M::\\\\x7f\\\\x45\\\\x4c\\\\x46\\\\x02\\\\x01\\\\x01\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x02\\\\x00\\\\xb7::/system/bin/houdini64:P' >> /proc/sys/fs/binfmt_misc/register"
exec -- /system/bin/sh -c "echo ':arm64_dyn:M::\\\\x7f\\\\x45\\\\x4c\\\\x46\\\\x02\\\\x01\\\\x01\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x03\\\\x00\\\\xb7::/system/bin/houdini64:P' >> /proc/sys/fs/binfmt_misc/register"
"""
dl_links = {
# 8.1.0 from cros R79 Android N not working
# "8.1.0":[
# "https://github.com/rote66/vendor_intel_proprietary_houdini/archive/9246014ddf22d5e34f294d726dc48ca446b0e20e.zip",
# "b0a9ddc0d817d290a15b132091a37f36"],
"8.1.0":[
"https://github.com/rote66/vendor_intel_proprietary_houdini/archive/46682f423b8497db3f96222f2669d770eff764c3.zip",
"cd4dd2891aa18e7699d33dcc3fe3ffd4"],
"9.0.0":[
"https://github.com/rote66/vendor_intel_proprietary_houdini/archive/46682f423b8497db3f96222f2669d770eff764c3.zip",
"cd4dd2891aa18e7699d33dcc3fe3ffd4"],
"11.0.0": [
"https://github.com/rote66/vendor_intel_proprietary_houdini/archive/debc3dc91cf12b5c5b8a1c546a5b0b7bf7f838a8.zip",
"cb7ffac26d47ec7c89df43818e126b47"],
"12.0.0": [
"https://github.com/rote66/vendor_intel_proprietary_houdini/archive/debc3dc91cf12b5c5b8a1c546a5b0b7bf7f838a8.zip",
"cb7ffac26d47ec7c89df43818e126b47"],
"13.0.0": [
"https://github.com/rote66/vendor_intel_proprietary_houdini/archive/debc3dc91cf12b5c5b8a1c546a5b0b7bf7f838a8.zip",
"cb7ffac26d47ec7c89df43818e126b47"],
"14.0.0": [
"https://github.com/rote66/vendor_intel_proprietary_houdini/archive/debc3dc91cf12b5c5b8a1c546a5b0b7bf7f838a8.zip",
"cb7ffac26d47ec7c89df43818e126b47"]
# "15.0.0": [
# "https://github.com/rote66/vendor_intel_proprietary_houdini/archive/debc3dc91cf12b5c5b8a1c546a5b0b7bf7f838a8.zip",
# "cb7ffac26d47ec7c89df43818e126b47"]
}
dl_file_name = os.path.join(download_loc, "libhoudini.zip")
extract_to = os.path.join(download_loc, "houdini_extract")
def __init__(self, version):
self.version = version
if version in self.dl_links.keys():
self.dl_link = self.dl_links[version][0]
self.act_md5 = self.dl_links[version][1]
else:
raise ValueError(
"No available libhoudini for Android {}".format(version))
def download(self):
print_color("Downloading libhoudini now .....", bcolors.GREEN)
super().download()
def copy(self):
if os.path.exists(self.copy_dir):
shutil.rmtree(self.copy_dir)
run(["chmod", "+x", self.extract_to, "-R"])
print_color("Copying libhoudini library files ...", bcolors.GREEN)
name = re.findall(r"([a-zA-Z0-9]+)\.zip", self.dl_link)[0]
shutil.copytree(os.path.join(self.extract_to, "vendor_intel_proprietary_houdini-" + name,
"prebuilts"), os.path.join(self.copy_dir, "system"), dirs_exist_ok=True)
system_binfmt_dir = os.path.join(self.copy_dir, "system", "etc", "binfmt_misc")
vendor_binfmt_dir = os.path.join(self.copy_dir, "vendor", "etc", "binfmt_misc")
os.makedirs(vendor_binfmt_dir, exist_ok=True)
for filename in ["arm_exe", "arm_dyn", "arm64_exe", "arm64_dyn"]:
shutil.copyfile(
os.path.join(system_binfmt_dir, filename),
os.path.join(vendor_binfmt_dir, filename),
)
init_path = os.path.join(self.copy_dir, "vendor", "etc", "init", "houdini.rc")
if not os.path.isfile(init_path):
os.makedirs(os.path.dirname(init_path), exist_ok=True)
with open(init_path, "w") as initfile:
initfile.write(self.init_rc_component)
os.chmod(init_path, 0o644)

33
stuff/houdini_hack.py Normal file
View File

@@ -0,0 +1,33 @@
import os
import re
import shutil
from stuff.general import General
from tools.helper import bcolors, get_download_dir, print_color, run
class Houdini_Hack(General):
download_loc = get_download_dir()
copy_dir = "./houdini"
dl_file_name = os.path.join(download_loc, "libhoudini_hack.zip")
extract_to = os.path.join(download_loc, "houdini_hack_extract")
def __init__(self, version):
self.version = version
self.dl_link = "https://github.com/rote66/redroid_libhoudini_hack/archive/a2194c5e294cbbfdfe87e51eb9eddb4c3621d8c3.zip"
self.act_md5 = "8f71a58f3e54eca879a2f7de64dbed58"
def download(self):
print_color("Downloading libhoudini_hack now .....", bcolors.GREEN)
super().download()
def copy(self):
run(["chmod", "+x", self.extract_to, "-R"])
print_color("Copying libhoudini hack files ...", bcolors.GREEN)
name = re.findall(r"([a-zA-Z0-9]+)\.zip", self.dl_link)[0]
shutil.copytree(os.path.join(self.extract_to, "redroid_libhoudini_hack-" + name,
self.version), os.path.join(self.copy_dir, "system"), dirs_exist_ok=True)
if not self.version == "9.0.0":
init_path = os.path.join(self.copy_dir, "system", "etc", "init", "hw", "init.rc")
os.chmod(init_path, 0o644)

275
stuff/magisk.py Normal file
View File

@@ -0,0 +1,275 @@
import os
import shutil
import re
import zipfile
import requests
from stuff.general import General
from tools.helper import bcolors, download_file, host, print_color, run, get_download_dir
class Magisk(General):
stable_json = "https://raw.githubusercontent.com/os-fork/HuskyDG-magisk-files/main/stable.json"
machine = host()
def __init__(self):
self.download_loc = get_download_dir()
self.dl_file_name = os.path.join(self.download_loc, "magisk.apk")
self.release_meta_file = os.path.join(self.download_loc, "magisk.version")
self.extract_to = os.path.join(self.download_loc, "magisk_unpack")
self.copy_dir = "./magisk"
self.magisk_dir = os.path.join(self.copy_dir, "vendor", "etc", "magisk")
self.rc_dir = os.path.join(self.copy_dir, "vendor", "etc", "init")
self.app_package = os.environ.get("REDROID_MAGISK_APP_PACKAGE", "io.github.huskydg.magisk")
self.custom_apk = os.environ.get("REDROID_MAGISK_APK", "").strip()
self.custom_url = os.environ.get("REDROID_MAGISK_URL", "").strip()
self.boot_complete_enabled = os.environ.get("REDROID_MAGISK_BOOT_COMPLETE", "0").strip().lower() in {
"1",
"true",
"yes",
"on",
}
self.preseed_sql = os.environ.get(
"REDROID_MAGISK_PRESEED_SQL",
"REPLACE INTO settings (key,value) VALUES "
"('root_access',3),('su_biometric',0),('multiuser_mode',0),('mnt_ns',1),"
"('denylist',0),('sulist',0),('zygisk',1); "
"REPLACE INTO policies (uid,policy,until,logging,notification) VALUES "
"(2000,2,0,0,0);",
).strip()
self.release_version = "unknown"
self.magisk_tmp = "/debug_ramdisk"
self.magisk_bootstrap = "magisk64" if self.machine[1] == 64 else "magisk32"
self.magisk_system_dir = "/vendor/etc/magisk"
boot_complete_component = ""
if self.boot_complete_enabled:
boot_complete_component = """
exec u:r:su:s0 root root -- {MAGISKTMP}/magisk --boot-complete""".format(
MAGISKTMP=self.magisk_tmp,
)
preseed_component = ""
if self.preseed_sql:
escaped_sql = self.preseed_sql.replace("\\", "\\\\").replace('"', '\\"')
preseed_component = """
exec u:r:su:s0 root root -- {MAGISKTMP}/magisk --sqlite "{PRESEED_SQL}" """.format(
MAGISKTMP=self.magisk_tmp,
PRESEED_SQL=escaped_sql,
)
self.bootanim_component = """
on post-fs-data
start logd
mount tmpfs tmpfs {MAGISKTMP} mode=1777
mkdir /data/adb 0700
mkdir /data/adb/magisk 0755
copy {MAGISKSYSTEMDIR}/busybox /data/adb/magisk/busybox
copy {MAGISKSYSTEMDIR}/magiskboot /data/adb/magisk/magiskboot
copy {MAGISKSYSTEMDIR}/magiskinit /data/adb/magisk/magiskinit
copy {MAGISKSYSTEMDIR}/magiskpolicy /data/adb/magisk/magiskpolicy
copy {MAGISKSYSTEMDIR}/util_functions.sh /data/adb/magisk/util_functions.sh
copy {MAGISKSYSTEMDIR}/boot_patch.sh /data/adb/magisk/boot_patch.sh
copy {MAGISKSYSTEMDIR}/addon.d.sh /data/adb/magisk/addon.d.sh
chmod 0755 /data/adb/magisk/busybox
chmod 0755 /data/adb/magisk/magiskboot
chmod 0755 /data/adb/magisk/magiskinit
chmod 0755 /data/adb/magisk/magiskpolicy
chmod 0644 /data/adb/magisk/util_functions.sh
chmod 0644 /data/adb/magisk/boot_patch.sh
chmod 0644 /data/adb/magisk/addon.d.sh
copy {MAGISKSYSTEMDIR}/magisk32 {MAGISKTMP}/magisk32
copy {MAGISKSYSTEMDIR}/magisk64 {MAGISKTMP}/magisk64
copy {MAGISKSYSTEMDIR}/magiskpolicy {MAGISKTMP}/magiskpolicy
copy {MAGISKSYSTEMDIR}/stub.apk {MAGISKTMP}/stub.apk
chmod 0755 {MAGISKTMP}/magisk32
chmod 0755 {MAGISKTMP}/magisk64
chmod 0755 {MAGISKTMP}/magiskpolicy
symlink {MAGISKTMP}/{magisk_bootstrap} {MAGISKTMP}/magisk
symlink {MAGISKTMP}/magisk {MAGISKTMP}/su
symlink {MAGISKTMP}/magisk {MAGISKTMP}/resetprop
symlink {MAGISKTMP}/magiskpolicy {MAGISKTMP}/supolicy
rm /vendor/bin/su
rm /vendor/bin/magisk
rm /vendor/bin/resetprop
rm /vendor/bin/magiskpolicy
symlink {MAGISKTMP}/magisk /vendor/bin/su
symlink {MAGISKTMP}/magisk /vendor/bin/magisk
symlink {MAGISKTMP}/magisk /vendor/bin/resetprop
symlink {MAGISKTMP}/magiskpolicy /vendor/bin/magiskpolicy
mkdir {MAGISKTMP}/.magisk 0711
mkdir {MAGISKTMP}/.magisk/device 0711
mkdir {MAGISKTMP}/.magisk/mirror 0755
mkdir {MAGISKTMP}/.magisk/block 0755
mkdir {MAGISKTMP}/.magisk/worker 0755
mkdir {MAGISKTMP}/.magisk/rootdir 0755
copy {MAGISKSYSTEMDIR}/config {MAGISKTMP}/.magisk/config
exec u:r:su:s0 root root -- {MAGISKTMP}/magisk --post-fs-data
on nonencrypted
exec u:r:su:s0 root root -- {MAGISKTMP}/magisk --service
on property:vold.decrypt=trigger_restart_framework
exec u:r:su:s0 root root -- {MAGISKTMP}/magisk --service
on property:sys.boot_completed=1
mkdir /data/adb/magisk 755
{PRESEED}
{BOOTCOMPLETE}
exec -- /system/bin/sh -c "if [ ! -e /data/data/{app_package} ] ; then pm install {MAGISKSYSTEMDIR}/magisk.apk ; fi"
on property:init.svc.zygote=restarting
exec u:r:su:s0 root root -- {MAGISKTMP}/magisk --zygote-restart
on property:init.svc.zygote=stopped
exec u:r:su:s0 root root -- {MAGISKTMP}/magisk --zygote-restart
""".format(
MAGISKSYSTEMDIR=self.magisk_system_dir,
MAGISKTMP=self.magisk_tmp,
PRESEED=preseed_component,
BOOTCOMPLETE=boot_complete_component,
magisk_bootstrap=self.magisk_bootstrap,
app_package=self.app_package,
)
def _cached_version(self):
if os.path.isfile(self.release_meta_file):
with open(self.release_meta_file, "r", encoding="utf-8") as meta_file:
return meta_file.read().strip()
return ""
def _write_version(self):
with open(self.release_meta_file, "w", encoding="utf-8") as meta_file:
meta_file.write(self.release_version)
def _validate_archive(self):
if not zipfile.is_zipfile(self.dl_file_name):
raise ValueError(
"Downloaded Magisk/Kitsune archive is not a valid APK/ZIP. "
"Set REDROID_MAGISK_APK to a local APK or REDROID_MAGISK_URL to a working direct APK URL."
)
def resolve_release(self):
print_color("Resolving Kitsune Mask stable release ...", bcolors.GREEN)
response = requests.get(self.stable_json, timeout=30)
response.raise_for_status()
payload = response.json()
magisk_info = payload["magisk"]
self.dl_link = magisk_info["link"]
self.release_version = magisk_info.get("version", "unknown")
def download(self):
if self.custom_apk:
self.release_version = "local:" + os.path.basename(self.custom_apk)
if (
os.path.isfile(self.dl_file_name)
and self._cached_version() == self.release_version
):
print_color(
"Using cached local Magisk/Kitsune APK {} ...".format(self.release_version),
bcolors.GREEN,
)
else:
print_color(
"Copying local Magisk/Kitsune APK {} ...".format(self.custom_apk),
bcolors.GREEN,
)
shutil.copyfile(self.custom_apk, self.dl_file_name)
self._write_version()
self._validate_archive()
return
if self.custom_url:
self.dl_link = self.custom_url
self.release_version = "url:" + os.path.basename(self.custom_url)
else:
self.resolve_release()
if (
os.path.isfile(self.dl_file_name)
and self._cached_version() == self.release_version
):
print_color(
"Using cached Kitsune Mask {} ...".format(self.release_version),
bcolors.GREEN,
)
self._validate_archive()
return
print_color(
"Downloading Kitsune Mask {} now .....".format(self.release_version),
bcolors.GREEN,
)
if os.path.isfile(self.dl_file_name):
os.remove(self.dl_file_name)
download_file(self.dl_link, self.dl_file_name)
self._validate_archive()
self._write_version()
def extract(self):
print_color("Extracting archive...", bcolors.GREEN)
if os.path.exists(self.extract_to):
shutil.rmtree(self.extract_to)
os.makedirs(self.extract_to, exist_ok=True)
with zipfile.ZipFile(self.dl_file_name) as zip_handle:
zip_handle.extractall(self.extract_to)
def copy(self):
if os.path.exists(self.copy_dir):
shutil.rmtree(self.copy_dir)
os.makedirs(self.magisk_dir, exist_ok=True)
os.makedirs(self.rc_dir, exist_ok=True)
os.chmod(os.path.join(self.copy_dir, "vendor"), 0o755)
os.chmod(os.path.join(self.copy_dir, "vendor", "etc"), 0o755)
os.chmod(self.magisk_dir, 0o755)
os.chmod(self.rc_dir, 0o755)
print_color("Copying magisk libs now ...", bcolors.GREEN)
arch_map = {
"x86": "x86",
"x86_64": "x86_64",
"arm": "armeabi-v7a",
"arm64": "arm64-v8a"
}
lib_dir = os.path.join(self.extract_to, "lib", arch_map[self.machine[0]])
for parent, dirnames, filenames in os.walk(lib_dir):
for filename in filenames:
o_path = os.path.join(lib_dir, filename)
filename = re.search(r'lib(.*)\.so', filename)
n_path = os.path.join(self.magisk_dir, filename.group(1))
shutil.copyfile(o_path, n_path)
os.chmod(n_path, 0o755)
if self.machine[1] == 64:
secondary_arch_map = {
"x86_64": "x86",
"arm64": "armeabi-v7a",
}
secondary_dir = os.path.join(
self.extract_to,
"lib",
secondary_arch_map[self.machine[0]],
)
magisk32_src = os.path.join(secondary_dir, "libmagisk32.so")
if os.path.exists(magisk32_src):
magisk32_dst = os.path.join(self.magisk_dir, "magisk32")
shutil.copyfile(magisk32_src, magisk32_dst)
os.chmod(magisk32_dst, 0o755)
assets_dir = os.path.join(self.extract_to, "assets")
for asset_name in ["util_functions.sh", "addon.d.sh", "boot_patch.sh", "stub.apk"]:
asset_src = os.path.join(assets_dir, asset_name)
if os.path.exists(asset_src):
asset_dst = os.path.join(self.magisk_dir, asset_name)
shutil.copyfile(asset_src, asset_dst)
os.chmod(asset_dst, 0o644)
config_path = os.path.join(self.magisk_dir, "config")
with open(config_path, "w", encoding="utf-8") as config_file:
config_file.write("SYSTEMMODE=true\nRECOVERYMODE=false\n")
os.chmod(config_path, 0o600)
magisk_apk_path = os.path.join(self.magisk_dir, "magisk.apk")
shutil.copyfile(self.dl_file_name, magisk_apk_path)
os.chmod(magisk_apk_path, 0o644)
magisk_rc_path = os.path.join(self.rc_dir, "magisk.rc")
with open(magisk_rc_path, "w", encoding="utf-8") as initfile:
initfile.write(self.bootanim_component)
os.chmod(magisk_rc_path, 0o644)

124
stuff/mindthegapps.py Normal file
View File

@@ -0,0 +1,124 @@
import os
import shutil
from stuff.general import General
from tools.helper import get_download_dir, host, print_color, run, bcolors
class MindTheGapps(General):
dl_links = {
"15.0.0": {
"x86_64": [
"https://github.com/s1204IT/MindTheGappsBuilder/releases/download/20250330/MindTheGapps-15.0.0-x86_64-20250330.zip",
"e54694828bd74e9066b2534a9675c31e",
],
"arm64": [
"https://github.com/s1204IT/MindTheGappsBuilder/releases/download/20250330/MindTheGapps-15.0.0-arm64-20250330.zip",
"79acb62f0f7c66b0f0bcadae5624f3d1",
],
"arm": [
"https://github.com/s1204IT/MindTheGappsBuilder/releases/download/20250330/MindTheGapps-15.0.0-arm-20250330.zip",
"4ced6a404a714e61831e16068c3642b3",
],
},
"14.0.0": {
"x86_64": [
"https://github.com/s1204IT/MindTheGappsBuilder/releases/download/20240226/MindTheGapps-14.0.0-x86_64-20240226.zip",
"a827a84ccb0cf5914756e8561257ed13",
],
"x86": [
"https://github.com/s1204IT/MindTheGappsBuilder/releases/download/20240226/MindTheGapps-14.0.0-x86-20240226.zip",
"45736b21475464e4a45196b9aa9d3b7f",
],
"arm64": [
"https://github.com/s1204IT/MindTheGappsBuilder/releases/download/20240226/MindTheGapps-14.0.0-arm64-20240226.zip",
"a0905cc7bf3f4f4f2e3f59a4e1fc789b",
],
"arm": [
"https://github.com/s1204IT/MindTheGappsBuilder/releases/download/20240226/MindTheGapps-14.0.0-arm-20240226.zip",
"fa167a3b7a10c4d3e688a59cd794f75b",
],
},
"13.0.0": {
"x86_64": [
"https://github.com/s1204IT/MindTheGappsBuilder/releases/download/20240226/MindTheGapps-13.0.0-x86_64-20240226.zip",
"eee87a540b6e778f3a114fff29e133aa",
],
"x86": [
"https://github.com/s1204IT/MindTheGappsBuilder/releases/download/20240226/MindTheGapps-13.0.0-x86-20240226.zip",
"d928c5eabb4394a97f2d7a5c663e7c2e",
],
"arm64": [
"https://github.com/s1204IT/MindTheGappsBuilder/releases/download/20240226/MindTheGapps-13.0.0-arm64-20240226.zip",
"ebdf35e17bc1c22337762fcf15cd6e97",
],
"arm": [
"https://github.com/s1204IT/MindTheGappsBuilder/releases/download/20240619/MindTheGapps-13.0.0-arm-20240619.zip",
"ec7aa5efc9e449b101bc2ee7448a49bf",
],
},
"13.0.0_64only": {
"x86_64": [
"https://github.com/s1204IT/MindTheGappsBuilder/releases/download/20240226/MindTheGapps-13.0.0-x86_64-20240226.zip",
"eee87a540b6e778f3a114fff29e133aa",
],
"arm64": [
"https://github.com/s1204IT/MindTheGappsBuilder/releases/download/20240226/MindTheGapps-13.0.0-arm64-20240226.zip",
"ebdf35e17bc1c22337762fcf15cd6e97",
],
},
"12.0.0_64only": {
"x86_64": [
"https://github.com/s1204IT/MindTheGappsBuilder/releases/download/20240619/MindTheGapps-12.1.0-x86_64-20240619.zip",
"05d6e99b6e6567e66d43774559b15fbd"
],
"arm64": [
"https://github.com/s1204IT/MindTheGappsBuilder/releases/download/20240619/MindTheGapps-12.1.0-arm64-20240619.zip",
"94dd174ff16c2f0006b66b25025efd04",
],
},
"12.0.0": {
"x86_64": [
"https://github.com/s1204IT/MindTheGappsBuilder/releases/download/20240619/MindTheGapps-12.1.0-x86_64-20240619.zip",
"05d6e99b6e6567e66d43774559b15fbd"
],
"x86": [
"https://github.com/s1204IT/MindTheGappsBuilder/releases/download/20240619/MindTheGapps-12.1.0-x86-20240619.zip",
"ff2421a75afbdda8a003e4fd25e95050"
],
"arm64": [
"https://github.com/s1204IT/MindTheGappsBuilder/releases/download/20240619/MindTheGapps-12.1.0-arm64-20240619.zip",
"94dd174ff16c2f0006b66b25025efd04",
],
"arm": [
"https://github.com/s1204IT/MindTheGappsBuilder/releases/download/20240619/MindTheGapps-12.1.0-arm-20240619.zip",
"5af756b3b5776c2f6ee024a9f7f42a2f",
],
},
}
arch = host()
download_loc = get_download_dir()
dl_file_name = os.path.join(download_loc, "mindthegapps.zip")
dl_link = ...
act_md5 = ...
copy_dir = "./mindthegapps"
extract_to = os.path.join(download_loc, "mindthegapps_extract")
def __init__(self, version):
self.version = version
self.dl_link = self.dl_links[self.version][self.arch[0]][0]
self.act_md5 = self.dl_links[self.version][self.arch[0]][1]
def download(self):
print_color("Downloading MindTheGapps now .....", bcolors.GREEN)
super().download()
def copy(self):
if os.path.exists(self.copy_dir):
shutil.rmtree(self.copy_dir)
if not os.path.exists(self.extract_to):
os.makedirs(self.extract_to)
shutil.copytree(
os.path.join(self.extract_to, "system", ),
os.path.join(self.copy_dir, "system"), dirs_exist_ok=True, )

95
tools/helper.py Normal file
View File

@@ -0,0 +1,95 @@
import os
import platform
import subprocess
import requests
from tqdm import tqdm
import hashlib
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
def run(args):
result = subprocess.run(args=args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.stderr:
print(result.stderr.decode("utf-8"))
raise subprocess.CalledProcessError(
returncode = result.returncode,
cmd = result.args,
stderr = result.stderr
)
return result
def download_file(url, f_name):
md5 = ""
response = requests.get(url, stream=True)
total_size_in_bytes = int(response.headers.get('content-length', 0))
block_size = 1024 # 1 Kibibyte
progress_bar = tqdm(total=total_size_in_bytes, unit='iB', unit_scale=True)
with open(f_name, 'wb') as file:
for data in response.iter_content(block_size):
progress_bar.update(len(data))
file.write(data)
progress_bar.close()
with open(f_name, "rb") as f:
bytes = f.read()
md5 = hashlib.md5(bytes).hexdigest()
if total_size_in_bytes != 0 and progress_bar.n != total_size_in_bytes:
raise ValueError("Something went wrong while downloading")
return md5
def host():
machine = platform.machine().lower()
mapping = {
"i686": ("x86", 32),
"x86_64": ("x86_64", 64),
"amd64": ("x86_64", 64),
"aarch64": ("arm64", 64),
"armv7l": ("arm", 32),
"armv8l": ("arm", 32)
}
if machine in mapping:
# if mapping[machine] == "x86_64":
# with open("/proc/cpuinfo") as f:
# if "sse4_2" not in f.read():
# print("x86_64 CPU does not support SSE4.2, falling back to x86...")
# return ("x86", 32)
return mapping[machine]
raise ValueError("platform.machine '" + machine + "'"
" architecture is not supported")
class bcolors:
RED = '\033[31m'
YELLOW = '\033[33m'
GREEN = '\033[32m'
ENDC = '\033[0m'
def print_color(str, color):
print(color+str+bcolors.ENDC)