#!/usr/bin/env python3 import argparse import hashlib import os import platform import re import shutil import subprocess import urllib.request import zipfile def calculate_md5(file_path): """计算文件的 MD5 值""" hash_md5 = hashlib.md5() with open(file_path, "rb") as f: for chunk in iter(lambda: f.read(4096), b""): hash_md5.update(chunk) return hash_md5.hexdigest() def download_file(url, file_path): """下载文件""" print(f"==> Downloading from {url}") urllib.request.urlretrieve(url, file_path) def get_host_arch(): """获取主机架构信息""" machine = platform.machine() mapping = { "i686": ("x86", 32), "x86_64": ("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] # 如果不在映射中,默认返回 arm64(适用于 Android 构建) print(f"Warning: platform.machine '{machine}' architecture not recognized, defaulting to arm64") return ("arm64", 64) def run_command(cmd): """运行命令""" try: subprocess.run(cmd, check=True) except subprocess.CalledProcessError as e: print(f"Command failed: {' '.join(cmd)}") raise e class Magisk: def __init__(self): self.download_loc = os.path.join(os.path.dirname(os.path.abspath(__file__)), "downloads") self.dl_link = "https://github.com/topjohnwu/Magisk/releases/download/v30.2/Magisk-v30.2.apk" self.dl_file_name = os.path.join(self.download_loc, "magisk.apk") self.act_md5 = "2691c30ccf059af2536cb0af803c787c" self.extract_to = os.path.join(os.path.dirname(os.path.abspath(__file__)), "temp") self.copy_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "magisk") self.magisk_dir = os.path.join(self.copy_dir, "system", "etc", "init", "magisk") self.machine = get_host_arch() # 返回 (arch, bits) 元组 def download(self): """下载 Magisk APK""" print("==> Downloading latest Magisk now .....") # 确保下载目录存在 os.makedirs(self.download_loc, exist_ok=True) # 检查并下载 Magisk APK need_download = True if os.path.exists(self.dl_file_name): print("==> Checking existing Magisk APK...") actual_md5 = calculate_md5(self.dl_file_name) if actual_md5 == self.act_md5: print("==> Magisk APK already exists and verified!") need_download = False else: print(f"==> MD5 mismatch. Expected: {self.act_md5}, Got: {actual_md5}") if need_download: download_file(self.dl_link, self.dl_file_name) actual_md5 = calculate_md5(self.dl_file_name) if actual_md5 != self.act_md5: raise Exception(f"Downloaded file MD5 verification failed! Expected: {self.act_md5}, Got: {actual_md5}") print("==> Download completed and verified!") def extract(self): """解压 APK 文件""" print("==> Extracting Magisk APK...") # 清理并创建解压目录 shutil.rmtree(self.extract_to, ignore_errors=True) os.makedirs(self.extract_to, exist_ok=True) # 解压 APK with zipfile.ZipFile(self.dl_file_name) as z: z.extractall(self.extract_to) def copy(self): """复制文件到目标目录""" print("==> Copying magisk files now ...") # 清理并创建目标目录 if os.path.exists(self.copy_dir): shutil.rmtree(self.copy_dir) os.makedirs(self.magisk_dir, exist_ok=True) os.makedirs(os.path.join(self.copy_dir, "sbin"), exist_ok=True) # 架构映射 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]]) if os.path.exists(lib_dir): for parent, dirnames, filenames in os.walk(lib_dir): for filename in filenames: o_path = os.path.join(lib_dir, filename) so_name = re.search(r'lib(.*)\.so', filename) if so_name: n_path = os.path.join(self.magisk_dir, so_name.group(1)) shutil.copyfile(o_path, n_path) run_command(["chmod", "+x", n_path]) # 复制 arm32 的 magisk 二进制文件(如果存在) lib32_path = os.path.join(self.extract_to, "lib", "armeabi-v7a") magisk32_src = os.path.join(lib32_path, "libmagisk32.so") magisk32_dst = os.path.join(self.magisk_dir, "magisk32") if os.path.exists(magisk32_src): shutil.copyfile(magisk32_src, magisk32_dst) run_command(["chmod", "+x", magisk32_dst]) # 复制 magisk.apk 到目标目录 apk_dst = os.path.join(self.magisk_dir, "magisk.apk") if os.path.exists(self.dl_file_name): shutil.copyfile(self.dl_file_name, apk_dst) print("==> Magisk installation completed!") def cleanup(self, keep_downloads=False): """清理临时文件和目录""" print("==> Cleaning up temporary files...") # 清理解压临时目录 if os.path.exists(self.extract_to): shutil.rmtree(self.extract_to, ignore_errors=True) print(f"==> Removed temporary extraction directory: {self.extract_to}") # 可选择性清理下载目录 if not keep_downloads and os.path.exists(self.download_loc): shutil.rmtree(self.download_loc, ignore_errors=True) print(f"==> Removed download directory: {self.download_loc}") elif keep_downloads: print(f"==> Keeping download directory: {self.download_loc}") def run(self, cleanup_after=True, keep_downloads=True): """执行完整的构建流程""" try: self.download() self.extract() self.copy() success = True except Exception as e: print(f"Error: {e}") success = False finally: # 无论成功还是失败都清理临时文件 if cleanup_after: self.cleanup(keep_downloads=keep_downloads) return success def main(): """主函数""" parser = argparse.ArgumentParser(description='Magisk vendor package builder') parser.add_argument('--no-cleanup', action='store_true', help='不清理临时文件和下载文件') parser.add_argument('--remove-downloads', action='store_true', help='同时删除下载的文件') args = parser.parse_args() # 根据参数确定清理行为 cleanup_after = not args.no_cleanup keep_downloads = not args.remove_downloads magisk = Magisk() success = magisk.run(cleanup_after=cleanup_after, keep_downloads=keep_downloads) if success: print("==> Magisk vendor package created successfully!") if cleanup_after: print("==> Temporary files cleaned up") else: print("==> Failed to create Magisk vendor package!") exit(1) if __name__ == '__main__': main()