This commit is contained in:
@@ -1,241 +1,474 @@
|
||||
from typing import List, Dict, Any
|
||||
__version__ = "1.0.0"
|
||||
import shutil
|
||||
from functools import wraps
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List
|
||||
|
||||
import typer
|
||||
import importlib
|
||||
import traceback
|
||||
import yaml
|
||||
|
||||
from plumbum import local, ProcessExecutionError
|
||||
from plumbum import ProcessExecutionError, local
|
||||
from rich.console import Console
|
||||
from rich.progress import Progress
|
||||
from rich.prompt import Prompt
|
||||
from rich.table import Table
|
||||
|
||||
from utils.config import *
|
||||
from utils.tools import *
|
||||
|
||||
from utils.apk import APKMeta, APKProcessor
|
||||
from utils.config import Config, PatchTemplate, load_config
|
||||
from utils.info import print_model_fields, print_model_table
|
||||
from utils.patch_manager import (BuildError, ConfigError, PatcherError,
|
||||
PatchManager, handle_errors)
|
||||
from utils.tools import (CONFIGS, DECOMPILED, MODIFIED, ORIGINAL, PATCHES,
|
||||
TOOLS, download, ensure_dirs, run, select_apk)
|
||||
|
||||
console = Console()
|
||||
app = typer.Typer()
|
||||
app = typer.Typer(
|
||||
name="anixarty-patcher",
|
||||
help="Инструмент для модификации Anixarty APK",
|
||||
add_completion=False,
|
||||
)
|
||||
|
||||
|
||||
# ======================= PATCHING =========================
|
||||
class Patch:
|
||||
def __init__(self, name: str, module):
|
||||
self.name = name
|
||||
self.module = module
|
||||
self.applied = False
|
||||
self.priority = getattr(module, "priority", 0)
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def generate_report(
|
||||
apk_path: Path,
|
||||
meta: APKMeta,
|
||||
patches: List[PatchTemplate],
|
||||
manager: PatchManager,
|
||||
) -> None:
|
||||
"""Генерирует отчёт о сборке в формате Markdown"""
|
||||
report_path = MODIFIED / "report.md"
|
||||
|
||||
applied_count = sum(1 for p in patches if p.applied)
|
||||
applied_patches = [p for p in patches if p.applied]
|
||||
failed_patches = [p for p in patches if not p.applied]
|
||||
|
||||
applied_patches.sort(key=lambda p: p.priority, reverse=True)
|
||||
failed_patches.sort(key=lambda p: p.priority, reverse=True)
|
||||
|
||||
def get_patch_info(patch: PatchTemplate) -> Dict[str, str]:
|
||||
"""Получает описание и автора патча из модуля"""
|
||||
info = {"doc": "", "author": "-"}
|
||||
try:
|
||||
self.config = module.Config.model_validate_json((CONFIGS / f"{name}.json").read_text())
|
||||
except Exception as e:
|
||||
console.print(f"[red]Ошибка при загрузке конфигурации патча {name}: {e}")
|
||||
console.print(f"[yellow]Используются значения по умолчанию")
|
||||
self.config = module.Config()
|
||||
patch_module = manager.load_patch_module(patch.name)
|
||||
doc = patch_module.__doc__
|
||||
if doc:
|
||||
info["doc"] = doc.strip().split("\n")[0]
|
||||
author = getattr(patch_module, "__author__", "")
|
||||
if author:
|
||||
info["author"] = f"`{author}`"
|
||||
except Exception:
|
||||
pass
|
||||
return info
|
||||
|
||||
def apply(self, conf: Dict[str, Any]) -> bool:
|
||||
try:
|
||||
self.applied = bool(self.module.apply(self.config, conf))
|
||||
return self.applied
|
||||
except Exception as e:
|
||||
console.print(f"[red]Ошибка в патче {self.name}: {e}")
|
||||
traceback.print_exc()
|
||||
return False
|
||||
lines = []
|
||||
lines.append(f"Anixarty {meta.version_name} (build {meta.version_code})")
|
||||
lines.append("")
|
||||
|
||||
lines.append("## 📦 Информация о сборке")
|
||||
lines.append("")
|
||||
lines.append("| Параметр | Значение |")
|
||||
lines.append("|----------|----------|")
|
||||
lines.append(f"| Версия | `{meta.version_name}` |")
|
||||
lines.append(f"| Код версии | `{meta.version_code}` |")
|
||||
lines.append(f"| Пакет | `{meta.package}` |")
|
||||
lines.append(f"| Файл | `{apk_path.name}` |")
|
||||
lines.append(f"| Дата сборки | {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} |")
|
||||
lines.append("")
|
||||
|
||||
# ========================= INIT =========================
|
||||
@app.command()
|
||||
def init():
|
||||
"""Создание директорий и скачивание инструментов"""
|
||||
ensure_dirs()
|
||||
conf = load_config(console)
|
||||
lines.append("## 🔧 Применённые патчи")
|
||||
lines.append("")
|
||||
|
||||
for f in PATCHES.glob("*.py"):
|
||||
if f.name.startswith("todo_") or f.name == "__init__.py":
|
||||
continue
|
||||
patch = Patch(f.stem, __import__(f"patches.{f.stem}", fromlist=[""]))
|
||||
json_string = patch.config.model_dump_json()
|
||||
(CONFIGS / f"{patch.name}.json").write_text(json_string)
|
||||
|
||||
if not (TOOLS / "apktool.jar").exists():
|
||||
download(console, conf.tools.apktool_jar_url, TOOLS / "apktool.jar")
|
||||
|
||||
if not (TOOLS / "apktool").exists():
|
||||
download(console, conf.tools.apktool_wrapper_url, TOOLS / "apktool")
|
||||
(TOOLS / "apktool").chmod(0o755)
|
||||
|
||||
try:
|
||||
local["java"]["-version"]()
|
||||
console.print("[green]Java найдена")
|
||||
except ProcessExecutionError:
|
||||
console.print("[red]Java не установлена")
|
||||
raise typer.Exit(1)
|
||||
|
||||
|
||||
# ========================= INFO =========================
|
||||
@app.command()
|
||||
def info(patch_name: str = ""):
|
||||
"""Вывод информации о патче"""
|
||||
conf = load_config(console).model_dump()
|
||||
if patch_name:
|
||||
patch = Patch(patch_name, __import__(f"patches.{patch_name}", fromlist=[""]))
|
||||
console.print(f"[green]Информация о патче {patch.name}:")
|
||||
console.print(f" [yellow]Приоритет: {patch.priority}")
|
||||
console.print(f" [yellow]Описание: {patch.module.__doc__}")
|
||||
|
||||
console.print(f"[blue]Поля конфигурации")
|
||||
for field_name, field_info in type(patch.config).model_fields.items():
|
||||
field_data = {
|
||||
'type': field_info.annotation.__name__,
|
||||
'description': field_info.description,
|
||||
'default': field_info.default,
|
||||
'json_schema_extra': field_info.json_schema_extra,
|
||||
}
|
||||
console.print(f'{field_name} {field_data}')
|
||||
console.print("\n[blue]" + "="*50 + "\n")
|
||||
if applied_patches:
|
||||
lines.append(
|
||||
f"> ✅ Успешно применено: **{applied_count}** из **{len(patches)}**"
|
||||
)
|
||||
lines.append("")
|
||||
lines.append("| Патч | Приоритет | Автор | Описание |")
|
||||
lines.append("|------|:---------:|-------|----------|")
|
||||
|
||||
for p in applied_patches:
|
||||
info = get_patch_info(p)
|
||||
lines.append(
|
||||
f"| ✅ `{p.name}` | {p.priority} | {info['author']} | {info['doc']} |"
|
||||
)
|
||||
else:
|
||||
conf = load_config(console)
|
||||
console.print("[cyan]Список патчей:")
|
||||
patch_list = []
|
||||
for f in PATCHES.glob("*.py"):
|
||||
if f.name == "__init__.py": continue
|
||||
if f.name.startswith("todo_"):
|
||||
try: priority = __import__(f"patches.{f.stem}.priority", fromlist=[""])
|
||||
except: priority = None
|
||||
patch_list.append((priority, f" [{priority}] [yellow]{f.stem}: [yellow]⚠ в разработке"))
|
||||
continue
|
||||
patch = Patch(f.stem, __import__(f"patches.{f.stem}", fromlist=[""]))
|
||||
if patch.config.enabled: patch_list.append((patch.priority, f" [{patch.priority}] [yellow]{f.stem}: [green]✔ включен"))
|
||||
else: patch_list.append((patch.priority, f" [{patch.priority}] [yellow]{f.stem}: [red]✘ выключен"))
|
||||
for _, patch in sorted(patch_list, key=lambda x: (x[0] is None, x[0]), reverse=True): console.print(patch)
|
||||
lines.append("> ⚠️ Нет применённых патчей")
|
||||
|
||||
lines.append("")
|
||||
|
||||
# ========================= UTIL =========================
|
||||
def select_apk() -> Path:
|
||||
apks = [f for f in ORIGINAL.glob("*.apk")]
|
||||
if not apks:
|
||||
console.print("[red]Нет apk-файлов в папке original")
|
||||
raise typer.Exit(1)
|
||||
if failed_patches:
|
||||
lines.append("## ❌ Ошибки")
|
||||
lines.append("")
|
||||
lines.append("| Патч | Приоритет | Автор | Описание |")
|
||||
lines.append("|------|:---------:|-------|----------|")
|
||||
|
||||
if len(apks) == 1:
|
||||
console.print(f"[green]Выбран {apks[0].name}")
|
||||
return apks[0]
|
||||
for p in failed_patches:
|
||||
info = get_patch_info(p)
|
||||
lines.append(
|
||||
f"| ❌ `{p.name}` | {p.priority} | {info['author']} | {info['doc']} |"
|
||||
)
|
||||
|
||||
options = {str(i): apk for i, apk in enumerate(apks, 1)}
|
||||
for k, v in options.items():
|
||||
console.print(f"{k}. {v.name}")
|
||||
lines.append("")
|
||||
|
||||
choice = Prompt.ask("Выберите номер", choices=list(options.keys()))
|
||||
return options[choice]
|
||||
|
||||
|
||||
def decompile(apk: Path):
|
||||
console.print("[yellow]Декомпиляция apk...")
|
||||
run(
|
||||
console,
|
||||
[
|
||||
"java",
|
||||
"-jar", str(TOOLS / "apktool.jar"),
|
||||
"d", "-f",
|
||||
"-o", str(DECOMPILED),
|
||||
str(apk),
|
||||
]
|
||||
lines.append("---")
|
||||
lines.append("")
|
||||
lines.append(
|
||||
"*Собрано с помощью [anixarty-patcher](https://git.0x174.su/anixart-mod/patcher)*"
|
||||
)
|
||||
|
||||
|
||||
def compile(apk: Path, patches: List[Patch]):
|
||||
console.print("[yellow]Сборка apk...")
|
||||
|
||||
with open(DECOMPILED / "apktool.yml", encoding="utf-8") as f:
|
||||
meta = yaml.safe_load(f)
|
||||
version_info = meta.get("versionInfo", {})
|
||||
version_code = version_info.get("versionCode", 0)
|
||||
version_name = version_info.get("versionName", "unknown")
|
||||
|
||||
filename_version = version_name.lower().replace(" ", "-").replace(".", "-")
|
||||
out_apk = MODIFIED / f"Anixarty-mod-v{filename_version}.apk"
|
||||
aligned = out_apk.with_stem(out_apk.stem + "-aligned")
|
||||
signed = out_apk.with_stem(out_apk.stem + "-mod")
|
||||
|
||||
run(
|
||||
console,
|
||||
[
|
||||
"java",
|
||||
"-jar", str(TOOLS / "apktool.jar"),
|
||||
"b", str(DECOMPILED),
|
||||
"-o", str(out_apk),
|
||||
]
|
||||
)
|
||||
run(
|
||||
console,
|
||||
["zipalign", "-v", "4", str(out_apk), str(aligned)]
|
||||
)
|
||||
run(
|
||||
console,
|
||||
[
|
||||
"apksigner", "sign",
|
||||
"--v1-signing-enabled", "false",
|
||||
"--v2-signing-enabled", "true",
|
||||
"--v3-signing-enabled", "true",
|
||||
"--ks", "keystore.jks",
|
||||
"--ks-pass", "file:keystore.pass",
|
||||
"--out", str(signed),
|
||||
str(aligned),
|
||||
]
|
||||
)
|
||||
|
||||
console.print("[green]✔ APK успешно собран и подписан")
|
||||
|
||||
with open(MODIFIED / "report.log", "w", encoding="utf-8") as f:
|
||||
f.write(f"Anixarty mod v {version_name} ({version_code})\n")
|
||||
for p in patches:
|
||||
f.write(f"{'✔' if p.applied else '✘'} {p.name}\n")
|
||||
report_path.write_text("\n".join(lines), encoding="utf-8")
|
||||
console.print(f"[dim]Отчёт сохранён: {report_path}[/dim]")
|
||||
|
||||
|
||||
# ========================= BUILD =========================
|
||||
# ========================= COMMANDS =========================
|
||||
@app.command()
|
||||
def build(
|
||||
force: bool = typer.Option(False, "--force", "-f", help="Принудительная сборка"),
|
||||
verbose: bool = typer.Option(False, "--verbose", "-v", help="Подробный вывод"),
|
||||
):
|
||||
"""Декомпиляция, патчи и сборка apk"""
|
||||
@handle_errors
|
||||
def init():
|
||||
"""Инициализация: создание директорий и скачивание инструментов"""
|
||||
ensure_dirs()
|
||||
|
||||
conf = load_config(console)
|
||||
apk = select_apk()
|
||||
decompile(apk)
|
||||
|
||||
patch_objs: List[Patch] = []
|
||||
conf.base |= {"verbose": verbose}
|
||||
# Проверка Java
|
||||
console.print("[cyan]Проверка Java...")
|
||||
try:
|
||||
local["java"]["-version"].run(retcode=None)
|
||||
console.print("[green]✔ Java найдена")
|
||||
except ProcessExecutionError:
|
||||
raise PatcherError("Java не установлена. Установите JDK 11+")
|
||||
|
||||
for f in PATCHES.glob("*.py"):
|
||||
if f.name.startswith("todo_") or f.name == "__init__.py":
|
||||
continue
|
||||
name = f.stem
|
||||
module = importlib.import_module(f"patches.{name}")
|
||||
if not module.Config.model_validate_json((CONFIGS / f"{name}.json").read_text()):
|
||||
console.print(f"[yellow]≫ Пропускаем {name}")
|
||||
continue
|
||||
patch_objs.append(Patch(name, module))
|
||||
# Скачивание apktool
|
||||
apktool_jar = TOOLS / "apktool.jar"
|
||||
if not apktool_jar.exists():
|
||||
download(console, conf.tools.apktool_jar_url, apktool_jar)
|
||||
else:
|
||||
console.print(f"[dim]✔ {apktool_jar.name} уже существует[/dim]")
|
||||
|
||||
patch_objs.sort(key=lambda p: p.priority, reverse=True)
|
||||
# Скачивание apktool wrapper
|
||||
apktool_wrapper = TOOLS / "apktool"
|
||||
if not apktool_wrapper.exists():
|
||||
download(console, conf.tools.apktool_wrapper_url, apktool_wrapper)
|
||||
apktool_wrapper.chmod(0o755)
|
||||
else:
|
||||
console.print(f"[dim]✔ {apktool_wrapper.name} уже существует[/dim]")
|
||||
|
||||
console.print("[cyan]Применение патчей")
|
||||
with Progress() as progress:
|
||||
task = progress.add_task("Патчи", total=len(patch_objs))
|
||||
for p in patch_objs:
|
||||
ok = p.apply(conf.base)
|
||||
progress.console.print(f"{'✔' if ok else '✘'} {p.name}")
|
||||
# Проверка zipalign и apksigner
|
||||
for tool in ["zipalign", "apksigner"]:
|
||||
try:
|
||||
local[tool]["--version"].run(retcode=None)
|
||||
console.print(f"[green]✔ {tool} найден")
|
||||
except Exception:
|
||||
console.print(f"[yellow]⚠ {tool} не найден в PATH")
|
||||
|
||||
# Проверка keystore
|
||||
if not Path("keystore.jks").exists():
|
||||
console.print("[yellow]⚠ keystore.jks не найден. Создайте его командой:")
|
||||
console.print(
|
||||
"[dim] keytool -genkey -v -keystore keystore.jks -keyalg RSA "
|
||||
"-keysize 2048 -validity 10000 -alias key[/dim]"
|
||||
)
|
||||
|
||||
# Инициализация конфигов патчей
|
||||
console.print("\n[cyan]Инициализация конфигураций патчей...")
|
||||
manager = PatchManager(console)
|
||||
|
||||
for name in manager.discover_patches():
|
||||
patch = manager.load_patch(name)
|
||||
config_path = CONFIGS / f"{name}.json"
|
||||
if not config_path.exists():
|
||||
patch.save_config()
|
||||
console.print(f" [green]✔ {name}.json создан")
|
||||
else:
|
||||
console.print(f" [dim]✔ {name}.json существует[/dim]")
|
||||
|
||||
console.print("\n[green]✔ Инициализация завершена")
|
||||
|
||||
|
||||
@app.command("list")
|
||||
@handle_errors
|
||||
def list_patches():
|
||||
"""Показать список всех патчей"""
|
||||
manager = PatchManager(console)
|
||||
all_patches = manager.discover_all()
|
||||
|
||||
table = Table(title="Доступные патчи")
|
||||
table.add_column("Приоритет", justify="center", style="cyan")
|
||||
table.add_column("Название", style="yellow")
|
||||
table.add_column("Статус", justify="center")
|
||||
table.add_column("Автор", style="magenta")
|
||||
table.add_column("Версия", style="yellow")
|
||||
table.add_column("Описание")
|
||||
|
||||
patch_rows = []
|
||||
|
||||
for name in all_patches["ready"]:
|
||||
try:
|
||||
patch = manager.load_patch(name)
|
||||
status = "[green]✔ вкл[/green]" if patch.enabled else "[red]✘ выкл[/red]"
|
||||
patch_class = manager.load_patch_class(name)
|
||||
priority = getattr(patch_class, "priority", 0)
|
||||
patch_module = manager.load_patch_module(name)
|
||||
author = getattr(patch_module, "__author__", "")
|
||||
version = getattr(patch_module, "__version__", "")
|
||||
description = (patch_module.__doc__ or "").strip().split("\n")[0]
|
||||
patch_rows.append(
|
||||
(patch.priority, name, status, author, version, description)
|
||||
)
|
||||
except Exception as e:
|
||||
raise e
|
||||
patch_rows.append((0, name, "[red]⚠ ошибка[/red]", "", "", str(e)[:40]))
|
||||
|
||||
for name in all_patches["todo"]:
|
||||
try:
|
||||
patch_class = manager.load_patch_class(name)
|
||||
priority = getattr(patch_class, "priority", 0)
|
||||
patch_module = manager.load_patch_module(name)
|
||||
author = getattr(patch_module, "__author__", "")
|
||||
version = getattr(patch_module, "__version__", "")
|
||||
description = (patch_module.__doc__ or "").strip().split("\n")[0]
|
||||
patch_rows.append(
|
||||
(
|
||||
priority,
|
||||
name,
|
||||
"[yellow]⚠ todo[/yellow]",
|
||||
author,
|
||||
version,
|
||||
description,
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
patch_rows.append((0, name, "[yellow]⚠ todo[/yellow]", "", "", ""))
|
||||
|
||||
patch_rows.sort(key=lambda x: x[0], reverse=True)
|
||||
|
||||
for priority, name, status, author, version, desc in patch_rows:
|
||||
table.add_row(str(priority), name, status, author, version, desc[:50])
|
||||
|
||||
console.print(table)
|
||||
|
||||
|
||||
@app.command()
|
||||
@handle_errors
|
||||
def info(
|
||||
patch_name: str = typer.Argument(..., help="Имя патча"),
|
||||
tree: bool = typer.Option(False, "--tree", "-t", help="Древовидный вывод полей"),
|
||||
):
|
||||
"""Показать подробную информацию о патче"""
|
||||
manager = PatchManager(console)
|
||||
|
||||
all_patches = manager.discover_all()
|
||||
all_names = all_patches["ready"] + all_patches["todo"]
|
||||
|
||||
if patch_name not in all_names:
|
||||
raise PatcherError(f"Патч '{patch_name}' не найден")
|
||||
|
||||
patch_class = manager.load_patch_class(patch_name)
|
||||
|
||||
console.print(f"\n[bold cyan]Патч: {patch_name}[/bold cyan]")
|
||||
console.print("-" * 50)
|
||||
|
||||
if patch_class.__doc__:
|
||||
console.print(f"[white]{patch_class.__doc__.strip()}[/white]\n")
|
||||
|
||||
is_todo = patch_name in all_patches["todo"]
|
||||
if is_todo:
|
||||
console.print("[yellow]Статус: в разработке[/yellow]\n")
|
||||
else:
|
||||
patch = manager.load_patch(patch_name)
|
||||
status = "[green]включён[/green]" if patch.enabled else "[red]выключен[/red]"
|
||||
console.print(f"Статус: {status}")
|
||||
console.print(f"Приоритет: [cyan]{patch.priority}[/cyan]\n")
|
||||
|
||||
console.print("[bold]Поля конфигурации:[/bold]")
|
||||
|
||||
if tree:
|
||||
print_model_fields(console, patch_class)
|
||||
else:
|
||||
table = print_model_table(console, patch_class)
|
||||
console.print(table)
|
||||
|
||||
table = Table(show_header=True)
|
||||
table.add_column("Поле", style="yellow")
|
||||
table.add_column("Тип", style="cyan")
|
||||
table.add_column("По умолчанию")
|
||||
table.add_column("Описание")
|
||||
|
||||
for field_name, field_info in patch_class.model_fields.items():
|
||||
field_type = getattr(
|
||||
field_info.annotation, "__name__", str(field_info.annotation)
|
||||
)
|
||||
default = str(field_info.default) if field_info.default is not None else "-"
|
||||
description = field_info.description or ""
|
||||
table.add_row(field_name, field_type, default, description)
|
||||
|
||||
console.print(table)
|
||||
|
||||
if not is_todo:
|
||||
config_path = CONFIGS / f"{patch_name}.json"
|
||||
if config_path.exists():
|
||||
console.print(f"\n[bold]Текущая конфигурация[/bold] ({config_path}):")
|
||||
console.print(config_path.read_text())
|
||||
|
||||
|
||||
@app.command()
|
||||
@handle_errors
|
||||
def enable(patch_name: str = typer.Argument(..., help="Имя патча")):
|
||||
"""Включить патч"""
|
||||
manager = PatchManager(console)
|
||||
|
||||
if patch_name not in manager.discover_patches():
|
||||
raise PatcherError(f"Патч '{patch_name}' не найден")
|
||||
|
||||
patch = manager.load_patch(patch_name)
|
||||
patch.enabled = True
|
||||
patch.save_config()
|
||||
|
||||
console.print(f"[green]✔ Патч {patch_name} включён")
|
||||
|
||||
|
||||
@app.command()
|
||||
@handle_errors
|
||||
def disable(patch_name: str = typer.Argument(..., help="Имя патча")):
|
||||
"""Выключить патч"""
|
||||
manager = PatchManager(console)
|
||||
|
||||
if patch_name not in manager.discover_patches():
|
||||
raise PatcherError(f"Патч '{patch_name}' не найден")
|
||||
|
||||
patch = manager.load_patch(patch_name)
|
||||
patch.enabled = False
|
||||
patch.save_config()
|
||||
|
||||
console.print(f"[yellow]✔ Патч {patch_name} выключен")
|
||||
|
||||
|
||||
@app.command()
|
||||
@handle_errors
|
||||
def build(
|
||||
force: bool = typer.Option(
|
||||
False, "--force", "-f", help="Принудительная сборка при ошибках"
|
||||
),
|
||||
verbose: bool = typer.Option(False, "--verbose", "-v", help="Подробный вывод"),
|
||||
skip_compile: bool = typer.Option(
|
||||
False, "--skip-compile", "-s", help="Пропустить компиляцию (только патчи)"
|
||||
),
|
||||
):
|
||||
"""Декомпиляция, применение патчей и сборка APK"""
|
||||
conf = load_config(console)
|
||||
|
||||
apk_processor = APKProcessor(console, TOOLS)
|
||||
|
||||
apk = select_apk(console)
|
||||
apk_processor.decompile(apk, DECOMPILED)
|
||||
|
||||
manager = PatchManager(console)
|
||||
patches = manager.load_enabled_patches()
|
||||
|
||||
if not patches:
|
||||
console.print("[yellow]Нет включённых патчей")
|
||||
if not force:
|
||||
raise typer.Exit(0)
|
||||
|
||||
base_config = conf.base.copy()
|
||||
base_config["verbose"] = verbose
|
||||
base_config["decompiled"] = str(DECOMPILED)
|
||||
|
||||
console.print(f"\n[cyan]Применение патчей ({len(patches)})...[/cyan]")
|
||||
|
||||
with Progress(console=console) as progress:
|
||||
task = progress.add_task("Патчи", total=len(patches))
|
||||
|
||||
for patch in patches:
|
||||
success = patch.safe_apply(base_config)
|
||||
status = "[green]✔[/green]" if success else "[red]✘[/red]"
|
||||
progress.console.print(f" {status} [{patch.priority:2d}] {patch.name}")
|
||||
progress.advance(task)
|
||||
|
||||
successes = sum(p.applied for p in patch_objs)
|
||||
if successes == len(patch_objs):
|
||||
compile(apk, patch_objs)
|
||||
elif successes > 0 and (
|
||||
force or Prompt.ask("Продолжить сборку?", choices=["y", "n"]) == "y"
|
||||
):
|
||||
compile(apk, patch_objs)
|
||||
applied = sum(1 for p in patches if p.applied)
|
||||
failed = len(patches) - applied
|
||||
|
||||
console.print()
|
||||
if failed == 0:
|
||||
console.print(f"[green]✔ Все патчи применены ({applied}/{len(patches)})")
|
||||
else:
|
||||
console.print(
|
||||
f"[yellow]⚠ Применено: {applied}/{len(patches)}, ошибок: {failed}"
|
||||
)
|
||||
|
||||
if skip_compile:
|
||||
console.print("[yellow]Компиляция пропущена (--skip-compile)")
|
||||
return
|
||||
|
||||
should_compile = (
|
||||
failed == 0
|
||||
or force
|
||||
or Prompt.ask(
|
||||
"\nПродолжить сборку несмотря на ошибки?", choices=["y", "n"], default="n"
|
||||
)
|
||||
== "y"
|
||||
)
|
||||
|
||||
if should_compile:
|
||||
console.print()
|
||||
signed_apk, meta = apk_processor.build_and_sign(
|
||||
source=DECOMPILED,
|
||||
output_dir=MODIFIED,
|
||||
)
|
||||
generate_report(signed_apk, meta, patches, manager)
|
||||
else:
|
||||
console.print("[red]Сборка отменена")
|
||||
raise typer.Exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__": app()
|
||||
@app.command()
|
||||
@handle_errors
|
||||
def clean(
|
||||
all_dirs: bool = typer.Option(
|
||||
False, "--all", "-a", help="Очистить все директории включая modified и configs"
|
||||
)
|
||||
):
|
||||
"""Очистка временных файлов"""
|
||||
dirs_to_clean = [DECOMPILED]
|
||||
|
||||
if all_dirs:
|
||||
dirs_to_clean.extend([MODIFIED, CONFIGS])
|
||||
|
||||
for d in dirs_to_clean:
|
||||
if d.exists():
|
||||
shutil.rmtree(d)
|
||||
d.mkdir()
|
||||
console.print(f"[yellow]✔ Очищено: {d}")
|
||||
else:
|
||||
console.print(f"[dim]≫ Пропущено (не существует): {d}[/dim]")
|
||||
|
||||
console.print("[green]✔ Очистка завершена")
|
||||
|
||||
|
||||
@app.command()
|
||||
@handle_errors
|
||||
def config():
|
||||
"""Показать текущую конфигурацию"""
|
||||
conf = load_config(console)
|
||||
|
||||
console.print("[bold cyan]Конфигурация (config.json):[/bold cyan]\n")
|
||||
|
||||
console.print("[yellow]Tools:[/yellow]")
|
||||
console.print(f" apktool_jar_url: {conf.tools.apktool_jar_url}")
|
||||
console.print(f" apktool_wrapper_url: {conf.tools.apktool_wrapper_url}")
|
||||
|
||||
if conf.base:
|
||||
console.print("\n[yellow]Base:[/yellow]")
|
||||
for key, value in conf.base.items():
|
||||
console.print(f" {key}: {value}")
|
||||
|
||||
|
||||
@app.command()
|
||||
@handle_errors
|
||||
def version():
|
||||
"""Показать версию инструмента"""
|
||||
console.print(f"[cyan]anixarty-patcher[/cyan] v{__version__}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app()
|
||||
|
||||
Reference in New Issue
Block a user