From 8a74245c9cc8b81c071b1537f5ce1f11771ed4a9 Mon Sep 17 00:00:00 2001 From: wowlikon Date: Mon, 8 Sep 2025 13:07:46 +0300 Subject: [PATCH] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=B0=D1=82=D1=87=D0=B5=D0=B9?= =?UTF-8?q?,=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=B1=D0=B0=D0=B7=D0=B2=D0=BE=D0=B3=D0=BE=20=D1=84?= =?UTF-8?q?=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE=D0=BD=D0=B0=D0=BB=D0=B0=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D1=81=D0=B1=D0=BE=D1=80=D0=BA=D0=B8=20apk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- patches/config.json => config.json | 5 ++- main.py | 35 ++++++++++++--- patches/change_server.py | 21 +++++++++ patches/cleanup.py | 6 ++- patches/color_theme.py | 57 ++++++++++++++++++++++- patches/compress_png.py | 11 +++-- patches/disable_beta_banner.py | 2 - patches/package_name.py | 1 + patches/todo_change_server.py | 16 ------- utils/public.py | 72 +++++++++++++++++++++++++++--- 10 files changed, 185 insertions(+), 41 deletions(-) rename patches/config.json => config.json (94%) create mode 100644 patches/change_server.py delete mode 100644 patches/todo_change_server.py diff --git a/patches/config.json b/config.json similarity index 94% rename from patches/config.json rename to config.json index c919f3b..6638a4d 100644 --- a/patches/config.json +++ b/config.json @@ -9,10 +9,11 @@ "colors": { "primary": "#ccff00", "secondary": "#ffffd700", - "background": "#FFFFFF", + "background": "#ffffff", "text": "#000000" }, "gradient": { + "angle": "135.0", "from": "#ffff6060", "to": "#ffccff00" } @@ -51,5 +52,5 @@ "android": "http://schemas.android.com/apk/res/android", "app": "http://schemas.android.com/apk/res-auto" }, - "speeds": [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0, 2.5, 3.0, 9.0] + "speeds": [9.0] } diff --git a/main.py b/main.py index 78c0974..a2a5fcf 100644 --- a/main.py +++ b/main.py @@ -4,6 +4,7 @@ import json import requests import colorama import importlib +import traceback import subprocess from tqdm import tqdm @@ -12,7 +13,7 @@ def init() -> dict: if not os.path.exists(directory): os.makedirs(directory) - with open("./patches/config.json", "r") as config_file: + with open("./config.json", "r") as config_file: conf = json.load(config_file) if not os.path.exists("./tools/apktool.jar"): @@ -106,6 +107,23 @@ def decompile_apk(apk: str): sys.exit(1) +def compile_apk(apk: str): + print("Компилируем apk...") + try: + result = subprocess.run( + "tools/apktool b decompiled -o " + os.path.join("modified", apk), + shell=True, + check=True, + text=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.PIPE, + ) + except subprocess.CalledProcessError as e: + print("Ошибка при выполнении команды:") + print(e.stderr) + sys.exit(1) + + class Patch: def __init__(self, name, pkg): self.name = name @@ -122,7 +140,8 @@ class Patch: return True except Exception as e: print(f"Ошибка при применении патча {self.name}: {e}") - print(e.args) + print(type(e), e.args) + traceback.print_exc() return False @@ -150,8 +169,14 @@ for patch in patches: print(f"{marker}{colorama.Style.RESET_ALL} {patch.name}") if all(statuses.values()): - print("Все патчи успешно применены") + print(f"{colorama.Fore.GREEN}Все патчи успешно применены{colorama.Style.RESET_ALL}") + compile_apk(apk) elif any(statuses.values()): - print("Некоторые патчи не были успешно применены") + print(f"{colorama.Fore.YELLOW}⚠{colorama.Style.RESET_ALL} Некоторые патчи не были успешно применены") + if input("Продолжить? (y/n): ").lower() == "y": + compile_apk(apk) + else: + print(colorama.Fore.RED + "Операция отменена" + colorama.Style.RESET_ALL) else: - print("Ни один патч не был успешно применен") + print(f"{colorama.Fore.RED}Ни один патч не был успешно применен{colorama.Style.RESET_ALL}") + sys.exit(1) diff --git a/patches/change_server.py b/patches/change_server.py new file mode 100644 index 0000000..d442675 --- /dev/null +++ b/patches/change_server.py @@ -0,0 +1,21 @@ +"""Change api server""" +priority = 0 +from tqdm import tqdm + +import json +import requests + + +def apply(config: dict) -> bool: + response = requests.get(config['server']) + assert response.status_code == 200, f"Failed to fetch data {response.status_code} {response.text}" + for item in json.loads(response.text)['modifications']: + tqdm.write(f"Изменение {item['file']}") + filepath = './decompiled/smali_classes2/com/swiftsoft/anixartd/network/api/'+item['file'] + with open(filepath, 'r') as f: + content = f.read() + with open(filepath, 'w') as f: + if content.count(item['src']) == 0: + tqdm.write(f"⚠ Не найдено {item['src']}") + f.write(content.replace(item['src'], item['dst'])) + return True diff --git a/patches/cleanup.py b/patches/cleanup.py index 108555d..7de37e5 100644 --- a/patches/cleanup.py +++ b/patches/cleanup.py @@ -12,9 +12,11 @@ def apply(config: dict) -> bool: if os.path.isfile(item_path): os.remove(item_path) - tqdm.write(f'Удалён файл: {item_path}') + if config.get("verbose", False): + tqdm.write(f'Удалён файл: {item_path}') elif os.path.isdir(item_path): if item not in config["cleanup"]["keep_dirs"]: shutil.rmtree(item_path) - tqdm.write(f'Удалена папка: {item_path}') + if config.get("verbose", False): + tqdm.write(f'Удалена папка: {item_path}') return True diff --git a/patches/color_theme.py b/patches/color_theme.py index 135f76a..0eb01dc 100644 --- a/patches/color_theme.py +++ b/patches/color_theme.py @@ -3,10 +3,17 @@ priority = 0 from lxml import etree +from utils.public import ( + insert_after_public, + insert_after_color, + insert_after_id, + change_color, +) def apply(config: dict) -> bool: main_color = config["theme"]["colors"]["primary"] splash_color = config["theme"]["colors"]["secondary"] + gradient_angle = config["theme"]["gradient"]["angle"] gradient_from = config["theme"]["gradient"]["from"] gradient_to = config["theme"]["gradient"]["to"] @@ -31,6 +38,7 @@ def apply(config: dict) -> bool: root = tree.getroot() # Change attributes with namespace + root.set(f"{{{config['xml_ns']['android']}}}angle", gradient_angle) root.set(f"{{{config['xml_ns']['android']}}}startColor", gradient_from) root.set(f"{{{config['xml_ns']['android']}}}endColor", gradient_to) @@ -45,7 +53,7 @@ def apply(config: dict) -> bool: root = tree.getroot() # Finding "path" - for el in root.findall("path", namespaces=config['xml_ns']): + for el in root.findall("path", namespaces=config["xml_ns"]): name = el.get(f"{{{config['xml_ns']['android']}}}name") if name == "path": el.set(f"{{{config['xml_ns']['android']}}}fillColor", splash_color) @@ -53,4 +61,51 @@ def apply(config: dict) -> bool: # Save back tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8") + for filename in ["$ic_launcher_foreground__0", "$ic_banner_foreground__0"]: + file_path = f"./decompiled/res/drawable-v24/{filename}.xml" + + parser = etree.XMLParser(remove_blank_text=True) + tree = etree.parse(file_path, parser) + root = tree.getroot() + + # Change attributes with namespace + root.set(f"{{{config['xml_ns']['android']}}}angle", gradient_angle) + items = root.findall("item", namespaces=config['xml_ns']) + assert len(items) == 2 + items[0].set(f"{{{config['xml_ns']['android']}}}color", gradient_from) + items[1].set(f"{{{config['xml_ns']['android']}}}color", gradient_to) + + # Save back + tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8") + + insert_after_public("carmine", "custom_color") + insert_after_public("carmine_alpha_10", "custom_color_alpha_10") + insert_after_color("carmine", "custom_color", main_color[0]+'ff'+main_color[1:]) + insert_after_color("carmine_alpha_10", "custom_color_alpha_10", main_color[0]+'1a'+main_color[1:]) + + change_color("accent_alpha_10", main_color[0]+'1a'+main_color[1:]) + change_color("accent_alpha_20", main_color[0]+'33'+main_color[1:]) + change_color("accent_alpha_50", main_color[0]+'80'+main_color[1:]) + change_color("accent_alpha_70", main_color[0]+'b3'+main_color[1:]) + change_color("colorAccent", main_color[0]+'ff'+main_color[1:]) + change_color("link_color", main_color[0]+'ff'+main_color[1:]) + change_color("link_color_alpha_70", main_color[0]+'b3'+main_color[1:]) + change_color("refresh_progress", main_color[0]+'ff'+main_color[1:]) + + change_color("ic_launcher_background", "#ff000000") + change_color("bottom_nav_indicator_active", "#ffffffff") + change_color("bottom_nav_indicator_icon_checked", main_color[0]+'ff'+main_color[1:]) + change_color("bottom_nav_indicator_label_checked", main_color[0]+'ff'+main_color[1:]) + + insert_after_public("warning_error_counter_background", "ic_custom_telegram") + insert_after_public("warning_error_counter_background", "ic_custom_crown") + + try: + last = "speed75" + for speed in config.get("speeds", []): + insert_after_public(last, f"speed{int(float(speed)*10)}") + insert_after_id(last, f"speed{int(float(speed)*10)}") + last = f"speed{int(float(speed)*10)}" + except Exception as e: + print(f"Error occurred while processing speeds: {e}") return True diff --git a/patches/compress_png.py b/patches/compress_png.py index 9fe40d4..d71b3ab 100644 --- a/patches/compress_png.py +++ b/patches/compress_png.py @@ -6,15 +6,15 @@ import os import subprocess -def compress_pngs(root_dir): +def compress_pngs(root_dir: str, verbose: bool = False): compressed_files = [] for dirpath, _, filenames in os.walk(root_dir): for filename in filenames: if filename.lower().endswith(".png"): filepath = os.path.join(dirpath, filename) - tqdm.write(f"Сжимаю: {filepath}") + if verbose: tqdm.write(f"Сжимаю: {filepath}") try: - subprocess.run( + assert subprocess.run( [ "pngquant", "--force", @@ -23,9 +23,8 @@ def compress_pngs(root_dir): "--quality=65-90", filepath, ], - check=True, capture_output=True, - ) + ).returncode in [0, 99] compressed_files.append(filepath) except subprocess.CalledProcessError as e: tqdm.write(f"Ошибка при сжатии {filepath}: {e}") @@ -33,5 +32,5 @@ def compress_pngs(root_dir): def apply(config: dict) -> bool: - files = compress_pngs("./decompiled") + files = compress_pngs("./decompiled", config.get("verbose", False)) return len(files) > 0 and any(files) diff --git a/patches/disable_beta_banner.py b/patches/disable_beta_banner.py index a5ee84b..765eb64 100644 --- a/patches/disable_beta_banner.py +++ b/patches/disable_beta_banner.py @@ -5,8 +5,6 @@ from tqdm import tqdm import os from lxml import etree -from typing import TypedDict - def apply(config) -> bool: attributes = [ diff --git a/patches/package_name.py b/patches/package_name.py index a95ecdc..03254a1 100644 --- a/patches/package_name.py +++ b/patches/package_name.py @@ -49,6 +49,7 @@ def apply(config: dict) -> bool: config["new_package_name"].replace(".", "/"), ), ) + os.rmdir("./decompiled/smali_classes2/com/swiftsoft") if os.path.exists("./decompiled/smali_classes4/com/swiftsoft"): rename_dir( "./decompiled/smali_classes4/com/swiftsoft", diff --git a/patches/todo_change_server.py b/patches/todo_change_server.py deleted file mode 100644 index 8b6aef3..0000000 --- a/patches/todo_change_server.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Change api server""" -priority = 0 -from tqdm import tqdm - -import json -import requests - - -def apply(config: dict) -> bool: - response = requests.get(config['server']) - if response.status_code == 200: - for item in json.loads(response.text)["modding"]: - tqdm.write(item) - return True - tqdm.write(f"Failed to fetch data {response.status_code} {response.text}") - return False diff --git a/utils/public.py b/utils/public.py index 1540eb9..c517384 100644 --- a/utils/public.py +++ b/utils/public.py @@ -2,15 +2,15 @@ from lxml import etree from copy import deepcopy -def insert_after(anchor_name: str, elem_name: str): - file_path = "../decompiled/res/values/public.xml" +def insert_after_public(anchor_name: str, elem_name: str): + file_path = "./decompiled/res/values/public.xml" parser = etree.XMLParser(remove_blank_text=True) tree = etree.parse(file_path, parser) root = tree.getroot() anchor = None types = {} - for idx, elem in enumerate(root): + for elem in root: assert elem.tag == "public" assert elem.keys() == ["type", "name", "id"] attrs = dict(zip(elem.keys(), elem.values())) @@ -25,7 +25,6 @@ def insert_after(anchor_name: str, elem_name: str): if i not in group: free_ids.add(i) - assert len(free_ids) > 0 new_id = None for i in free_ids: if i > int(anchor[1]["id"], 16): @@ -38,12 +37,71 @@ def insert_after(anchor_name: str, elem_name: str): if name == anchor[1]["type"]: continue if new_id in group: - new_id = max(group) + assert False, f"ID {new_id} already exists in group {name}" new_elem = deepcopy(anchor[0]) - new_elem.set("id", new_id) + new_elem.set("id", hex(new_id)) new_elem.set("name", elem_name) anchor[0].addnext(new_elem) - tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8") return new_id + + +def insert_after_id(anchor_name: str, elem_name: str): + file_path = "./decompiled/res/values/ids.xml" + parser = etree.XMLParser(remove_blank_text=True) + tree = etree.parse(file_path, parser) + root = tree.getroot() + anchor = None + + for elem in root: + assert elem.tag == "item" + assert elem.keys() == ["type", "name"] + attrs = dict(zip(elem.keys(), elem.values())) + if attrs["name"] == anchor_name: + assert anchor == None + anchor = (elem, attrs) + + new_elem = deepcopy(anchor[0]) + new_elem.set("name", elem_name) + anchor[0].addnext(new_elem) + tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8") + + +def change_color(name: str, value: str): + file_path = "./decompiled/res/values/colors.xml" + parser = etree.XMLParser(remove_blank_text=True) + tree = etree.parse(file_path, parser) + root = tree.getroot() + replacements = 0 + + for elem in root: + assert elem.tag == "color" + assert elem.keys() == ["name"] + attrs = dict(zip(elem.keys(), elem.values())) + if attrs["name"] == name: + elem.set("name", name) + elem.text = value + replacements += 1 + assert replacements >= 1 + tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8") + +def insert_after_color(anchor_name: str, elem_name: str, elem_value: str): + file_path = "./decompiled/res/values/colors.xml" + parser = etree.XMLParser(remove_blank_text=True) + tree = etree.parse(file_path, parser) + root = tree.getroot() + anchor = None + + for elem in root: + assert elem.tag == "color" + assert elem.keys() == ["name"] + attrs = dict(zip(elem.keys(), elem.values())) + if attrs["name"] == anchor_name: + assert anchor == None + anchor = (elem, attrs) + + new_elem = deepcopy(anchor[0]) + new_elem.set("name", elem_name) + anchor[0].addnext(new_elem) + tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8")