Compare commits

..
5 changed files with 179 additions and 73 deletions
+8 -3
View File
@@ -18,6 +18,7 @@ from stuff.nodataperm import Nodataperm
from stuff.smartdock import Smartdock from stuff.smartdock import Smartdock
from stuff.widevine import Widevine from stuff.widevine import Widevine
from stuff.fdroidpriv import FDroidPriv from stuff.fdroidpriv import FDroidPriv
from stuff.tflitefix import TfliteFix
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
@@ -144,6 +145,8 @@ def remove_app(args):
remove_list.append(Nodataperm(args.android_version)) remove_list.append(Nodataperm(args.android_version))
if "hidestatusbar" in app: if "hidestatusbar" in app:
remove_list.append(HideStatusBar()) remove_list.append(HideStatusBar())
if "tflitefix" in app:
remove_list.append(TfliteFix())
if not container.use_overlayfs(): if not container.use_overlayfs():
copy_dir = "/tmp/waydroid" copy_dir = "/tmp/waydroid"
@@ -169,6 +172,8 @@ def hack_option(args):
hack_list.append(Nodataperm()) hack_list.append(Nodataperm())
if "hidestatusbar" in options: if "hidestatusbar" in options:
hack_list.append(HideStatusBar()) hack_list.append(HideStatusBar())
if "tflitefix" in options:
hack_list.append(TfliteFix())
if not container.use_overlayfs(): if not container.use_overlayfs():
copy_dir = "/tmp/waydroid" copy_dir = "/tmp/waydroid"
@@ -204,7 +209,7 @@ def interact():
args = argparse.Namespace() 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="(↑↓ Select Item)",
choices=[ choices=[
Choice(name="Android 11", value="11"), Choice(name="Android 11", value="11"),
Choice(name="Android 13", value="13"), Choice(name="Android 13", value="13"),
@@ -230,7 +235,7 @@ def interact():
exit() exit()
install_choices = ["gapps", "microg", "libndk", "libhoudini", "magisk", "smartdock", "fdroidpriv", "widevine",] install_choices = ["gapps", "microg", "libndk", "libhoudini", "magisk", "smartdock", "fdroidpriv", "widevine",]
hack_choices = [] hack_choices = ["tflitefix"]
if android_version=="11": if android_version=="11":
hack_choices.extend(["nodataperm", "hidestatusbar"]) hack_choices.extend(["nodataperm", "hidestatusbar"])
@@ -297,7 +302,7 @@ def main():
install_choices = ["gapps", "microg", "libndk", "libhoudini", install_choices = ["gapps", "microg", "libndk", "libhoudini",
"magisk", "mitm", "smartdock", "widevine"] "magisk", "mitm", "smartdock", "widevine"]
hack_choices = ["nodataperm", "hidestatusbar"] hack_choices = ["nodataperm", "hidestatusbar", "tflitefix"]
micrg_variants = ["Standard", "NoGoolag", "UNLP", "Minimal", "MinimalIAP"] micrg_variants = ["Standard", "NoGoolag", "UNLP", "Minimal", "MinimalIAP"]
remove_choices = install_choices remove_choices = install_choices
+78
View File
@@ -0,0 +1,78 @@
import glob
import os
import shutil
from stuff.general import General
from tools.logger import Logger
class TfliteFix(General):
id = "tflitefix"
partition = "system"
TARGET_OFFSET = 0x2E9204
PATCH_BYTES = b"\xc0\x03\x5f\xd6" # ret in AArch64 little-endian
def __init__(self, android_version="13") -> None:
super().__init__()
def download(self):
pass
@property
def skip_extract(self):
return True
def copy(self):
pass
def extra1(self):
"""Applies binary patch to libtensorflowlite.so in installed apps"""
search_paths = [
os.path.expanduser("~/.local/share/waydroid/data/app/~~*==/*/lib/arm64/libtensorflowlite.so"),
"/var/lib/waydroid/data/app/~~*==/*/lib/arm64/libtensorflowlite.so",
]
found = []
for p in search_paths:
found.extend(glob.glob(p))
if not found:
Logger.warning("No installed apps with libtensorflowlite.so found in Waydroid data.")
return
for lib_path in found:
try:
with open(lib_path, "rb") as f:
data = bytearray(f.read())
if len(data) > self.TARGET_OFFSET + 4:
if bytes(data[self.TARGET_OFFSET:self.TARGET_OFFSET+4]) == self.PATCH_BYTES:
Logger.info(f"Patch already applied to {lib_path}")
continue
backup = lib_path + ".orig"
if not os.path.exists(backup):
shutil.copy2(lib_path, backup)
data[self.TARGET_OFFSET:self.TARGET_OFFSET+4] = self.PATCH_BYTES
with open(lib_path, "wb") as f:
f.write(data)
os.chmod(lib_path, 0o755)
Logger.info(f"Successfully patched {lib_path}")
except Exception as e:
Logger.error(f"Failed to patch {lib_path}: {e}")
def extra2(self):
"""Restores original libtensorflowlite.so from backup"""
search_paths = [
os.path.expanduser("~/.local/share/waydroid/data/app/~~*==/*/lib/arm64/libtensorflowlite.so.orig"),
"/var/lib/waydroid/data/app/~~*==/*/lib/arm64/libtensorflowlite.so.orig",
]
for p in search_paths:
for orig in glob.glob(p):
target = orig[:-5]
try:
shutil.copy2(orig, target)
os.remove(orig)
os.chmod(target, 0o755)
Logger.info(f"Restored {target}")
except Exception as e:
Logger.error(f"Failed to restore {target}: {e}")
+24 -4
View File
@@ -1,9 +1,23 @@
import configparser import configparser
import os import os
import sys import sys
# import dbus
from tools.helper import run from tools.helper import run
from tools.logger import Logger from tools.logger import Logger
# def DBusContainerService(object_path="/ContainerManager", intf="id.waydro.ContainerManager"):
# return dbus.Interface(dbus.SystemBus().get_object("id.waydro.Container", object_path), intf)
# def DBusSessionService(object_path="/SessionManager", intf="id.waydro.SessionManager"):
# return dbus.Interface(dbus.SessionBus().get_object("id.waydro.Session", object_path), intf)
# def use_dbus():
# try:
# DBusContainerService()
# except:
# return False
# return True
def use_overlayfs(): def use_overlayfs():
cfg = configparser.ConfigParser() cfg = configparser.ConfigParser()
cfg_file = os.environ.get("WAYDROID_CONFIG", "/var/lib/waydroid/waydroid.cfg") cfg_file = os.environ.get("WAYDROID_CONFIG", "/var/lib/waydroid/waydroid.cfg")
@@ -20,14 +34,20 @@ def use_overlayfs():
return False return False
# def get_session():
# return DBusContainerService().GetSession()
def stop(): def stop():
# if use_dbus():
# session = DBusContainerService().GetSession()
# if session:
# DBusContainerService().Stop(False)
# else:
run(["waydroid", "container", "stop"]) run(["waydroid", "container", "stop"])
def is_running(): def is_running():
return "Session:\tRUNNING" in run(["waydroid", "status"]).stdout.decode() return "Session:\tRUNNING" in run(["waydroid", "status"]).stdout.decode()
def upgrade(): def upgrade():
try: run(["waydroid", "upgrade", "-o"], ignore=r"\[.*\] Stopping container\n\[.*\] Starting container")
run(["waydroid", "upgrade"], ignore=r"\[.*\] Stopping container\n\[.*\] Starting container")
except subprocess.CalledProcessError:
Logger.warning("Waydroid upgrade reported an error (likely system_ota), but we are continuing...")
+66 -64
View File
@@ -2,6 +2,7 @@ import gzip
import os import os
import re import re
import platform import platform
import re
import subprocess import subprocess
import sys import sys
import requests import requests
@@ -10,24 +11,27 @@ from tqdm import tqdm
import hashlib import hashlib
from typing import Optional from typing import Optional
def get_download_dir(): def get_download_dir():
if os.environ.get("XDG_CACHE_HOME"): download_loc = ""
if os.environ.get("XDG_CACHE_HOME", None) is None:
download_loc = os.path.join('/', "home", os.environ.get(
"SUDO_USER", os.environ["USER"]), ".cache", "waydroid-script", "downloads"
)
else:
download_loc = os.path.join( download_loc = os.path.join(
os.environ["XDG_CACHE_HOME"], "waydroid-script", "downloads" os.environ["XDG_CACHE_HOME"], "waydroid-script", "downloads"
) )
else:
user = os.environ.get("SUDO_USER", os.environ.get("USER", "root"))
download_loc = os.path.join("/", "home", user, ".cache", "waydroid-script", "downloads")
if not os.path.exists(download_loc): if not os.path.exists(download_loc):
os.makedirs(download_loc, exist_ok=True) os.makedirs(download_loc)
return download_loc return download_loc
# not good
def get_data_dir(): def get_data_dir():
user = os.environ.get("SUDO_USER", os.environ.get("USER", "root")) return os.path.join('/', "home", os.environ.get("SUDO_USER", os.environ["USER"]), ".local", "share", "waydroid", "data")
return os.path.join("/", "home", user, ".local", "share", "waydroid", "data")
def run(args: list, env: Optional[dict] = None, ignore: Optional[str] = None): # execute on host
def run(args: list, env: Optional[str] = None, ignore: Optional[str] = None):
result = subprocess.run( result = subprocess.run(
args=args, args=args,
env=env, env=env,
@@ -35,12 +39,11 @@ def run(args: list, env: Optional[dict] = None, ignore: Optional[str] = None):
stderr=subprocess.PIPE stderr=subprocess.PIPE
) )
# print(result.stdout.decode())
if result.stderr: if result.stderr:
error = result.stderr.decode("utf-8").strip() error = result.stderr.decode("utf-8")
if ignore and re.match(ignore, error):
if (ignore and re.search(ignore, error)) or "system_ota" in error:
return result return result
Logger.error(error) Logger.error(error)
raise subprocess.CalledProcessError( raise subprocess.CalledProcessError(
returncode=result.returncode, returncode=result.returncode,
@@ -49,58 +52,64 @@ def run(args: list, env: Optional[dict] = None, ignore: Optional[str] = None):
) )
return result return result
# execute on waydroid shell
def shell(arg: str, env: Optional[str] = None): def shell(arg: str, env: Optional[str] = None):
cmd = ["sudo", "waydroid", "shell"] a = subprocess.Popen(
process = subprocess.Popen( args=["sudo", "waydroid", "shell"],
args=cmd,
stdin=subprocess.PIPE, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stderr=subprocess.PIPE
text=True
) )
subprocess.Popen(
args=["echo", "export BOOTCLASSPATH=/apex/com.android.art/javalib/core-oj.jar:/apex/com.android.art/javalib/core-libart.jar:/apex/com.android.art/javalib/core-icu4j.jar:/apex/com.android.art/javalib/okhttp.jar:/apex/com.android.art/javalib/bouncycastle.jar:/apex/com.android.art/javalib/apache-xml.jar:/system/framework/framework.jar:/system/framework/ext.jar:/system/framework/telephony-common.jar:/system/framework/voip-common.jar:/system/framework/ims-common.jar:/system/framework/framework-atb-backward-compatibility.jar:/apex/com.android.conscrypt/javalib/conscrypt.jar:/apex/com.android.media/javalib/updatable-media.jar:/apex/com.android.mediaprovider/javalib/framework-mediaprovider.jar:/apex/com.android.os.statsd/javalib/framework-statsd.jar:/apex/com.android.permission/javalib/framework-permission.jar:/apex/com.android.sdkext/javalib/framework-sdkextensions.jar:/apex/com.android.wifi/javalib/framework-wifi.jar:/apex/com.android.tethering/javalib/framework-tethering.jar"],
stdout=a.stdin,
stdin=subprocess.PIPE
).communicate()
boot_classpath = "export BOOTCLASSPATH=/apex/com.android.art/javalib/core-oj.jar:/apex/com.android.art/javalib/core-libart.jar:/apex/com.android.art/javalib/core-icu4j.jar:/apex/com.android.art/javalib/okhttp.jar:/apex/com.android.art/javalib/bouncycastle.jar:/apex/com.android.art/javalib/apache-xml.jar:/system/framework/framework.jar:/system/framework/ext.jar:/system/framework/telephony-common.jar:/system/framework/voip-common.jar:/system/framework/ims-common.jar:/system/framework/framework-atb-backward-compatibility.jar:/apex/com.android.conscrypt/javalib/conscrypt.jar:/apex/com.android.media/javalib/updatable-media.jar:/apex/com.android.mediaprovider/javalib/framework-mediaprovider.jar:/apex/com.android.os.statsd/javalib/framework-statsd.jar:/apex/com.android.permission/javalib/framework-permission.jar:/apex/com.android.sdkext/javalib/framework-sdkextensions.jar:/apex/com.android.wifi/javalib/framework-wifi.jar:/apex/com.android.tethering/javalib/framework-tethering.jar"
full_script = f"{boot_classpath}\n"
if env: if env:
full_script += f"{env}\n" subprocess.Popen(
full_script += f"{arg}\n" args=["echo", env],
stdout=a.stdin,
stdin=subprocess.PIPE
).communicate()
stdout, stderr = process.communicate(input=full_script) subprocess.Popen(
args=["echo", arg],
stdout=a.stdin,
stdin=subprocess.PIPE
).communicate()
if process.returncode != 0 and stderr: a.stdin.close()
Logger.error(stderr.strip()) if a.stderr.read():
Logger.error(a.stderr.read().decode('utf-8'))
raise subprocess.CalledProcessError( raise subprocess.CalledProcessError(
returncode=process.returncode, returncode=a.returncode,
cmd=cmd, cmd=a.args,
stderr=stderr.encode() stderr=a.stderr
) )
return stdout return a.stdout.read().decode("utf-8")
def download_file(url, f_name): def download_file(url, f_name):
md5 = ""
response = requests.get(url, stream=True) response = requests.get(url, stream=True)
total_size_in_bytes = int(response.headers.get('content-length', 0)) total_size_in_bytes = int(response.headers.get('content-length', 0))
block_size = 1024 block_size = 1024 # 1 Kibibyte
progress_bar = tqdm(total=total_size_in_bytes, unit='iB', unit_scale=True) progress_bar = tqdm(total=total_size_in_bytes, unit='iB', unit_scale=True)
with open(f_name, 'wb') as file: with open(f_name, 'wb') as file:
for data in response.iter_content(block_size): for data in response.iter_content(block_size):
progress_bar.update(len(data)) progress_bar.update(len(data))
file.write(data) file.write(data)
progress_bar.close() progress_bar.close()
hash_md5 = hashlib.md5()
with open(f_name, "rb") as f: with open(f_name, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""): bytes = f.read()
hash_md5.update(chunk) md5 = hashlib.md5(bytes).hexdigest()
if total_size_in_bytes != 0 and progress_bar.n != total_size_in_bytes: if total_size_in_bytes != 0 and progress_bar.n != total_size_in_bytes:
raise ValueError("Download incomplete or corrupted") raise ValueError("Something went wrong while downloading")
return md5
return hash_md5.hexdigest()
def host(): def host():
machine = platform.machine() machine = platform.machine()
mapping = { mapping = {
"i686": ("x86", 32), "i686": ("x86", 32),
"x86_64": ("x86_64", 64), "x86_64": ("x86_64", 64),
@@ -108,37 +117,30 @@ def host():
"armv7l": ("armeabi-v7a", 32), "armv7l": ("armeabi-v7a", 32),
"armv8l": ("armeabi-v7a", 32) "armv8l": ("armeabi-v7a", 32)
} }
if machine in mapping: if machine in mapping:
if machine == "x86_64": if mapping[machine] == "x86_64":
try: with open("/proc/cpuinfo") as f:
with open("/proc/cpuinfo", "r") as f: if "sse4_2" not in f.read():
if "sse4_2" not in f.read(): Logger.warning("x86_64 CPU does not support SSE4.2, falling back to x86...")
Logger.warning("CPU does not support SSE4.2, falling back to x86...") return ("x86", 32)
return ("x86", 32)
except Exception:
pass
return mapping[machine] return mapping[machine]
raise ValueError("platform.machine '" + machine + "'"
raise ValueError(f"Architecture '{machine}' is not supported") " architecture is not supported")
def check_root(): def check_root():
if os.geteuid() != 0: if os.geteuid() != 0:
Logger.error("This script must be run as root (sudo). Aborting.") Logger.error("This script must be run as root. Aborting.")
sys.exit(1) sys.exit(1)
def backup(path): def backup(path):
if not os.path.exists(path): gz_filename = path+".gz"
return with gzip.open(gz_filename, 'wb') as f_gz:
gz_filename = path + ".gz" with open(path, "rb") as f:
with open(path, "rb") as f_in: f_gz.write(f.read())
with gzip.open(gz_filename, "wb") as f_out:
f_out.writelines(f_in)
def restore(path): def restore(path):
gz_filename = path + ".gz" gz_filename = path+".gz"
if not os.path.exists(gz_filename): with gzip.GzipFile(gz_filename) as f_gz:
return with open(path, "wb") as f:
with gzip.open(gz_filename, "rb") as f_in: f.writelines(f_gz)
with open(path, "wb") as f_out:
f_out.write(f_in.read())
+3 -2
View File
@@ -28,6 +28,7 @@ def resize(img_file, size):
run(["sudo", "resize2fs", img_file, size], ignore="^resize2fs \d+\.\d+\.\d (.+)\n$") run(["sudo", "resize2fs", img_file, size], ignore="^resize2fs \d+\.\d+\.\d (.+)\n$")
def get_image_dir(): def get_image_dir():
# Read waydroid config to get image location
cfg = configparser.ConfigParser() cfg = configparser.ConfigParser()
cfg_file = os.environ.get("WAYDROID_CONFIG", "/var/lib/waydroid/waydroid.cfg") cfg_file = os.environ.get("WAYDROID_CONFIG", "/var/lib/waydroid/waydroid.cfg")
if not os.path.isfile(cfg_file): if not os.path.isfile(cfg_file):
@@ -35,6 +36,6 @@ def get_image_dir():
sys.exit(1) sys.exit(1)
cfg.read(cfg_file) cfg.read(cfg_file)
if "waydroid" not in cfg: if "waydroid" not in cfg:
Logger.error("Required entry in config was not found, Cannot continue!") Logger.error("Required entry in config was not found, Cannot continue!") #magisk
sys.exit(1) sys.exit(1)
return cfg["waydroid"]["images_path"] return cfg["waydroid"]["images_path"]