Compare commits

..
Author SHA1 Message Date
MisterWizzardandClaude Opus 5 b09e018894 Fix /data/adb/magisk permissions so Zygisk can initialise
General.install() runs copy() -> extra1() -> set_perm(). set_perm() only walks
self.files, which are paths under copy_dir (the overlay), so the copy that
Magisk.setup() makes into <data_dir>/adb/magisk keeps the 0644 the binaries
inherit from the apk extraction.

A non-executable /data/adb/magisk/magisk64 makes magisk_env() abort at
post-fs-data. Root still works, because that runs from the overlay copy, but
Zygisk never initialises: the Magisk app reports "Zygisk: No" no matter what the
zygisk setting says, and no error is surfaced to explain why. This breaks every
Zygisk module (LSPosed, Shamiko, PlayIntegrityFix, ...).

Fix setup() to apply the same 0755 to its own destination that
Magisk.set_path_perm() already applies to magisk paths.

Adds a regression test that passes with the fix and fails without it. It needs no
Waydroid install, no root and no extra dependencies:

    python3 tests/test_magisk_setup_perms.py

Fixes #283

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 09:49:05 +10:00
ayasa520 d5289cfd89 Update libhoudini (#256)
* Update download link for Houdini version 13
2026-01-05 09:56:03 +01:00
NullBite ddaa6b190f Add Nix flake and improve derivation
This adds a Nix flake to the repo as well as updates the derivation so
that it includes `lzip` as a dependency
2025-11-17 10:08:53 +01:00
Gerry Bagaskoro Putro 15020ec20a fix(nodataperm): prevent crash when uninstalling on Android 13 2025-11-11 19:37:38 +01:00
Alessandro Astone fcb15624db Fix A13 widevine
Closes: https://github.com/casualsnek/waydroid_script/issues/236
2025-08-31 22:56:59 +02:00
Alessandro Astone db43c7c9cf main: Default to A13
Following Waydroid which now defaults to A13
2025-08-18 12:41:31 +02:00
Mio 3e344b360f fix https://github.com/casualsnek/waydroid_script/issues/229 2025-07-13 09:11:10 +02:00
金雄镕 5527d390eb Reorder ro.product.cpu.abilist to prioritize 64-bit ABIs 2025-07-11 19:43:32 +02:00
MasterKia 6f44d95070 Fix microg installation guide
The previous command returns an error ("ModuleNotFoundError: No module named 'InquirerPy'") even after installing the InquirerPy module using pip.
2025-07-04 10:43:21 +02:00
Alessandro Astone 32d21c8074 Change ndk_translation for A13 2025-05-30 18:36:04 +02:00
Alessandro Astone eb337a1351 Change houdini for A13
Even though this is extracted from an Android 14 image, it works better
including on AMD CPUs

Closes: #224
2025-05-21 21:40:38 +02:00
Alessandro Astone 48186df8af A13 ndk_translation 2025-05-20 00:02:29 +02:00
Alessandro Astone c14650010e A13 houdini 2025-05-20 00:02:29 +02:00
13 changed files with 388 additions and 61 deletions
+1 -1
View File
@@ -166,7 +166,7 @@ chmod 777 -R /mnt/*/*/*/*/Android/obb
![](assets/7.png) ![](assets/7.png)
``` ```
sudo python main.py install microg sudo venv/bin/python3 main.py install microg
``` ```
## Hide Status Bar ## Hide Status Bar
+4 -22
View File
@@ -1,22 +1,4 @@
with (import <nixpkgs> {}); let
pkgs = import <nixpkgs> {};
stdenv.mkDerivation { in
name = "waydroid_script"; pkgs.callPackage ./package.nix { }
buildInputs = [
(python3.withPackages(ps: with ps; [ tqdm requests inquirerpy ]))
];
src = ./.;
postPatch = ''
patchShebangs main.py
'';
installPhase = ''
mkdir -p $out/libexec
cp -r . $out/libexec/waydroid_script
mkdir -p $out/bin
ln -s $out/libexec/waydroid_script/main.py $out/bin/waydroid_script
'';
}
Generated
+41
View File
@@ -0,0 +1,41 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1707650010,
"narHash": "sha256-dOhphIA4MGrH4ElNCy/OlwmN24MsnEqFjRR6+RY7jZw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "809cca784b9f72a5ad4b991e0e7bcf8890f9c3a6",
"type": "github"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"systems": "systems"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}
+21
View File
@@ -0,0 +1,21 @@
{
description = "Waydroid Extras Script";
inputs = {
systems.url = "github:nix-systems/default";
};
outputs = { self, nixpkgs, systems }:
let
inherit (nixpkgs) lib;
eachSystem = lib.genAttrs (import systems);
mkApp = program: { type = "app"; inherit program; };
in {
packages = eachSystem (system: rec {
waydroid_script = nixpkgs.legacyPackages."${system}".callPackage ./package.nix { };
default = waydroid_script;
});
apps = eachSystem (system: rec {
waydroid_script = mkApp "${self.outputs.packages.${system}.waydroid_script}/bin/waydroid_script";
default = waydroid_script;
});
};
}
+55 -25
View File
@@ -21,29 +21,45 @@ from stuff.fdroidpriv import FDroidPriv
import tools.helper as helper import tools.helper as helper
from tools import container from tools import container
from tools import images from tools import images
import argparse
from tools.logger import Logger from tools.logger import Logger
def get_certified():
def get_certified(args):
AndroidId().get_id() AndroidId().get_id()
def mount(partition, copy_dir): def mount(partition, copy_dir):
img = os.path.join(images.get_image_dir(), f"{partition}.img") img = os.path.join(images.get_image_dir(), partition+".img")
mount_point = os.path.join(copy_dir) if partition == "system" else os.path.join(copy_dir, partition) mount_point = ""
Logger.info(f"Mounting {img} to {mount_point}") if partition == "system":
mount_point = os.path.join(copy_dir)
else:
mount_point = os.path.join(copy_dir, partition)
Logger.info("Mounting {} to {}".format(img, mount_point))
images.mount(img, mount_point) images.mount(img, mount_point)
def resize(partition): def resize(partition):
img = os.path.join(images.get_image_dir(), f"{partition}.img") img = os.path.join(images.get_image_dir(), partition+".img")
img_size = int(os.path.getsize(img) / (1024 * 1024)) img_size = int(os.path.getsize(img)/(1024*1024))
new_size = f"{img_size + 500}M" new_size = "{}M".format(img_size+500)
Logger.info(f"Resizing {img} to {new_size}") Logger.info("Resizing {} to {}".format(img, new_size))
images.resize(img, new_size) images.resize(img, new_size)
def umount(partition, copy_dir): def umount(partition, copy_dir):
mount_point = os.path.join(copy_dir) if partition == "system" else os.path.join(copy_dir, partition) mount_point = ""
Logger.info(f"Unmounting {mount_point}") if partition == "system":
mount_point = os.path.join(copy_dir)
else:
mount_point = os.path.join(copy_dir, partition)
Logger.info("Umounting {}".format(mount_point))
images.umount(mount_point) images.umount(mount_point)
def install_app(args): def install_app(args):
install_list: List[General] = [] install_list: List[General] = []
app = args.app app = args.app
@@ -102,6 +118,7 @@ def install_app(args):
container.upgrade() container.upgrade()
def remove_app(args): def remove_app(args):
remove_list: List[General] = [] remove_list: List[General] = []
app = args.app app = args.app
@@ -141,8 +158,10 @@ def remove_app(args):
container.upgrade() container.upgrade()
def hack_option(args): def hack_option(args):
Logger.warning("If these hacks cause any problems, run `sudo python main.py remove <hack_option>` to remove") Logger.warning(
"If these hacks cause any problems, run `sudo python main.py remove <hack_option>` to remove")
hack_list: List[General] = [] hack_list: List[General] = []
options = args.option_name options = args.option_name
@@ -179,8 +198,10 @@ def hack_option(args):
container.upgrade() container.upgrade()
def interact(): def interact():
os.system("clear") os.system("clear")
args = argparse.Namespace()
android_version = inquirer.select( android_version = inquirer.select(
message="Select Android version", message="Select Android version",
instruction="(\u2191\u2193 Select Item)", instruction="(\u2191\u2193 Select Item)",
@@ -189,26 +210,28 @@ def interact():
Choice(name="Android 13", value="13"), Choice(name="Android 13", value="13"),
Choice(name="Exit", value=None) Choice(name="Exit", value=None)
], ],
default="11", default="13",
).execute() ).execute()
if not android_version: if not android_version:
exit() exit()
args.android_version = android_version
args = argparse.Namespace(android_version=android_version, microg_variant="Standard")
action = inquirer.select( action = inquirer.select(
message="Please select an action", message="Please select an action",
choices=["Install", "Remove", "Hack", "Get Google Device ID to Get Certified"], choices=[
"Install",
"Remove",
"Hack",
"Get Google Device ID to Get Certified"
],
instruction="([↑↓]: Select Item)", instruction="([↑↓]: Select Item)",
default=None, default=None,
).execute() ).execute()
if not action: if not action:
exit() exit()
install_choices = ["gapps", "microg", "libndk", "magisk", "smartdock", "fdroidpriv",] install_choices = ["gapps", "microg", "libndk", "libhoudini", "magisk", "smartdock", "fdroidpriv", "widevine",]
hack_choices = [] hack_choices = []
if android_version == "11": if android_version=="11":
install_choices.extend(["libhoudini", "widevine"])
hack_choices.extend(["nodataperm", "hidestatusbar"]) hack_choices.extend(["nodataperm", "hidestatusbar"])
if action == "Install": if action == "Install":
@@ -219,7 +242,8 @@ def interact():
invalid_message="should be at least 1 selection", invalid_message="should be at least 1 selection",
choices=install_choices choices=install_choices
).execute() ).execute()
microg_variants = ["Standard", "NoGoolag", "UNLP", "Minimal", "MinimalIAP"] microg_variants = ["Standard", "NoGoolag",
"UNLP", "Minimal", "MinimalIAP"]
if "microg" in apps: if "microg" in apps:
microg_variant = inquirer.select( microg_variant = inquirer.select(
message="Select MicroG variant", message="Select MicroG variant",
@@ -238,7 +262,7 @@ def interact():
choices=[*install_choices, *hack_choices] choices=[*install_choices, *hack_choices]
).execute() ).execute()
args.app = apps args.app = apps
args.microg_variant = "Standard" args.microg_variant="Standard"
remove_app(args) remove_app(args)
elif action == "Hack": elif action == "Hack":
apps = inquirer.checkbox( apps = inquirer.checkbox(
@@ -251,18 +275,19 @@ def interact():
args.option_name = apps args.option_name = apps
hack_option(args) hack_option(args)
elif action == "Get Google Device ID to Get Certified": elif action == "Get Google Device ID to Get Certified":
get_certified() AndroidId().get_id()
def main(): def main():
parser = argparse.ArgumentParser(description=''' parser = argparse.ArgumentParser(description='''
Does stuff like installing Gapps, installing Magisk, installing NDK Translation and getting Android ID for device registration. Does stuff like installing Gapps, installing Magisk, installing NDK Translation and getting Android ID for device registration.
Use -h flag for help!''') Use -h flag for help!''')
subparsers = parser.add_subparsers(title="command", dest='command') subparsers = parser.add_subparsers(title="coomand", dest='command')
parser.add_argument('-a', '--android-version', parser.add_argument('-a', '--android-version',
dest='android_version', dest='android_version',
help='Specify the Android version', help='Specify the Android version',
default="11", default="13",
choices=["11", "13"]) choices=["11", "13"])
# android command # android command
@@ -270,7 +295,8 @@ def main():
'certified', help='Get device ID to obtain Play Store certification') 'certified', help='Get device ID to obtain Play Store certification')
certified.set_defaults(func=get_certified) certified.set_defaults(func=get_certified)
install_choices = ["gapps", "microg", "libndk", "libhoudini", "magisk", "mitm", "smartdock", "widevine"] install_choices = ["gapps", "microg", "libndk", "libhoudini",
"magisk", "mitm", "smartdock", "widevine"]
hack_choices = ["nodataperm", "hidestatusbar"] hack_choices = ["nodataperm", "hidestatusbar"]
micrg_variants = ["Standard", "NoGoolag", "UNLP", "Minimal", "MinimalIAP"] micrg_variants = ["Standard", "NoGoolag", "UNLP", "Minimal", "MinimalIAP"]
remove_choices = install_choices remove_choices = install_choices
@@ -279,6 +305,7 @@ def main():
"dest": "app", "dest": "app",
"type": str, "type": str,
"nargs": '+', "nargs": '+',
# "metavar":"",
} }
install_help = """ install_help = """
@@ -316,12 +343,15 @@ widevine: Add support for widevine DRM L3
hack_parser.set_defaults(func=hack_option) hack_parser.set_defaults(func=hack_option)
args = parser.parse_args() args = parser.parse_args()
args.microg_variant = "Standard"
if hasattr(args, 'func'): if hasattr(args, 'func'):
args_dict = vars(args)
helper.check_root() helper.check_root()
args.func(args) args.func(args)
else: else:
helper.check_root() helper.check_root()
interact() interact()
if __name__ == "__main__": if __name__ == "__main__":
main() main()
+32
View File
@@ -0,0 +1,32 @@
{ lib,
stdenvNoCC,
lzip,
python3,
makeWrapper }:
let
wrappedPath = lib.makeBinPath [ lzip ];
in stdenvNoCC.mkDerivation {
name = "waydroid_script";
buildInputs = [
(python3.withPackages(ps: with ps; [ tqdm requests inquirerpy ]))
];
nativeBuildInputs = [
makeWrapper
];
src = ./.;
postPatch = ''
patchShebangs main.py
'';
installPhase = ''
mkdir -p $out/libexec
cp -r . $out/libexec/waydroid_script
mkdir -p $out/bin
makeShellWrapper $out/libexec/waydroid_script/main.py $out/bin/waydroid_script \
--prefix PATH : "${wrappedPath}"
'';
}
+2
View File
@@ -6,6 +6,8 @@ from stuff.general import General
class FDroidPriv(General): class FDroidPriv(General):
id = "fdroid priv" id = "fdroid priv"
dl_links = {"11": ["https://f-droid.org/repo/org.fdroid.fdroid.privileged.ota_2130.zip", dl_links = {"11": ["https://f-droid.org/repo/org.fdroid.fdroid.privileged.ota_2130.zip",
"6242cab56d197d80c598593a46da62e4"],
"13": ["https://f-droid.org/repo/org.fdroid.fdroid.privileged.ota_2130.zip",
"6242cab56d197d80c598593a46da62e4"]} "6242cab56d197d80c598593a46da62e4"]}
partition = "system" partition = "system"
dl_file_name = "org.fdroid.fdroid.privileged.ota_2130.zip" dl_file_name = "org.fdroid.fdroid.privileged.ota_2130.zip"
+1 -1
View File
@@ -10,7 +10,7 @@ class Houdini(General):
partition = "system" partition = "system"
dl_links = { dl_links = {
"11": ["https://github.com/supremegamers/vendor_intel_proprietary_houdini/archive/81f2a51ef539a35aead396ab7fce2adf89f46e88.zip", "fbff756612b4144797fbc99eadcb6653"], "11": ["https://github.com/supremegamers/vendor_intel_proprietary_houdini/archive/81f2a51ef539a35aead396ab7fce2adf89f46e88.zip", "fbff756612b4144797fbc99eadcb6653"],
# "13": ["https://github.com/supremegamers/vendor_intel_proprietary_houdini/archive/978d8cba061a08837b7e520cd03b635af643ba08.zip", "1e139054c05034648fae58a1810573b4"] "13": ["https://github.com/supremegamers/vendor_intel_proprietary_houdini/archive/9e77896350caccd228b36b2e1b4a994aa4bd48da.zip", "3807fe029559db3037efe245d9e74270"]
} }
act_md5 = ... act_md5 = ...
dl_link = ... dl_link = ...
+22 -1
View File
@@ -64,7 +64,28 @@ on property:init.svc.zygote=stopped
Logger.info("Additional setup") Logger.info("Additional setup")
magisk_absolute_dir = os.path.join(self.copy_dir, self.magisk_dir) magisk_absolute_dir = os.path.join(self.copy_dir, self.magisk_dir)
data_dir = get_data_dir() data_dir = get_data_dir()
shutil.copytree(magisk_absolute_dir, os.path.join(data_dir, "adb", "magisk"), dirs_exist_ok=True) data_magisk_dir = os.path.join(data_dir, "adb", "magisk")
shutil.copytree(magisk_absolute_dir, data_magisk_dir, dirs_exist_ok=True)
# General.install() calls set_perm() only over self.files, which are paths
# under copy_dir (the overlay). This /data/adb copy is made here, before
# that runs, so it keeps the 0644 the binaries inherited from the apk
# extraction. A non-executable /data/adb/magisk/magisk64 makes magisk_env()
# abort at post-fs-data: root still works from the overlay copy, but Zygisk
# silently never initialises (Magisk reports "Zygisk: No" no matter what
# the zygisk setting says). Apply the same perms set_path_perm() gives
# magisk paths, to this copy too.
self.set_data_magisk_perm(data_magisk_dir)
def set_data_magisk_perm(self, data_magisk_dir):
if not os.path.isdir(data_magisk_dir):
return
os.chmod(data_magisk_dir, 0o755)
for root, dirnames, filenames in os.walk(data_magisk_dir):
for dirname in dirnames:
os.chmod(os.path.join(root, dirname), 0o755)
for filename in filenames:
os.chmod(os.path.join(root, filename), 0o755)
def copy(self): def copy(self):
magisk_absolute_dir = os.path.join(self.copy_dir, self.magisk_dir) magisk_absolute_dir = os.path.join(self.copy_dir, self.magisk_dir)
+2 -2
View File
@@ -9,12 +9,12 @@ class Ndk(General):
partition = "system" partition = "system"
dl_links = { dl_links = {
"11": ["https://github.com/supremegamers/vendor_google_proprietary_ndk_translation-prebuilt/archive/9324a8914b649b885dad6f2bfd14a67e5d1520bf.zip", "c9572672d1045594448068079b34c350"], "11": ["https://github.com/supremegamers/vendor_google_proprietary_ndk_translation-prebuilt/archive/9324a8914b649b885dad6f2bfd14a67e5d1520bf.zip", "c9572672d1045594448068079b34c350"],
"13": ["https://github.com/supremegamers/vendor_google_proprietary_ndk_translation-prebuilt/archive/a090003c60df53a9eadb2df09bd4fd2fa86ea629.zip", "e6f0d9fc28ebc427b59a3942a9a4ffc0"] "13": ["https://github.com/supremegamers/vendor_google_proprietary_ndk_translation-prebuilt/archive/68734c52556d3d7a6db34c603dd9276915c29f2f.zip", "0b2207c490fcb400aa5c87fcf0d52d38"]
} }
dl_file_name = "libndktranslation.zip" dl_file_name = "libndktranslation.zip"
extract_to = "/tmp/libndkunpack" extract_to = "/tmp/libndkunpack"
apply_props = { apply_props = {
"ro.product.cpu.abilist": "x86_64,x86,armeabi-v7a,armeabi,arm64-v8a", "ro.product.cpu.abilist": "x86_64,x86,arm64-v8a,armeabi-v7a,armeabi",
"ro.product.cpu.abilist32": "x86,armeabi-v7a,armeabi", "ro.product.cpu.abilist32": "x86,armeabi-v7a,armeabi",
"ro.product.cpu.abilist64": "x86_64,arm64-v8a", "ro.product.cpu.abilist64": "x86_64,arm64-v8a",
"ro.dalvik.vm.native.bridge": "libndk_translation.so", "ro.dalvik.vm.native.bridge": "libndk_translation.so",
+14 -6
View File
@@ -10,19 +10,23 @@ from tools import container
class Nodataperm(General): class Nodataperm(General):
id = "nodataperm" id = "nodataperm"
dl_links = { dl_links = {
"11": "11": {
{ "x86_64": [
"https://github.com/ayasa520/hack_full_data_permission/archive/d4beab7780eb792059d33e77d865579c9ee41546.zip",
"b0e3908ffcf5df8ea62f4929aa680f1a"
],
},
"13": {
"x86_64": [ "x86_64": [
"https://github.com/ayasa520/hack_full_data_permission/archive/d4beab7780eb792059d33e77d865579c9ee41546.zip", "https://github.com/ayasa520/hack_full_data_permission/archive/d4beab7780eb792059d33e77d865579c9ee41546.zip",
"b0e3908ffcf5df8ea62f4929aa680f1a" "b0e3908ffcf5df8ea62f4929aa680f1a"
], ],
}, },
"13": {}
} }
dl_file_name = "nodataperm.zip" dl_file_name = "nodataperm.zip"
extract_to = "/tmp/nodataperm" extract_to = "/tmp/nodataperm"
dl_link = ... dl_link = None
act_md5 = ... act_md5 = None
partition = "system" partition = "system"
files = [ files = [
"etc/nodataperm.sh", "etc/nodataperm.sh",
@@ -36,11 +40,15 @@ class Nodataperm(General):
super().__init__() super().__init__()
print("ok") print("ok")
arch = self.arch[0] arch = self.arch[0]
if android_version not in self.dl_links:
raise KeyError(f"No download links for Android version '{android_version}'")
if arch not in self.dl_links[android_version]:
raise KeyError(f"No download links for architecture '{arch}' in Android version '{android_version}'")
self.dl_link = self.dl_links[android_version][arch][0] self.dl_link = self.dl_links[android_version][arch][0]
self.act_md5 = self.dl_links[android_version][arch][1] self.act_md5 = self.dl_links[android_version][arch][1]
def copy(self): def copy(self):
name = re.findall("([a-zA-Z0-9]+)\.zip", self.dl_link)[0] name = re.findall(r"([a-zA-Z0-9]+)\.zip", self.dl_link)[0]
extract_path = os.path.join( extract_path = os.path.join(
self.extract_to, f"hack_full_data_permission-{name}") self.extract_to, f"hack_full_data_permission-{name}")
if not container.use_overlayfs(): if not container.use_overlayfs():
+13 -2
View File
@@ -13,8 +13,8 @@ class Widevine(General):
"x86_64": { "x86_64": {
"11": ["https://github.com/supremegamers/vendor_google_proprietary_widevine-prebuilt/archive/48d1076a570837be6cdce8252d5d143363e37cc1.zip", "11": ["https://github.com/supremegamers/vendor_google_proprietary_widevine-prebuilt/archive/48d1076a570837be6cdce8252d5d143363e37cc1.zip",
"f587b8859f9071da4bca6cea1b9bed6a"], "f587b8859f9071da4bca6cea1b9bed6a"],
"13": ["https://github.com/supremegamers/vendor_google_proprietary_widevine-prebuilt/archive/a8524d608431573ef1c9313822d271f78728f9a6.zip", "13": ["https://github.com/WayDroid-ATV/vendor_google_proprietary_widevine-prebuilt/archive/679552343d8b2e8d7a19b6df61c7a03963d0c75b.zip",
"5c55df61da5c012b4e43746547ab730f"] "80ab79ea85c7b2556baedb371a54e01c"]
}, },
# "armeabi-v7a": ["https://github.com/supremegamers/vendor_google_proprietary_widevine-prebuilt/archive/a1a19361d36311bee042da8cf4ced798d2c76d98.zip", "fed6898b5cfd2a908cb134df97802554"], # "armeabi-v7a": ["https://github.com/supremegamers/vendor_google_proprietary_widevine-prebuilt/archive/a1a19361d36311bee042da8cf4ced798d2c76d98.zip", "fed6898b5cfd2a908cb134df97802554"],
"arm64-v8a": { "arm64-v8a": {
@@ -30,7 +30,10 @@ class Widevine(General):
"etc/init/*widevine.rc", "etc/init/*widevine.rc",
"etc/vintf/manifest/*widevine.xml", "etc/vintf/manifest/*widevine.xml",
"lib/libwvhidl.so", "lib/libwvhidl.so",
"lib/libwvaidl.so",
"lib/mediadrm", "lib/mediadrm",
"lib64/libwvhidl.so",
"lib64/libwvaidl.so",
"lib64/mediadrm" "lib64/mediadrm"
] ]
@@ -38,9 +41,17 @@ class Widevine(General):
super().__init__() super().__init__()
self.dl_link = self.dl_links[self.arch[0]][android_version][0] self.dl_link = self.dl_links[self.arch[0]][android_version][0]
self.act_md5 = self.dl_links[self.arch[0]][android_version][1] self.act_md5 = self.dl_links[self.arch[0]][android_version][1]
self.android_version = android_version
self.libdir = "lib64" if self.arch[1] == 64 else "lib"
def copy(self): def copy(self):
name = re.findall("([a-zA-Z0-9]+)\.zip", self.dl_link)[0] name = re.findall("([a-zA-Z0-9]+)\.zip", self.dl_link)[0]
Logger.info("Copying widevine library files ...") Logger.info("Copying widevine library files ...")
shutil.copytree(os.path.join(self.extract_to, "vendor_google_proprietary_widevine-prebuilt-"+name, shutil.copytree(os.path.join(self.extract_to, "vendor_google_proprietary_widevine-prebuilt-"+name,
"prebuilts"), os.path.join(self.copy_dir, self.partition), dirs_exist_ok=True) "prebuilts"), os.path.join(self.copy_dir, self.partition), dirs_exist_ok=True)
if self.android_version == "13":
try:
os.symlink("libprotobuf-cpp-lite-3.9.1.so",
os.path.join(self.copy_dir, self.partition, self.libdir, "libprotobuf-cpp-lite.so"))
except FileExistsError:
pass
+179
View File
@@ -0,0 +1,179 @@
#!/usr/bin/env python3
"""Regression test for Magisk.setup() permissions on the /data/adb/magisk copy.
install() runs copy() -> extra1() -> set_perm(). set_perm() only walks self.files,
which are paths under copy_dir (the overlay), so the copy that setup() makes into
<data>/adb/magisk keeps the 0644 the binaries inherit from the apk extraction.
A non-executable /data/adb/magisk/magisk64 makes magisk_env() abort at post-fs-data.
Root still works (it runs from the overlay copy), but Zygisk never initialises and
the Magisk app reports "Zygisk: No" with no error explaining why.
Runs as an ordinary user, needs no Waydroid install and no extra dependencies:
python3 tests/test_magisk_setup_perms.py
"""
import os
import shutil
import stat
import sys
import tempfile
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import stuff.magisk as magisk_module
from stuff.magisk import Magisk
MAGISK_BINARIES = [
"magisk64",
"magiskinit",
"magiskboot",
"magiskpolicy",
"busybox",
"util_functions.sh",
]
def make_overlay(base):
"""Mimic the tree copy() leaves behind: binaries written 0644 by shutil.copyfile."""
magisk_dir = os.path.join(base, "system", "etc", "init", "magisk")
os.makedirs(os.path.join(magisk_dir, "chromeos"), exist_ok=True)
for name in MAGISK_BINARIES:
path = os.path.join(magisk_dir, name)
with open(path, "wb") as f:
f.write(b"\x7fELF placeholder")
os.chmod(path, 0o644)
nested = os.path.join(magisk_dir, "chromeos", "futility")
with open(nested, "wb") as f:
f.write(b"placeholder")
os.chmod(nested, 0o644)
return magisk_dir
def file_modes(root):
modes = {}
for dirpath, _dirnames, filenames in os.walk(root):
for name in filenames:
path = os.path.join(dirpath, name)
modes[os.path.relpath(path, root)] = stat.S_IMODE(os.stat(path).st_mode)
return modes
def test_plain_copytree_loses_exec_bit():
"""Document the original behaviour: a bare copytree leaves the binaries 0644."""
base = tempfile.mkdtemp()
try:
src = make_overlay(base)
dst = os.path.join(base, "data", "adb", "magisk")
os.makedirs(os.path.dirname(dst), exist_ok=True)
shutil.copytree(src, dst, dirs_exist_ok=True)
modes = file_modes(dst)
assert modes["magisk64"] == 0o644, (
"expected the pre-fix behaviour to reproduce, got %o" % modes["magisk64"]
)
finally:
shutil.rmtree(base, ignore_errors=True)
def test_setup_makes_data_copy_executable():
"""setup() must leave every file and dir in <data>/adb/magisk executable."""
base = tempfile.mkdtemp()
try:
make_overlay(base)
data_dir = os.path.join(base, "data")
os.makedirs(data_dir, exist_ok=True)
original_get_data_dir = magisk_module.get_data_dir
original_info = magisk_module.Logger.info
magisk_module.get_data_dir = lambda: data_dir
magisk_module.Logger.info = lambda *a, **k: None
class StubMagisk(Magisk):
@property
def copy_dir(self):
return base
try:
StubMagisk().setup()
finally:
magisk_module.get_data_dir = original_get_data_dir
magisk_module.Logger.info = original_info
dst = os.path.join(data_dir, "adb", "magisk")
modes = file_modes(dst)
assert modes, "setup() copied nothing"
not_executable = {n: oct(m) for n, m in modes.items() if m != 0o755}
assert not not_executable, "files left non-0755: %s" % not_executable
assert stat.S_IMODE(os.stat(dst).st_mode) == 0o755
nested = os.path.join(dst, "chromeos")
assert stat.S_IMODE(os.stat(nested).st_mode) == 0o755, "nested dir not fixed"
finally:
shutil.rmtree(base, ignore_errors=True)
def test_setup_is_idempotent():
base = tempfile.mkdtemp()
try:
make_overlay(base)
data_dir = os.path.join(base, "data")
os.makedirs(data_dir, exist_ok=True)
original_get_data_dir = magisk_module.get_data_dir
original_info = magisk_module.Logger.info
magisk_module.get_data_dir = lambda: data_dir
magisk_module.Logger.info = lambda *a, **k: None
class StubMagisk(Magisk):
@property
def copy_dir(self):
return base
try:
StubMagisk().setup()
StubMagisk().setup()
finally:
magisk_module.get_data_dir = original_get_data_dir
magisk_module.Logger.info = original_info
dst = os.path.join(data_dir, "adb", "magisk")
assert all(m == 0o755 for m in file_modes(dst).values())
finally:
shutil.rmtree(base, ignore_errors=True)
def test_perm_helper_tolerates_missing_dir():
Magisk().set_data_magisk_perm(os.path.join(tempfile.gettempdir(), "definitely-not-here"))
def main():
tests = [
test_plain_copytree_loses_exec_bit,
test_setup_makes_data_copy_executable,
test_setup_is_idempotent,
test_perm_helper_tolerates_missing_dir,
]
failed = 0
for test in tests:
try:
test()
except AssertionError as exc:
failed += 1
print("FAIL %s: %s" % (test.__name__, exc))
except Exception as exc: # noqa: BLE001 - surface anything unexpected
failed += 1
print("ERROR %s: %r" % (test.__name__, exc))
else:
print("ok %s" % test.__name__)
if failed:
print("\n%d test(s) failed" % failed)
return 1
print("\nall %d tests passed" % len(tests))
return 0
if __name__ == "__main__":
sys.exit(main())