diff --git a/.gitignore b/.gitignore index 750dd16..09c0165 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,6 @@ __pycache__ keystore.jks help dev_patches -dev.config.json \ No newline at end of file +dev.config.json +release_patches +release.config.json diff --git a/main.py b/main.py index 75e0efd..2b12d95 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,13 @@ 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 scripts.utils import ( + check_java_version, + compile_apk, + decompile_apk, + sign_apk, + init_patch, +) from config import args, config, log, console from time import time @@ -10,9 +16,19 @@ import yaml def patch(): + app_version: str = None + app_build: str = None + + with open( + f"{config['folders']['decompiled']}/apktool.yml", "r", encoding="utf-8" + ) as f: + data = yaml.load(f.read(), Loader=yaml.Loader) + app_version = data.get("versionInfo").get("versionName", "None") + app_build = data.get("versionInfo").get("versionCode", 0) + patches = get_patches() patches = select_patches(patches) - statuses = apply_patches(patches) + statuses = apply_patches(patches, app_version, int(app_build)) statuses_ok = [] statuses_err = [] @@ -29,7 +45,7 @@ def patch(): if __name__ == "__main__": check_and_download_all_tools() check_java_version() - + if args.init: init_patch() exit(0) diff --git a/patches/add_settings_menu_items.config.json b/patches/add_settings_menu_items.config.json new file mode 100644 index 0000000..e340672 --- /dev/null +++ b/patches/add_settings_menu_items.config.json @@ -0,0 +1,24 @@ +{ + "add_patch_info": true, + "main_settings_categories": [ + { + "title": "anixart-patcher", + "items": [ + { + "title": "Radiquum", + "summary": "Разработчик", + "url": "https://radiquum.wah.su/", + "icon": "@drawable/solar_code_2_bold", + "icon_space_reserved": false + }, + { + "title": "Репозиторий", + "summary": "https://github.com/Radiquum/anixart-patcher", + "url": "https://github.com/Radiquum/anixart-patcher", + "icon": "@drawable/github_mark", + "icon_space_reserved": false + } + ] + } + ] +} \ No newline at end of file diff --git a/patches/add_settings_menu_items.py b/patches/add_settings_menu_items.py new file mode 100644 index 0000000..8d2b0c5 --- /dev/null +++ b/patches/add_settings_menu_items.py @@ -0,0 +1,210 @@ +"""Adds used patches and custom menu items to app settings""" +# Developer: Radiquum +# URL: + +# patch settings +# priority, default: -95 +priority = -95 + +# imports +## bundled +import os +import shutil +import random +import string +from typing import TypedDict + +## installed +from lxml import etree + +## custom +from config import config, log + + +# Patch +class PatchConfig_AddSettingsMenuItemsCategoryItem(TypedDict): + title: str + summary: str | None + url: str | None + icon: str | None + icon_space_reserved: bool + + +class PatchConfig_AddSettingsMenuItemsCategory(TypedDict): + title: str + items: list[PatchConfig_AddSettingsMenuItemsCategoryItem] + + +class PatchConfig_AddSettingsMenuItems(TypedDict): + _internal_all_patch_statuses: list + add_patch_info: bool + main_settings_categories: list[PatchConfig_AddSettingsMenuItemsCategory] + + +def random_key(): + return "".join(random.choices(string.ascii_letters, k=8)) + + +def add_separator(): + ns = config["xml_ns"] + item = etree.Element("Preference", nsmap=ns) + item.set(f"{{{ns['android']}}}layout", "@layout/preference_separator") + item.set(f"{{{ns['android']}}}selectable", "false") + item.set(f"{{{ns['android']}}}key", f"separator_{random_key()}") + + +def create_intent( + action: str = "android.intent.action.VIEW", + data: str | None = None, +): + ns = config["xml_ns"] + item = etree.Element("intent", nsmap=ns) + item.set(f"{{{ns['android']}}}action", action) + item.set(f"{{{ns['android']}}}data", data or "") + item.set(f"{{{ns['app']}}}iconSpaceReserved", "false") + item.set(f"{{{ns['android']}}}key", f"intent_{random_key()}") + return item + + +def create_Preference( + title: str, + summary: str | None = None, + icon: str | None = None, + icon_space_reserved: bool = False, +): + ns = config["xml_ns"] + item = etree.Element("Preference", nsmap=ns) + item.set(f"{{{ns['android']}}}title", title) + item.set(f"{{{ns['android']}}}summary", summary or "") + if icon: + item.set(f"{{{ns['app']}}}icon", icon) + item.set(f"{{{ns['app']}}}iconSpaceReserved", str(icon_space_reserved).lower()) + item.set(f"{{{ns['android']}}}key", f"preference_{random_key()}") + return item + + +def create_PreferenceCategory(title: str): + ns = config["xml_ns"] + category = etree.Element("PreferenceCategory", nsmap=ns) + category.set(f"{{{ns['android']}}}title", title) + category.set(f"{{{ns['app']}}}iconSpaceReserved", "false") + category.set(f"{{{ns['android']}}}key", f"category_{random_key()}") + return category + + +def add_icons(): + src_icon_path = f"{config["folders"]["patches"]}/resources/icons" + src_icon_night_path = f"{config["folders"]["patches"]}/resources/icons-night" + dst_icon_path = f"{config["folders"]["decompiled"]}/res/drawable" + dst_icon_night_path = f"{config["folders"]["decompiled"]}/res/drawable-night" + icons = os.listdir(src_icon_path) + if len(icons) == 0: + return + + for icon in icons: + shutil.copy(f"{src_icon_path}/{icon}", f"{dst_icon_path}/{icon}") + if os.path.exists(f"{src_icon_night_path}/{icon}"): + shutil.copy( + f"{src_icon_night_path}/{icon}", f"{dst_icon_night_path}/{icon}" + ) + + +def add_patch_info(patch_statuses: list): + category = create_PreferenceCategory("Использованные патчи") + for patch in patch_statuses: + if patch["status"] is True: + description = [] + url = None + if os.path.exists(f"{config['folders']['patches']}/{patch['name']}.py"): + with open( + f"{config['folders']['patches']}/{patch['name']}.py", + "r", + encoding="utf-8", + ) as f: + line = f.readline() + if line.startswith('"""'): + description.append( + line.strip().removeprefix('"""').removesuffix('"""').strip() + ) + line = f.readline() + if line.startswith("# Developer:"): + description.append("by") + description.append( + line.strip().removeprefix("# Developer:").strip() + ) + line = f.readline() + if line.startswith("# URL:"): + url = line.strip().removeprefix("# URL:").strip() + + item = create_Preference( + patch["name"].replace("_", " ").strip().title(), + description=" ".join(description), + ) + if url: + item.append(create_intent(data=url)) + category.append(item) + return category + + +def add_custom_category( + title: str, items: list[PatchConfig_AddSettingsMenuItemsCategoryItem] +): + category = create_PreferenceCategory(title) + for item in items: + new_item = create_Preference( + item["title"], + item["summary"], + item["icon"], + item["icon_space_reserved"], + ) + if item["url"]: + new_item.append(create_intent(data=item["url"])) + category.append(new_item) + return category + + +def apply(patch_conf: PatchConfig_AddSettingsMenuItems) -> bool: + parser = etree.XMLParser(remove_blank_text=True) + preference_main_xml = ( + f"{config['folders']['decompiled']}/res/xml/preference_main.xml" + ) + preference_additional_xml = ( + f"{config['folders']['decompiled']}/res/xml/preference_additional.xml" + ) + + add_icons() + + if os.path.exists(preference_main_xml): + tree = etree.parse(preference_main_xml, parser) + root = tree.getroot() + + last = root[-1]; pos = root.index(last) + for item in patch_conf["main_settings_categories"]: + root.insert(pos, add_custom_category(item["title"], item["items"])); pos += 1 + + tree.write( + preference_main_xml, + pretty_print=True, + xml_declaration=True, + encoding="utf-8", + ) + + if os.path.exists(preference_additional_xml): + tree = etree.parse(preference_additional_xml, parser) + root = tree.getroot() + + if patch_conf["add_patch_info"]: + root.append(add_patch_info(patch_conf["_internal_all_patch_statuses"])) + + tree.write( + preference_additional_xml, + pretty_print=True, + xml_declaration=True, + encoding="utf-8", + ) + + return True + + +if __name__ == "__main__": + apply({}) diff --git a/patches/change_app_version.config.json b/patches/change_app_version.config.json new file mode 100644 index 0000000..d2ddc6d --- /dev/null +++ b/patches/change_app_version.config.json @@ -0,0 +1,4 @@ +{ + "version_code": 25082901, + "version_name": "9.0 BETA 7" +} \ No newline at end of file diff --git a/patches/change_app_version.py b/patches/change_app_version.py new file mode 100644 index 0000000..1ba1049 --- /dev/null +++ b/patches/change_app_version.py @@ -0,0 +1,50 @@ +"""Changes the version string and build number""" +# Developer: Radiquum + +# patch settings +# priority, default: -98 +priority = -98 + +# imports +## bundled +from typing import TypedDict + +## installed +import yaml + +## custom +from config import config, log + + +# Patch +class PatchConfig_ChangeAppVersion(TypedDict): + _internal_app_version: str + _internal_app_build: int + version_name: str + version_code: int + + +def apply(patch_conf: PatchConfig_ChangeAppVersion) -> bool: + apktool_yaml = None + with open( + f"{config['folders']['decompiled']}/apktool.yml", "r", encoding="utf-8" + ) as f: + apktool_yaml = yaml.load(f.read(), Loader=yaml.Loader) + + apktool_yaml.update( + { + "versionInfo": { + "versionName": patch_conf["version_name"] + or patch_conf["_internal_app_version"], + "versionCode": patch_conf["version_code"] + or patch_conf["_internal_app_build"], + } + } + ) + + with open( + f"{config['folders']['decompiled']}/apktool.yml", "w", encoding="utf-8" + ) as f: + apktool_yaml = yaml.dump(apktool_yaml, f, indent=2, Dumper=yaml.Dumper) + + return True diff --git a/patches/change_color_theme.py b/patches/change_color_theme.py index 889e035..57d060a 100644 --- a/patches/change_color_theme.py +++ b/patches/change_color_theme.py @@ -1,4 +1,5 @@ """Change app color theme""" +# Developer: Radiquum, themes based on code by Seele # patch settings # priority, default: -99 (run before last) @@ -28,14 +29,14 @@ class PatchConfig_ChangeColorTheme(TypedDict): def apply(patch_config: PatchConfig_ChangeColorTheme) -> bool: - + console.print("select color theme to apply (press [bold]enter[/bold] to confirm)") theme = select(patch_config["themes"], cursor="->", cursor_style="cyan") if not theme: console.print(f"theme: default") return False console.print(f"theme: {theme}") - + theme_attr = patch_config[theme]["attributes"] theme_text = patch_config[theme]["text"] theme_files = patch_config[theme]["files"] diff --git a/patches/change_navigation_bar.py b/patches/change_navigation_bar.py index 0202cc0..d6fca80 100644 --- a/patches/change_navigation_bar.py +++ b/patches/change_navigation_bar.py @@ -1,4 +1,5 @@ """Move and replace navigation bar tabs""" +# Developer: Radiquum # patch settings # priority, default: 0 @@ -30,7 +31,7 @@ def modify_menu(menu: list[str], path: str) -> None: 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']}) + 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}") @@ -47,6 +48,11 @@ def modify_menu(menu: list[str], path: str) -> None: 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") + 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.py b/patches/change_package_name.py index daae8f9..671d698 100644 --- a/patches/change_package_name.py +++ b/patches/change_package_name.py @@ -1,4 +1,6 @@ """Change package name""" +# Developer: Radiquum, based of similar patch by wowlikon +# URL: # patch settings # priority, default: -100 (run last) diff --git a/patches/compress.py b/patches/compress.py index 6b2c83f..22799d6 100644 --- a/patches/compress.py +++ b/patches/compress.py @@ -1,4 +1,6 @@ """Remove and compress resources""" +# Developer: Radiquum, based of similar patch by wowlikon +# URL: https://github.com/Radiquum/anixart-patcher/blob/master/patches/compress.md # patch settings # priority, default: 0 diff --git a/patches/disable_ad.py b/patches/disable_ad.py index 6bac24e..bc76040 100644 --- a/patches/disable_ad.py +++ b/patches/disable_ad.py @@ -1,4 +1,6 @@ """Disable ad banners""" +# Developer: Radiquum, based of code by seele +# URL: # patch settings # priority, default: 0 @@ -20,7 +22,7 @@ replace = """ .locals 0 const/4 p0, 0x1 - return p0 + return p0 """ diff --git a/patches/disable_beta_banner.py b/patches/disable_beta_banner.py index a8e02fc..b4bcc9e 100644 --- a/patches/disable_beta_banner.py +++ b/patches/disable_beta_banner.py @@ -1,4 +1,6 @@ """Remove beta banner""" +# Developer: Radiquum, based of code by ModdingApps +# URL: # patch settings # priority, default: 0 diff --git a/patches/force_static_request_urls.py b/patches/force_static_request_urls.py index 59f8a52..68e1a33 100644 --- a/patches/force_static_request_urls.py +++ b/patches/force_static_request_urls.py @@ -1,4 +1,6 @@ """Change `value="something/endpoint"` to `value="https://example.com/something/endpoint" """ +# Developer: Radiquum +# URL: # patch settings # priority, default: 0 @@ -15,9 +17,6 @@ from scripts.smali_parser import ( get_smali_lines, save_smali_lines, find_and_replace_smali_line, - find_smali_method_start, - find_smali_method_end, - replace_smali_method_body, ) @@ -39,21 +38,13 @@ class PatchConfig_ForceStaticRequestUrls(TypedDict): constants: PatchConfig_ForceStaticRequestUrlsConst -replace_should_use_mirror_urls = """ .locals 0 - - const/4 p0, 0x0 - - return p0 -""" - - def apply(patch_config: PatchConfig_ForceStaticRequestUrls) -> bool: for value in patch_config["values"]: if os.path.exists(f"{config['folders']['decompiled']}/{value['file_path']}"): path = f"{config['folders']['decompiled']}/{value['file_path']}" lines = get_smali_lines(path) lines = find_and_replace_smali_line( - lines, value["value"], f"{patch_config['base_url']}{value['value']}" + lines, f'value = "{value['value']}"', f'value = "{patch_config['base_url']}{value['value']}"' ) save_smali_lines(path, lines) log.debug(f"[FORCE_STATIC_REQUEST_URLS] file {path} has been modified") @@ -71,21 +62,6 @@ def apply(patch_config: PatchConfig_ForceStaticRequestUrls) -> bool: save_smali_lines(path, lines) log.debug(f"[FORCE_STATIC_REQUEST_URLS] file {path} has been modified") - # IDK If it is actually needed, will leave it for now, but seems like it should not be needed, since patch is working - # path = f"{config['folders']['decompiled']}/smali_classes2/com/swiftsoft/anixartd/Prefs.smali" - # if os.path.exists(path): - # lines = get_smali_lines(path) - # new_content = [] - # for index, line in enumerate(lines): - # if line.find("SHOULD_USE_MIRROR_URLS") >= 0: - # method_start = find_smali_method_start(lines, index) - # method_end = find_smali_method_end(lines, index) - # new_content = replace_smali_method_body( - # lines, method_start, method_end, replace_should_use_mirror_urls - # ) - # save_smali_lines(path, new_content) - # log.debug(f"[FORCE_STATIC_REQUEST_URLS] file {path} has been modified") - path = f"{config['folders']['decompiled']}/smali_classes2/com/swiftsoft/anixartd/DaggerApp_HiltComponents_SingletonC$SingletonCImpl$SwitchingProvider.smali" pathInterceptor = f"{config['folders']['decompiled']}/smali_classes2/com/swiftsoft/anixartd/dagger/module/ApiModule$provideRetrofit$lambda$2$$inlined$-addInterceptor$1.smali" if os.path.exists(path) and os.path.exists(pathInterceptor): @@ -93,10 +69,9 @@ def apply(patch_config: PatchConfig_ForceStaticRequestUrls) -> bool: new_content = [] for index, line in enumerate(lines): if line.find("addInterceptor") >= 0: - continue + continue new_content.append(line) save_smali_lines(path, new_content) log.debug(f"[FORCE_STATIC_REQUEST_URLS] file {path} has been modified") - return True diff --git a/patches/resources/icons-night/github_mark.xml b/patches/resources/icons-night/github_mark.xml new file mode 100644 index 0000000..42c9d27 --- /dev/null +++ b/patches/resources/icons-night/github_mark.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/patches/resources/icons/github_mark.xml b/patches/resources/icons/github_mark.xml new file mode 100644 index 0000000..ce12ac8 --- /dev/null +++ b/patches/resources/icons/github_mark.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/patches/resources/icons/solar_code_2_bold.xml b/patches/resources/icons/solar_code_2_bold.xml new file mode 100644 index 0000000..8f42ffc --- /dev/null +++ b/patches/resources/icons/solar_code_2_bold.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/scripts/select_patches.py b/scripts/select_patches.py index 2c44a79..06a90c0 100644 --- a/scripts/select_patches.py +++ b/scripts/select_patches.py @@ -69,7 +69,29 @@ progress = Progress( ) -def apply_patches(patches: list[str]) -> list[PatchStatus]: +def get_patch_config( + patch_name: str, + all_patch_statuses: list, + app_version: str, + app_build: int, +) -> dict: + _config = {} + if os.path.exists(f"{config['folders']['patches']}/{patch_name}.config.json"): + with open( + f"{config['folders']['patches']}/{patch_name}.config.json", + "r", + encoding="utf-8", + ) as f: + _config = json.loads(f.read()) + _config["_internal_all_patch_statuses"] = all_patch_statuses + _config["_internal_app_version"] = app_version + _config["_internal_app_build"] = app_build + return _config + + +def apply_patches( + patches: list[str], app_version: str, app_build: int +) -> list[PatchStatus]: modules = [] statuses = [] @@ -84,19 +106,7 @@ def apply_patches(patches: list[str]) -> list[PatchStatus]: task = progress.add_task("applying patch:", total=len(modules), patch="") for module in modules: progress.update(task, patch=module.name) - - patch_conf = {} - if os.path.exists( - f"{config['folders']['patches']}/{module.name}.config.json" - ): - with open( - f"{config['folders']['patches']}/{module.name}.config.json", - "r", - encoding="utf-8", - ) as f: - patch_conf = json.loads(f.read()) - - status = module.apply(patch_conf) + status = module.apply(get_patch_config(module.name, statuses, app_version, app_build)) statuses.append({"name": module.name, "status": status}) progress.update(task, advance=1) diff --git a/scripts/utils.py b/scripts/utils.py index 310e5d4..1214a1f 100644 --- a/scripts/utils.py +++ b/scripts/utils.py @@ -137,10 +137,14 @@ def init_patch(): f.write("") name = prompt("Patch name: ", lambda x: x.strip().lower().replace(" ", "_")) - description = prompt("Patch description: ", lambda x: x.strip()) + summary = prompt("Patch summary: ", lambda x: x.strip()) + developer = prompt("Patch developer: ", lambda x: x.strip()) + URL = prompt("URL: ", lambda x: x.strip()) priority = prompt("Patch priority: ", target_type=int, initial_value="0") - patch_content = f"""\"\"\"{description}\"\"\" + patch_content = f"""\"\"\"{summary}\"\"\" +# Developer: {developer} +# URL: {URL} # patch settings # priority, default: {priority}