├── mp ├── __init__.py ├── splash.py ├── utils.py ├── lang.py ├── boot_patch.py └── ui.py ├── run.bat ├── requirements.txt ├── bin ├── logo.ico ├── logo.png ├── zfbhb.png ├── alipay.png ├── splash.png ├── wechat.png ├── macos │ ├── 11 │ │ └── x86_64 │ │ │ └── magiskboot │ ├── 12 │ │ └── x86_64 │ │ │ └── magiskboot │ └── 13 │ │ └── x86_64 │ │ └── magiskboot └── windows │ └── x86_64 │ └── magiskboot.exe ├── screenshots ├── home.png ├── other.png └── download.png ├── .gitignore ├── magiskpatcher.py ├── README.md ├── shell.nix └── LICENSE /mp/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /run.bat: -------------------------------------------------------------------------------- 1 | @chcp 65001 2 | @python magiskpatcher.py 3 | @pause -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | customtkinter 2 | pillow 3 | requests 4 | packaging -------------------------------------------------------------------------------- /bin/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/affggh/Magisk_patcher/HEAD/bin/logo.ico -------------------------------------------------------------------------------- /bin/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/affggh/Magisk_patcher/HEAD/bin/logo.png -------------------------------------------------------------------------------- /bin/zfbhb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/affggh/Magisk_patcher/HEAD/bin/zfbhb.png -------------------------------------------------------------------------------- /bin/alipay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/affggh/Magisk_patcher/HEAD/bin/alipay.png -------------------------------------------------------------------------------- /bin/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/affggh/Magisk_patcher/HEAD/bin/splash.png -------------------------------------------------------------------------------- /bin/wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/affggh/Magisk_patcher/HEAD/bin/wechat.png -------------------------------------------------------------------------------- /screenshots/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/affggh/Magisk_patcher/HEAD/screenshots/home.png -------------------------------------------------------------------------------- /screenshots/other.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/affggh/Magisk_patcher/HEAD/screenshots/other.png -------------------------------------------------------------------------------- /mp/splash.py: -------------------------------------------------------------------------------- 1 | import pyi_splash 2 | 3 | pyi_splash.update_text('Loading...') 4 | 5 | pyi_splash.close() -------------------------------------------------------------------------------- /screenshots/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/affggh/Magisk_patcher/HEAD/screenshots/download.png -------------------------------------------------------------------------------- /bin/macos/11/x86_64/magiskboot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/affggh/Magisk_patcher/HEAD/bin/macos/11/x86_64/magiskboot -------------------------------------------------------------------------------- /bin/macos/12/x86_64/magiskboot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/affggh/Magisk_patcher/HEAD/bin/macos/12/x86_64/magiskboot -------------------------------------------------------------------------------- /bin/macos/13/x86_64/magiskboot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/affggh/Magisk_patcher/HEAD/bin/macos/13/x86_64/magiskboot -------------------------------------------------------------------------------- /bin/windows/x86_64/magiskboot.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/affggh/Magisk_patcher/HEAD/bin/windows/x86_64/magiskboot.exe -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 不要上传img文件 2 | *.img 3 | # config文件不是所有设备通用,自行生成或编写 4 | config.txt 5 | test.py 6 | prebuilt/*.apk 7 | __pycache__ 8 | test/* 9 | # pyinstaller 10 | build/* 11 | dist/* 12 | *.desc 13 | 14 | # nuitka 15 | *.build/ 16 | *.dist/ 17 | 18 | .vscode 19 | bin/magiskboot -------------------------------------------------------------------------------- /magiskpatcher.py: -------------------------------------------------------------------------------- 1 | from mp.ui import * 2 | 3 | # For pyinstaller 4 | try: 5 | from mp import splash 6 | except: pass 7 | 8 | if __name__ == '__main__': 9 | root = MagiskPatcherUI() 10 | root.title(TITLE) 11 | root.geometry("%dx%d" % (WIDTH, HEIGHT)) 12 | 13 | # Fix high dpi 14 | if osname == 'nt': 15 | # Tell system using self dpi adapt 16 | ctypes.windll.shcore.SetProcessDpiAwareness(1) 17 | # Get screen resize scale factor 18 | scalefactor = ctypes.windll.shcore.GetScaleFactorForDevice(0) 19 | root.tk.call('tk', 'scaling', scalefactor/75) 20 | 21 | root.update() 22 | centerWindow(root) 23 | #ctk.set_window_scaling(1.25) 24 | 25 | root.mainloop() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Magisk Patcher v4 2 | - We are trying move this into a static website to make patch more easy 3 | - try this new one [click me](https://circlecashteam.github.io/MagiskPatcher/) 4 | 5 | 6 | `A tool to patch boot image with magisk on desktop` 7 | 8 | # Screenshots 9 | ![](screenshots/home.png) 10 | ![](screenshots/download.png) 11 | ![](screenshots/other.png) 12 | 13 | # Usage 14 | ## Setup python env 15 | `pip install -r requirements` 16 | 17 | ## Run 18 | `python magiskpatcher.py` 19 | 20 | # Theme 21 | `A very pretty tkinter` 22 | ***[CustomTkinter](https://customtkinter.tomschimansky.com)*** 23 | 24 | # Magiskboot binary 25 | [magiskboot_on_mingw](https://github.com/svoboda18/magiskboot) 26 | [magiskboot_build_on_all_target](https://github.com/ookiineko/magiskboot_build) 27 | 28 | # Thanks 29 | [ookiineko](https://github.com/ookiineko) 30 | [svoboda18](https://github.com/svoboda18) 31 | 32 | # Donate me 33 | ![](bin/wechat.png) 34 | ![](bin/alipay.png) 35 | ![](bin/zfbhb.png) 36 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import {}, lib ? pkgs.lib }: 2 | 3 | let 4 | python3 = pkgs.python3; 5 | 6 | customtkinter = with python3.pkgs; buildPythonPackage rec { 7 | pname = "customtkinter"; 8 | version = "5.2.0"; 9 | format = "pyproject"; 10 | 11 | src = fetchPypi { 12 | inherit pname version; 13 | hash = "sha256-6TRIqNIhIeIOwW6VlgqDBuF89+AHl2b1gEsuhV5hSTc="; 14 | }; 15 | 16 | buildInputs = [ setuptools ]; 17 | propagatedBuildInputs = [ darkdetect typing-extensions ]; 18 | 19 | meta = with lib; { 20 | changelog = "https://github.com/TomSchimansky/CustomTkinter/releases/tag/${version}"; 21 | homepage = "https://github.com/TomSchimansky/CustomTkinter"; 22 | description = "A modern and customizable python UI-library based on Tkinter"; 23 | license = licenses.mit; 24 | maintainers = with maintainers; [ ataraxiasjel ]; 25 | }; 26 | }; 27 | 28 | myPythonEnv = python3.withPackages (ps: with ps; [ 29 | customtkinter 30 | pillow 31 | requests 32 | packaging 33 | ]); 34 | in 35 | 36 | pkgs.mkShell { 37 | buildInputs = [ 38 | python3 39 | myPythonEnv 40 | pkgs.python3Packages.tkinter 41 | pkgs.android-tools 42 | ]; 43 | 44 | shellHook = '' 45 | echo "You are now using a NIX environment" 46 | ''; 47 | } 48 | 49 | -------------------------------------------------------------------------------- /mp/utils.py: -------------------------------------------------------------------------------- 1 | from os import name as osname 2 | from sys import stderr 3 | import subprocess 4 | import requests 5 | import zipfile 6 | from multiprocessing.dummy import DummyProcess 7 | import platform 8 | from os import chmod 9 | 10 | from .lang import Language, langget 11 | 12 | DEFAULT_MAGISK_API_URL = "https://api.github.com/repos/topjohnwu/Magisk/releases" 13 | DELTA_MAGISK_API_URL = "https://api.github.com/repos/HuskyDG/magisk-files/releases" 14 | 15 | def retTypeAndMachine(): 16 | # Detect machine and ostype 17 | ostype = platform.system().lower() 18 | if ostype.find("cygwin") >= 0: # Support cygwin x11 19 | ostype = "windows" 20 | rel = '' 21 | if ostype == "darwin": 22 | ostype = "macos" 23 | v, _, _ = platform.mac_ver() 24 | rel = v.split('.', 1)[0] 25 | machine = platform.machine().lower() 26 | if machine == 'aarch64_be' \ 27 | or machine == 'armv8b' \ 28 | or machine == 'armv8l': 29 | machine = 'aarch64' 30 | if machine == 'i386' or machine == 'i686': 31 | machine = 'x86' 32 | if machine == "amd64": 33 | machine = 'x86_64' 34 | return ostype, rel, machine 35 | 36 | def runcmd(cmd): 37 | if osname == 'posix': 38 | creationflags = 0 39 | elif osname == 'nt': # if on windows ,create a process with hidden console 40 | creationflags = subprocess.CREATE_NO_WINDOW 41 | else: 42 | creationflags = 0 43 | ret = subprocess.run(cmd, 44 | shell=False, 45 | #stdin=subprocess.PIPE, 46 | stdout=subprocess.PIPE, 47 | stderr=subprocess.STDOUT, 48 | creationflags=creationflags 49 | ) 50 | return (ret.returncode, ret.stdout.decode('utf-8')) 51 | 52 | def getReleaseList(url: str = DEFAULT_MAGISK_API_URL, isproxy: bool=False, proxyaddr: str="127.0.0.1:7890", isjsdelivr:bool=True, log=stderr): 53 | buf = url.split('/') 54 | user = buf[-3] 55 | repo = buf[-2] 56 | 57 | proxies = { 58 | 'http': f"{proxyaddr}", 59 | 'https': f"{proxyaddr}", 60 | } 61 | r = requests.get(url, 62 | proxies=proxies if isproxy else None, 63 | timeout=3) 64 | if not r.ok: 65 | print(langget('get version faild, please check net or add proxy'), file=log) 66 | return {} 67 | data = r.json() 68 | dlink = {} 69 | 70 | if url == DEFAULT_MAGISK_API_URL: 71 | for i in data: 72 | tag_name = i['tag_name'] 73 | for j in i['assets']: 74 | if j['name'].startswith("Magisk") and j['name'].endswith(r".apk"): 75 | if "Manager" in j['name']: continue # skip magisk manager apk 76 | if isjsdelivr: 77 | dlink.update({j['name'] : magiskTag2jsdelivr(user, repo, tag_name, j['name'])}) 78 | else: 79 | dlink.update({j['name'] : j['browser_download_url']}) 80 | else: # maybe delta magisk 81 | for i in data: 82 | tag_name = i['tag_name'] 83 | for j in i['assets']: 84 | if j['name'].endswith(r".apk") and j['name'].startswith('app'): 85 | if isjsdelivr: 86 | dlink.update({tag_name + j['name'].lstrip('app') : magiskTag2jsdelivr(user, repo, tag_name, j['name'])}) 87 | else: 88 | dlink.update({tag_name + j['name'].lstrip('app') : j['browser_download_url']}) 89 | return dlink 90 | 91 | def magiskTag2jsdelivr(user, repo, tag, fname): 92 | ''' 93 | input link and tag get jsdilivr link 94 | ''' 95 | if user == "topjohnwu": # official magisk 96 | return f"https://cdn.jsdelivr.net/gh/{user}/magisk-files@{tag.lstrip('v')}/app-release.apk" 97 | return f"https://cdn.jsdelivr.net/gh/{user}/{repo}@{tag}/{fname}" 98 | 99 | def getMagiskApkVersion(fname: str) -> str | None: 100 | ''' 101 | Give magisk apk file and return version code 102 | ''' 103 | valid_flag = False 104 | magisk_ver_code = "00000" 105 | with zipfile.ZipFile(fname, 'r') as z: 106 | for i in z.filelist: 107 | if "util_functions.sh" in i.filename: 108 | valid_flag = True 109 | for line in z.read(i).splitlines(): 110 | if b"MAGISK_VER_CODE" in line: 111 | magisk_ver_code = line.split(b"=")[1] 112 | if not valid_flag: return None 113 | return magisk_ver_code 114 | 115 | def convertVercode2Ver(value: str) -> str: 116 | return value[0:2] + b"." + value[2:3] 117 | 118 | def downloadFile(url: str, to: str, isproxy: bool = False, proxy:str="127.0.0.1:7890", progress=None, log=stderr): 119 | """ 120 | Can accept a ttk.ProgressBar as progress 121 | """ 122 | proxies = { 123 | 'http': proxy, 124 | 'https': proxy, 125 | } 126 | p = lambda now, total: int((now/total)*100) 127 | chunk_size = 10240 128 | try: 129 | r = requests.get(url, stream=True, allow_redirects=True, 130 | proxies=proxies if isproxy else None) 131 | except: 132 | print(langget('internet connect faild or cannot connect target url'), file=log) 133 | return False 134 | print(f"- {langget('start download')}[{url}] -> [{to}]", file=log) 135 | total_size = int(r.headers['content-length']) 136 | now = 0 137 | with open(to, 'wb') as f: 138 | for chunk in r.iter_content(chunk_size=chunk_size): 139 | if chunk: 140 | before = now 141 | f.write(chunk) 142 | now += chunk_size 143 | if now > before: 144 | progress.set(p(now, total_size)) 145 | print(langget('download complete'), file=log) 146 | progress.set(0) 147 | return True 148 | 149 | def thdownloadFile(*args): 150 | dp = DummyProcess(target=downloadFile, args=[*args, ]) 151 | dp.start() 152 | 153 | def parseMagiskApk(apk: str, arch:["arm64", "arm", "x86", "x86_64"]="arm64", log=stderr): 154 | """ 155 | This function will extract useful file from magisk.apk 156 | """ 157 | def archconv(a): 158 | ret = a 159 | match a: 160 | case "arm64": 161 | ret = "arm64-v8a" 162 | case "arm": 163 | ret = "armeabi-v7a" 164 | return ret 165 | 166 | def archto32(a): 167 | ret = a 168 | match a: 169 | case "arm64-v8a": 170 | ret = "armeabi-v7a" 171 | case "x86_64": 172 | ret = "x86" 173 | return ret 174 | 175 | def saveto(bytes, path): 176 | with open(path, 'wb') as f: 177 | f.write(bytes) 178 | 179 | print(langget('start decompress needed'), file=log) 180 | arch = archconv(arch) 181 | os, _, p = retTypeAndMachine() 182 | pp = "x86_64" 183 | if p == "aarch64": 184 | pp = "arm64-v8a" 185 | elif p == "arm": 186 | pp = "armeabi-v7a" 187 | with zipfile.ZipFile(apk) as z: 188 | for l in z.filelist: 189 | # 26.0+ 190 | if "stub.apk" in l.filename: 191 | saveto(z.read(l), "stub.apk") 192 | # Save a platform magiskboot into bin if linux 193 | if os!='windows' and osname !='nt': 194 | if f"lib/{pp}/libmagiskboot.so" in l.filename: 195 | saveto(z.read(l), "bin/magiskboot") 196 | chmod("bin/magiskboot", 0o755) 197 | 198 | if f"lib/{arch}/libmagiskinit.so" in l.filename: 199 | saveto(z.read(f"lib/{archto32(arch)}/libmagisk32.so"), "magisk32") 200 | if arch in ["arm64-v8a", "x86_64"]: 201 | saveto(z.read(f"lib/{arch}/libmagisk64.so"), "magisk64") 202 | saveto(z.read(f"lib/{arch}/libmagiskinit.so"), "magiskinit") 203 | 204 | if __name__ == '__main__': 205 | print(getReleaseList(url=DELTA_MAGISK_API_URL)) 206 | -------------------------------------------------------------------------------- /mp/lang.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | @dataclass 4 | class Language: 5 | supports = ['zh_CN', 'en_US'] 6 | select = supports[0] 7 | zh_CN = { 8 | 'Home': "主页", 9 | 'boot img': "boot镜像", 10 | 'choose file': "选择文件", 11 | 'arch': "架构", 12 | 'keep verity': "保持验证", 13 | 'keep encypt': "保持强制加密", 14 | 'patch vbmeta flag': "修补vbmeta标志", 15 | 'legacy sar': "传统SAR设备 (如果你的设备是system-as-root但不是动态设备需勾选此项)", 16 | 'start patch': "开始修补", 17 | 'clean': "清除输出", 18 | 'recovery': "修补recovery", 19 | 'Select and Download': "选择与下载", 20 | 'settings': "设置", 21 | 'use proxy': "使用代理", 22 | 'github mirror': "Github镜像源", 23 | 'input your git mirror here': "在此处填入你的镜像链接", 24 | 'use jsdelivr': "使用jsdelivr加速下载", 25 | 'use local file': "使用本地文件修补", 26 | 'use delta magisk': "使用delta面具修补", 27 | 'refresh list': "刷新列表", 28 | 'available magisk list': "可用的magisk版本", 29 | 'magisk is not select': "当前未选择magisk安装包", 30 | 'Other': "其他", 31 | 'introduce': "介绍", 32 | 'vist github': "访问开源地址", 33 | 'log level': "日志等级", 34 | 'scaling': "缩放", 35 | 'Based on CustomTkinter': "此界面基于[CustomTkinter]库", 36 | 'progress': "进度", 37 | 38 | 'please select a exist boot image': "- 请选择一个存在的boot镜像", 39 | 'please select a valid magisk apk': "- 请选择一个合理并存在的magisk安装包", 40 | 'detect select magisk version is': "- 检测到apk的magisk版本为", 41 | 'file not exist, ready to download': "- 文件不存在, 准备下载...", 42 | 'use mirror download': "- 使用镜像网站下载...", 43 | 'file exist, no need download': "- 文件存在,无需重复下载...", 44 | 'current magisk': "- 当前magisk版本...", 45 | 'refresh magisk list': "- 刷新magisk列表", 46 | 'use from local prebuilt dir': "- 从本地prebuil目录获取", 47 | 'no magisk in prebuilt, please downloadn and place': "- 没有找到magisk安装包,请手动下载后放置在工作目录下", 48 | 'work dir': "工作目录", 49 | 'cannot find any apk, please download and put them into prebuilt dir':"- 没有在本地目录找到任何安装包,请手动下载后放入prebuilt目录", 50 | 'select a boot image': "选择一个boot镜像", 51 | 52 | 'get version faild, please check net or add proxy': "获取版本失败,请检查网络或尝试添加代理...", 53 | 'internet connect faild or cannot connect target url': "- 网络异常或无法连接到目标链接...", 54 | 'start download': "开始下载", 55 | 'download complete': "下载完成", 56 | 'start decompress needed': "- 开始解压需要的文件...", 57 | 58 | 'cannot initilazed with magiskboot': "- magiskboot文件不存在,无法完成初始化", 59 | 'boot image does not exist':"- boot 镜像不存在", 60 | 'unpack boot image': "- 解包boot镜像...", 61 | 'unknow/unsupport format': "! 不支持/未知 镜像格式", 62 | 'chromeos format boot':"- ChromeOS 格式boot镜像", 63 | 'not support yet': "- 暂不支持", 64 | 'unable to unpack boot': "! 无法解包此boot镜像", 65 | 'check ramdisk status': "- 检查ramdisk状态", 66 | 'detect original boot': "- 检测到原始未修改的boot镜像", 67 | 'detect magisk patched boot': "- 检测到经过magisk修补过的boot镜像", 68 | 'boot patched by unknow program': "- boot镜像被未知的程序修改过", 69 | 'please resotre original boot':"- 请先将其还原之原始的boot镜像", 70 | 'patch ramdisk': "- 修补ramdisk", 71 | 'unable to patch ramdisk':"- 无法修补ramdisk", 72 | 'boot image patched by old magisk': "- Boot 镜像中的%s被旧的magisk修补过", 73 | 'please try again with original boot': "- 请使用没有修改过的boot镜像再试一次", 74 | 'patch boot image fstab': "- 修补boot镜像中%s的fstab", 75 | 'repack boot image': "- 打包boot镜像", 76 | 'faild to repack boot image': "- 打包boot镜像失败", 77 | 'done': "- 完成!", 78 | 'cleanup': "- 清理文件", 79 | 80 | 81 | 82 | 83 | } 84 | en_US = { 85 | 'Home': "Home", 86 | 'boot img': "Boot Image", 87 | 'choose file': "ChooseFile", 88 | 'arch': "Arch", 89 | 'keep verity': "Keep Verity", 90 | 'keep encypt': "Keep Force Encrypt", 91 | 'patch vbmeta flag': "Patch vbmeta Flag", 92 | 'legacy sar': "Legacy SAR (If your device is system-as-root BUT not dynamic partitions)", 93 | 'start patch': "StartPatch", 94 | 'clean': 'Clean', 95 | 'recovery': "Patch recovery", 96 | 'Select and Download': "Select and Download", 97 | 'settings': "Settings", 98 | 'use proxy': "Use Proxy", 99 | 'github mirror': "Github Mirror", 100 | 'input your git mirror here': "Insert your mirror url here", 101 | 'use jsdelivr': "Use jsdilivr download", 102 | 'use local file': "Use Local Magisk Apk", 103 | 'use delta magisk': "Use Delta Magisk", 104 | 'refresh list': "RefreshList", 105 | 'available magisk list': "Available Magisk APK", 106 | 'magisk is not select': "No magisk APK selected", 107 | 'Other': "Other", 108 | 'introduce': "Introduce", 109 | 'vist github': "Visit Source", 110 | 'log level': "Log Level", 111 | 'scaling': "Scaling", 112 | 'Based on CustomTkinter': "Based on [CustomTkinter]", 113 | 'progress': "Progress", 114 | 115 | 'please select a exist boot image': "- Please select a Exist boot image", 116 | 'please select a valid magisk apk': "- Please Select a Valid magisk apk", 117 | 'detect select magisk version is': "- Detect select magisk version is", 118 | 'file not exist, ready to download': "- File does not exist , Prepare to download...", 119 | 'use mirror download': "- Download from github mirror...", 120 | 'file exist, no need download': "- File already exist, no need to download...", 121 | 'current magisk': "- Current Magisk version...", 122 | 'refresh magisk list': "- Refresh Magisk list", 123 | 'use from local prebuilt dir': "- Get magisk from local prebuilt dir", 124 | 'no magisk in prebuilt, please downloadn and place': "- Cannot find magisk apk, please download and put into prebuilt dir", 125 | 'work dir': "WorkDir", 126 | 'cannot find any apk, please download and put them into prebuilt dir':"- Cannot find any magisk apk in prebuilt, please downlaod and put into prebuilt dir", 127 | 'select a boot image': "Select a boot image", 128 | 129 | 'get version faild, please check net or add proxy': "Get Releases failed, please try to redownload or add proxy...", 130 | 'internet connect faild or cannot connect target url': "- Connect Failed...", 131 | 'start download': "Start download", 132 | 'download complete': "Downlaod Complete", 133 | 'start decompress needed': "- Start decompress needed...", 134 | 135 | 'cannot initilazed with magiskboot': "- Cannot initialized with magiskboot", 136 | 'boot image does not exist':"- boot does not exist", 137 | 'unpack boot image': "- Unpacking boot image", 138 | 'unknow/unsupport format': "! Unsupported/Unknown image format", 139 | 'chromeos format boot':"- ChromeOS boot image detected", 140 | 'not support yet': "- Unsupport yet", 141 | 'unable to unpack boot': "! Unable to unpack boot image", 142 | 'check ramdisk status': "- Checking ramdisk status", 143 | 'detect original boot': "- Stock boot image detected", 144 | 'detect magisk patched boot': "- Magisk patched boot image detected", 145 | 'boot patched by unknow program': "- Boot image patched by unsupported programs", 146 | 'please resotre original boot':"- Please restore back to stock boot image", 147 | 'patch ramdisk': "- Patching ramdisk", 148 | 'unable to patch ramdisk':"- Unable to patch ramdisk", 149 | 'boot image patched by old magisk': "- Boot image %s was patched by old (unsupported) Magisk", 150 | 'please try again with original boot': "- Please try again with *unpatched* boot image", 151 | 'patch boot image fstab': "- Patch fstab in boot image %s", 152 | 'repack boot image': "- Repacking boot image", 153 | 'faild to repack boot image': "- Unable to repack boot image", 154 | 'done': "- Done !", 155 | 'cleanup': "- Cleanup...", 156 | } 157 | 158 | def langget(key) -> str: 159 | try: 160 | val = getattr(Language, Language.select).get(key) 161 | if val: 162 | return val 163 | else: return key 164 | except: return key 165 | -------------------------------------------------------------------------------- /mp/boot_patch.py: -------------------------------------------------------------------------------- 1 | from sys import stderr 2 | import subprocess 3 | from os import unlink 4 | from os import name as osname 5 | from os.path import isfile, isdir 6 | import logging 7 | from hashlib import sha1 8 | from shutil import copyfile, rmtree 9 | 10 | from .lang import Language, langget 11 | 12 | def getsha1(filename): 13 | with open(filename, 'rb') as f: 14 | return sha1(f.read()).hexdigest() 15 | 16 | def cp(src, dest): 17 | if isfile(src): 18 | copyfile(src, dest) 19 | 20 | def rm(*files): 21 | for i in files: 22 | if isdir(i): 23 | rmtree(i) 24 | else: 25 | if isfile(i): 26 | unlink(i) 27 | 28 | def grep_prop(key, file) -> str: 29 | with open(file, 'r') as f: 30 | for i in iter(f.readline, ""): 31 | if key in i: 32 | return i.split("=")[1].rstrip("\n") 33 | return "" 34 | 35 | class BootPatcher(object): 36 | def __init__( 37 | self, 38 | magiskboot, 39 | keep_verity: bool = True, 40 | keep_forceencrypt: bool = True, 41 | patchvbmeta_flag: bool = False, 42 | recovery_mode: bool = False, 43 | legacysar: bool = False, 44 | progress=None, 45 | log=stderr, 46 | ): 47 | self.magiskboot = magiskboot 48 | 49 | self.keep_verity = keep_verity 50 | self.keep_forceencrypt = keep_forceencrypt 51 | self.patchvbmeta_flag = patchvbmeta_flag 52 | self.recovery_mode = recovery_mode 53 | self.legacysar = legacysar 54 | self.progress = progress 55 | 56 | self.log = log 57 | 58 | self.__check() 59 | self.__prepare_env() 60 | 61 | def __check(self): 62 | if not isfile(self.magiskboot): 63 | print(langget('cannot initilazed with magiskboot'), file=self.log) 64 | return False 65 | 66 | def __prepare_env(self): 67 | bool2str = lambda x: "true" if x else "flase" 68 | self.env = { 69 | "KEEPVERITY": bool2str(self.keep_verity), 70 | "KEEPFORCEENCRYPT": bool2str(self.keep_forceencrypt), 71 | "PATCHVBMETAFLAG": bool2str(self.patchvbmeta_flag), 72 | "RECOVERYMODE": bool2str(self.recovery_mode), 73 | "LEGACYSAR": bool2str(self.legacysar), 74 | # Ignore win case sensitive 75 | "MAGISKBOOT_WINSUP_NOCASE": "1" 76 | } 77 | 78 | # This maybe no need 79 | #for i in self.env: 80 | # putenv(i, self.env[i]) 81 | 82 | def __execv(self, cmd:list): 83 | """ 84 | Run magiskboot command, already include magiskboot 85 | return returncode and output 86 | """ 87 | full = [ 88 | self.magiskboot, 89 | *cmd 90 | ] 91 | 92 | if osname == 'nt': 93 | creationflags = subprocess.CREATE_NO_WINDOW 94 | else: 95 | creationflags = 0 96 | logging.info("Run command : \n"+ " ".join(full)) 97 | ret = subprocess.run(full, 98 | stderr=subprocess.STDOUT, 99 | stdout=subprocess.PIPE, 100 | shell=False, 101 | env=self.env, 102 | creationflags=creationflags, 103 | ) 104 | logging.info(ret.stdout.decode(encoding="utf-8")) 105 | return ret.returncode, ret.stdout.decode(encoding="utf-8") 106 | 107 | def patch(self, bootimg:str) -> bool: 108 | # Check bootimg exist 109 | if not isfile(bootimg): 110 | print(langget('boot image does not exist'), file=self.log) 111 | return False 112 | 113 | # Unpack bootimg 114 | print(langget('unpack boot image'), file=self.log) 115 | err, ret = self.__execv(["unpack", bootimg]) 116 | logging.info(ret) 117 | 118 | match err: 119 | case 0: pass 120 | case 1: 121 | print(langget('unknow/unsupport format'), file=self.log) 122 | return False 123 | case 2: 124 | print(langget('chromeos format boot'), file=self.log) 125 | print(langget('not support yet')) 126 | return False 127 | case _: 128 | print(langget('unable to unpack boot'), file=self.log) 129 | return False 130 | 131 | print(langget('check ramdisk status'), file=self.log) 132 | if isfile("ramdisk.cpio"): 133 | err, ret = self.__execv(["cpio", "ramdisk.cpio", "test"]) 134 | status = err 135 | skip_backup = "" 136 | else: 137 | status = 0 138 | skip_backup = "#" 139 | 140 | sha = "" 141 | match (status & 3): 142 | case 0: # Stock boot 143 | print(langget('detect original boot'), file=self.log) 144 | sha = getsha1(bootimg) 145 | cp(bootimg, "stock_boot.img") 146 | cp("ramdisk.cpio", "ramdisk.cpio.orig") 147 | case 1: # Magisk patched 148 | print(langget('detect magisk patched boot'), file=self.log) 149 | err, ret = self.__execv(["cpio", "ramdisk.cpio", "extract .backup/.magisk config.orig", "restore"]) 150 | cp("ramdisk.cpio", "ramdisk.cpio.orig") 151 | rm("stock_boot.img") 152 | case 2: # Unsupported 153 | print(langget('boot patched by unknow program'), file=self.log) 154 | print(langget('please resotre original boot'), file=self.log) 155 | return False 156 | 157 | # Sony device 158 | init = "init" 159 | if not (status&4) == 0: 160 | init = "init.real" 161 | 162 | if isfile("config.orig"): 163 | sha = grep_prop("SHA1", "config.orig") 164 | rm("config.orig") 165 | 166 | print(langget('patch ramdisk'), file=self.log) 167 | 168 | skip32 = "#" 169 | skip64 = "#" 170 | 171 | if isfile("magisk64"): 172 | self.__execv(["compress=xz", "magisk64", "magisk64.xz"]) 173 | skip64 = "" 174 | if isfile("magisk32"): 175 | self.__execv(["compress=xz", "magisk32", "magisk32.xz"]) 176 | skip32 = "" 177 | 178 | stub = False 179 | if isfile("stub.apk"): 180 | stub = True 181 | 182 | if stub: 183 | self.__execv(["compress=xz", "stub.apk", "stub.xz"]) 184 | with open("config", 'w') as config: 185 | config.write( 186 | f"KEEPVERITY={self.env['KEEPVERITY']}" + "\n" + 187 | f"KEEPFORCEENCRYPT={self.env['KEEPFORCEENCRYPT']}" + "\n" + 188 | f"RECOVERYMODE={self.env['RECOVERYMODE']}" + "\n") 189 | if sha != "": 190 | config.write(f"SHA1={sha}\n") 191 | 192 | err, _ = self.__execv([ 193 | "cpio", "ramdisk.cpio", 194 | f"add 0750 {init} magiskinit", 195 | "mkdir 0750 overlay.d", 196 | "mkdir 0750 overlay.d/sbin", 197 | f"{skip32} add 0644 overlay.d/sbin/magisk32.xz magisk32.xz", 198 | f"{skip64} add 0644 overlay.d/sbin/magisk64.xz magisk64.xz", 199 | "add 0644 overlay.d/sbin/stub.xz stub.xz" if stub else "", 200 | "patch", 201 | f"{skip_backup} backup ramdisk.cpio.orig", 202 | "mkdir 000 .backup", 203 | "add 000 .backup/.magisk config", 204 | ]) 205 | if err != 0: 206 | print(langget('unable to patch ramdisk'), file=self.log) 207 | return False 208 | 209 | rm("ramdisk.cpio.orig", "config", "magisk32.xz", "magisk64.xz", "stub.xz") 210 | 211 | for dt in "dtb", "kernel_dtb", "extra": 212 | if isfile(dt): 213 | err, _ = self.__execv([ 214 | "dtb", dt, "test" 215 | ]) 216 | if err != 0: 217 | print(langget('boot image patched by old magisk') %dt, file=self.log) 218 | print(langget('please try again with original boot'), file=self.log) 219 | return False 220 | err, _ = self.__execv([ 221 | "dtb", dt, "patch" 222 | ]) 223 | if err == 0: 224 | print(langget('patch boot image fstab') %dt) 225 | 226 | if isfile("kernel"): 227 | patchedkernel = False 228 | err, _ = self.__execv([ 229 | "hexpatch", "kernel", 230 | "49010054011440B93FA00F71E9000054010840B93FA00F7189000054001840B91FA00F7188010054", 231 | "A1020054011440B93FA00F7140020054010840B93FA00F71E0010054001840B91FA00F7181010054" 232 | ]) 233 | if err == 0: patchedkernel = True 234 | err, _ = self.__execv([ 235 | "hexpatch", "kernel", "821B8012", "E2FF8F12" 236 | ]) 237 | if err == 0: patchedkernel = True 238 | if self.legacysar: 239 | err, _ = self.__execv([ 240 | "hexpatch", "kernel", 241 | "736B69705F696E697472616D667300", 242 | "77616E745F696E697472616D667300" 243 | ]) 244 | if err == 0: patchedkernel = True 245 | if not patchedkernel: rm("kernel") 246 | 247 | print(langget('repack boot image'), file=self.log) 248 | err, _ = self.__execv([ 249 | "repack", bootimg 250 | ]) 251 | if err != 0: 252 | print(langget('faild to repack boot image'), file=self.log) 253 | return False 254 | 255 | self.cleanup() 256 | print(langget('done'), file=self.log) 257 | return True 258 | 259 | def cleanup(self): 260 | rmlist = [ 261 | "magisk32", "magisk32.xz", "magisk64", "magisk64.xz", "magiskinit", "stub.apk" 262 | ] 263 | rm(*rmlist) 264 | print(langget('cleanup'), file=self.log) 265 | self.__execv(["cleanup"]) 266 | 267 | if __name__ == "__main__": 268 | print(grep_prop("B", "config")) 269 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /mp/ui.py: -------------------------------------------------------------------------------- 1 | import customtkinter as ctk 2 | from tkinter.ttk import Progressbar 3 | from .magisk_logo import rawdata as logodata 4 | from PIL import Image 5 | from io import BytesIO 6 | from os import getcwd, makedirs, walk 7 | import os.path as op 8 | from os import name as osname 9 | from sys import version as pyversion 10 | from sys import argv 11 | import webbrowser 12 | import logging 13 | from multiprocessing.dummy import DummyProcess 14 | 15 | from . import utils 16 | from . import boot_patch 17 | 18 | # multi lang support 19 | from .lang import Language 20 | 21 | if osname == 'nt': 22 | import ctypes 23 | 24 | if osname == 'nt': 25 | EXT = ".exe" 26 | else: 27 | EXT = "" 28 | 29 | VERSION = "4.1.0" 30 | AUTHOR = "affggh" 31 | TITLE = "Magisk Patcher v%s by %s" % (VERSION, AUTHOR) 32 | WIDTH = 900 33 | HEIGHT = 420 34 | OS, REL, ARCH = utils.retTypeAndMachine() 35 | LICENSE = "GPLv3" 36 | INTRODUCE = """\ 37 | - Native OS \t: %s 38 | - Native Arch \t: %s 39 | - Version \t: %s 40 | - Author \t: %s 41 | - License \t: %s 42 | - PythonVersion : %s 43 | - Work Dir \t: %s 44 | - 介绍: 45 | \tMagisk Patcher 是由 %s 开发的用来在桌面系统上修补手机magisk的一个简单的小程序,基于官方的magiskboot 46 | - 感谢: 47 | \t- magiskboot on mingw32 from https://github.com/svoboda18/magiskboot 48 | \t- customtkinter ui界面库,有一说一确实好看 49 | """ %(f'{OS} ({REL})' if REL else OS, ARCH, VERSION, AUTHOR, LICENSE, pyversion, getcwd(), AUTHOR) 50 | if OS == 'windows': 51 | prebuilt_magiskboot = op.abspath(op.join(op.dirname(argv[0]), "bin", OS, ARCH, "magiskboot" + EXT)) 52 | elif OS == 'macos': 53 | prebuilt_magiskboot = op.abspath(op.join(op.dirname(argv[0]), "bin", OS, REL, ARCH, "magiskboot" + EXT)) 54 | else: 55 | prebuilt_magiskboot = op.abspath(op.join(op.dirname(argv[0]), "bin", "magiskboot"+EXT)) 56 | 57 | def visit_customtkinter_website(event): 58 | webbrowser.open("https://customtkinter.tomschimansky.com") 59 | 60 | def visit_magisk_website(event): 61 | webbrowser.open("https://github.com/topjohnwu/Magisk") 62 | 63 | class MagiskPatcherUI(ctk.CTk): 64 | def __init__(self, *args): 65 | super().__init__(*args) 66 | 67 | self.grid_rowconfigure(0, weight=1) 68 | self.grid_columnconfigure(1, weight=1) 69 | 70 | self.lang = ctk.StringVar(value=Language.supports[0]) 71 | self.lang_dict = getattr(Language, self.lang.get()) 72 | 73 | self.logo = ctk.CTkImage(Image.open(BytesIO(logodata), "r"), size=(240, 100)) 74 | self.bootimg = ctk.StringVar() 75 | self.arch = ctk.StringVar() 76 | self.magisk_select = ctk.StringVar(value=self.langget('magisk is not select')) 77 | self.magisk_select_int = ctk.StringVar() 78 | 79 | self.keep_verity = ctk.BooleanVar(value=True) 80 | self.keep_forceencrypt = ctk.BooleanVar(value=True) 81 | self.patchvbmeta_flag = ctk.BooleanVar(value=False) 82 | self.recoverymode = ctk.BooleanVar(value=False) 83 | self.legacysar = ctk.BooleanVar(value=False) 84 | 85 | self.progress = ctk.DoubleVar(value=0) 86 | self.loglevel = ctk.IntVar(value=logging.WARNING) 87 | 88 | # download 89 | self.isproxy = ctk.BooleanVar(value=False) 90 | self.proxy = ctk.StringVar(value="127.0.0.1:7890") # default clash 91 | 92 | self.ismirror = ctk.BooleanVar(value=False) 93 | self.mirror = ctk.StringVar(value="") 94 | 95 | self.isjsdelivr = ctk.BooleanVar(value=True) 96 | 97 | self.uselocal = ctk.BooleanVar(value=True) 98 | self.usedeltamagisk = ctk.BooleanVar(value=False) 99 | 100 | # magisk list 101 | self.magisk_list = [] 102 | 103 | self.__setup_widgets() 104 | 105 | # initial 106 | 107 | # log 108 | logging.basicConfig(level=logging.DEBUG) 109 | self.log = logging.getLogger() 110 | self.loghandler = logging.StreamHandler(self) 111 | logformatter = logging.Formatter("%(asctime)s - %(filename)s[line: %(lineno)d] - %(levelname)s: \n\t%(message)s") 112 | self.loghandler.setFormatter(logformatter) 113 | self.loghandler.setLevel(logging.DEBUG) 114 | self.log.addHandler(self.loghandler) 115 | 116 | self.log.setLevel(logging.WARN) 117 | 118 | print("- Detect env:", file=self) 119 | if REL: 120 | print(f"\tOS \t: {OS} ({REL})", file=self) 121 | else: 122 | print(f"\tOS \t: {OS}", file=self) 123 | print(f"\tARCH\t: {ARCH}", file=self) 124 | print(f"\tCurrent Dir\t: {getcwd()}", file=self) 125 | if OS in ['windows', 'macos']: 126 | print(f"- Windows/macOS Use prebuilt magiskboot.", file=self) 127 | print(f"\tFile should be here: {prebuilt_magiskboot}", file=self) 128 | if not prebuilt_magiskboot: 129 | print("- Error: Cannot find prebuilt magiskboot.", file=self) 130 | print("\tFix this to patch boot image correctly.", file=self) 131 | print(f"{prebuilt_magiskboot}") 132 | elif OS == 'linux': 133 | print(f"- Linux use magisk.apk inner magiskboot insted prebuilt magiskboot.", file=self) 134 | print(f"\t It will extract when patching a boot image.", file=self) 135 | 136 | # as stdout, you can print(..., file=self) 137 | def write(self, *args): 138 | self.textbox.insert('end', " ".join([str(i) for i in args])) 139 | self.textbox.yview('end') 140 | 141 | def flush(self): # void flush function 142 | pass 143 | 144 | def langget(self, key: str) -> str: 145 | if self.lang_dict.get(key): 146 | return self.lang_dict.get(key) 147 | else: return key 148 | 149 | def __setup_widgets(self): 150 | self.navigation_frame = ctk.CTkFrame(self, corner_radius=0) 151 | self.navigation_label = ctk.CTkLabel( 152 | self.navigation_frame, image=self.logo, compound="left", text="" 153 | ) 154 | self.navigation_label.bind("", visit_magisk_website) 155 | self.navigation_label.pack(side="top", fill="x") 156 | 157 | self.patcher_frame_button = ctk.CTkButton( 158 | self.navigation_frame, 159 | height=30, 160 | text=self.langget('Home'), 161 | fg_color="transparent", 162 | text_color=("gray10", "gray90"), 163 | hover_color=("gray70", "gray30"), 164 | corner_radius=0, 165 | anchor="w", 166 | border_spacing=10, 167 | font=ctk.CTkFont(size=20), 168 | command=self.change_frame_patcher, 169 | ) 170 | self.patcher_frame_button.pack(side="top", fill="x") 171 | self.download_frame_button = ctk.CTkButton( 172 | self.navigation_frame, 173 | height=30, 174 | text=self.langget('Select and Download'), 175 | fg_color="transparent", 176 | text_color=("gray10", "gray90"), 177 | hover_color=("gray70", "gray30"), 178 | corner_radius=0, 179 | anchor="w", 180 | border_spacing=10, 181 | font=ctk.CTkFont(size=20), 182 | command=self.change_frame_download, 183 | ) 184 | self.download_frame_button.pack(side="top", fill="x") 185 | self.other_frame_button = ctk.CTkButton( 186 | self.navigation_frame, 187 | height=30, 188 | text=self.langget('Other'), 189 | fg_color="transparent", 190 | text_color=("gray10", "gray90"), 191 | hover_color=("gray70", "gray30"), 192 | corner_radius=0, 193 | anchor="w", 194 | border_spacing=10, 195 | font=ctk.CTkFont(size=20), 196 | command=self.change_frame_other, 197 | ) 198 | self.other_frame_button.pack(side="top", fill="x") 199 | 200 | self.theme_select_button = ctk.CTkSegmentedButton( 201 | self.navigation_frame, 202 | corner_radius=0, 203 | values=["dark", "light", "system"], 204 | command=self.change_theme, 205 | ) 206 | self.theme_select_button.set("system") 207 | self.theme_select_button.pack(side="bottom", fill="x") 208 | 209 | lang_frame = ctk.CTkFrame(self.navigation_frame, corner_radius=0) 210 | lang_label = ctk.CTkLabel(lang_frame, text="🌏", font=ctk.CTkFont(size=25), anchor='center', compound='center') 211 | lang_combo = ctk.CTkComboBox(lang_frame, corner_radius=0, values=Language.supports, variable=self.lang) 212 | lang_button = ctk.CTkButton(lang_frame, text="Confirm", command=self.refresh_widgets, width=80) 213 | lang_label.pack(side='left') 214 | lang_combo.pack(side='left', fill='x', expand='yes', padx=5) 215 | lang_button.pack(side='left') 216 | lang_frame.pack(side='bottom', pady=5, fill='x') 217 | 218 | base_on_label = ctk.CTkLabel(self.navigation_frame, text=self.langget('Based on CustomTkinter'), text_color=('blue', 'light blue'), font=ctk.CTkFont(underline=True), anchor='w') 219 | base_on_label.pack(side="bottom", fill="x", padx=5, pady=5) 220 | base_on_label.bind("", visit_customtkinter_website) 221 | 222 | self.navigation_frame.grid(row=0, rowspan=2, column=0, sticky="nsew") 223 | 224 | self.patcher_frame = ctk.CTkFrame(self, corner_radius=0, fg_color="transparent") 225 | self.download_frame = ctk.CTkFrame( 226 | self, corner_radius=0, fg_color="transparent" 227 | ) 228 | self.other_frame = ctk.CTkFrame(self, corner_radius=0, fg_color="transparent") 229 | 230 | file_select_frame = ctk.CTkFrame(self.patcher_frame, corner_radius=5) 231 | file_select_label = ctk.CTkLabel(file_select_frame, text=self.langget('boot img')) 232 | file_select_entry = ctk.CTkEntry(file_select_frame, textvariable=self.bootimg) 233 | file_select_button = ctk.CTkButton( 234 | file_select_frame, text=self.langget('choose file'), command=self.file_choose_dialog 235 | ) 236 | file_select_label.pack(side="left", padx=5, pady=5) 237 | 238 | file_select_entry.pack(side="left", fill="x", expand="yes") 239 | file_select_button.pack(side="right", padx=5, pady=5) 240 | file_select_frame.pack(side="top", fill="x", padx=5, pady=5) 241 | 242 | config_frame = ctk.CTkFrame(self.patcher_frame, corner_radius=5) 243 | 244 | arch_select_label = ctk.CTkLabel(config_frame, text=self.langget('arch')+'\t') 245 | arch_select_button = ctk.CTkSegmentedButton( 246 | config_frame, 247 | values=["arm64", "arm", "x86_64", "x86"], 248 | corner_radius=50, 249 | variable=self.arch, 250 | ) 251 | arch_select_label.grid(column=0, row=0, padx=5, pady=5) 252 | arch_select_button.grid( 253 | column=1, columnspan=4, row=0, sticky="nsew", padx=5, pady=5 254 | ) 255 | arch_select_button.set("arm64") 256 | 257 | keep_verify_check = ctk.CTkSwitch(config_frame, text=self.langget('keep verity'), variable=self.keep_verity) 258 | keep_verify_check.grid(column=1, row=1, padx=5, pady=5) 259 | 260 | keep_forceencrypt_check = ctk.CTkSwitch(config_frame, text=self.langget('keep encypt'), variable=self.keep_forceencrypt) 261 | keep_forceencrypt_check.grid(column=2, row=1, padx=5, pady=5) 262 | 263 | patch_vbmeta_flag = ctk.CTkSwitch(config_frame, text=self.langget('patch vbmeta flag'), variable=self.patchvbmeta_flag) 264 | patch_vbmeta_flag.grid(column=3, row=1, padx=5, pady=5) 265 | 266 | recovery_flag = ctk.CTkSwitch(config_frame, text=self.langget('recovery'), variable=self.recoverymode) 267 | recovery_flag.grid(column=4, row=1, padx=5, pady=5) 268 | 269 | legacy_sar_flag = ctk.CTkSwitch(config_frame, text=self.langget('legacy sar'), variable=self.legacysar) 270 | legacy_sar_flag.grid(column=1, row=2, sticky='nsew', padx=5, pady=5, columnspan=4) 271 | 272 | config_frame.pack(side="top", fill="x", expand="no", padx=5, pady=5) 273 | 274 | confirm_frame = ctk.CTkFrame(self.patcher_frame, corner_radius=5) 275 | confirm_info = ctk.CTkLabel(confirm_frame, textvariable=self.magisk_select) 276 | confirm_info.pack(side="left", padx=5, pady=5, fill="x") 277 | confirm_button = ctk.CTkButton( 278 | confirm_frame, text=self.langget('start patch'), fg_color="green", hover_color="dark green", command=self.start_patch 279 | ) 280 | confirm_button.pack(side="right", anchor="e", padx=5, pady=5) 281 | 282 | confirm_frame.pack(side="top", fill="x", padx=5, pady=5) 283 | 284 | progress_frame = ctk.CTkFrame(self, corner_radius=0) 285 | self.progress_label = ctk.CTkLabel(progress_frame, text=self.langget('progress')+":") 286 | #progress_bar = ctk.CTkProgressBar(progress_frame, variable=self.progress) 287 | #progress_bar._determinate_value = 100 288 | progress_bar = Progressbar(progress_frame, variable=self.progress, maximum=100) 289 | 290 | self.progress_label.pack(side='left', padx=5) 291 | progress_bar.pack(side='left', expand='yes', padx=5, fill='x') 292 | 293 | 294 | progress_process = ctk.CTkLabel(progress_frame, textvariable=self.progress, width=25, anchor='e') 295 | progress_process.pack(side='left', padx=5) 296 | ctk.CTkLabel(progress_frame, text="%").pack(side='left', padx=(0, 5)) 297 | 298 | progress_frame.grid(row=1, column=1, sticky='ew') 299 | 300 | # keep_verity_checkbox = ctk.CTkCheckBox(self.patcher) 301 | 302 | self.textbox = ctk.CTkTextbox(self.patcher_frame, border_width=0, corner_radius=20, font=ctk.CTkFont("console")) 303 | self.textbox.pack(side='top', fill='both', padx=5, pady=5, expand='yes') 304 | 305 | textbox_clear_button = ctk.CTkButton(confirm_frame, text=self.langget('clean'), command=lambda: self.textbox.delete(1.0, 'end')) 306 | textbox_clear_button.pack(side='right', padx=5, pady=5) 307 | 308 | # Download Frame 309 | self.download_list_frame = ctk.CTkScrollableFrame(self.download_frame, corner_radius=5, label_text=self.langget('available magisk list')) 310 | download_config_frame = ctk.CTkFrame(self.download_frame, corner_radius=5) 311 | 312 | download_setting_label = ctk.CTkButton(download_config_frame, state='disable', text=self.langget('settints'), fg_color=('grey78', 'grey23'), text_color=('black', 'grey85'), width=200) 313 | download_setting_label.pack(side='top', fill='x', padx=5, pady=5) 314 | 315 | download_proxy_frame = ctk.CTkFrame(download_config_frame) 316 | download_proxy_checkbox = ctk.CTkSwitch(download_proxy_frame, text=self.langget('use proxy'), variable=self.isproxy) 317 | download_proxy_checkbox.pack(side='top', fill='x', anchor='w', padx=5, pady=5) 318 | # bind if proxy is not allow, then forget proxy url label 319 | download_proxy_checkbox.bind("", self.update_proxy_widgets) 320 | 321 | self.download_proxy_url = ctk.CTkEntry(download_proxy_frame, textvariable=self.proxy) 322 | #self.download_proxy_url.pack(side='top', fill='x', anchor='w', padx=5, pady=5) 323 | 324 | download_proxy_frame.pack(side='top', fill='x', padx=5, pady=5, expand='no') 325 | 326 | 327 | # mirror source list 328 | download_mirror_frame = ctk.CTkFrame(download_config_frame) 329 | download_mirror_checkbox = ctk.CTkSwitch(download_mirror_frame, text=self.langget('github mirror'), variable=self.ismirror) 330 | download_mirror_checkbox.pack(side='top', fill='x', padx=5, pady=5, expand='no') 331 | download_mirror_checkbox.bind("", self.update_mirror_widgets) 332 | 333 | self.download_mirror_label = ctk.CTkEntry(download_mirror_frame, placeholder_text=self.langget('input your git mirror here')) 334 | 335 | download_mirror_frame.pack(side='top', fill='x', padx=5, pady=5, expand='no') 336 | 337 | # jsdelivr 338 | downlaod_jsdelivr = ctk.CTkSwitch(download_config_frame, text=self.langget('use jsdelivr'), variable=self.isjsdelivr) 339 | downlaod_jsdelivr.pack(side='top', padx=10, pady=5, fill='x') 340 | 341 | download_config_local_frame = ctk.CTkFrame(download_config_frame) 342 | download_use_local_checkbox = ctk.CTkSwitch(download_config_local_frame, text=self.langget('use local file'), variable=self.uselocal) 343 | download_use_local_checkbox.bind("", self.update_local_widgets) 344 | download_use_local_checkbox.pack(side='top', padx=5, pady=5, fill='x') 345 | 346 | # delta switch 347 | self.download_delta_magisk = ctk.CTkSwitch(download_config_local_frame, text=self.langget('use delta magisk'), variable=self.usedeltamagisk) 348 | download_config_local_frame.pack(side='top', fill='x', padx=5, pady=5) 349 | 350 | download_refresh_button = ctk.CTkButton(download_config_frame, text=self.langget('refresh list'), command=self.refresh_magisk) 351 | download_refresh_button.pack(side='bottom', fill='x', padx=10, pady=5) 352 | 353 | download_config_frame.pack(side='left', fill='both', padx=5, pady=5, expand='no') 354 | self.download_list_frame.pack(side='left', fill='both', padx=5, pady=5, expand='yes') 355 | 356 | # other frame 357 | other_frame = ctk.CTkFrame(self.other_frame) 358 | other_introduce_label = ctk.CTkButton(other_frame, state='disable', text=self.langget('introduce'), fg_color=('grey78', 'grey23'), text_color=('black', 'grey85')) 359 | other_introduce_label.pack(side='top', padx=5, pady=5, fill='x') 360 | other_introduce_logo = ctk.CTkLabel(other_frame, text=" Magisk Patcher", font=ctk.CTkFont(size=30, weight='bold'), image=ctk.CTkImage(Image.open(BytesIO(logodata)), size=(240,100)), compound='left', anchor='sw') 361 | other_introduce_logo.pack(side='top', fill='x', anchor='w') 362 | other_introduce_full = ctk.CTkTextbox(other_frame, font=ctk.CTkFont("console"), height=160) 363 | other_introduce_full.insert('end', INTRODUCE) 364 | other_introduce_full.configure(state='disable') 365 | other_introduce_full.pack(side='top', padx=5, pady=5, fill='both', anchor='w', expand='yes') 366 | #other_introduce_longlabel = ctk.CTkLabel() 367 | other_button_frame = ctk.CTkFrame(other_frame) 368 | other_visit_button = ctk.CTkButton(other_button_frame, text=self.langget('vist github'), command=lambda: webbrowser.open("https://github.com/affggh/magisk_patcher")) 369 | other_visit_button.grid(column=0, row=0, padx=5, pady=5) 370 | 371 | loglevel_label = ctk.CTkLabel(other_button_frame, text=self.langget('log level')) 372 | loglevel_label.grid(column=1, row=0, padx=5, pady=5) 373 | 374 | loglevel_slide_bar = ctk.CTkSlider(other_button_frame, from_=logging.DEBUG, to=logging.CRITICAL, number_of_steps=4, variable=self.loglevel, command=self.set_log_level) 375 | loglevel_slide_bar.grid(column=2, row=0, padx=5, pady=5) 376 | loglevel_slide_bar.set(logging.WARN) # Default loglevel 377 | ctk.CTkLabel(other_button_frame, textvariable=self.loglevel).grid(column=3, row=0, padx=5, pady=5) 378 | 379 | ctk.CTkLabel(other_button_frame, text=self.langget('scaling')+":").grid(column=4, row=0, padx=(5,0), pady=5) 380 | scaling_bar = ctk.CTkOptionMenu(other_button_frame, values=["0.75", "0.8", "1.0", "1.25", "1.5", "2"], command=self.ui_scaling_event) 381 | scaling_bar.set("1.0") 382 | scaling_bar.grid(column=5, row=0, padx=5, pady=5) 383 | other_button_frame.pack(side='top', padx=5, pady=5, fill='x', expand='no') 384 | 385 | other_frame.pack(side='top', padx=5, pady=5, fill='both', expand='yes') 386 | 387 | self._change_frame_byname("patcher") 388 | 389 | def start_patch(self): 390 | if not op.isfile(self.bootimg.get()): 391 | print(self.langget('please select a exist boot image'), file=self) 392 | return 393 | 394 | if not op.isfile(op.join("prebuilt", self.magisk_select_int.get())): 395 | print(self.langget('please select a valid magisk apk'), file=self) 396 | return 397 | 398 | magisk_version = utils.getMagiskApkVersion(op.join("prebuilt", self.magisk_select_int.get())) 399 | print(f"{self.langget('detect select magisk version is')} [{str(utils.convertVercode2Ver(magisk_version))}]", file=self) 400 | 401 | utils.parseMagiskApk(op.join("prebuilt", self.magisk_select_int.get()), arch=self.arch.get(), log=self) 402 | 403 | patcher = boot_patch.BootPatcher(prebuilt_magiskboot, 404 | self.keep_verity.get(), 405 | self.keep_forceencrypt.get(), 406 | self.patchvbmeta_flag.get(), 407 | self.recoverymode.get(), 408 | self.legacysar.get(), 409 | self.progress, 410 | self) 411 | th = DummyProcess(target=patcher.patch, args=[self.bootimg.get(),]) 412 | th.start() 413 | 414 | def refresh_magisk(self): 415 | def download(magisk: str): 416 | if not self.uselocal.get(): 417 | if not op.isdir(op.join("prebuilt")): 418 | makedirs("prebuilt", exist_ok=True) 419 | 420 | if not op.isfile(op.join("prebuilt", magisk)): 421 | print(f"{self.langget('file not exist, ready to download')} [{magisk}]", file=self) 422 | if not self.isjsdelivr.get() and self.ismirror.get(): 423 | print(self.langget('use mirror download'), file=self) 424 | url = magisk_list[magisk].replace(self.mirror.get().rstrip('/'), "https://github.com") 425 | else: 426 | url = magisk_list[magisk] 427 | utils.thdownloadFile(url, 428 | op.join("prebuilt", magisk), 429 | self.isproxy.get(), 430 | self.proxy.get(), 431 | self.progress, 432 | self) 433 | else: 434 | print(self.langget('file exist, no need download'), file=self) 435 | self.magisk_select.set(f"- {self.langget('current magisk')} [{magisk}]") 436 | self.change_frame_patcher() 437 | 438 | print(self.langget('refresh magisk list'), file=self) 439 | 440 | # delete widgets 441 | for i in self.magisk_list: 442 | i.destroy() 443 | self.magisk_list = [] 444 | 445 | if self.uselocal.get(): 446 | print(self.langget('use from local prebuilt dir'), file=self) 447 | if not op.isdir("prebuilt"): 448 | print(self.langget('no magisk in prebuilt, please downloadn and place'), file=self) 449 | print(f"\t{self.langget('work dir')}: {getcwd()}", file=self) 450 | makedirs("prebuilt", exist_ok=True) 451 | 452 | magisk_list = [] 453 | 454 | for root, dirs, files in walk("prebuilt"): 455 | for file in files: 456 | if ".apk" in file: 457 | magisk_list.append(file) 458 | 459 | if magisk_list.__len__() == 0: 460 | print(self.langget('cannot find any apk, please download and put them into prebuilt dir'), file=self) 461 | self.change_frame_patcher() 462 | 463 | for index, current in enumerate(magisk_list): 464 | self.magisk_list.append( 465 | ctk.CTkRadioButton(self.download_list_frame, 466 | text=current, 467 | command=lambda x=current: download(x), 468 | value=current, 469 | variable=self.magisk_select_int) 470 | ) 471 | 472 | else: 473 | magisk_list = utils.getReleaseList(url=utils.DEFAULT_MAGISK_API_URL if not self.usedeltamagisk.get() else utils.DELTA_MAGISK_API_URL, 474 | isproxy=self.isproxy.get(), 475 | proxyaddr=self.proxy.get(), 476 | isjsdelivr=self.isjsdelivr.get(), 477 | log=self.log) 478 | for index, current in enumerate(magisk_list): 479 | self.magisk_list.append( 480 | ctk.CTkRadioButton(self.download_list_frame, 481 | text=current, 482 | command=lambda x=current: download(x), 483 | value=current, 484 | variable=self.magisk_select_int) 485 | ) 486 | 487 | # place all widgets 488 | for i in self.magisk_list: 489 | i.pack(side='top', fill='x', anchor='w', padx=10, pady=5) 490 | 491 | def change_theme(self, theme: str): 492 | ctk.set_appearance_mode(theme) 493 | 494 | def file_choose_dialog(self): 495 | fname = ctk.filedialog.askopenfilename(title=self.langget('select a boot image'), initialdir=getcwd()) 496 | self.bootimg.set(fname) 497 | 498 | def set_progress(self, value: int): 499 | self.progress.set("%d" %value/100) 500 | self.progress.trace_variable 501 | 502 | def set_log_level(self, value): 503 | self.log.setLevel(int(value)) 504 | 505 | def update_local_widgets(self, event): 506 | if not self.uselocal.get(): 507 | self.download_delta_magisk.pack(side='top', fill='x', anchor='w', padx=5, pady=5) 508 | else: 509 | self.download_delta_magisk.pack_forget() 510 | 511 | def update_proxy_widgets(self, event): 512 | if self.isproxy.get(): 513 | self.download_proxy_url.pack(side='top', fill='x', anchor='w', padx=5, pady=5) 514 | else: 515 | self.download_proxy_url.pack_forget() 516 | 517 | def update_mirror_widgets(self, event): 518 | if self.ismirror.get(): 519 | self.download_mirror_label.pack(side='top', fill='x', anchor='w', padx=5, pady=5) 520 | else: 521 | self.download_mirror_label.pack_forget() 522 | 523 | def _change_frame_byname(self, name: str): 524 | self.patcher_frame_button.configure( 525 | fg_color=("gray75", "gray25") if name == "patcher" else "transparent" 526 | ) 527 | self.download_frame_button.configure( 528 | fg_color=("gray75", "gray25") if name == "download" else "transparent" 529 | ) 530 | self.other_frame_button.configure( 531 | fg_color=("gray75", "gray25") if name == "other" else "transparent" 532 | ) 533 | 534 | if name == "patcher": 535 | self.patcher_frame.grid(row=0, column=1, sticky="nsew") 536 | else: 537 | self.patcher_frame.grid_forget() 538 | 539 | if name == "download": 540 | self.download_frame.grid(row=0, column=1, sticky="nsew") 541 | else: 542 | self.download_frame.grid_forget() 543 | 544 | if name == "other": 545 | self.other_frame.grid(row=0, column=1, sticky="nsew") 546 | else: 547 | self.other_frame.grid_forget() 548 | 549 | def change_frame_patcher(self): 550 | self._change_frame_byname("patcher") 551 | 552 | def change_frame_download(self): 553 | self._change_frame_byname("download") 554 | 555 | def change_frame_other(self): 556 | self._change_frame_byname("other") 557 | 558 | def ui_scaling_event(self, value): 559 | ctk.set_widget_scaling(float(value)) 560 | ctk.set_window_scaling(float(value)) 561 | 562 | def refresh_widgets(self): 563 | for child in self.winfo_children(): 564 | child.destroy() 565 | 566 | Language.select = self.lang.get() 567 | self.lang_dict = getattr(Language, self.lang.get()) 568 | self.magisk_select.set(self.langget('magisk is not select')) 569 | for i in self.magisk_list: 570 | i.destory() 571 | self.magisk_list = [] 572 | self.__setup_widgets() 573 | 574 | def centerWindow(parent: ctk.CTk): 575 | width, height = parent.winfo_screenwidth(), parent.winfo_screenheight() 576 | parent.geometry("+%d+%d" % ((width / 2) - (WIDTH / 2), (height / 2) - (HEIGHT / 2))) 577 | 578 | if __name__ == "__main__": 579 | root = MagiskPatcherUI() 580 | root.title(TITLE) 581 | root.geometry("%dx%d" % (WIDTH, HEIGHT)) 582 | 583 | # Fix high dpi 584 | if osname == 'nt': 585 | # Tell system using self dpi adapt 586 | ctypes.windll.shcore.SetProcessDpiAwareness(1) 587 | # Get screen resize scale factor 588 | scalefactor = ctypes.windll.shcore.GetScaleFactorForDevice(0) 589 | root.tk.call('tk', 'scaling', scalefactor/75) 590 | 591 | root.update() 592 | centerWindow(root) 593 | #ctk.set_window_scaling(1.25) 594 | 595 | root.mainloop() 596 | --------------------------------------------------------------------------------