Files
snark13 8b30dc20c8 roomtest: loose-полы — тряска (knock), падение с окклюзией, фикс дабл-буфера
Проваливающиеся полы в roomtest (порт SDLPoP seg007/seg008), проверено в MAME:

- Падающий кусок (mob): правый край env-42 (w=26, доходит до mob_x+57)
  теперь полностью покрыт heal-коридором (MOB_W 56->64) — убран тёмный
  хвост-тень на полу 2,7 ПОСЛЕ падения.
- Окклюзия правого куска во ВРЕМЯ падения: после отрисовки плиты
  перерисовывается сосед-пол draw_tile(row,col+1) в том же кадре — край
  прячется за полом 2,7 (порт redraw_at_cur_mob: set_redraw_full+1).
- Тряска от сотрясения (knock): seqtbl-команды KNOCK_DOWN/UP в play_seq ->
  флаг knock -> do_knock(ряд) трясёт loose ряда из покоя (modif=0x80).
  KNOCK_DOWN в land-seq (приземление) и runcyc (footstep).
- Фикс «бесконечной тряски» (дабл-буфер): при завершении тряски (0x84->0)
  перерисовать покойный кадр плиты на ОБЕИХ страницах (loose_rest, 2 кадра) —
  иначе на одной странице застревает дрожащий кадр правой грани (живёт в
  тайлах col и col+1) -> мерцание через флип.

Инфраструктура/документация:
- app.mk: цель `make hdd` (упаковка в D: для MCP-моста MAME).
- docs: POP-DAT-FormatSpecifications.pdf/.txt как каноническая спецификация
  форматов ресурсов; ссылки в README/MSDOS_RESOURCE_FORMAT.
- README.md/CLAUDE.md для applications/PoP и roomtest (правило «SDLPoP —
  источник истины», порядок слоёв, режим отладки freeze 1/2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 15:27:47 +03:00

302 lines
29 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Формат ресурсов 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` | 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` | 40–55 | 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+ | 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<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.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<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`).