2026-09-06 05:22:12 +04:00
|
|
|
|
# I. Путеводитель
|
|
|
|
|
|
|
|
|
|
|
|
Игра выглядит как непрерывное движение, но компьютер строит её из отдельных
|
|
|
|
|
|
шагов. Он читает ввод, обновляет мир, вычисляет положение моделей и рисует
|
|
|
|
|
|
очередной кадр. Чтобы воспроизвести старую игру, нужно понять как её файлы,
|
|
|
|
|
|
так и правила этих шагов.
|
|
|
|
|
|
|
|
|
|
|
|
Книга не требует опыта игровой разработки. Ниже объясняются основные понятия,
|
|
|
|
|
|
а последующие главы показывают их на конкретных данных «Паркана».
|
|
|
|
|
|
Подробные таблицы адресов и байтов полезны при реализации; при первом чтении
|
|
|
|
|
|
их можно пропустить.
|
|
|
|
|
|
|
|
|
|
|
|
`+0x10` означает смещение в байтах от начала записи. `RVA` — адрес относительно
|
|
|
|
|
|
начала загруженной DLL. `u16`, `u32`, `i16`, `float32` — числа фиксированного
|
|
|
|
|
|
размера. `LE` означает little-endian: младший байт числа записан первым.
|
|
|
|
|
|
`EOF` — конец файла. Неизвестные поля сохраняются без изменений.
|
2026-06-22 01:58:51 +04:00
|
|
|
|
|
|
|
|
|
|
## Движок как программа длительного действия
|
|
|
|
|
|
|
|
|
|
|
|
Обычная прикладная программа получает запрос, вычисляет результат и заканчивает
|
|
|
|
|
|
работу. Игра живёт в цикле: прочитать ввод, обновить состояние мира,
|
|
|
|
|
|
сформировать звук и изображение, показать кадр и повторить. Движок -- набор
|
|
|
|
|
|
подсистем и соглашений, которые делают этот цикл устойчивым.
|
|
|
|
|
|
|
|
|
|
|
|
**Simulation** отвечает на вопрос "что произошло в мире": куда переместился
|
|
|
|
|
|
объект, кого он видит, сколько у него здоровья, сработал ли эффект, изменился
|
|
|
|
|
|
ли маршрут или приказ. **Rendering** отвечает на другой вопрос: "как текущее
|
|
|
|
|
|
состояние показать". В корректной архитектуре рендер не решает игровые правила,
|
|
|
|
|
|
а читает подготовленное состояние.
|
|
|
|
|
|
|
|
|
|
|
|
**Tick** -- один шаг расчёта. **Frame** -- одно изображение. Они могут
|
|
|
|
|
|
выполняться с разной частотой: игра способна рассчитать несколько шагов между
|
|
|
|
|
|
двумя показами или временно не рисовать, не останавливая логику. Поэтому время,
|
|
|
|
|
|
накопление input, порядок callbacks и момент удаления объектов считаются частью
|
|
|
|
|
|
контракта.
|
|
|
|
|
|
|
|
|
|
|
|
## Мир, сцена и объект
|
|
|
|
|
|
|
|
|
|
|
|
**Мир** -- долгоживущее состояние миссии: ландшафт, объекты, время, погода,
|
|
|
|
|
|
принадлежность к кланам и глобальные сервисы. **Сцена** -- представление той
|
|
|
|
|
|
части мира, которую можно обработать для текущей камеры. **Игровой объект** --
|
|
|
|
|
|
сущность с идентификатором, положением, набором свойств и поведением.
|
|
|
|
|
|
|
|
|
|
|
|
В Iron3D объектами управляет World3D. Объекты регистрируются в общей очереди,
|
|
|
|
|
|
получают события, участвуют в расчёте и могут быть удалены отложенно, чтобы не
|
|
|
|
|
|
разрушить обход коллекции посреди шага. Это важнее, чем конкретный контейнер в
|
|
|
|
|
|
новой реализации: совместимость определяется моментом наблюдаемого добавления,
|
|
|
|
|
|
обновления и удаления.
|
|
|
|
|
|
|
|
|
|
|
|
Мир не равен renderer scene graph. Один объект может иметь runtime state,
|
|
|
|
|
|
controller, сетевой mirror, визуальную модель, collision bounds и script state.
|
|
|
|
|
|
Часть этих данных нужна для gameplay, часть -- для вывода, часть -- для
|
|
|
|
|
|
сохранения и воспроизведения.
|
|
|
|
|
|
|
|
|
|
|
|
## Ресурс, модель и материал
|
|
|
|
|
|
|
|
|
|
|
|
**Ресурс** -- именованный блок данных, который можно найти и загрузить. Архивы
|
|
|
|
|
|
`NRes` и `RsLi` содержат таблицы таких блоков. Имя, индекс, размер, offset,
|
|
|
|
|
|
compression method и fallback-правило являются частью контракта загрузки.
|
|
|
|
|
|
|
|
|
|
|
|
**Модель** описывает форму объекта. Она состоит из вершин, индексов, узлов,
|
|
|
|
|
|
групп треугольников, слотов материалов и auxiliary streams. **Vertex** хранит
|
|
|
|
|
|
положение и обычно дополнительные атрибуты: нормаль для освещения и
|
|
|
|
|
|
UV-координату для выборки texture. **Triangle** -- три вершины, образующие
|
|
|
|
|
|
примитив. **Index buffer** хранит номера вершин и позволяет переиспользовать их
|
|
|
|
|
|
между треугольниками. **Batch** -- непрерывный диапазон индексов, который
|
|
|
|
|
|
рисуется одним материалом и одним набором состояний.
|
|
|
|
|
|
|
|
|
|
|
|
**Материал** описывает способ отображения поверхности: texture references,
|
|
|
|
|
|
цвет, прозрачность, режимы смешивания и анимацию параметров. **Texture** --
|
|
|
|
|
|
изображение в памяти графической системы. **Mip-уровни** -- уменьшенные копии
|
|
|
|
|
|
изображения для дальних объектов. **Lightmap** -- дополнительная texture с
|
|
|
|
|
|
заранее рассчитанным освещением.
|
|
|
|
|
|
|
|
|
|
|
|
Runtime должен связывать эти уровни по цепочке: миссия выбирает объект, объект
|
|
|
|
|
|
ссылается на prototype, prototype приводит к модели, модель -- к WEAR,
|
|
|
|
|
|
материалам, textures и lightmaps. Ошибка на любом участке этой цепочки может
|
|
|
|
|
|
не проявиться в parser-е, но проявится в игровом кадре.
|
|
|
|
|
|
|
|
|
|
|
|
## Пространственные понятия
|
|
|
|
|
|
|
|
|
|
|
|
**Transform** переводит точку из локальных координат модели в координаты мира,
|
|
|
|
|
|
камеры и экрана. **Иерархия узлов** позволяет одному элементу наследовать
|
|
|
|
|
|
движение другого. **LOD** выбирает менее подробную геометрию вдали. **Culling**
|
|
|
|
|
|
отбрасывает то, что не видно. **Bounds** -- упрощённая оболочка объекта,
|
|
|
|
|
|
обычно сфера или AABB, используемая для быстрых тестов.
|
|
|
|
|
|
|
|
|
|
|
|
**Collision** отвечает на геометрические пересечения. **Navigation** ищет
|
|
|
|
|
|
допустимый маршрут. В Iron3D эти задачи разделены: Control обслуживает
|
|
|
|
|
|
физическую модель и столкновения, а ArealMap хранит пространственные области и
|
|
|
|
|
|
связи между ними.
|
|
|
|
|
|
|
|
|
|
|
|
Важно не смешивать визуальные и игровые упрощения. Render bounds могут быть
|
|
|
|
|
|
достаточны для отсечения, но не обязаны совпадать с collision shape. Навигация
|
|
|
|
|
|
может использовать areal graph, который не является ни mesh-ем модели, ни
|
|
|
|
|
|
геометрией ландшафта в renderer-е.
|
|
|
|
|
|
|
|
|
|
|
|
## Графический конвейер
|
|
|
|
|
|
|
|
|
|
|
|
Процессор выбирает видимые объекты, готовит матрицы, материалы и списки
|
|
|
|
|
|
примитивов. Графический backend передаёт вершины, индексы, textures и state
|
|
|
|
|
|
драйверу. Видеокарта преобразует вершины в координаты экрана, разбивает
|
|
|
|
|
|
треугольники на фрагменты, проверяет глубину, смешивает цвет и записывает
|
|
|
|
|
|
результат в буфер кадра. После завершения буфер становится видимым
|
|
|
|
|
|
пользователю.
|
|
|
|
|
|
|
|
|
|
|
|
Для совместимости важны не только данные draw call. Контракт включает frame
|
|
|
|
|
|
boundaries, viewport, camera state, порядок world traversal, material resolve,
|
|
|
|
|
|
shadow/transparent/FX subpasses, завершение renderer-а, восстановление state и
|
|
|
|
|
|
callbacks после рендера. Если часть имён vtable slots ещё не доказана, новая
|
|
|
|
|
|
реализация должна фиксировать крупный порядок операций и оставлять
|
|
|
|
|
|
детализацию проверяемой.
|
|
|
|
|
|
|
|
|
|
|
|
## Практический словарь реализации
|
|
|
|
|
|
|
|
|
|
|
|
**Handle** -- компактная ссылка на управляемый объект. **Cache** -- сохранённый
|
|
|
|
|
|
результат загрузки или декодирования. **Reference count** -- число владельцев
|
|
|
|
|
|
ресурса. **Fallback** -- предписанный запасной вариант при отсутствии данных.
|
|
|
|
|
|
**Invariant** -- условие, которое всегда должно быть истинным для корректного
|
|
|
|
|
|
файла или runtime-состояния. **Determinism** -- повторяемость результата при
|
|
|
|
|
|
одинаковых входных данных и порядке событий.
|
|
|
|
|
|
|
|
|
|
|
|
**Strict mode** -- режим parser-а, который принимает только корректный файл:
|
|
|
|
|
|
верные magic, версии, размеры, ranges, индексы и точный EOF. **Lossless mode**
|
|
|
|
|
|
-- режим чтения/записи, который сохраняет неизвестные поля, padding, gaps и raw
|
|
|
|
|
|
payload без нормализации. **Quirk** -- именованное отклонение, разрешённое
|
|
|
|
|
|
только после проверки на реальных данных или исполняемом коде.
|
|
|
|
|
|
|
|
|
|
|
|
Эти слова используются как технические термины. Если глава называет значение
|
|
|
|
|
|
fallback-ом, invariant-ом или quirk-ом, это должно иметь проверяемое
|
|
|
|
|
|
последствие в reader-е, writer-е или runtime.
|
|
|
|
|
|
|
|
|
|
|
|
## Как читать C/C++-схемы структур
|
|
|
|
|
|
|
|
|
|
|
|
Структуры в главах описывают байтовый layout, а не переносимый C++ object
|
|
|
|
|
|
model. Если поля на диске идут без padding, reader должен читать их по offsets
|
|
|
|
|
|
либо использовать явно проверенный packed layout. Прямое отображение native
|
|
|
|
|
|
struct допустимо только при доказанном размере, выравнивании и endian-правиле.
|
|
|
|
|
|
|
|
|
|
|
|
`sizeof` обязательно проверяется `static_assert` или эквивалентным compile-time
|
|
|
|
|
|
test. Это особенно важно для records, где 32-битное поле начинается после
|
|
|
|
|
|
нечётного числа 16-битных или 8-битных полей: стандартное выравнивание
|
|
|
|
|
|
современного compiler-а может вставить скрытые bytes и изменить offsets.
|
|
|
|
|
|
|
|
|
|
|
|
Для variable-length форматов предпочтителен bounded cursor:
|
|
|
|
|
|
|
|
|
|
|
|
1. Прочитать header и проверить минимальный размер.
|
|
|
|
|
|
2. Проверить, что offsets и sizes лежат внутри текущего блока.
|
|
|
|
|
|
3. Прочитать таблицы до объявленного count, не до "пока получается".
|
|
|
|
|
|
4. Проверить ссылки между таблицами.
|
|
|
|
|
|
5. Дойти до точного EOF или сохранить явно разрешённый trailing payload.
|
|
|
|
|
|
|
|
|
|
|
|
Writer пересчитывает только производные значения: размеры, offsets, число
|
|
|
|
|
|
записей, сортировочные таблицы и padding, если правило доказано. Unknown fields
|
|
|
|
|
|
и reserved ranges сохраняются побайтно.
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-09-06 05:22:12 +04:00
|
|
|
|
## Проверка предположений
|
2026-06-22 01:58:51 +04:00
|
|
|
|
|
2026-09-06 05:22:12 +04:00
|
|
|
|
Формат подтверждается тем, как оригинальная программа читает запись, и тем,
|
|
|
|
|
|
какие значения встречаются в игровых ресурсах. Изображение из стороннего
|
|
|
|
|
|
просмотрщика помогает заметить ошибку, но само по себе не доказывает формулу.
|
|
|
|
|
|
Для восстановленного поведения оставляем небольшой тест с конкретным входом
|
|
|
|
|
|
и ожидаемым результатом. Для неопределённого поля прямо пишем, что его смысл
|
|
|
|
|
|
неизвестен.
|
2026-06-22 01:58:51 +04:00
|
|
|
|
|
2026-09-06 05:22:12 +04:00
|
|
|
|
RVA зависит от сборки DLL. Сравнивая инструкции, сначала сверяют соответствующий
|
|
|
|
|
|
файл с [таблицей оригинальных модулей](../reference/original-binaries.md).
|