# Формат ресурсов Prince of Persia (MS-DOS, каталог `MSDOS/`) Документ описывает бинарный формат `*.DAT`-файлов ресурсов DOS-версии PoP. Исходников для этой версии нет, поэтому всё, что ниже — результат структурного (эмпирического) анализа реальных файлов из `MSDOS/`, а не чтения кода. Уровень уверенности указан для каждого раздела. Все находки проверены скриптами (Python), которые разбирают файл и валидируют согласованность (например: смещение+размер последней записи таблицы точно совпадает с началом самой таблицы — то есть данные и каталог стыкуются без дыр). **Основной источник спецификации формата — `POP-DAT-FormatSpecifications.pdf`** (и его текстовая конверсия `POP-DAT-FormatSpecifications.txt` в этой же папке, для grep/цитирования): *«Prince of Persia — Specifications of File Formats»*, Princed Development Team, 2008 — каноническая спецификация формата `DAT v1.0`, на которой построен и SDLPoP, и Princed Resources. Разбирает контейнер, индекс, чек-сумму, кодеки изображений (RLE / LZG), палитры, формат уровней (room mapping, wall-drawing, room-linking, guards, start position, door events), звук (digital waves / MIDI / PC speaker), бинарные файлы и Mac-варианты. При любом расхождении между эмпирическими находками ниже и этим документом — источником истины считать спецификацию (сверять §-номера: её §3.x). Дополнительно как справка при реализации (порт на ZX Sprinter): - **SDLPoP** (github.com/NagyD/SDLPoP, GPLv3) — open-source реализация DOS-версии на основе дизассемблирования оригинального `PRINCE.EXE`. Содержит рабочий код чтения `.DAT`-файлов и полный кодек изображений/уровней. Точные структуры (`dat_table_type` и т.п.), процитированные ниже, получены через автоматический пересказ содержимого файла третьей стороной, а не через прямое чтение исходника — поэтому такие детали помечены как "требует сверки при реализации", в отличие от эмпирически подтверждённых байтовых оффсетов. - **Princed Resources / PR** (github.com/NagyD/PR, princed.org, GPLv2) — это профильный инструмент именно для распаковки/запаковки `.DAT`-ресурсов PoP (версии DAT 1 и 2), сделанный тем же автором. В его документации (`doc/Dataformats.md`) официально описаны экспортные форматы ресурсов — это подтверждает и уточняет часть находок ниже (см. §3–4), и является более надёжным источником, чем самостоятельная догадка по байтам. **Важная находка:** репозиторий SDLPoP в папке `data/` содержит не только код движка, но и **реальные ресурсы игры** — как сырые `.DAT`-контейнеры, так и уже распакованные поштучно файлы (PNG-кадры спрайтов, `.pal`-палитры, `.bin`-дампы уровней), см. §7. Это готовый источник ассетов и одновременно независимая проверка формата, описанного в этом документе. --- ## 1. Общий контейнер `.DAT` (уверенность: высокая, подтверждено на 28 файлах) Каждый `*.DAT`-файл (кроме служебных `config.dat`/`setup.dat`, см. §5) — это простой архив-контейнер: блок данных + оглавление (каталог ресурсов) в конце файла. ### 1.1 Заголовок файла (6 байт, смещение 0x00) | Смещение | Размер | Поле | Значение | |----------|--------|--------------|----------| | 0x00 | 4 | `tableOffset`| LE u32. Абсолютное смещение в файле, с которого начинается таблица оглавления. Совпадает с "концом данных". | | 0x04 | 2 | `tableSize` | LE u16. Размер таблицы оглавления в байтах. | Инвариант, подтверждённый на всех 28 `.dat`-файлах в каталоге: ``` tableOffset + tableSize == размер файла (без исключений) ``` Данные ресурсов идут сразу после заголовка, начиная с байта 0x06, и заканчиваются на `tableOffset`. ### 1.2 Таблица оглавления (по смещению `tableOffset`, длиной `tableSize`) Таблица — плоский массив записей по 8 байт. Количество записей: `tableSize / 8` (округление вниз; в файле почти всегда остаётся 2 "лишних" байта в хвосте таблицы — назначение не установлено, вероятно, служебное поле инструмента-упаковщика или паддинг; на итоговый разбор не влияет). Запись (8 байт): | Смещение в записи | Размер | Поле | Описание | |---|---|---|---| | 0 | 2 | `size` | LE u16 — размер данных ресурса в байтах | | 2 | 2 | `id` | LE u16 — идентификатор ресурса | | 4 | 2 | `offset` | LE u16 — **абсолютное** смещение данных ресурса в файле (не относительное!) | | 6 | 2 | `reserved` | во всех проверенных записях (сотни штук) всегда `0x0000` | Проверено на `levels.dat`: 16 записей, `id`=2000..2015, и `offset[i] + size[i] == offset[i+1]` для всех соседних записей, а последняя запись заканчивается ровно на `tableOffset` — то есть данные абсолютно плотно упакованы, без пробелов, для этого файла. В других файлах (например `guard.dat`) между записями изредка есть небольшие зазоры в несколько байт (вероятно, выравнивание или "мёртвые" байты от инструмента-компоновщика) — не является нарушением формата. ### 1.3 Диапазоны `id` по типам файлов (собрано эмпирически) Похоже, что числовые ID образуют условные "пространства имён" по типу контента — вероятно, глобальные константы в оригинальном коде: | Файл(ы) | Диапазон `id` | Кол-во записей | Предполагаемое содержимое | |---|---|---|---| | `levels.dat` | 2000–2015 | 16 | id=2000 — служебный блок (16 байт, см. §3); id=2001..2015 — 15 уровней | | `guard.dat`, `fat.dat`, `skel.dat`, `shadow.dat` | 750–784 (варьируется) | ~30–35 | id=751(750) — служебный блок; остальные — кадры анимации спрайта | | `vizier.dat` | аналогично guard | — | кадры анимации визиря | | `kid.dat` | ~400+ | 220 | кадры анимации игрока (намного больше — герой умеет гораздо больше действий) | | `guard1.dat`, `guard2.dat` | 750 (1 запись) | 1 | вероятно, дополнительные/альтернативные кадры/варианты | | `title.dat` | 40–55 | 12 | картинки титульного экрана/логотипов | | `cpalace.dat`,`epalace.dat`,`vpalace.dat`,`cdungeon.dat`,`edungeon.dat`,`vdungeon.dat` | 200–1343 | 205–238 | фоновые тайлы дворца/подземелья, отдельно для CGA(`c*`)/EGA(`e*`)/VGA(`v*`) | | `pv.dat` | 800–981 | 103 | доп. графика (возможно, "Prince/Vizier" катсцены) | | `digisnd1/2/3.dat` | 10000+ | 20–44 | оцифрованный звук (Covox/Disney Sound Source) | | `midisnd1/2.dat` | 10024+ / аналог | 16 | General MIDI музыка | | `mt32snd1/2.dat` | 10000+ | 24/7 | музыка для Roland MT-32 | | `ibm_snd1/2.dat` | 10000+ | 44 | музыка/эффекты через PC-спикер | | `prince.dat` | — (1 крупный ресурс) | — | MIDI-тема (вероятно, финальная тема "Принц"/титры — см. текстовые события "The Princess awaits") | Во всех файлах первая (наименьшая по `id`) запись — маленький "служебный" ресурс (6–44 байта), стоящий перед основным контентом. Скорее всего это локальная мини-таблица/палитра/список ссылок для данного набора ресурсов — по аналогии с тем, что у уровней id=2000 отдельно от самих уровней (см. §3). --- ## 2. Формат уровня (`levels.dat`, id=2001..2015) — уверенность: высокая Каждая запись уровня имеет размер **2305 байт** и по данным полностью совпадает по объёму с уровнями из Apple II версии (`01 POP Source/Levels/LEVELn` — ровно **2304 байта** каждый, см. `docs/APPLEII_RESOURCE_FORMAT.md`). Вывод: формат карты уровня в DOS-версии, судя по всему, **унаследован практически без изменений от оригинального Apple II формата** (Джордан Мехнер писал игру на 6502 и данные уровней переносились как есть), с добавлением одного лишнего байта в DOS-упаковке (2304+1=2305 — вероятно, контрольный байт/ маркер конца, добавленный DOS-упаковщиком ресурсов, а не часть игровых данных). **Практическое следствие:** байтовая структура самого уровня (тайлы 3×10 на экран, 24 экрана, таблицы стражников, дверей и т.д.) должна документироваться один раз — по исходникам Apple II (см. соответствующий раздел), и напрямую применяться к DOS `levels.dat`, отбросив 1 лишний байт в конце каждой записи. Байтовые значения тайлов в дампе (в основном 0x00–0x39) визуально согласуются с диапазоном небольших целых кодов тайлов, что для формата карты и ожидается. Первая запись, id=2000, размер 16 байт — не уровень, а отдельный маленький блок (возможно: количество уровней, начальный уровень, версия формата, стартовые координаты игрока/охраны по умолчанию). Точное назначение не установлено — требует сопоставления с диз­ассемблированным кодом загрузчика уровней (в SDLPoP это, по всем признакам, отдельная процедура чтения `level` ресурса). **Сверка с независимой распаковкой SDLPoP (`data/LEVELS/`):** там лежат файлы `res2000.bin`…`res2015.bin` (16 штук — количество совпадает). Байты `res2001.bin` содержательно совпадают с тайловыми данными нашей записи id=2001 (та же последовательность значений тайлов) — это подтверждает, что нумерация id верна. Но есть нестыковка по размеру: у SDLPoP `res2000.bin` — **2305 байт** (как и все остальные), тогда как в нашем локальном `levels.dat` запись id=2000 — всего **16 байт**. Скорее всего, это разные релизы/сборки игры (см. §7 — размеры некоторых `.dat` у SDLPoP и у нас уже отличались), и в версии SDLPoP маленький служебный блок либо отсутствует, либо пронумерован иначе. Это не меняет сам формат контейнера, но означает, что **точную семантику 16-байтного блока id=2000 в нашей копии игры пока нельзя проверить через данные SDLPoP** — открытый вопрос. --- ### 2.1 Кросс-подтверждение по исходникам Apple II Фоновый анализ исходников Apple II (см. `docs/APPLEII_RESOURCE_FORMAT.md`) подтверждает и объясняет структуру уровня напрямую по коду. Уровень на Apple II — дамп структуры `blueprnt` (`EQ.S`): `BLUETYPE`(720Б, 24 экрана×30 тайлов) + `BLUESPEC`(720Б) + `LINKLOC`(256Б) + `LINKMAP`(256Б) + `MAP`(96Б, граф соседних экранов) + `INFO`(256Б, метаданные/старт Кида/стражников) = ровно 2304 байта. Учитывая, что DOS-запись уровня — это ровно 2304+1 байт с байтовыми значениями тайлов, укладывающимися в диапазон 0–29 (id тайла) плюс служебные биты (аналогично `idmask=%00011111`, `reqmask=%00100000` из `EQ.S:484-486`), можно с высокой уверенностью считать, что **DOS-версия использует ту же самую раскладку `blueprnt`**, лишь с добавлением одного байта (вероятно, контрольной суммы) в конце DOS-упаковки. Это снимает необходимость отдельно реверсить формат уровня для DOS — таблица тайлов, enum id (0=space...29=archtop4), формат `LINKLOC`/`LINKMAP` и `INFO` из Apple II документа применимы напрямую. ## 3. Графика (спрайты и фоновые тайлы) — уверенность: средняя/низкая Файлы `kid.dat`, `guard.dat`, `fat.dat`, `shadow.dat`, `skel.dat`, `vizier.dat`, `title.dat`, `c/e/v-palace.dat`, `c/e/v-dungeon.dat`, `pv.dat` хранят по контейнерному формату (§1) множество мелких чанков (десятки—сотни байт каждый). Что подтверждено: - Наборы `shadow.dat`/`kid.dat` и `fat.dat`/`vizier.dat` содержат **побайтово идентичные фрагменты** данных в начале файла — это ожидаемо: "Тень" (Shadow) визуально копирует анимацию Кида, а "Толстый страж" (Fat guard, пасхалка) переиспользует модель Визиря. Подтверждает, что персонажи одного "типа тела" используют общий набор геометрии/анимации. - Отдельные чанки *не* имеют очевидного унифицированного заголовка (высота/ширина/палитра) фиксированного размера — попытка интерпретировать первые байты чанка как `{height:u16, width:u16, flags:u16}` не подтвердилась на реальных данных (получаются нереалистичные размеры для маленьких чанков). Вероятно, как и в Apple II версии (см. `FRAMEDEF.S`/`SEQTABLE.S`), геометрия кадра (ширина, высота, точка привязки) хранится **отдельно от самих пиксельных данных** — в таблицах внутри `PRINCE.EXE`, а не в `.DAT`-чанке. Сам чанк, вероятно, содержит только упакованные пиксельные данные (RLE/дельта-упаковка, по аналогии с `UNPACK.S` в Apple II исходниках). - Точный алгоритм упаковки пикселей **не восстановлен** в рамках этого анализа по сырым байтам — байт-в-байт разбор распаковщика без дизассемблирования `PRINCE.EXE` надёжно не сделать. **Но для практических целей это не требуется**: см. §7 — в SDLPoP уже есть тот же самый набор изображений в готовом, распакованном виде (PNG), которым можно пользоваться напрямую как источником ассетов, не реализуя свой декодер `.DAT`-пикселей. Писать собственный декодер имеет смысл только если понадобится читать оригинальные `.DAT` "на лету" (например, для точной сверки контента именно нашей копии игры) — тогда ориентир — исходник SDLPoP (`src/seg009.c`). --- ## 4. Звук — уверенность: высокая (по структуре), низкая (по деталям кодека) Обнаружено 4 параллельных набора звуковых ресурсов под разные звуковые устройства DOS-эпохи — типично для игр начала 1990-х с "звуковым меню": | Файл | Устройство | Формат чанка | |---|---|---| | `midisnd1.dat`, `midisnd2.dat` | General MIDI / MPU-401 | каждый чанк = 2-байтовый LE-префикс длины + встроенный Standard MIDI File (`MThd`...`MTrk`...) | | `mt32snd1.dat`, `mt32snd2.dat` | Roland MT-32/CM-32L | тот же формат: префикс длины + `MThd`/`MTrk`, с MT-32-специфичными SysEx (видны строки `MT-32.mff`, текстовые мета-события вроде `"The Princess awaits"`) | | `prince.dat` | (аналогично MIDI) | отдельный крупный музыкальный ресурс, тот же MIDI-контейнер — вероятно, финальная тема | | `digisnd1/2/3.dat` | Covox / Disney Sound Source / Sound Blaster (оцифрованный звук) | чанк начинается с нескольких служебных байт, среди которых слово `0x2AF8` = 11000 — похоже на частоту дискретизации 11 кГц; далее — сырые 8-битные PCM-сэмплы (значения кластеризуются вокруг ~0x7A–0x90, типично для беззнакового 8-бит аудио, смещённого к середине шкалы) | | `ibm_snd1.dat`, `ibm_snd2.dat` | PC Speaker | чанк — последовательность троек байт похожих на (длительность, делитель_частоты) — простой формат "бипера", отличный от MIDI | Подтверждено разбором первых чанков в каждом файле (см. байтовые дампы, проверялись скриптом). Точная семантика полей внутри `digisnd`/`ibm_snd` (разрядность, порядок байт служебного заголовка) не выведена до конца — при реализации порта достаточно распознавания по типу файла и (для MIDI-семейства) можно напрямую воспроизводить встроенный Standard MIDI File, пропустив 2-байтовый префикс длины. --- ## 5. Готовые распакованные ассеты в SDLPoP (`data/`) — практический источник для порта Репозиторий github.com/NagyD/SDLPoP содержит папку `data/`, где, помимо самих `.DAT`-контейнеров, каждый ресурс **продублирован в виде отдельно распакованного файла**, названного по его `id` из таблицы оглавления (§1.2). Проверено через GitHub API (`api.github.com/repos/NagyD/SDLPoP/contents/...`): | Подпапка/файл в `data/` | Содержимое | Соответствие нашему разбору | |---|---|---| | `GUARD.DAT`, `GUARD1.DAT`, `GUARD2.DAT` | сырые `.DAT` | размер **побайтово совпадает** с нашими локальными `guard.dat`/`guard1.dat`/`guard2.dat` (6950 / 117 / 117 байт) | | `DIGISND1.DAT`, `MIDISND2.DAT` и др. | сырые `.DAT` | размер **не совпадает** с нашими локальными файлами (48545 vs 50101, 18408 vs 18958) — другой релиз/сборка игры | | `GUARD/res751.png` … `res784.png` | готовые PNG, по одному на кадр анимации, имя = `res.png` | id-диапазон (751-784) точно совпадает с нашим разбором `guard.dat` | | `VPALACE/res200.pal`, `res201.png`, `res202.png`, … | палитра (JASC `.pal`) + PNG-кадры фонов дворца, **VGA-вариант (256 цветов)** | id-диапазон (200+) совпадает с `vpalace.dat` | | `LEVELS/res2000.bin` … `res2015.bin` | сырые дампы уровней по 2304-2305 байт | id совпадает с `levels.dat`; содержимое `res2001.bin` **сверено побайтово** с нашим id=2001 — тайловые данные совпадают | | `KID/`, `PRINCE/`, `SHADOW/`, `SKEL/`, `VIZIER/`, `FAT/`, `TITLE/`, `VDUNGEON/`, `PV/`, `IBM_SND1/`, `IBM_SND2/`, `font/`, `music/` | аналогичные наборы для остальных ресурсов | не проверялись по отдельности, но структура (папка на каждый `.dat`, файлы `res.ext`) наблюдается одинаково | **Вывод:** это данные из немного **другого релиза DOS-версии**, чем те, что лежат у нас в `MSDOS/` (см. расхождение в размере `digisnd`/`midisnd`), но формат контейнера и нумерация `id` — те же самые. Практически это значит: 1. Для получения играбельных PNG-спрайтов и VGA-фонов **не нужно реализовывать декодер сжатия пикселей** — можно взять готовые файлы `data/<ИМЯ>/res.png` напрямую как исходный материал для конвертации под видеорежим ZX Sprinter (в т.ч. `VPALACE`/`VDUNGEON` — уже 256-цветный VGA-арт, что прямо отвечает на вопрос про полноцветность). 2. Если в проекте важно использовать именно ту версию контента, что в наших `MSDOS/*.dat` (а не версию из SDLPoP) — распаковку своих файлов всё же придётся делать (кодек пикселей по-прежнему не восстановлен для сырых `.DAT`, см. §3), либо принять решение работать с версией SDLPoP как мастер-источником ассетов вместо своей. --- ## 6. Служебные не-ресурсные файлы - `config.dat`, `setup.dat` — 28 байт, не являются ресурсными контейнерами (не проходят проверку §1.1 — "размер" получается больше самого файла). Скорее всего простые бинарные структуры настроек (звук/видеорежим, выбранный на этапе `SETUP.EXE`/`INSTALL.EXE`), не связаны с игровым контентом. - `desktopd.cfg`, `setup.cfg` — текстовые/бинарные конфиги DOS-инсталлятора, вне скоупа игровых ресурсов. - `PRINCE.EXE` / `PRINCE.REM` — почти идентичны (отличие в единичных байтах в районе смещения ~0x4ED0), похоже на кряк/патч одного байта проверки — не относится к формату ресурсов. - `old-games.nfo` — ASCII-арт NFO релиз-группы (old-games.ru), не игровые данные. --- ## 7. Итоговая таблица уверенности | Раздел | Уверенность | Как подтверждено | |---|---|---| | Контейнер `.DAT` (заголовок + таблица) | Высокая | Проверено скриптом на всех 28 файлах, инвариант offset+size выполняется без исключений; независимо подтверждено именованием `res.*` в SDLPoP `data/` | | ID-пространства ресурсов | Средняя-высокая | Наблюдение по диапазонам + сверка с `res` именами файлов SDLPoP и побайтовым содержимым `res2001.bin` | | Формат уровня = формату Apple II | Высокая (по размеру и содержимому), служебный блок id=2000 — открытый вопрос | Совпадение размера (2304 vs 2305), тайловые байты сходятся с `res2001.bin` из SDLPoP | | Формат изображений/спрайтов (сырой `.DAT`) | Низкая-средняя | Контейнер подтверждён, кодек пикселей — нет; но практически закрыто наличием готовых PNG в SDLPoP `data/` (§5) | | Формат звука (тип контейнера) | Высокая для MIDI-семейств, средняя для digisnd/ibm_snd | Явные MIDI-сигнатуры `MThd`/`MTrk` видны в байтах | **Рекомендация для дальнейшей работы:** для получения арт-ассетов (спрайты, фоны, палитры) — использовать готовые распакованные файлы из `github.com/NagyD/SDLPoP/tree/master/data` (§5), это быстрее и надёжнее самостоятельной реализации декодера. Декодер сырого `.DAT`-формата изображений и точную семантику служебных полей `digisnd`/`ibm_snd` (§3, §4) стоит восстанавливать только если понадобится читать именно нашу локальную копию `MSDOS/*.dat` "как есть" — тогда ориентир прежний: исходник SDLPoP (`src/seg009.c`, `src/data.c`/`data.h`).