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}