Files
Sprinter-SDCC/applications/PoP/docs/MSDOS_RESOURCE_FORMAT.md
T
snark13 cd8d566d82 applications/PoP: порт Prince of Persia — PoC (roomtest) + пайплайн
Порт PoP на Sprinter.  Текущий PoC — applications/PoP/roomtest/:
комната 1 (фон-композиция тайлов) + Kid с управлением на raw-клавиатуре
и коллизией с картой.

- roomtest — pop_bg (фон), pop_kid (спрайты Kid, column-major флип,
  seqtbl-анимация), pop_ctrl (порт control() PoP на held-state
  kbd_raw), pop_map (коллизия seg004/005: бег/стоп у стены,
  падение/приземление, отскок seq_47, вертикальный прыжок K4.1).
  MEMORY=small (DATA сразу за CODE, ~23КБ кода не лезет в huge).
- toolchain (PoP) — pop_pack_kid/pop_pack_bg/render_room/extract —
  распаковка res-графики MSDOS в атласы + композиция комнат.
- toolchain/ (корень) — make_hdd.sh (быстрый HDD-тест вместо FDD),
  png_strip.py / room_compose.py (ассет-пайплайн).
- docs — PORT_PLAN, KID_PLAN, форматы ресурсов (Apple II / MSDOS / DAT).
- bgtest/coltest/poc — ранние PoC (фон, коллизия, первый прототип).

.gitignore: build-артефакты applications/*/*/*; исключены внешние
референс-репозитории (SDLPoP/mininim/PR/Apple-II — свои git-клоны) и
оригинальные game-данные MSDOS/ (копирайт, только для реверса форматов).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 17:51:08 +03:00

28 KiB
Raw Blame History

Формат ресурсов Prince of Persia (MS-DOS, каталог MSDOS/)

Документ описывает бинарный формат *.DAT-файлов ресурсов DOS-версии PoP. Исходников для этой версии нет, поэтому всё, что ниже — результат структурного (эмпирического) анализа реальных файлов из MSDOS/, а не чтения кода. Уровень уверенности указан для каждого раздела. Все находки проверены скриптами (Python), которые разбирают файл и валидируют согласованность (например: смещение+размер последней записи таблицы точно совпадает с началом самой таблицы — то есть данные и каталог стыкуются без дыр).

Для справки при последующей реализации (порт на 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 20002015 16 id=2000 — служебный блок (16 байт, см. §3); id=2001..2015 — 15 уровней
guard.dat, fat.dat, skel.dat, shadow.dat 750–784 (варьируется) ~3035 id=751(750) — служебный блок; остальные — кадры анимации спрайта
vizier.dat аналогично guard кадры анимации визиря
kid.dat ~400+ 220 кадры анимации игрока (намного больше — герой умеет гораздо больше действий)
guard1.dat, guard2.dat 750 (1 запись) 1 вероятно, дополнительные/альтернативные кадры/варианты
title.dat 4055 12 картинки титульного экрана/логотипов
cpalace.dat,epalace.dat,vpalace.dat,cdungeon.dat,edungeon.dat,vdungeon.dat 2001343 205238 фоновые тайлы дворца/подземелья, отдельно для CGA(c*)/EGA(e*)/VGA(v*)
pv.dat 800981 103 доп. графика (возможно, "Prince/Vizier" катсцены)
digisnd1/2/3.dat 10000+ 2044 оцифрованный звук (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.binres2015.bin (16 штук — количество совпадает). Байты res2001.bin содержательно совпадают с тайловыми данными нашей записи id=2001 (та же последовательность значений тайлов) — это подтверждает, что нумерация id верна. Но есть нестыковка по размеру: у SDLPoP res2000.bin2305 байт (как и все остальные), тогда как в нашем локальном 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.pngres784.png готовые PNG, по одному на кадр анимации, имя = res<id>.png id-диапазон (751-784) точно совпадает с нашим разбором guard.dat
VPALACE/res200.pal, res201.png, res202.png, … палитра (JASC .pal) + PNG-кадры фонов дворца, VGA-вариант (256 цветов) id-диапазон (200+) совпадает с vpalace.dat
LEVELS/res2000.binres2015.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<id>.ext) наблюдается одинаково

Вывод: это данные из немного другого релиза DOS-версии, чем те, что лежат у нас в MSDOS/ (см. расхождение в размере digisnd/midisnd), но формат контейнера и нумерация id — те же самые. Практически это значит:

  1. Для получения играбельных PNG-спрайтов и VGA-фонов не нужно реализовывать декодер сжатия пикселей — можно взять готовые файлы data/<ИМЯ>/res<id>.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<id>.* в SDLPoP data/
ID-пространства ресурсов Средняя-высокая Наблюдение по диапазонам + сверка с res<id> именами файлов 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).