From 25bd39dd4363b7b5a20f7b7bf215dde4c17b460e Mon Sep 17 00:00:00 2001 From: mrleo1nid <43583385+mrleo1nid@users.noreply.github.com> Date: Mon, 26 Oct 2026 04:02:22 +0300 Subject: [PATCH] feat: skip a verified placeholder file in The Only Doe 0.05 instead of failing the media conversion gui/journal_icon.png in that build is a text note ("PLACEHOLDER: Download a journal icon...") with an .svg next to it, so Ren'Py cannot load it either. A build can now list placeholder files by path and SHA-256; only an exact match is skipped and the report carries a non-blocking warning. The full conversion of the game (2898 files, 2.77 GB) now finishes without failed media. Co-Authored-By: Claude Sonnet 5.5 --- backend/app/converter/pipeline.py | 7 +++++-- backend/app/games/renpy_builds.py | 15 +++++++++++++++ backend/tests/test_game_profiles.py | 12 ++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/backend/app/converter/pipeline.py b/backend/app/converter/pipeline.py index adcf29d..d138f03 100644 --- a/backend/app/converter/pipeline.py +++ b/backend/app/converter/pipeline.py @@ -86,7 +86,7 @@ def convert_archive(archive: Path, filename: str, work: Path, progress: Progress return profile.convert(detection.root, work, progress) game_dir = find_game_dir(src_root) - from app.games.renpy_builds import identify, prepare_source, skipped_files, waive_known + from app.games.renpy_builds import identify, placeholder_files, prepare_source, skipped_files, waive_known build = identify(game_dir) # before the archives are unpacked and scripts decompiled rpas = sorted(game_dir.rglob("*.rpa")) for i, rpa in enumerate(rpas, 1): @@ -125,8 +125,11 @@ def convert_archive(archive: Path, filename: str, work: Path, progress: Progress files: dict[str, dict[str, Any]] = {} aliases: dict[str, str] = {} + placeholders = placeholder_files(build, game_dir) + for p in sorted(placeholders): + data.warn("Файл-заглушка оригинала не конвертируется", p.relative_to(game_dir).as_posix(), blocking=False) media = [p for p in game_dir.rglob("*") - if p.is_file() and assets.kind_of(p) and not _is_engine_file(game_dir, p)] + if p.is_file() and assets.kind_of(p) and not _is_engine_file(game_dir, p) and p not in placeholders] counts = {"image": 0, "audio": 0, "video": 0, "font": 0} failed: list[str] = [] done = 0 diff --git a/backend/app/games/renpy_builds.py b/backend/app/games/renpy_builds.py index f1fea16..28970ec 100644 --- a/backend/app/games/renpy_builds.py +++ b/backend/app/games/renpy_builds.py @@ -92,6 +92,8 @@ BUILDS: dict[str, dict] = { }, "bb19a33af00324fa027a629c052aee885e72f1b7bee7074403a5b9e20c69e2d8": { "id": "renpy/the-only-doe-0.05", + # The author left a text note instead of a picture; Ren'Py cannot load it either (there is an .svg next to it) + "placeholders": {"gui/journal_icon.png": "4b45bd642d6e43c85dabb5e8b0df3e52e628cde7640519af1a64751750f6b711"}, # One menu branch tests `... before_sleeping=2` (assignment), which Ren'Py cannot compile either: # sleep.rpyc holds the same text. The port corrects the operator; the digest pins the exact line. "lines": { @@ -134,6 +136,19 @@ def skipped_files(build_id: str | None) -> set[str]: return {name.lower() for name in build.get("skip", ())} if build else set() +def placeholder_files(build_id: str | None, game_dir: Path) -> set[Path]: + """Files of the build that carry text instead of media; only an exact SHA-256 match is skipped.""" + build = next((b for b in BUILDS.values() if b["id"] == build_id), {}) + found = set() + for relative, digest in build.get("placeholders", {}).items(): + path = game_dir / relative + if path.is_file(): + with path.open("rb") as stream: + if hashlib.file_digest(stream, "sha256").hexdigest() == digest: + found.add(path) + return found + + def identify(game_dir: Path) -> str | None: """The id of a known build, or None.""" build = BUILDS.get(fingerprint(game_dir)) diff --git a/backend/tests/test_game_profiles.py b/backend/tests/test_game_profiles.py index 9d851a1..f9a0192 100644 --- a/backend/tests/test_game_profiles.py +++ b/backend/tests/test_game_profiles.py @@ -71,3 +71,15 @@ def test_known_build_line_patch_requires_the_exact_line(): assert renpy_builds.prepare_source("renpy/the-only-doe-0.05", "other.rpy", source) == source edited = source.replace("spitroast", "other") assert renpy_builds.prepare_source("renpy/the-only-doe-0.05", "sleep.rpy", edited) == edited + + +def test_placeholder_media_is_skipped_only_on_an_exact_match(tmp_path): + from app.games import renpy_builds + + icon = tmp_path / "gui" / "journal_icon.png" + icon.parent.mkdir() + icon.write_text("PLACEHOLDER: Download a journal icon from a free resource and save as journal_icon.png in gui folder.") + assert renpy_builds.placeholder_files("renpy/the-only-doe-0.05", tmp_path) == {icon} + assert renpy_builds.placeholder_files(None, tmp_path) == set() + icon.write_text("a real file now") + assert renpy_builds.placeholder_files("renpy/the-only-doe-0.05", tmp_path) == set()