"""paths.py — единственное место, где инструменты SprPoP знают раскладку каталогов приложения. Все упаковщики берут пути отсюда, поэтому папку SprPoP можно перенести куда угодно: важно лишь, что рядом с tools/ лежат assets/, gen/ и build/. Раскладка: assets/orig/ ОРИГИНАЛЬНЫЕ данные (SDLPoP, записи музыки и, необязательно, MSDOS). В репозиторий не идут — копирайт; выкачивает их tools/fetch_orig.py (`make fetch`), см. assets/orig/README.md. build/atl// промежуточный выхлоп упаковщиков: отдельные атласы, из которых потом склеиваются архивы. Пересоздаётся, в репозиторий не идёт. assets/packed/ ровно то, что ложится на диск игры, уже в раскладке каталогов диска (BG/, KID/, ...). В репозитории — иначе из чистого клона без assets/orig/ ничего не собрать. gen/ генерируемые C-заголовки. Тоже в репозитории и по той же причине: это такой же производный артефакт, как .arc, и без него не скомпилировать src/. """ from pathlib import Path TOOLS = Path(__file__).resolve().parent APP = TOOLS.parent # --- вход: оригинальные данные ------------------------------------------- ORIG = APP / "assets" / "orig" SDLPOP = ORIG / "SDLPoP" SDLPOP_DATA = SDLPOP / "data" SDLPOP_SRC = SDLPOP / "src" MSDOS = ORIG / "MSDOS" MUSIC_ROOT = ORIG / "PoP1_DOS_music" # Записи DOS-версии лежат в четырёх вариантах. Имена треков во всех наборах # одинаковые, различаются каталог и расширение; ogg_MT-32 — та же музыка в # исполнении Roland MT-32, а не другой кодек. Ключ = имя формата в Makefile. MUSIC_FORMATS = { "flac": ("flac", ".flac"), "mp3": ("mp3", ".mp3"), "ogg": ("ogg", ".ogg"), "mt32": ("ogg_MT-32", ".ogg"), } def music_src(fmt="flac"): """(каталог, расширение) исходников музыки для формата.""" if fmt not in MUSIC_FORMATS: raise SystemExit( "неизвестный формат музыки %r; есть: %s" % (fmt, ", ".join(sorted(MUSIC_FORMATS)))) sub, ext = MUSIC_FORMATS[fmt] return MUSIC_ROOT / sub, ext # --- источники звуковых эффектов ------------------------------------------ # Контейнеры digisnd1..3 есть в ДВУХ поставках, и они не одинаковы: в SDLPoP # лежит версия из репозитория (её и качает fetch_orig.py), в MSDOS/ — из # оригинального дистрибутива 1.0, если пользователь его себе положил. # ПО УМОЛЧАНИЮ БЕРЁМ SDLPoP: сборка обязана работать без MSDOS вообще. # Ключ = значение SND_SRC в Makefile; регистр имён в поставках разный. SND_SOURCES = { "sdlpop": (SDLPOP_DATA, "DIGISND%d.DAT"), "msdos": (MSDOS, "digisnd%d.dat"), } # Ноты PC-СПИКЕРА и МЕЛОДИИ — два других набора того же движка. Нужны нам # по разным причинам: мелодии мы играем отдельно (MUS/), а из нот спикера # добираем те номера, которых нет НИ в оцифровке, НИ среди мелодий — сейчас # это ровно звук 38 (мигание надписи «Press Button»). В поставке SDLPoP # наборы лежат распакованными каталогами res1XXXX.bin, в дистрибутиве DOS — # контейнерами .DAT; поддерживаем оба вида. SND_MIDI = { "sdlpop": (SDLPOP_DATA, "MIDISND%d.DAT", 2), "msdos": (MSDOS, "midisnd%d.dat", 2), } SND_SPEAKER = { "sdlpop": (SDLPOP_DATA, "IBM_SND%d", 2), "msdos": (MSDOS, "ibm_snd%d.dat", 2), } def snd_extra(kind, name="sdlpop"): """(каталог, шаблон, сколько контейнеров) для набора нот или мелодий. kind — "midi" или "speaker". Набора может не быть вовсе (у пользователя нет MSDOS, поставка неполная) — это НЕ ошибка: вызывающий просто пропускает добор. Возвращает None, если источник неизвестен. """ table = {"midi": SND_MIDI, "speaker": SND_SPEAKER}.get(kind) if table is None: raise SystemExit("неизвестный набор %r; есть: midi, speaker" % kind) return table.get(name) def snd_src(name="sdlpop"): """(каталог, шаблон имени) контейнеров оцифровки для источника.""" if name not in SND_SOURCES: raise SystemExit( "неизвестный источник звука %r; есть: %s" % (name, ", ".join(sorted(SND_SOURCES)))) return SND_SOURCES[name] def find_ci(directory, name): """Путь к файлу без оглядки на регистр (FAT-имена приезжают по-разному). Возвращает None, если файла нет. Точное совпадение проверяется первым, чтобы на case-insensitive ФС не читать каталог зря. """ p = directory / name if p.exists(): return p if not directory.is_dir(): return None low = name.lower() for e in directory.iterdir(): if e.name.lower() == low: return e return None # --- выход ---------------------------------------------------------------- ATL = APP / "build" / "atl" # промежуточные атласы, по набору в папке PACKED = APP / "assets" / "packed" # раскладка диска игры GEN = APP / "gen" # генерируемые заголовки def atl(name): """Каталог промежуточного выхлопа набора; создаётся при обращении.""" p = ATL / name p.mkdir(parents=True, exist_ok=True) return p def packed(name): """Каталог диска игры (BG, KID, ...); создаётся при обращении.""" p = PACKED / name p.mkdir(parents=True, exist_ok=True) return p