import os import shutil import subprocess from config import log, config from beaupy import prompt def check_java_version(): try: result = subprocess.run( ["java", "-version"], capture_output=True, text=True, check=True ) version_line = result.stderr.splitlines()[0] if not any(f"{i}." in version_line for i in range(9, 100)): log.error(f"java 8+ is not installed") exit(1) except subprocess.CalledProcessError: log.error(f"java 8+ is not found") exit(1) log.info(f"found java: {version_line}") def decompile_apk(apk: str): if not os.path.exists(config["folders"]["decompiled"]): log.info(f"creating `decompiled` folder: {config['folders']['decompiled']}") os.mkdir(config["folders"]["decompiled"]) else: log.info(f"resetting `decompiled` folder: {config['folders']['decompiled']}") shutil.rmtree(config["folders"]["decompiled"]) os.mkdir(config["folders"]["decompiled"]) log.info(f"decompile apk: `{apk}`") try: result = subprocess.run( f"java -jar {config['folders']['tools']}/apktool.jar d -f -o {config['folders']['decompiled']} {config['folders']['apks']}/{apk}", shell=True, check=True, text=True, # stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, ) except subprocess.CalledProcessError as e: log.fatal( f"error of running a command: %s :: %s", f"java -jar {config['folders']['tools']}/apktool.jar d -f -o {config['folders']['decompiled']} {config['folders']['apks']}/{apk}", e.stderr, exc_info=True, ) exit(1) def compile_apk(apk: str): if not os.path.exists(config["folders"]["dist"]): log.info(f"creating `dist` folder: {config['folders']['dist']}") os.mkdir(config["folders"]["dist"]) else: log.info(f"resetting `dist` folder: {config['folders']['dist']}") shutil.rmtree(config["folders"]["dist"]) os.mkdir(config["folders"]["dist"]) log.info(f"compile apk: `{apk}`") try: result = subprocess.run( f"java -jar {config['folders']['tools']}/apktool.jar b -f -o {config['folders']['dist']}/{apk} {config['folders']['decompiled']}", shell=True, check=True, text=True, # stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, ) except subprocess.CalledProcessError as e: log.fatal( f"error of running a command: %s :: %s", f"java -jar {config['folders']['tools']}/apktool.jar b -f -o {config['folders']['dist']}/{apk} {config['folders']['decompiled']}", e.stderr, exc_info=True, ) exit(1) def sign_apk(apk: str): log.info(f"sign and align apk: `{apk}`") if os.path.exists( f"{config['folders']['dist']}/{apk.removesuffix('.apk')}-aligned.apk" ): os.remove(f"{config['folders']['dist']}/{apk.removesuffix('.apk')}-aligned.apk") if os.path.exists( f"{config['folders']['dist']}/{apk.removesuffix('.apk')}-aligned-signed.apk" ): os.remove( f"{config['folders']['dist']}/{apk.removesuffix('.apk')}-aligned-signed.apk" ) command = "" try: command = ( f"zipalign -p 4 {config['folders']['dist']}/{apk} {config['folders']['dist']}/{apk.removesuffix(".apk")}-aligned.apk", ) result = subprocess.run( f"zipalign -p 4 {config['folders']['dist']}/{apk} {config['folders']['dist']}/{apk.removesuffix(".apk")}-aligned.apk", shell=True, check=True, text=True, # stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, ) command = ( f"apksigner sign --ks ./keystore.jks --out {config['folders']['dist']}/{apk.removesuffix(".apk")}-aligned-signed.apk {config['folders']['dist']}/{apk.removesuffix(".apk")}-aligned.apk", ) result = subprocess.run( f"apksigner sign --ks ./keystore.jks --out {config['folders']['dist']}/{apk.removesuffix(".apk")}-aligned-signed.apk {config['folders']['dist']}/{apk.removesuffix(".apk")}-aligned.apk", shell=True, check=True, text=True, # stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, ) except subprocess.CalledProcessError as e: log.fatal( f"error of running a command: %s :: %s", command, e.stderr, exc_info=True ) exit(1) def patch_config_name(patch_name: str) -> str: components = patch_name.split("_") return "PatchConfig_" + "".join(x.title() for x in components) def init_patch(): if not os.path.exists(config["folders"]["patches"]): log.info(f"creating `patches` folder: {config['folders']['patches']}") os.mkdir(config["folders"]["patches"]) if not os.path.exists(f"{config['folders']['patches']}/__init__.py"): with open(f"{config['folders']['patches']}/__init__.py", "w") as f: f.write("") name = prompt("Patch name: ", lambda x: x.strip().lower().replace(" ", "_")) description = prompt("Patch description: ", lambda x: x.strip()) priority = prompt("Patch priority: ", target_type=int, initial_value="0") patch_content = f"""\"\"\"{description}\"\"\" # patch settings # priority, default: {priority} priority = {priority} # imports ## bundled from typing import TypedDict ## custom from config import config, log # Patch class {patch_config_name(name)}(TypedDict): pass def apply(patch_conf: {patch_config_name(name)}) -> bool: log.info("patch `{name}` applied, nothing changed") return True """ with open(f"{config['folders']['patches']}/{name}.py", "w", encoding="utf-8") as f: f.write(patch_content) with open(f"{config['folders']['patches']}/{name}.config.json", "w", encoding="utf-8") as f: f.write("{}") log.info(f"patch `{name}` created")