diff --git a/.gitignore b/.gitignore index 750dd16..f412ff3 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,4 @@ decompiled dist __pycache__ keystore.jks -help -dev_patches -dev.config.json \ No newline at end of file +help \ No newline at end of file diff --git a/config.py b/config.py index 8b1d7cb..d8a9199 100644 --- a/config.py +++ b/config.py @@ -1,4 +1,3 @@ -import argparse import json import logging import os @@ -43,27 +42,19 @@ class Config(TypedDict): xml_ns: ConfigXmlNS -parser = argparse.ArgumentParser(prog="anixart patcher") -parser.add_argument("--config", help="path to config.json file", default="config.json") -parser.add_argument("--no-decompile", action="store_true") -parser.add_argument("--no-compile", action="store_true") -parser.add_argument("--patch", action="store_true", help="only patch decompiled") -parser.add_argument("--sign", action="store_true", help="only sign compiled apk") -parser.add_argument("--init", action="store_true", help="init a new patch") -args = parser.parse_args() - - def load_config() -> Config: config = None - if not os.path.exists(args.config): + if not os.path.exists("config.json"): log.exception("file `config.json` is not found!") exit(1) - with open(args.config, "r", encoding="utf-8") as file: + with open("./config.json", "r", encoding="utf-8") as file: config = json.loads(file.read()) log.setLevel(config.get("log_level", "NOTSET").upper()) return config -config = load_config() \ No newline at end of file + + +config = load_config() diff --git a/main.py b/main.py index 75e0efd..75b4003 100644 --- a/main.py +++ b/main.py @@ -1,21 +1,28 @@ from scripts.download_tools import check_and_download_all_tools from scripts.select_apk import get_apks, select_apk from scripts.select_patches import apply_patches, get_patches, select_patches -from scripts.utils import check_java_version, compile_apk, decompile_apk, sign_apk, init_patch -from config import args, config, log, console +from scripts.utils import check_java_version, compile_apk, decompile_apk, sign_apk +from config import log, console from time import time import math -import yaml + +import argparse +parser = argparse.ArgumentParser(prog='anixart patcher') +parser.add_argument("--no-decompile", action='store_true') +parser.add_argument("--no-compile", action='store_true') + +parser.add_argument("--patch", action='store_true') +parser.add_argument("--sign", action='store_true') def patch(): patches = get_patches() patches = select_patches(patches) statuses = apply_patches(patches) - statuses_ok = [] statuses_err = [] + for status in statuses: if status["status"]: console.print(f"{status['name']}: ✔", style="bold green") @@ -23,58 +30,51 @@ def patch(): else: console.print(f"{status['name']}: ✘", style="bold red") statuses_err.append(status["name"]) + return patches, statuses_ok, statuses_err if __name__ == "__main__": + args = parser.parse_args() + check_and_download_all_tools() check_java_version() - - if args.init: - init_patch() - exit(0) - if not args.patch and not args.sign: + if not args.patch: apks = get_apks() if not apks: log.fatal(f"apks folder is empty") exit(1) apk = select_apk(apks) if not apk: - log.info("cancelled") + log.info('cancelled') exit(0) - elif args.patch: + else: patch() exit(0) - elif args.sign: - apkFileName = None - with open( - f"{config["folders"]["decompiled"]}/apktool.yml", "r", encoding="utf-8" - ) as f: - data = yaml.load(f.read(), Loader=yaml.Loader) - apkFileName = data.get("apkFileName", None) - if not apkFileName: - log.fatal( - f"can't find apk file name in {config['folders']['decompiled']}/apktool.yml" - ) - exit(1) - sign_apk(f"{apkFileName.removesuffix('.apk')}-patched.apk") + + if args.sign: + sign_apk(f"{apk.removesuffix('.apk')}-patched.apk") exit(0) start_time = time() + if not args.no_decompile: decompile_apk(apk) + patches, statuses_ok, statuses_err = patch() + if not args.no_compile: compile_apk(f"{apk.removesuffix(".apk")}-patched.apk") + end_time = time() + if not args.no_compile: sign_apk(f"{apk.removesuffix(".apk")}-patched.apk") + log.info("Finished") - log.info( - f"install this apk file: `{config["folders"]["dist"]}/{apk.removesuffix(".apk")}-patched-aligned-signed.apk`" - ) + log.info(f"install this apk file: {apk.removesuffix(".apk")}-patched-aligned-signed.apk") log.info(f"used and successful patches: {", ".join(statuses_ok)}") log.info(f"used and unsuccessful patches: {", ".join(statuses_err)}") log.info(f"time taken: {math.floor(end_time - start_time)}s") diff --git a/patches/change_navigation_bar.config.json b/patches/change_navigation_bar.config.json deleted file mode 100644 index 379e7df..0000000 --- a/patches/change_navigation_bar.config.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "portrait": [ - "home", - "discover", - "feed", - "bookmarks", - "profile" - ], - "landscape": [ - "home", - "discover", - "feed", - "bookmarks", - "profile" - ] -} \ No newline at end of file diff --git a/patches/change_navigation_bar.py b/patches/change_navigation_bar.py deleted file mode 100644 index 0202cc0..0000000 --- a/patches/change_navigation_bar.py +++ /dev/null @@ -1,52 +0,0 @@ -"""Move and replace navigation bar tabs""" - -# patch settings -# priority, default: 0 -priority = 0 - -# imports -## bundled -from typing import TypedDict - -## installed -from lxml import etree - -## custom -from config import config, log - - -# Patch -class PatchConfig_ChangeNavigationBar(TypedDict): - portrait: list[str] - landscape: list[str] - - -allowed_items = ["home", "discover", "feed", "bookmarks", "profile"] - - -def modify_menu(menu: list[str], path: str) -> None: - for item in menu: - if item not in allowed_items: - log.warning(f"menu item `{item}` is not allowed, removing from list") - menu.remove(item) - - root = etree.Element("menu", nsmap={"android": config['xml_ns']['android']}) - for item in menu: - element = etree.SubElement(root, "item") - element.set(f"{{{config['xml_ns']['android']}}}icon", f"@drawable/nav_{item}") - element.set(f"{{{config['xml_ns']['android']}}}id", f"@id/tab_{item}") - element.set(f"{{{config['xml_ns']['android']}}}title", f"@string/{item}") - - tree = etree.ElementTree(root) - tree.write( - path, - pretty_print=True, - xml_declaration=True, - encoding="utf-8", - ) - - -def apply(patch_conf: PatchConfig_ChangeNavigationBar) -> bool: - modify_menu(patch_conf["portrait"], f"{config['folders']['decompiled']}/res/menu/bottom.xml") - modify_menu(patch_conf["landscape"], f"{config['folders']['decompiled']}/res/menu/navigation_rail_menu.xml") - return True diff --git a/patches/change_package_name.config.json b/patches/change_package_name.config.json index 6c73991..56b2452 100644 --- a/patches/change_package_name.config.json +++ b/patches/change_package_name.config.json @@ -1,3 +1,3 @@ { - "new_package_name": "com.swiftsoft.anixartd" + "new_package_name": "com.radiquum.anixart" } \ No newline at end of file diff --git a/patches/compress.config.json b/patches/compress.config.json index 62b15f9..645e742 100644 --- a/patches/compress.config.json +++ b/patches/compress.config.json @@ -1,9 +1,3 @@ { - "remove_language_files": true, - "remove_AI_voiceover": true, - "remove_debug_lines": true, - "remove_drawable_files": false, - "remove_unknown_files": false, - "remove_unknown_files_keep_dirs": ["META-INF", "kotlin"], - "compress_png_files": false + "keep_dirs": ["META-INF", "kotlin"] } \ No newline at end of file diff --git a/patches/compress.md b/patches/compress.md deleted file mode 100644 index 581f1a1..0000000 --- a/patches/compress.md +++ /dev/null @@ -1,30 +0,0 @@ -# Compress - -Patch is used to compress and remove resources to reduce final apk filesize - -## settings (compress.config.json) - -- remove_unknown_files: true/false - removes files from decompiled/unknown directory -- remove_unknown_files_keep_dirs: list[str] - keeps specified directories in decompiled/unknown directory -- remove_debug_lines: true/false - removes `.line n` from decompiled smali files -- remove_AI_voiceover: true/false - replaces voiceover of anixa character with a blank mp3 -- compress_png_files: true/false - compresses PNG in decompiled/res directory -- remove_drawable_files: true/false - removes some drawable-* from decompiled/res -- remove_language_files: true/false - removes all languages except ru and en - -## efficiency - -Tested with 9.0 Beta 7 - -diff = original apk bytes - patch apk bytes - -| Setting | Filesize | Diff | % | -| :----------- | :-------------------: | :-----------------: | :-: | -| None | 17092 bytes - 17.1 MB | - | - | -| Compress PNG | 17072 bytes - 17.1 MB | 20 bytes - 0.0 MB | 0.11% | -| Remove files | 17020 bytes - 17.0 MB | 72 bytes - 0.1 MB | 0.42% | -| Remove draws | 16940 bytes - 16.9 MB | 152 bytes - 0.2 MB | 0.89% | -| Remove lines | 16444 bytes - 16.4 MB | 648 bytes - 0.7 MB | 3.79% | -| Remove ai vo | 15812 bytes - 15.8 MB | 1280 bytes - 1.3 MB | 7.49% | -| Remove langs | 15764 bytes - 15.7 MB | 1328 bytes - 1.3 MB | 7.76% | -| All enabled | 13592 bytes - 13.6 MB | 3500 bytes - 4.8 MB | 20.5% | diff --git a/patches/compress.py b/patches/compress.py index 6b2c83f..bdf0635 100644 --- a/patches/compress.py +++ b/patches/compress.py @@ -18,16 +18,10 @@ from scripts.smali_parser import get_smali_lines, save_smali_lines # Patch class PatchConfig_Compress(TypedDict): - remove_language_files: bool - remove_AI_voiceover: bool - remove_debug_lines: bool - remove_drawable_files: bool - remove_unknown_files: bool - remove_unknown_files_keep_dirs: list[str] - compress_png_files: bool + keep_dirs: list[str] -def remove_unknown_files(patch_config: PatchConfig_Compress): +def remove_files(patch_config: PatchConfig_Compress): path = f"{config['folders']['decompiled']}/unknown" items = os.listdir(path) @@ -37,7 +31,7 @@ def remove_unknown_files(patch_config: PatchConfig_Compress): os.remove(item_path) log.debug(f"[COMPRESS] removed file: {item_path}") elif os.path.isdir(item_path): - if item not in patch_config["remove_unknown_files_keep_dirs"]: + if item not in patch_config["keep_dirs"]: shutil.rmtree(item_path) log.debug(f"[COMPRESS] removed directory: {item_path}") @@ -53,7 +47,7 @@ def remove_debug_lines(): file_content = get_smali_lines(file_path) new_content = [] for line in file_content: - if line.find(".line") >= 0: + if line.find(".line") >= 0 or line.find(".source") >= 0: continue new_content.append(line) save_smali_lines(file_path, new_content) @@ -93,7 +87,7 @@ def compress_png(png_path: str): exit(1) -def compress_png_files(): +def compress_pngs(): compressed = [] for root, _, files in os.walk(f"{config['folders']['decompiled']}"): if len(files) < 0: @@ -107,190 +101,10 @@ def compress_png_files(): log.debug(f"[COMPRESS] {len(compressed)} pngs have been compressed") -def remove_AI_voiceover(): - blank = f"{config['folders']['patches']}/resources/blank.mp3" - path = f"{config['folders']['decompiled']}/res/raw" - files = [ - "reputation_1.mp3", - "reputation_2.mp3", - "reputation_3.mp3", - "sound_beta_1.mp3", - "sound_create_blog_1.mp3", - "sound_create_blog_2.mp3", - "sound_create_blog_3.mp3", - "sound_create_blog_4.mp3", - "sound_create_blog_5.mp3", - "sound_create_blog_6.mp3", - "sound_create_blog_reputation_1.mp3", - "sound_create_blog_reputation_2.mp3", - "sound_create_blog_reputation_3.mp3", - "sound_create_blog_reputation_4.mp3", - "sound_create_blog_reputation_5.mp3", - "sound_create_blog_reputation_6.mp3", - ] - - for file in files: - if os.path.exists(f"{path}/{file}"): - os.remove(f"{path}/{file}") - shutil.copyfile(blank, f"{path}/{file}") - log.debug(f"[COMPRESS] {file} has been replaced with blank.mp3") - - log.debug(f"[COMPRESS] ai voiceover has been removed") - - -def remove_language_files(): - path = f"{config['folders']['decompiled']}/res" - folders = [ - "values-af", - "values-am", - "values-ar", - "values-as", - "values-az", - "values-b+es+419", - "values-b+sr+Latn", - "values-be", - "values-bg", - "values-bn", - "values-bs", - "values-ca", - "values-cs", - "values-da", - "values-de", - "values-el", - "values-en-rAU", - "values-en-rCA", - "values-en-rGB", - "values-en-rIN", - "values-en-rXC", - "values-es", - "values-es-rGT", - "values-es-rUS", - "values-et", - "values-eu", - "values-fa", - "values-fi", - "values-fr", - "values-fr-rCA", - "values-gl", - "values-gu", - "values-hi", - "values-hr", - "values-hu", - "values-hy", - "values-in", - "values-is", - "values-it", - "values-iw", - "values-ja", - "values-ka", - "values-kk", - "values-km", - "values-kn", - "values-ko", - "values-ky", - "values-lo", - "values-lt", - "values-lv", - "values-mk", - "values-ml", - "values-mn", - "values-mr", - "values-ms", - "values-my", - "values-nb", - "values-ne", - "values-nl", - "values-or", - "values-pa", - "values-pl", - "values-pt", - "values-pt-rBR", - "values-pt-rPT", - "values-ro", - "values-si", - "values-sk", - "values-sl", - "values-sq", - "values-sr", - "values-sv", - "values-sw", - "values-ta", - "values-te", - "values-th", - "values-tl", - "values-tr", - "values-uk", - "values-ur", - "values-uz", - "values-vi", - "values-zh", - "values-zh-rCN", - "values-zh-rHK", - "values-zh-rTW", - "values-zu", - "values-watch", - ] - - for folder in folders: - if os.path.exists(f"{path}/{folder}"): - shutil.rmtree(f"{path}/{folder}") - log.debug(f"[COMPRESS] {folder} has been removed") - - -def remove_drawable_files(): - path = f"{config['folders']['decompiled']}/res" - folders = [ - "drawable-en-hdpi", - "drawable-en-ldpi", - "drawable-en-mdpi", - "drawable-en-xhdpi", - "drawable-en-xxhdpi", - "drawable-en-xxxhdpi", - "drawable-ldrtl-hdpi", - "drawable-ldrtl-mdpi", - "drawable-ldrtl-xhdpi", - "drawable-ldrtl-xxhdpi", - "drawable-ldrtl-xxxhdpi", - "drawable-tr-anydpi", - "drawable-tr-hdpi", - "drawable-tr-ldpi", - "drawable-tr-mdpi", - "drawable-tr-xhdpi", - "drawable-tr-xxhdpi", - "drawable-tr-xxxhdpi", - "drawable-watch", - "layout-watch", - ] - - for folder in folders: - if os.path.exists(f"{path}/{folder}"): - shutil.rmtree(f"{path}/{folder}") - log.debug(f"[COMPRESS] {folder} has been removed") - - def apply(patch_config: PatchConfig_Compress) -> bool: - if patch_config['remove_unknown_files']: - log.info("[COMPRESS] removing unknown files") - remove_unknown_files(patch_config) - - if patch_config["remove_drawable_files"]: - log.info("[COMPRESS] removing drawable-xx dirs") - remove_drawable_files() - if patch_config["compress_png_files"]: - log.info("[COMPRESS] compressing PNGs") - compress_png_files() - - if patch_config["remove_language_files"]: - log.info("[COMPRESS] removing languages") - remove_language_files() - - if patch_config["remove_AI_voiceover"]: - log.info("[COMPRESS] removing AI voiceover") - remove_AI_voiceover() - - if patch_config["remove_debug_lines"]: - log.info("[COMPRESS] stripping debug lines") - remove_debug_lines() + remove_files(patch_config) + remove_debug_lines() + # compress_pngs() return True diff --git a/patches/force_static_request_urls.config.json b/patches/force_static_request_urls.config.json index 8415c42..6827b01 100644 --- a/patches/force_static_request_urls.config.json +++ b/patches/force_static_request_urls.config.json @@ -1,5 +1,5 @@ { - "base_url": "https://api-s.anixsekai.com/", + "base_url": "https://anix-api.wah.su/", "values": [ { "file_path": "smali_classes2/com/swiftsoft/anixartd/network/api/ConfigApi.smali", diff --git a/patches/resources/blank.mp3 b/patches/resources/blank.mp3 deleted file mode 100644 index bfab560..0000000 Binary files a/patches/resources/blank.mp3 and /dev/null differ diff --git a/requirements.txt b/requirements.txt index 97fc3bc..154e25e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ requests lxml rich -beaupy -pyyaml \ No newline at end of file +beaupy \ No newline at end of file diff --git a/scripts/utils.py b/scripts/utils.py index 310e5d4..d72319e 100644 --- a/scripts/utils.py +++ b/scripts/utils.py @@ -2,7 +2,6 @@ import os import shutil import subprocess from config import log, config -from beaupy import prompt def check_java_version(): @@ -81,16 +80,10 @@ def compile_apk(apk: str): 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" - ): + 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" - ) + 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: @@ -118,55 +111,5 @@ def sign_apk(apk: str): stderr=subprocess.PIPE, ) except subprocess.CalledProcessError as e: - log.fatal( - f"error of running a command: %s :: %s", command, e.stderr, exc_info=True - ) + 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") \ No newline at end of file