From 73fa3744236eb369c7459702d530c88c89817d8f Mon Sep 17 00:00:00 2001 From: wowlikon Date: Thu, 28 Aug 2025 12:56:50 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=B0=D1=82=D1=87=D0=B0=20=D1=81?= =?UTF-8?q?=D0=B6=D0=B0=D1=82=D0=B8=D1=8F=20png,=20=D0=BE=D0=B1=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=BE=D0=BA?= =?UTF-8?q?=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 22 +++++++++++++++ README.md | 56 +++++++++++++++++++++++++++++++++++++ main.py | 25 +++++++++-------- patches/application_icon.py | 4 ++- patches/change_server.py | 8 ++++-- patches/cleanup.py | 8 ++++-- patches/color_theme.py | 5 ++-- patches/compress_png.py | 27 ++++++++++++++++++ patches/config.json | 4 +++ patches/insert_new.py | 9 +++--- patches/package_name.py | 6 ++-- patches/settings_urls.py | 5 ++-- 12 files changed, 151 insertions(+), 28 deletions(-) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 patches/compress_png.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cd4a9dd --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2025 wowlikon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..a4c0b0b --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# Anixarty autopatcher + +### Описание: +Автоматический патчер для приложения anixart. + +--- + +### Структура проекта: +- `main.py` Главный файл +- `patches` Модули патчей +- `tools` Инструменты для модификации +- `patches/resources` Ресурсы, используемые патчами + +### Установка и использование: + +1. Клонируйте репозиторий: + ```bash + git clone https://git.wowlikon.tech/anixart-mod/autopatcher.git + ``` + Требования: + - Python 3.6+ + - Java 8+ + - zipalign + - apksigner + - pngquant + + Все остальные инструменты и зависимости будут автоматически установлены при запуске `main.py`. + +2. Создайте keystore с помощью `keytool` (требуется только один раз): + ```bash + keytool -genkey -v -keystore keystore.jks -alias [имя_пользователя] -keyalg RSA -keysize 2048 -validity 10000 + ``` + +2. Измените настройки мода в файле `patches/config.json` +3. Поместите оригинальный apk файла anixart в папку `original` +4. Запустите `main.py` и выберите файл apk + +## ПОКА ЕЩЁ В РАЗРАБОТКЕ И ПОЭТОМУ НЕ В СКРИПТЕ +1. Перейдите в папку `anixart/dist` и запустите `zipalign`: + ```bash + zipalign -p 4 anixart.apk anixart-aligned.apk + ``` +2. Запустите `apksigner` для подписи apk файла: + ```bash + apksigner sign --ks /путь/до/keystore.jks --out anixart-modded.apk anixart-aligned.apk + ``` +3. Установите приложение на ваше устройство. + + +## Лицензия: +Этот проект лицензирован под лицензией MIT. См. [LICENSE](./LICENSE) для получения подробной информации. + +### Вклад в проект: +- Seele - Все оригинальные патчи основаны на модификации приложения от Seele [[GitHub](https://github.com/seeleme) | [Telegram](https://t.me/seele_off)] +- Kentai Radiquum - Разработка неофициального сайта и помощь с изучением API [[GitHub](https://github.com/Radiquum) | [Telegram](https://t.me/radiquum)] +- ReCode Liner - Помощь в модификации приложения [[Telegram](https://t.me/recodius)] diff --git a/main.py b/main.py index 2c0bb02..29d9b88 100644 --- a/main.py +++ b/main.py @@ -7,22 +7,24 @@ import importlib import subprocess from tqdm import tqdm - -def init(apktool_jar_url: str, apktool_wrapper_url: str) -> dict: +def init() -> dict: for directory in ["original", "modified", "patches", "tools", "decompiled"]: if not os.path.exists(directory): os.makedirs(directory) + with open("./patches/config.json", "r") as config_file: + conf = json.load(config_file) + if not os.path.exists("./tools/apktool.jar"): try: print("Скачивание Apktool...") - jar_response = requests.get(apktool_jar_url, stream=True) + jar_response = requests.get(conf["tools"]["apktool_jar_url"], stream=True) jar_path = "tools/apktool.jar" with open(jar_path, "wb") as f: for chunk in jar_response.iter_content(chunk_size=8192): f.write(chunk) - wrapper_response = requests.get(apktool_wrapper_url) + wrapper_response = requests.get(conf["tools"]["apktool_wrapper_url"]) wrapper_path = "tools/apktool" with open(wrapper_path, "w") as f: f.write(wrapper_response.text) @@ -47,9 +49,7 @@ def init(apktool_jar_url: str, apktool_wrapper_url: str) -> dict: print("Java не установлена. Установите Java 8 или более позднюю версию.") exit(1) - with open("./patches/config.json", "r") as config_file: - return json.load(config_file) - + return conf def select_apk() -> str: apks = [] @@ -106,6 +106,10 @@ class Patch: self.name = name self.package = pkg self.applied = False + try: + self.priority = pkg.priority + except AttributeError: + self.priority = 0 def apply(self, conf: dict) -> bool: try: @@ -117,10 +121,7 @@ class Patch: return False -conf = init( - apktool_jar_url="https://bitbucket.org/iBotPeaches/apktool/downloads/apktool_2.12.0.jar", - apktool_wrapper_url="https://raw.githubusercontent.com/iBotPeaches/Apktool/master/scripts/linux/apktool", -) +conf = init() apk = select_apk() patch = decompile_apk(apk) @@ -131,6 +132,8 @@ for filename in os.listdir("patches/"): module = importlib.import_module(f"patches.{module_name}") patches.append(Patch(module_name, module)) +patches.sort(key=lambda x: x.package.priority, reverse=True) + for patch in tqdm(patches, colour="green", desc="Применение патчей"): tqdm.write(f"Применение патча: {patch.name}") patch.apply(conf) diff --git a/patches/application_icon.py b/patches/application_icon.py index 6b792ae..c32849d 100644 --- a/patches/application_icon.py +++ b/patches/application_icon.py @@ -1,4 +1,6 @@ -# Change package icon of apk +"""Change application icon""" +priority = 0 +from tqdm import tqdm import time diff --git a/patches/change_server.py b/patches/change_server.py index 2ba47ec..2105ce9 100644 --- a/patches/change_server.py +++ b/patches/change_server.py @@ -1,4 +1,6 @@ -# Change api server of apk +"""Change api server""" +priority = 0 +from tqdm import tqdm import json import requests @@ -6,6 +8,8 @@ import requests def apply(config: dict) -> bool: response = requests.get(config['server']) if response.status_code == 200: - print(json.loads(response.text)) + 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/patches/cleanup.py b/patches/cleanup.py index b30c587..2a1ec4a 100644 --- a/patches/cleanup.py +++ b/patches/cleanup.py @@ -1,4 +1,6 @@ -# Remove unnecessary files +"""Remove unnecessary files""" +priority = 0 +from tqdm import tqdm import os import shutil @@ -9,9 +11,9 @@ def apply(config: dict) -> bool: if os.path.isfile(item_path): os.remove(item_path) - print(f'Удалён файл: {item_path}') + tqdm.write(f'Удалён файл: {item_path}') elif os.path.isdir(item_path): if item not in config["cleanup"]["keep_dirs"]: shutil.rmtree(item_path) - print(f'Удалена папка: {item_path}') + tqdm.write(f'Удалена папка: {item_path}') return True diff --git a/patches/color_theme.py b/patches/color_theme.py index 740cffe..61cc846 100644 --- a/patches/color_theme.py +++ b/patches/color_theme.py @@ -1,8 +1,9 @@ -# Change application theme +"""Change application theme""" +priority = 0 +from tqdm import tqdm from lxml import etree - def apply(config: dict) -> bool: main_color = config["theme"]["colors"]["primary"] splash_color = config["theme"]["colors"]["secondary"] diff --git a/patches/compress_png.py b/patches/compress_png.py new file mode 100644 index 0000000..34dbe41 --- /dev/null +++ b/patches/compress_png.py @@ -0,0 +1,27 @@ +"""Compress PNGs""" +priority = 1 +from tqdm import tqdm + +import os +import subprocess + +def compress_pngs(root_dir): + 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}") + try: + subprocess.run( + ["pngquant", "--force", "--ext", ".png", "--quality=65-90", filepath], + check=True, capture_output=True + ) + compressed_files.append(filepath) + except subprocess.CalledProcessError as e: + tqdm.write(f"Ошибка при сжатии {filepath}: {e}") + return compressed_files + +def apply(config: dict) -> bool: + files = compress_pngs("./decompiled") + return len(files) > 0 and any(files) diff --git a/patches/config.json b/patches/config.json index f1fdd08..1bfa521 100644 --- a/patches/config.json +++ b/patches/config.json @@ -1,4 +1,8 @@ { + "tools": { + "apktool_jar_url": "https://bitbucket.org/iBotPeaches/apktool/downloads/apktool_2.12.0.jar", + "apktool_wrapper_url": "https://raw.githubusercontent.com/iBotPeaches/Apktool/master/scripts/linux/apktool" + }, "new_package_name": "com.wowlikon.anixart", "server": "https://anixarty.wowlikon.tech/modding", "theme": { diff --git a/patches/insert_new.py b/patches/insert_new.py index 5252e64..6734538 100644 --- a/patches/insert_new.py +++ b/patches/insert_new.py @@ -1,13 +1,15 @@ -# Change package icon of apk +"""Insert new files""" +priority = 0 +from tqdm import tqdm import shutil import os - def apply(config: dict) -> bool: # Mod first launch window shutil.copytree( - "./patches/resources/smali_classes4/", "./decompiled/smali_classes4/" + "./patches/resources/smali_classes4/", + "./decompiled/smali_classes4/" ) # Mod assets @@ -43,5 +45,4 @@ def apply(config: dict) -> bool: "./decompiled/res/raw/sdkinternalca.cer", ) - os.remove("./decompiled/res/font/ytsans_medium.otf") return True diff --git a/patches/package_name.py b/patches/package_name.py index ca23722..068c428 100644 --- a/patches/package_name.py +++ b/patches/package_name.py @@ -1,13 +1,13 @@ -# Change package name of apk +"""Change package name of apk""" +priority = -1 +from tqdm import tqdm import os - def rename_dir(src, dst): os.makedirs(os.path.dirname(dst), exist_ok=True) os.rename(src, dst) - def apply(config: dict) -> bool: assert config["new_package_name"] is not None, "new_package_name is not configured" diff --git a/patches/settings_urls.py b/patches/settings_urls.py index d32324a..8d18f36 100644 --- a/patches/settings_urls.py +++ b/patches/settings_urls.py @@ -1,8 +1,9 @@ -# Change application theme +"""Add new settings""" +priority = 0 +from tqdm import tqdm from lxml import etree - # Generate PreferenceCategory def make_category(ns, name, items): cat = etree.Element("PreferenceCategory", nsmap=ns)