feat: add mod editing, revision merges and secure game storage

Add immutable revisions, PGVN editor, asset replacement, merge and rebase workflows, authenticated media delivery and reachability GC. Harden the script interpreter, pin unrpyc and remove tracked build metadata. Include current compatibility checks, Naninovel import and regression tests.
This commit is contained in:
mrleo1nid committed 2026-10-25 04:42:48 +03:00
1 parent 06ff19ac4a
commit c8d83b3fe7
65 files changed
+3825 -68

No files matched your search

+26
View File
@@ -19,6 +19,7 @@ from app.converter.rpa import RpaError, extract_rpa
from app.converter.rpy_lexer import ParseError
from app.converter.rpy_parser import RpyParser, ScriptData
from app.converter.rpyc import decompile_missing
from app.converter.compatibility import CompatibilityError, Issue, audit_ir, detect, require_supported_scripts
log = logging.getLogger(__name__)
FORMAT = "pgvn/1"
@@ -46,12 +47,26 @@ def convert_archive(archive: Path, filename: str, work: Path, progress: Progress
data = ScriptData()
if archive.is_dir():
detection = detect(archive)
if detection.engine == "naninovel":
from app.converter.naninovel import convert_naninovel
return convert_naninovel(detection.root, work, progress)
# Inspect scripts before copying/converting multi-gigabyte media collections.
from app.converter.preflight import inspect_directory
inspect_directory(detection.root)
progress("Копирование файлов игры")
game_dir = src_root / "game"
shutil.copytree(find_game_dir(archive), game_dir)
else:
from app.converter.preflight import inspect_archive
progress("Проверка движка и сценариев до распаковки медиа")
inspect_archive(archive, filename)
progress("Распаковка архива")
extract(archive, filename, src_root)
detection = detect(src_root)
if detection.engine == "naninovel":
from app.converter.naninovel import convert_naninovel
return convert_naninovel(detection.root, work, progress)
game_dir = find_game_dir(src_root)
rpas = sorted(game_dir.rglob("*.rpa"))
@@ -79,6 +94,8 @@ def convert_archive(archive: Path, filename: str, work: Path, progress: Progress
data.warn("Слишком глубокая вложенность", rel)
if not any(lbl["name"] == "start" for labels in data.modules.values() for lbl in labels):
raise RuntimeError("В скриптах не найдена метка start — нечего запускать")
audit_ir(data)
require_supported_scripts(data)
files: dict[str, dict[str, Any]] = {}
aliases: dict[str, str] = {}
@@ -112,6 +129,10 @@ def convert_archive(archive: Path, filename: str, work: Path, progress: Progress
aliases[rel.lower()] = logical
counts[assets.kind_of(p)] += 1
if failed:
raise CompatibilityError("renpy", [Issue("media_conversion", "Не удалось конвертировать обязательное медиа", name)
for name in failed])
progress("Сборка формата PGVN")
_auto_images(data, aliases)
missing: set[str] = set()
@@ -176,6 +197,11 @@ def convert_archive(archive: Path, filename: str, work: Path, progress: Progress
total = sum(f["size"] for f in files.values())
report = {
"engine": "renpy",
"compatible": True,
"converterVersion": "pgvn-tools/2",
"unrpycRevision": (get_settings().unrpyc_path.parent / "REVISION").read_text().strip()
if (get_settings().unrpyc_path.parent / "REVISION").is_file() else "unavailable",
"format": FORMAT,
"durationSec": round(time.monotonic() - started, 1),
"scripts": len(rpy_files),