Обновление структуры проекта, использование pydantic для конфигураций, улучшение отчёта о патчах
Build mod / build (push) Successful in 6m39s
Build mod / build (push) Successful in 6m39s
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
|
||||
"change_server": {
|
||||
"enabled": true,
|
||||
"server": "https://anixarty.wowlikon.tech/modding"
|
||||
"server": "https://anixarty.0x174.su/patch"
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -13,11 +13,18 @@ priority = 0
|
||||
import json
|
||||
import requests
|
||||
from tqdm import tqdm
|
||||
from typing import Dict, Any
|
||||
from pydantic import Field
|
||||
|
||||
from utils.config import PatchConfig
|
||||
|
||||
#Config
|
||||
class Config(PatchConfig):
|
||||
server: str = Field("https://anixarty.0x174.su/patch", description="URL сервера")
|
||||
|
||||
# Patch
|
||||
def apply(config: dict) -> bool:
|
||||
response = requests.get(config['server'])
|
||||
def apply(config: Config, base: Dict[str, Any]) -> bool:
|
||||
response = requests.get(config.server)
|
||||
assert response.status_code == 200, f"Failed to fetch data {response.status_code} {response.text}"
|
||||
|
||||
new_api = json.loads(response.text)
|
||||
|
||||
+47
-22
@@ -3,16 +3,19 @@
|
||||
|
||||
"color_theme": {
|
||||
"enabled": true,
|
||||
"logo": {
|
||||
"gradient": {
|
||||
"angle": 0.0,
|
||||
"start_color": "#ffccff00",
|
||||
"end_color": "#ffcccc00"
|
||||
},
|
||||
"ears_color": "#ffffd0d0"
|
||||
},
|
||||
"colors": {
|
||||
"primary": "#ccff00",
|
||||
"secondary": "#ffffd700",
|
||||
"secondary": "#ffcccc00",
|
||||
"background": "#ffffff",
|
||||
"text": "#000000"
|
||||
},
|
||||
"gradient": {
|
||||
"angle": "135.0",
|
||||
"from": "#ffff6060",
|
||||
"to": "#ffccff00"
|
||||
}
|
||||
}
|
||||
"""
|
||||
@@ -21,20 +24,40 @@ priority = 0
|
||||
|
||||
# imports
|
||||
from lxml import etree
|
||||
from typing import Dict, Any
|
||||
from pydantic import Field, BaseModel
|
||||
|
||||
from utils.config import PatchConfig
|
||||
from utils.public import (
|
||||
insert_after_public,
|
||||
insert_after_color,
|
||||
change_color,
|
||||
)
|
||||
|
||||
#Config
|
||||
class Gradient(BaseModel):
|
||||
angle: float = Field(0.0, description="Угол градиента")
|
||||
start_color: str = Field("#ffccff00", description="Начальный цвет градиента")
|
||||
end_color: str = Field("#ffcccc00", description="Конечный цвет градиента")
|
||||
|
||||
class Logo(BaseModel):
|
||||
gradient: Gradient = Field(Gradient(), description="Настройки градиента") # type: ignore [reportCallIssue]
|
||||
ears_color: str = Field("#ffd0d0d0", description="Цвет ушей логотипа")
|
||||
|
||||
class Colors(BaseModel):
|
||||
primary: str = Field("#ccff00", description="Основной цвет")
|
||||
secondary: str = Field("#ffcccc00", description="Вторичный цвет")
|
||||
background: str = Field("#ffffff", description="Фоновый цвет")
|
||||
text: str = Field("#000000", description="Цвет текста")
|
||||
|
||||
class Config(PatchConfig):
|
||||
logo: Logo = Field(Logo(), description="Настройки цветов логотипа") # type: ignore [reportCallIssue]
|
||||
colors: Colors = Field(Colors(), description="Настройки цветов") # type: ignore [reportCallIssue]
|
||||
|
||||
# Patch
|
||||
def apply(config: dict) -> bool:
|
||||
main_color = config["colors"]["primary"]
|
||||
splash_color = config["colors"]["secondary"]
|
||||
gradient_angle = config["gradient"]["angle"]
|
||||
gradient_from = config["gradient"]["from"]
|
||||
gradient_to = config["gradient"]["to"]
|
||||
def apply(config: Config, base: Dict[str, Any]) -> bool:
|
||||
main_color = config.colors.primary
|
||||
splash_color = config.colors.secondary
|
||||
|
||||
# No connection alert coolor
|
||||
with open("./decompiled/assets/no_connection.html", "r", encoding="utf-8") as file:
|
||||
@@ -57,9 +80,9 @@ 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)
|
||||
root.set(f"{{{base['xml_ns']['android']}}}angle", str(config.logo.gradient.angle))
|
||||
root.set(f"{{{base['xml_ns']['android']}}}startColor", config.logo.gradient.start_color)
|
||||
root.set(f"{{{base['xml_ns']['android']}}}endColor", config.logo.gradient.end_color)
|
||||
|
||||
# Save back
|
||||
tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8")
|
||||
@@ -72,10 +95,12 @@ def apply(config: dict) -> bool:
|
||||
root = tree.getroot()
|
||||
|
||||
# Finding "path"
|
||||
for el in root.findall("path", namespaces=config["xml_ns"]):
|
||||
name = el.get(f"{{{config['xml_ns']['android']}}}name")
|
||||
for el in root.findall("path", namespaces=base["xml_ns"]):
|
||||
name = el.get(f"{{{base['xml_ns']['android']}}}name")
|
||||
if name == "path":
|
||||
el.set(f"{{{config['xml_ns']['android']}}}fillColor", splash_color)
|
||||
el.set(f"{{{base['xml_ns']['android']}}}fillColor", config.colors.secondary)
|
||||
elif name in ["path_1", "path_2"]:
|
||||
el.set(f"{{{base['xml_ns']['android']}}}fillColor", config.logo.ears_color)
|
||||
|
||||
# Save back
|
||||
tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8")
|
||||
@@ -88,11 +113,11 @@ def apply(config: dict) -> bool:
|
||||
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'])
|
||||
root.set(f"{{{base['xml_ns']['android']}}}angle", str(config.logo.gradient.angle))
|
||||
items = root.findall("item", namespaces=base['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)
|
||||
items[0].set(f"{{{base['xml_ns']['android']}}}color", config.logo.gradient.start_color)
|
||||
items[1].set(f"{{{base['xml_ns']['android']}}}color", config.logo.gradient.end_color)
|
||||
|
||||
# Save back
|
||||
tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8")
|
||||
|
||||
+37
-25
@@ -34,28 +34,41 @@ import os
|
||||
import shutil
|
||||
import subprocess
|
||||
from tqdm import tqdm
|
||||
from typing import Dict, List, Any
|
||||
from pydantic import Field
|
||||
|
||||
from utils.config import PatchConfig
|
||||
from utils.smali_parser import get_smali_lines, save_smali_lines
|
||||
|
||||
#Config
|
||||
class Config(PatchConfig):
|
||||
remove_language_files: bool = Field(True, description="Удаляет все языки кроме русского и английского")
|
||||
remove_AI_voiceover: bool = Field(True, description="Заменяет ИИ озвучку маскота аниксы пустыми mp3 файлами")
|
||||
remove_debug_lines: bool = Field(False, description="Удаляет строки `.line n` из smali файлов использованные для дебага")
|
||||
remove_drawable_files: bool = Field(False, description="Удаляет неиспользованные drawable-* из директории decompiled/res")
|
||||
remove_unknown_files: bool = Field(True, description="Удаляет файлы из директории decompiled/unknown")
|
||||
remove_unknown_files_keep_dirs: List[str] = Field(["META-INF", "kotlin"], description="Оставляет указанные директории в decompiled/unknown")
|
||||
compress_png_files: bool = Field(True, description="Сжимает PNG в директории decompiled/res")
|
||||
|
||||
# Patch
|
||||
def remove_unknown_files(config):
|
||||
def remove_unknown_files(config: Config, base: Dict[str, Any]):
|
||||
path = "./decompiled/unknown"
|
||||
items = os.listdir(path)
|
||||
for item in items:
|
||||
item_path = f"{path}/{item}"
|
||||
if os.path.isfile(item_path):
|
||||
os.remove(item_path)
|
||||
if config.get("verbose", False):
|
||||
if base.get("verbose", False):
|
||||
tqdm.write(f"Удалён файл: {item_path}")
|
||||
elif os.path.isdir(item_path):
|
||||
if item not in config["remove_unknown_files_keep_dirs"]:
|
||||
if item not in config.remove_unknown_files_keep_dirs:
|
||||
shutil.rmtree(item_path)
|
||||
if config.get("verbose", False):
|
||||
if base.get("verbose", False):
|
||||
tqdm.write(f"Удалёна директория: {item_path}")
|
||||
return True
|
||||
|
||||
|
||||
def remove_debug_lines(config):
|
||||
def remove_debug_lines(config: Dict[str, Any]):
|
||||
for root, _, files in os.walk("./decompiled"):
|
||||
for filename in files:
|
||||
file_path = os.path.join(root, filename)
|
||||
@@ -72,14 +85,13 @@ def remove_debug_lines(config):
|
||||
return True
|
||||
|
||||
|
||||
def compress_png(config, png_path: str):
|
||||
def compress_png(config: Dict[str, Any], png_path: str):
|
||||
try:
|
||||
assert subprocess.run(
|
||||
[
|
||||
"pngquant",
|
||||
"--force",
|
||||
"--ext",
|
||||
".png",
|
||||
"--ext", ".png",
|
||||
"--quality=65-90",
|
||||
png_path,
|
||||
],
|
||||
@@ -93,7 +105,7 @@ def compress_png(config, png_path: str):
|
||||
return False
|
||||
|
||||
|
||||
def compress_png_files(config):
|
||||
def compress_png_files(config: Dict[str, Any]):
|
||||
compressed = []
|
||||
for root, _, files in os.walk("./decompiled"):
|
||||
for file in files:
|
||||
@@ -103,7 +115,7 @@ def compress_png_files(config):
|
||||
return len(compressed) > 0 and any(compressed)
|
||||
|
||||
|
||||
def remove_AI_voiceover(config):
|
||||
def remove_AI_voiceover(config: Dict[str, Any]):
|
||||
blank = "./resources/blank.mp3"
|
||||
path = "./decompiled/res/raw"
|
||||
files = [
|
||||
@@ -135,7 +147,7 @@ def remove_AI_voiceover(config):
|
||||
return True
|
||||
|
||||
|
||||
def remove_language_files(config):
|
||||
def remove_language_files(config: Dict[str, Any]):
|
||||
path = "./decompiled/res"
|
||||
folders = [
|
||||
"values-af",
|
||||
@@ -236,7 +248,7 @@ def remove_language_files(config):
|
||||
return True
|
||||
|
||||
|
||||
def remove_drawable_files(config):
|
||||
def remove_drawable_files(config: Dict[str, Any]):
|
||||
path = "./decompiled/res"
|
||||
folders = [
|
||||
"drawable-en-hdpi",
|
||||
@@ -269,29 +281,29 @@ def remove_drawable_files(config):
|
||||
return True
|
||||
|
||||
|
||||
def apply(config) -> bool:
|
||||
if config["remove_unknown_files"]:
|
||||
def apply(config: Config, base: Dict[str, Any]) -> bool:
|
||||
if config.remove_unknown_files:
|
||||
tqdm.write(f"Удаление неизвестных файлов...")
|
||||
remove_unknown_files(config)
|
||||
remove_unknown_files(config, base)
|
||||
|
||||
if config["remove_drawable_files"]:
|
||||
if config.remove_drawable_files:
|
||||
tqdm.write(f"Удаление директорий drawable-xx...")
|
||||
remove_drawable_files(config)
|
||||
remove_drawable_files(base)
|
||||
|
||||
if config["compress_png_files"]:
|
||||
if config.compress_png_files:
|
||||
tqdm.write(f"Сжатие PNG файлов...")
|
||||
compress_png_files(config)
|
||||
compress_png_files(base)
|
||||
|
||||
if config["remove_language_files"]:
|
||||
if config.remove_language_files:
|
||||
tqdm.write(f"Удаление языков...")
|
||||
remove_language_files(config)
|
||||
remove_language_files(base)
|
||||
|
||||
if config["remove_AI_voiceover"]:
|
||||
if config.remove_AI_voiceover:
|
||||
tqdm.write(f"Удаление ИИ озвучки...")
|
||||
remove_AI_voiceover(config)
|
||||
remove_AI_voiceover(base)
|
||||
|
||||
if config["remove_debug_lines"]:
|
||||
if config.remove_debug_lines:
|
||||
tqdm.write(f"Удаление дебаг линий...")
|
||||
remove_debug_lines(config)
|
||||
remove_debug_lines(base)
|
||||
|
||||
return True
|
||||
|
||||
+11
-3
@@ -10,6 +10,10 @@ priority = 0
|
||||
|
||||
# imports
|
||||
import textwrap
|
||||
from tqdm import tqdm
|
||||
from typing import Dict, Any
|
||||
|
||||
from utils.config import PatchConfig
|
||||
from utils.smali_parser import (
|
||||
find_smali_method_end,
|
||||
find_smali_method_start,
|
||||
@@ -18,13 +22,17 @@ from utils.smali_parser import (
|
||||
)
|
||||
|
||||
|
||||
#Config
|
||||
class Config(PatchConfig): ...
|
||||
|
||||
|
||||
# Patch
|
||||
def apply(config) -> bool:
|
||||
replacement = textwrap.dedent("""\
|
||||
def apply(config: Config, base: Dict[str, Any]) -> bool:
|
||||
replacement = [f'\t{line}\n' for line in textwrap.dedent("""\
|
||||
.locals 0
|
||||
const/4 p0, 0x1
|
||||
return p0
|
||||
""").splitlines()
|
||||
""").splitlines()]
|
||||
|
||||
path = "./decompiled/smali_classes2/com/swiftsoft/anixartd/Prefs.smali"
|
||||
lines = get_smali_lines(path)
|
||||
|
||||
@@ -12,10 +12,16 @@ priority = 0
|
||||
import os
|
||||
from tqdm import tqdm
|
||||
from lxml import etree
|
||||
from typing import Dict, Any
|
||||
|
||||
from utils.config import PatchConfig
|
||||
from utils.smali_parser import get_smali_lines, save_smali_lines
|
||||
|
||||
#Config
|
||||
class Config(PatchConfig): ...
|
||||
|
||||
# Patch
|
||||
def apply(config) -> bool:
|
||||
def apply(config: Config, base: Dict[str, Any]) -> bool:
|
||||
attributes = [
|
||||
"paddingTop",
|
||||
"paddingBottom",
|
||||
@@ -36,9 +42,9 @@ def apply(config) -> bool:
|
||||
root = tree.getroot()
|
||||
|
||||
for attr in attributes:
|
||||
if config["verbose"]:
|
||||
if base.get("verbose", False):
|
||||
tqdm.write(f"set {attr} = 0.0dip")
|
||||
root.set(f"{{{config["xml_ns"]['android']}}}{attr}", "0.0dip")
|
||||
root.set(f"{{{base['xml_ns']['android']}}}{attr}", "0.0dip")
|
||||
|
||||
tree.write(
|
||||
beta_banner_xml, pretty_print=True, xml_declaration=True, encoding="utf-8"
|
||||
|
||||
@@ -11,11 +11,16 @@ priority = 0
|
||||
# imports
|
||||
import os
|
||||
import shutil
|
||||
from typing import Dict, Any
|
||||
from utils.config import PatchConfig
|
||||
from utils.public import insert_after_public
|
||||
|
||||
|
||||
#Config
|
||||
class Config(PatchConfig): ...
|
||||
|
||||
# Patch
|
||||
def apply(config: dict) -> bool:
|
||||
def apply(config: Config, base: Dict[str, Any]) -> bool:
|
||||
# Mod first launch window
|
||||
shutil.copy("./resources/avatar.png", "./decompiled/assets/avatar.png")
|
||||
shutil.copy(
|
||||
|
||||
+16
-11
@@ -12,7 +12,15 @@ priority = -1
|
||||
# imports
|
||||
import os
|
||||
from lxml import etree
|
||||
from tqdm import tqdm
|
||||
from typing import Dict, Any
|
||||
from pydantic import Field
|
||||
|
||||
from utils.config import PatchConfig
|
||||
|
||||
#Config
|
||||
class Config(PatchConfig):
|
||||
package_name: str = Field("com.wowlikon.anixart", description="Название пакета")
|
||||
|
||||
# Patch
|
||||
def rename_dir(src, dst):
|
||||
@@ -20,7 +28,7 @@ def rename_dir(src, dst):
|
||||
os.rename(src, dst)
|
||||
|
||||
|
||||
def apply(config: dict) -> bool:
|
||||
def apply(config: Config, base: Dict[str, Any]) -> bool:
|
||||
for root, dirs, files in os.walk("./decompiled"):
|
||||
for filename in files:
|
||||
file_path = os.path.join(root, filename)
|
||||
@@ -31,11 +39,11 @@ def apply(config: dict) -> bool:
|
||||
file_contents = file.read()
|
||||
|
||||
new_contents = file_contents.replace(
|
||||
"com.swiftsoft.anixartd", config["new_package_name"]
|
||||
"com.swiftsoft.anixartd", config.package_name
|
||||
)
|
||||
new_contents = new_contents.replace(
|
||||
"com/swiftsoft/anixartd",
|
||||
config["new_package_name"].replace(".", "/"),
|
||||
config.package_name.replace(".", "/"),
|
||||
)
|
||||
with open(file_path, "w", encoding="utf-8") as file:
|
||||
file.write(new_contents)
|
||||
@@ -46,7 +54,7 @@ def apply(config: dict) -> bool:
|
||||
rename_dir(
|
||||
"./decompiled/smali/com/swiftsoft/anixartd",
|
||||
os.path.join(
|
||||
"./decompiled", "smali", config["new_package_name"].replace(".", "/")
|
||||
"./decompiled", "smali", config.package_name.replace(".", "/")
|
||||
),
|
||||
)
|
||||
if os.path.exists("./decompiled/smali_classes2/com/swiftsoft/anixartd"):
|
||||
@@ -55,7 +63,7 @@ def apply(config: dict) -> bool:
|
||||
os.path.join(
|
||||
"./decompiled",
|
||||
"smali_classes2",
|
||||
config["new_package_name"].replace(".", "/"),
|
||||
config.package_name.replace(".", "/"),
|
||||
),
|
||||
)
|
||||
if os.path.exists("./decompiled/smali_classes4/com/swiftsoft"):
|
||||
@@ -64,7 +72,7 @@ def apply(config: dict) -> bool:
|
||||
os.path.join(
|
||||
"./decompiled",
|
||||
"smali_classes4",
|
||||
"/".join(config["new_package_name"].split(".")[:-1]),
|
||||
"/".join(config.package_name.split(".")[:-1]),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -88,7 +96,7 @@ def apply(config: dict) -> bool:
|
||||
|
||||
new_contents = file_contents.replace(
|
||||
"com/swiftsoft",
|
||||
"/".join(config["new_package_name"].split(".")[:-1]),
|
||||
"/".join(config.package_name.split(".")[:-1]),
|
||||
)
|
||||
with open(file_path, "w", encoding="utf-8") as file:
|
||||
file.write(new_contents)
|
||||
@@ -101,11 +109,8 @@ def apply(config: dict) -> bool:
|
||||
root = tree.getroot()
|
||||
|
||||
last_linear = root.xpath("//LinearLayout/LinearLayout[4]")[0]
|
||||
last_linear.set(f"{{{config['xml_ns']['android']}}}visibility", "gone")
|
||||
last_linear.set(f"{{{base['xml_ns']['android']}}}visibility", "gone")
|
||||
|
||||
tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
# smali_classes2/com/wowlikon/anixart/utils/DeviceInfoUtil.smali: const-string v3, "\u0411\u0430\u0433-\u0440\u0435\u043f\u043e\u0440\u0442 9.0 BETA 5 (25062213)"
|
||||
|
||||
@@ -12,36 +12,43 @@ priority = 0
|
||||
# imports
|
||||
from lxml import etree
|
||||
from tqdm import tqdm
|
||||
from typing import Dict, List, Any
|
||||
from pydantic import Field
|
||||
|
||||
from utils.config import PatchConfig
|
||||
|
||||
#Config
|
||||
class Config(PatchConfig):
|
||||
items: List[str] = Field(["home", "discover", "feed", "bookmarks", "profile"], description="Список элементов в панели навигации")
|
||||
|
||||
# Patch
|
||||
def apply(config: dict) -> bool:
|
||||
def apply(config: Config, base: Dict[str, Any]) -> bool:
|
||||
file_path = "./decompiled/res/menu/bottom.xml"
|
||||
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
tree = etree.parse(file_path, parser)
|
||||
root = tree.getroot()
|
||||
|
||||
items = root.findall("item", namespaces=config['xml_ns'])
|
||||
items = root.findall("item", namespaces=base['xml_ns'])
|
||||
|
||||
def get_id_suffix(item):
|
||||
full_id = item.get(f"{{{config['xml_ns']['android']}}}id")
|
||||
full_id = item.get(f"{{{base['xml_ns']['android']}}}id")
|
||||
return full_id.split("tab_")[-1] if full_id else None
|
||||
|
||||
items_by_id = {get_id_suffix(item): item for item in items}
|
||||
existing_order = [get_id_suffix(item) for item in items]
|
||||
|
||||
ordered_items = []
|
||||
for key in config['items']:
|
||||
for key in config.items:
|
||||
if key in items_by_id:
|
||||
ordered_items.append(items_by_id[key])
|
||||
|
||||
extra = [i for i in items if get_id_suffix(i) not in config['items']]
|
||||
extra = [i for i in items if get_id_suffix(i) not in config.items]
|
||||
if extra:
|
||||
tqdm.write("⚠Найдены лишние элементы: " + str([get_id_suffix(i) for i in extra]))
|
||||
ordered_items.extend(extra)
|
||||
|
||||
for i in root.findall("item", namespaces=config['xml_ns']):
|
||||
for i in root.findall("item", namespaces=base['xml_ns']):
|
||||
root.remove(i)
|
||||
|
||||
for item in ordered_items:
|
||||
|
||||
@@ -26,8 +26,52 @@ priority = 0
|
||||
# imports
|
||||
import shutil
|
||||
from lxml import etree
|
||||
from tqdm import tqdm
|
||||
from typing import Dict, List, Any
|
||||
from pydantic import Field
|
||||
|
||||
from utils.config import PatchConfig
|
||||
from utils.public import insert_after_public
|
||||
|
||||
#Config
|
||||
DEFAULT_MENU = {
|
||||
"Мы в социальных сетях": [
|
||||
{
|
||||
"title": "wowlikon",
|
||||
"description": "Разработчик",
|
||||
"url": "https://t.me/wowlikon",
|
||||
"icon": "@drawable/ic_custom_telegram",
|
||||
"icon_space_reserved": "false"
|
||||
},
|
||||
{
|
||||
"title": "Kentai Radiquum",
|
||||
"description": "Разработчик",
|
||||
"url": "https://t.me/radiquum",
|
||||
"icon": "@drawable/ic_custom_telegram",
|
||||
"icon_space_reserved": "false"
|
||||
},
|
||||
{
|
||||
"title": "Мы в Telegram",
|
||||
"description": "Подпишитесь на канал, чтобы быть в курсе последних новостей.",
|
||||
"url": "https://t.me/http_teapod",
|
||||
"icon": "@drawable/ic_custom_telegram",
|
||||
"icon_space_reserved": "false"
|
||||
}
|
||||
],
|
||||
"Прочее": [
|
||||
{
|
||||
"title": "Помочь проекту",
|
||||
"description": "Вы можете помочь нам в разработке мода, написании кода или тестировании.",
|
||||
"url": "https://git.wowlikon.tech/anixart-mod",
|
||||
"icon": "@drawable/ic_custom_crown",
|
||||
"icon_space_reserved": "false"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
class Config(PatchConfig):
|
||||
version: str = Field(" by wowlikon", description="Суффикс версии")
|
||||
menu: Dict[str, List[Dict[str, str]]] = Field(DEFAULT_MENU, description="Меню")
|
||||
|
||||
# Patch
|
||||
def make_category(ns, name, items):
|
||||
@@ -49,7 +93,7 @@ def make_category(ns, name, items):
|
||||
|
||||
return cat
|
||||
|
||||
def apply(config: dict) -> bool:
|
||||
def apply(config: Config, base: Dict[str, Any]) -> bool:
|
||||
shutil.copy(
|
||||
"./resources/ic_custom_crown.xml",
|
||||
"./decompiled/res/drawable/ic_custom_crown.xml",
|
||||
@@ -70,8 +114,8 @@ def apply(config: dict) -> bool:
|
||||
# Insert new PreferenceCategory before the last element
|
||||
last = root[-1] # last element
|
||||
pos = root.index(last)
|
||||
for section, items in config["menu"].items():
|
||||
root.insert(pos, make_category(config["xml_ns"], section, items))
|
||||
for section, items in config.menu.items():
|
||||
root.insert(pos, make_category(base["xml_ns"], section, items))
|
||||
pos += 1
|
||||
|
||||
# Save back
|
||||
@@ -86,7 +130,7 @@ def apply(config: dict) -> bool:
|
||||
with open(filepath, "r", encoding="utf-8") as file:
|
||||
for line in file.readlines():
|
||||
if '"\u0412\u0435\u0440\u0441\u0438\u044f'.encode("unicode_escape").decode() in line:
|
||||
content += line[:line.rindex('"')] + config["version"] + line[line.rindex('"'):]
|
||||
content += line[:line.rindex('"')] + config.version + line[line.rindex('"'):]
|
||||
else:
|
||||
content += line
|
||||
with open(filepath, "w", encoding="utf-8") as file:
|
||||
|
||||
@@ -10,19 +10,28 @@
|
||||
priority = 0
|
||||
|
||||
# imports
|
||||
from tqdm import tqdm
|
||||
from typing import Dict, List, Any
|
||||
from pydantic import Field
|
||||
|
||||
from utils.config import PatchConfig
|
||||
from utils.smali_parser import float_to_hex
|
||||
from utils.public import (
|
||||
insert_after_public,
|
||||
insert_after_id,
|
||||
)
|
||||
|
||||
#Config
|
||||
class Config(PatchConfig):
|
||||
speeds: List[float] = Field([9.0], description="Список пользовательских скоростей воспроизведения")
|
||||
|
||||
|
||||
# Patch
|
||||
def apply(config: dict) -> bool:
|
||||
def apply(config: Config, base: Dict[str, Any]) -> bool:
|
||||
assert float_to_hex(1.5) == "0x3fc00000"
|
||||
|
||||
last = "speed75"
|
||||
for speed in config.get("speeds", []):
|
||||
for speed in config.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)}"
|
||||
|
||||
+18
-7
@@ -5,22 +5,24 @@
|
||||
|
||||
Каждый патч должен независеть от других патчей и проверять себя при применении. Он не должен вернуть True, если есть проблемы.
|
||||
На данный момент каждый патч должен иметь функцию `apply`, которая принимает на вход конфигурацию и возвращает True или False.
|
||||
И модель `Config`, которая наследуется от `PatchConfig` (поле `enabled` добавлять не нужно).
|
||||
Это позволяет упростить конфигурирование и проверять типы данных, и делать значения по умолчанию.
|
||||
При успешном применении патча, функция apply должна вернуть True, иначе False.
|
||||
Ошибка будет интерпретирована как False. С выводом ошибки в консоль.
|
||||
Ещё патч должен иметь переменную `priority`, которая указывает приоритет патча, чем выше, тем раньше он будет применен.
|
||||
|
||||
Коротко о конфигурации. Она получается из `config.json` из config["patches"][patch_name].
|
||||
Дополнительно в основном методе `apply` кроме этого есть "verbose" и содержимое "base".
|
||||
Эти поля передаются всем патчам. `verbose` может быть указан так-же как флаг запуска:
|
||||
Коротко о конфигурации. Она состоит из двух частей `config` (на основе модели `Config` и из файла `configs/название_патча.json`).
|
||||
И постоянной не типизированной переменной `base` из `config.json` и флага `verbose`.
|
||||
```
|
||||
python ./main.py build --verbose
|
||||
```
|
||||
|
||||
В конце файла должно быть описание конфигурации патча.
|
||||
В конце docstring может быть дополнительное описание конфигурации патча (основное описание получается из модели `Config`).
|
||||
Это может быть как короткий фрагмент из названия патча и одной опции "enabled", которая обрабатывается в коде патчера.
|
||||
|
||||
"todo_template": {
|
||||
"enabled": true // Пример описания тк этот текст просто пример
|
||||
"enabled": true, // Пример описания тк этот текст просто пример
|
||||
"example": true // Пример кастомного параметра
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -28,11 +30,20 @@ priority = 0 # Приоритет патча, чем выше, тем раньш
|
||||
|
||||
# imports
|
||||
from tqdm import tqdm
|
||||
from typing import Dict, List, Any
|
||||
from pydantic import Field
|
||||
|
||||
from utils.config import PatchConfig
|
||||
|
||||
#Config
|
||||
class Config(PatchConfig):
|
||||
example: bool = Field(True, description="Пример кастомного параметра")
|
||||
|
||||
|
||||
# Patch
|
||||
def apply(config: dict) -> bool: # Анотации типов для удобства, читаемости и поддержки IDE
|
||||
def apply(config: Config, base: Dict[str, Any]) -> bool: # Анотации типов для удобства, читаемости и поддержки IDE
|
||||
tqdm.write("Вывод информации через tqdm, чтобы не мешать прогресс-бару")
|
||||
if config["verbose"]:
|
||||
tqdm.write("Пример включен" if config.example else "Пример отключен")
|
||||
if base["verbose"]:
|
||||
tqdm.write("Для вывода подробной и отладочной информации используйте флаг --verbose")
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user