Главная » Хабрахабр » [Из песочницы] «Камень я не дам» или как устроены ресурсы игры «Проклятые Земли»

[Из песочницы] «Камень я не дам» или как устроены ресурсы игры «Проклятые Земли»

Качественных? Много ли вы вспомните российских игр? Да, такие были. Запоминающихся? Если вам больше 35 или вы фанат российского игропрома, то с "Проклятыми Землями" вы наверняка знакомы.

Делать особо нечего, а при ленивом просмотре содержимого жёсткого диска ноутбука взгляд зацепился за папку со знакомой иконкой-дракончиком, лежащую без дела уже пару лет. История начиналась весьма прозаично: лето, жара.

Какому фанату игры не будет интересно узнать, что же там внутри?

Информация об игре

Разработкой игры занималась студия Nival Interactive, на тот момент уже зарекомендовавшая себя серией игр Аллоды (Rage of Mages за рубежом). Проклятые Земли — или, как они назывались за пределами СНГ, Evil Islands: Curse of the Lost Soul, stealth-RPG игра, вышедшая в 2000 году. Ru (информация), однако игра продаётся в магазине GOG всё ещё от лица Nival. Работали в ней, в основном, выпускники МГУ — им было вполне по силам реализовать одну из первых игр с полностью трёхмерным миром.
В 2010 году права на название перешли Mail.

Несмотря на возраст, официальный мастер-сервер ещё в строю: периодически кто-то решает поползать по лесам Гипата да стукнуть десяток-другой скелетов с отрядом товарищей. Относительно недавно игре исполнилось 18 лет — днём рождения считается 26 октября, дата выхода в СНГ.

Коротко о статье

Однако в процессе плавно началось написание документации по форматам, попытки как-то стандартизировать вывод. Изначально, моей целью было лишь написать односторонний конвертер "для себя" на Python 3, причём с использованием исключительно стандартных библиотек. В результате всё вылилось в написание данной статьи и wiki по форматам. Для части форматов была описана структура с помощью Kaitai Struct.

Однако информация крайне разрознена, а более-менее целостного описания форматов в открытом доступе нет, как и адекватного набора для создания модификаций. Сразу отмечу: большей частью, файлы игры уже исследованы, к ним были написаны фанатские редакторы.

… и о том, как её читать

Для всех форматов приведены схемы (.ksy файлы), которые можно в два клика сконвертировать в код на нескольких самых популярных языках.

Это не должно стать большой проблемой, но если читать схему неудобно, могу посоветовать скопировать в сторонний редактор, например, NPP. К сожалению, уже на последних этапах написания этой статьи, я обнаружил, что многоуважаемый Хабр не умеет в подсветку YAML (и JSON), а все схемы использует именно его.

Игра представляет собой портативное приложение, содержащее движок с библиотеками, лаунчер и, собственно, упакованные ресурсы.

Баг камеры в GOG версии связан с тем, что установщик не прописывает корректные значения по-умолчанию. Это интересно: настройки игры практически целиком хранятся в реестре.

При первом взгляде на содержимое папки game, мы сразу заметим пару новых расширений файлов: ASI и REG.
Первый — динамическая библиотека, которую рассматривать мы не будем (этим занимаются специалисты по реверс-инжинирингу), а вот второе — первый собственный формат файлов игры.

REG

REG файл сохраняет эту иерархию, однако ускоряет чтение и разбор данных — в 2000 году это, видимо, было критично. Файлы этого типа — бинарная сериализация общеизвестных текстовых INI файлов.
Содержимое разбивается на секции, хранящие ключи и их значения.

В общем виде, можно описать структуру данной диаграммой:

Описание структуры

meta: id: reg title: Evil Islands, REG file (packed INI) application: Evil Islands file-extension: reg license: MIT endian: le
doc: Packed INI file
seq: - id: magic contents: [0xFB, 0x3E, 0xAB, 0x45] doc: Magic bytes - id: sections_count type: u2 doc: Number of sections - id: sections_offsets type: section_offset doc: Sections offset table repeat: expr repeat-expr: sections_count
types: section_offset: doc: Section position in file seq: - id: order type: s2 doc: Section order number - id: offset type: u4 doc: Global offset of section in file instances: section: pos: offset type: section types: section: doc: Section representation seq: - id: keys_count type: u2 doc: Number of keys in section - id: name_len type: u2 doc: Section name lenght - id: name type: str encoding: cp1251 size: name_len doc: Section name - id: keys type: key doc: Section's keys repeat: expr repeat-expr: keys_count types: key: doc: Named key seq: - id: order type: s2 doc: Key order in section - id: offset type: u4 doc: Key offset in section instances: key_record: pos: _parent._parent.offset + offset type: key_data key_data: seq: - id: packed_type type: u1 doc: Key value info - id: name_len type: u2 doc: Key name lenght - id: name type: str encoding: cp1251 size: name_len doc: Key name - id: value type: value doc: Key value instances: is_array: value: packed_type > 127 doc: Is this key contain array value_type: value: packed_type & 0x7F doc: Key value type types: value: doc: Key value seq: - id: array_size type: u2 if: _parent.is_array doc: Value array size - id: data type: switch-on: _parent.value_type cases: 0: s4 1: f4 2: string repeat: expr repeat-expr: '_parent.is_array ? array_size : 1' doc: Key value data string: doc: Sized string seq: - id: len type: u2 doc: String lenght - id: value type: str encoding: cp1251 size: len doc: String

Как можно догадаться, почти сразу появился и десериализатор, пусть и не официальный. Это интересно: в 2002 году Nival поделился некоторыми инструментами с коммьюнити игры (снапшот сайта) — одним из них был сериализатор INI в REG.

Со стартовой папкой разобрались, перейдём к подкаталогам.
Первым взгляд падает на папку Cameras, содержащую CAM файлы.

CAM

Камера описывается позицией и вращением. Очень простой формат — просто упаковки положений камер во времени. Два остальных поля — предположительно, время и шаг в последовательности перемещений.

Описание структуры

meta: id: cam title: Evil Islands, CAM file (cameras) application: Evil Islands file-extension: cam license: MIT endian: le
doc: Camera representation
seq: - id: cams type: camera repeat: eos
types: vec3: doc: 3d vector seq: - id: x type: f4 doc: x axis - id: y type: f4 doc: y axis - id: z type: f4 doc: z axis quat: doc: quaternion seq: - id: w type: f4 doc: w component - id: x type: f4 doc: x component - id: y type: f4 doc: y component - id: z type: f4 doc: z component camera: doc: Camera parameters seq: - id: unkn0 type: u4 doc: unknown - id: unkn1 type: u4 doc: unknown - id: position type: vec3 doc: camera's position - id: rotation type: quat doc: camera's rotation

В соседней папке — Res, хранятся (неожиданно!) RES файлы, являющиеся архивами.

RES

Этот формат иногда прячется под другими расширениями, но оригинальное всё же именно RES.
Структура данных весьма типична для архива с произвольным доступом к файлам: есть таблицы для хранения информации о файлах внутри, таблица имён, само содержимое файлов.
Структура каталогов содержится прямо в именах.

Стоит отметить два крайне интересных факта:

  1. Архив оптимизирован под загрузку информации о файлах в связный список с закрытым хэшированием.
  2. Можно хранить содержимое файла один раз, но ссылаться на него под разными именами. Насколько мне известно, этот факт использовался в фанатском репаке, где за счёт этого был сильно уменьшен размер игры. В оригинальном дистрибутиве оптимизация архивов не использовалась.

Описание структуры

meta: id: res title: Evil Islands, RES file (resources archive) application: Evil Islands file-extension: res license: MIT endian: le
doc: Resources archive
seq: - id: magic contents: [0x3C, 0xE2, 0x9C, 0x01] doc: Magic bytes - id: files_count type: u4 doc: Number of files in archive - id: filetable_offset type: u4 doc: Filetable offset - id: nametable_size type: u4 doc: Size of filenames
instances: nametable_offset: value: filetable_offset + 22 * files_count doc: Offset of filenames table filetable: pos: filetable_offset type: file_record repeat: expr repeat-expr: files_count doc: Files metadata table
types: file_record: doc: File metadata seq: - id: next_index type: s4 doc: Next file index - id: file_size type: u4 doc: Size of file in bytes - id: file_offset type: u4 doc: File data offset - id: last_change type: u4 doc: Unix timestamp of last change time - id: name_len type: u2 doc: Lenght of filename - id: name_offset type: u4 doc: Filename offset in name array instances: name: io: _root._io pos: name_offset + _parent.nametable_offset type: str encoding: cp1251 size: name_len doc: File name data: io: _root._io pos: file_offset size: file_size doc: Content of file

Это интересно: в русской версии игры, архив Speech.res содержит два подкаталога s и t с полностью идентичным содержанием, из-за чего размер архива в два раза больше — именно поэтому игра не помещается на один CD.

Теперь можно распаковать все архивы (могут быть вложенными):

  • RES — просто архив,
  • MPR — ландшафт игровых уровней,
  • MQ — информация о заданиях мультиплеера,
  • ANM — набор анимаций,
  • MOD — 3d модель,
  • BON — расположение костей модели.

Если файлы внутри архива не имеют расширения, будем ставить расширение родителя — касается BON и ANM архивов.

Также можно разбить все полученные файлы на четыре группы:

  1. Текстуры;
  2. Базы данных;
  3. Модели;
  4. Файлы уровня.

Начнём с простого — с текстур.

MMP

Имеет небольшой заголовок, указывающий на параметры изображения, число MIP уровней и использованное сжатие. Собственно, текстура. После заголовка располагаются MIP уровни изображения по убыванию размера.

Описание структуры

meta: id: mmp title: Evil Islands, MMP file (texture) application: Evil Islands file-extension: mmp license: MIT endian: le
doc: MIP-mapping texture
seq: - id: magic contents: [0x4D, 0x4D, 0x50, 0x00] doc: Magic bytes - id: width type: u4 doc: Texture width - id: height type: u4 doc: Texture height - id: mip_levels_count type: u4 doc: Number of MIP-mapping stored levels - id: fourcc type: u4 enum: pixel_formats doc: FourCC label of pixel format - id: bits_per_pixel type: u4 doc: Number of bits per pixel - id: alpha_format type: channel_format doc: Description of alpha bits - id: red_format type: channel_format doc: Description of red bits - id: green_format type: channel_format doc: Description of green bits - id: blue_format type: channel_format doc: Description of blue bits - id: unused size: 4 doc: Empty space - id: base_texture type: switch-on: fourcc cases: 'pixel_formats::argb4': block_custom 'pixel_formats::dxt1': block_dxt1 'pixel_formats::dxt3': block_dxt3 'pixel_formats::pnt3': block_pnt3 'pixel_formats::r5g6b5': block_custom 'pixel_formats::a1r5g5b5': block_custom 'pixel_formats::argb8': block_custom _: block_custom
types: block_pnt3: seq: - id: raw size: _root.bits_per_pixel block_dxt1: seq: - id: raw size: _root.width * _root.height >> 1 block_dxt3: seq: - id: raw size: _root.width * _root.height block_custom: seq: - id: lines type: line_custom repeat: expr repeat-expr: _root.height types: line_custom: seq: - id: pixels type: pixel_custom repeat: expr repeat-expr: _root.width types: pixel_custom: seq: - id: raw type: switch-on: _root.bits_per_pixel cases: 8: u1 16: u2 32: u4 instances: alpha: value: '_root.alpha_format.count == 0 ? 255 : 255 * ((raw & _root.alpha_format.mask) >> _root.alpha_format.shift) / (_root.alpha_format.mask >> _root.alpha_format.shift)' red: value: '255 * ((raw & _root.red_format.mask) >> _root.red_format.shift) / (_root.red_format.mask >> _root.red_format.shift)' green: value: '255 * ((raw & _root.green_format.mask) >> _root.green_format.shift) / (_root.green_format.mask >> _root.green_format.shift)' blue: value: '255 * ((raw & _root.blue_format.mask) >> _root.blue_format.shift) / (_root.blue_format.mask >> _root.blue_format.shift)' channel_format: doc: Description of bits for color channel seq: - id: mask type: u4 doc: Binary mask for channel bits - id: shift type: u4 doc: Binary shift for channel bits - id: count type: u4 doc: Count of channel bits
enums: pixel_formats: 0x00004444: argb4 0x31545844: dxt1 0x33545844: dxt3 0x33544E50: pnt3 0x00005650: r5g6b5 0x00005551: a1r5g5b5 0x00008888: argb8

Возможные форматы упаковки пикселей:

fourcc

Описание

44 44 00 00

ARGB4

44 58 54 31

DXT1

44 58 54 33

DXT3

50 4E 54 33

PNT3 — RLE сжатый ARGB8

50 56 00 00

R5G5B5

51 55 00 00

A1R5G5B5

88 88 00 00

ARGB8

О PNT3

Если формат изображения PNT3, то структура пикселей после распаковки — ARGB8; bits_per_pixel — размер сжатого изображения в байтах.

Распаковка PNT3

n = 0
destination = b"" while src < size: v = int.from_bytes(source[src:src + 4], byteorder='little') src += 4 if v > 1000000 or v == 0: n += 1 else: destination += source[src - (1 + n) * 4:src - 4] destination += b"\x00" * v n = 0

Или белым — тут как повезёт. Это интересно: часть текстур отражена по вертикали (или некоторые не отражены?).
А ещё игра весьма ревностно относится к прозрачности — если изображение с альфа каналом, цвет прозрачных пикселов должен быть точно чёрным.

Я вас предупредил. Простые форматы закончились, переходим к более жёстким — в своё время, ряды модмейкеров яростно хранили свои собственные инструменты редактирования следующих форматов, и не зря.

Базы данных (*DB и иже с ними)

Файл состоит из нескольких таблиц с заданными типами полей. Этот формат крайне неудобно описывать — по существу, это сериализованное дерево нод (или таблиц записей). Общая структура: таблицы вложены в общую "корневую" ноду, записи — ноды внутри таблицы.

В каждой ноде задаётся её тип и размер:

unsigned char type_index;
unsigned char raw_size; // не используется вне этого блока
unsigned length; // не читается из файла read(raw_size); if (raw_size & 1)
{ length = raw_size >> 1; for (int i = 0; i < 3; i++) length <<= 8; read(raw_size); length += raw_size;
}
else length = raw_size >> 1;

Тип поля таблицы берётся по индексу из форматной строки для таблицы, по полученному значению определяется реальный тип.

Типы полей

обозначение

описание

S

string

I

4b int

U

4b unsigned

F

4b float

X

bits byte

f

float array

i

int array

B

bool

b

bool array

H

unknown hex bytes

T

time

0

not stated

1

0FII

2

SUFF

3

FFFF

4

0SISS

5

0SISS00000U

Описание баз

Предметы (.idb)

таблица

структура

Материалы

SSSIFFFIFIFfIX

Оружие

SSISIIIFFFFIFIXB00000IHFFFfHHFF

Броня

SSISIIIFFFFIFIXB00000ffBiHH

Быстрые предметы

SSISIIIFFFFIFIXB00000IIFFSbH

Квестовые предметы

SSISIIIFFFFIFIXB00000Is

Продаваемые предметы

SSISIIIFFFFIFIXB00000IHI

Переключатели (.ldb)

таблица

структура

Прототип переключателя

SfIFTSSS

Умения и навыки (.pdb)

таблица

структура

Умения

SSI0000000s

Навыки

SSI0000000SSIIIFFFIIIIBI

Следы (prints.db)

таблица

структура

Следы крови

0S11

Следы пламени

0S110000001

Следы ног

0S11

Заклинания (.sdb)

таблица

структура

Прототипы

SSSFIFIFFFFIIIIUSSIIbIXFFFFF

Модификаторы

SSFIFFISX

Шаблоны

0SssSX

Шаблоны для брони

0SssSX

Шаблоны для оружия

0SssSX

Существа (.udb)

таблица

структура

Повреждаемые части

SffUU

Расы

SUFFUUFfFUUf222222000000000000SssFSsfUUfUUIUSBFUUUU

Прототипы монстров

SSIUIFFFSFFFFFFFFFUFFFFFFff33sfssSFFFFFUFUSF

NPC

SUFFFFbbssssFUB

Выкрики (acks.db)

таблица

структура

Ответы

0S0000000044444444444444444444445444444444444

Крики

0S0000000044444

Прочее

0S0000000044

Задания (.qdb)

таблица

структура

Задания

SFIISIIs

Брифинги

SFFsSsssssI

01. Это интересно: 16. Естественно, обратный конвертер не замедлил появиться. 2002 Nival выложил исходные базы для мультиплеера в csv формате, а также утилиту-конвертер в игровой формат (снапшот сайта). Также есть минимум два документа с описанием полей и их типов от модмейкеров, но читать их весьма тяжело.

ADB

В отличии от упомянутых выше *DB, достаточно "человечна" — это одноуровневая таблица со статичными размерами полей. База данных анимации для конкретного типа юнитов.

Описание структуры

meta: id: adb title: Evil Islands, ADB file (animations database) application: Evil Islands file-extension: adb license: MIT endian: le
doc: Animations database
seq: - id: magic contents: [0x41, 0x44, 0x42, 0x00] doc: Magic bytes - id: animations_count type: u4 doc: Number of animations in base - id: unit_name type: str encoding: cp1251 size: 24 doc: Name of unit - id: min_height type: f4 doc: Minimal height of unit - id: mid_height type: f4 doc: Middle height of unit - id: max_height type: f4 doc: Maximal height of unit - id: animations type: animation doc: Array of animations repeat: expr repeat-expr: animations_count
types: animation: doc: Animation's parameters seq: - id: name type: str encoding: cp1251 size: 16 doc: Animation's name - id: number type: u4 doc: Index in animations array - id: additionals type: additional doc: Packed structure with animation parameters - id: action_probability type: u4 doc: Percents of action probability - id: animation_length type: u4 doc: Lenght of animation in game ticks - id: movement_speed type: f4 doc: Movement speed - id: start_show_hide1 type: u4 - id: start_show_hide2 type: u4 - id: start_step_sound1 type: u4 - id: start_step_sound2 type: u4 - id: start_step_sound3 type: u4 - id: start_step_sound4 type: u4 - id: start_hit_frame type: u4 - id: start_special_sound type: u4 - id: spec_sound_id1 type: u4 - id: spec_sound_id2 type: u4 - id: spec_sound_id3 type: u4 - id: spec_sound_id4 type: u4 types: additional: seq: - id: packed type: u8 instances: weapons: value: 'packed & 127' allowed_states: value: '(packed >> 15) & 7' action_type: value: '(packed >> 18) & 15' action_modifyer: value: '(packed >> 22) & 255' animation_stage: value: '(packed >> 30) & 3' action_forms: value: '(packed >> 36) & 63'

Это интересно: для нескольких юнитов используется частично урезанный формат базы, практически не исследованный.

Но рекламировать ничего не будем — не наш метод. Разобравшись с базами данных, объявляем рекламную паузу. Лучше обозначим то, что пригодится дальше — как именуются файлы существ.

Формат имён моделей

Имя собирается из групп по два символа — сокращений логического "уровня".
Например, персонаж-женщина будет unhufeUnit > Human > Female, а initwespInventory > Item > Weapon > Spear, то есть, копьё в инвентаре (не спине, и то хорошо).

Полное дерево элементов имени:

un: # unit an: # animal wi: # wild ti # tiger ba # bat bo # boar hy # hyen de # deer gi # rat ra # rat cr # crawler wo # wolf ho: # home co # cow pi # pig do # dog ho # horse ha # hare or: # orc fe # female ma # male mo: # monster co # column (menu) un # unicorn cu # Curse be # beholder tr # troll el # elemental su # succub (harpie) ba # banshee dr # driad sh # shadow li # lizard sk # skeleton sp # spider go # golem, goblin ri # Rick og # ogre zo # zombie bi # Rik's dragon cy # cyclope dg # dragon wi # willwisp mi # octopus to # toad hu: # human fe # female ma # male
in: # inventory it: # item qu # quest qi # interactive ar: # armor pl # plate gl # gloves lg # leggins bt # boots sh # shirt hl # helm pt # pants li: # loot mt # material tr # trade we: # weapon hm # hammer dg # dagger sp # spear cb # crossbow sw # sword ax # axe bw # bow gm # game menu fa: # faces un: # unit an: # animal wi: # wild ti: # tiger face # face ba: # bat face # face bo: # boar face # face de: # deer face # face ra: # rat face # face cr: # crawler face # face wo: # wolf face # face ho: # home co: # cow face # face pi: # pig face # face do: # dog face # face ho: # horse face # face ha: # hare face # face hu: # human fe: # female fa # me # th # ma: # male fa # me # th # mo: # monster to: # toad face # face tr: # troll face # face or: # orc face # face sp: # spider face # face li: # lizard face # face
na: # nature fl: # flora bu # bush te # termitary tr # tree li # waterplant wa # waterfall sk # sky st # stone
ef: # effects cu # ar # co # components
st: # static si # switch bu: # building to # tower ho # house tr # trap br # bridge ga # gate we # well (waterhole) wa: # wall me # medium li # light to # torch st # static

Также тут можно заметить "рабочие" имена монстров, подозрительно похожие на таковые из D&D — beholder (злобоглаз), succub (гарпия), ogre (людоед), driad (лесовики). Это интересно: по данной классификации, грибы — деревья, големы с гоблинами — братья, а Тка-Рик — монстр.

Они представлены несколькими форматами, которые компонуются между собой. Морально отдохнув, окунёмся с головой в модели.

LNK

Описывает иерархию частей модели, в терминах современного 3d моделирования — иерархию костей. Логически — основа модели.

Описание структуры

meta: id: lnk title: Evil Islands, LNK file (bones hierarchy) application: Evil Islands file-extension: lnk license: MIT endian: le
doc: Bones hierarchy
seq: - id: bones_count type: u4 doc: Number of bones - id: bones_array type: bone repeat: expr repeat-expr: bones_count doc: Array of bones
types: bone: doc: Bone node seq: - id: bone_name_len type: u4 doc: Length of bone's name - id: bone_name type: str encoding: cp1251 size: bone_name_len doc: Bone's name - id: parent_name_len type: u4 doc: Length of bone's parent name - id: parent_name type: str encoding: cp1251 size: parent_name_len doc: Bone's parent name

Имя родителя основной кости — пустая строка (длины 0).

Кости есть, однако недостаточно назвать их и сложить кучкой — нужно собрать их в скелет.

BON

Хранится лишь смещение, без вращения — одно из отличий от современных форматов. Ранее упоминавшийся, этот формат (если он не архив) задаёт положение частей (костей) модели относительно части-родителя.

Описание структуры

meta: id: bon title: Evil Islands, BON file (bone position) application: Evil Islands file-extension: bon license: MIT endian: le
doc: Bone position
seq: - id: position type: vec3 doc: Bone translation repeat: eos
types: vec3: doc: 3d vector seq: - id: x type: f4 doc: x axis - id: y type: f4 doc: y axis - id: z type: f4 doc: z axis

Как можно заметить, чисел здесь слишком много для одного смещения — дело в том, что здесь мы впервые наткнулись на одну из ключевых фишек движка игры — трилинейную интерполяцию моделей.

Также есть 8 крайних состояний модели. Как это работает: у модели есть три параметра интерполяции — условно, сила, ловкость, рост. Используя параметры, можем получить итоговую модель трилинейной интерполяцией.

Непосредственно, алгоритм

def trilinear(val, coefs=[0, 0, 0]): # Linear interpolation by str t1 = val[0] + (val[1] - val[0]) * coefs[1] t2 = val[2] + (val[3] - val[2]) * coefs[1] # Bilinear interpolation by dex v1 = t1 + (t2 - t1) * coefs[0] # Linear interpolation by str t1 = val[4] + (val[5] - val[4]) * coefs[1] t2 = val[6] + (val[7] - val[6]) * coefs[1] # Bilinear interpolation by dex v2 = t1 + (t2 - t1) * coefs[0] # Trilinear interpolation by height return v1 + (v2 - v1) * coefs[2]

Это интересно: трилинейная интерполяция модели используется для анимации некоторых объектов, например, открытия каменной двери и сундуков.

Теперь самое время посмотреть на сами части модели.

FIG

В сети можно найти его описание и плагин для блендера, но даже с ними осознание приходит не сразу. Пожалуй, этот формат понять слёту невозможно. Взгляните:

Описание структуры

meta: id: fig title: Evil Islands, FIG file (figure) application: Evil Islands file-extension: fig license: MIT endian: le
doc: 3d mesh
seq: - id: magic contents: [0x46, 0x49, 0x47, 0x38] doc: Magic bytes - id: vertex_count type: u4 doc: Number of vertices blocks - id: normal_count type: u4 doc: Number of normals blocks - id: texcoord_count type: u4 doc: Number of UV pairs - id: index_count type: u4 doc: Number of indeces - id: vertex_components_count type: u4 doc: Number of vertex components - id: morph_components_count type: u4 doc: Number of morphing components - id: unknown contents: [0, 0, 0, 0] doc: Unknown (aligment) - id: group type: u4 doc: Render group - id: texture_index type: u4 doc: Texture offset - id: center type: vec3 doc: Center of mesh repeat: expr repeat-expr: 8 - id: aabb_min type: vec3 doc: AABB point of mesh repeat: expr repeat-expr: 8 - id: aabb_max type: vec3 doc: AABB point of mesh repeat: expr repeat-expr: 8 - id: radius type: f4 doc: Radius of boundings repeat: expr repeat-expr: 8 - id: vertex_array type: vertex_block doc: Blocks of raw vertex data repeat: expr repeat-expr: 8 - id: normal_array type: vec4x4 doc: Packed normal data repeat: expr repeat-expr: normal_count - id: texcoord_array type: vec2 doc: Texture coordinates data repeat: expr repeat-expr: texcoord_count - id: index_array type: u2 doc: Triangles indeces repeat: expr repeat-expr: index_count - id: vertex_components_array type: vertex_component doc: Vertex components array repeat: expr repeat-expr: vertex_components_count - id: morph_components_array type: morph_component doc: Morphing components array repeat: expr repeat-expr: morph_components_count
types: morph_component: doc: Morphing components indeces seq: - id: morph_index type: u2 doc: Index of morphing data - id: vertex_index type: u2 doc: Index of vertex vertex_component: doc: Vertex components indeces seq: - id: position_index type: u2 doc: Index of position data - id: normal_index type: u2 doc: Index of normal data - id: texture_index type: u2 doc: Index of texcoord data vec2: doc: 2d vector seq: - id: u type: f4 doc: u axis - id: v type: f4 doc: v axis vec3: doc: 3d vector seq: - id: x type: f4 doc: x axis - id: y type: f4 doc: y axis - id: z type: f4 doc: z axis vec3x4: doc: 3d vector with 4 values per axis seq: - id: x type: f4 doc: x axis repeat: expr repeat-expr: 4 - id: y type: f4 doc: y axis repeat: expr repeat-expr: 4 - id: z type: f4 doc: z axis repeat: expr repeat-expr: 4 vertex_block: doc: Vertex raw block seq: - id: block type: vec3x4 doc: Vertex data repeat: expr repeat-expr: _root.vertex_count vec4x4: doc: 4d vector with 4 values per axis seq: - id: x type: f4 doc: x axis repeat: expr repeat-expr: 4 - id: y type: f4 doc: y axis repeat: expr repeat-expr: 4 - id: z type: f4 doc: z axis repeat: expr repeat-expr: 4 - id: w type: f4 doc: w axis repeat: expr repeat-expr: 4

Так ведь данные нормалей и вершин хранятся в блоках по 4, а вершины ещё и скомпонованы в 8 блоков для интерполяции. В чём сложность?

Это интересно: предположительно, такая группировка сделана для ускорения обработки с помощью SSE инструкций, появившихся в процессорах Intel с 1999.

Точно — анимации! Что ж, модель мы прочли и составили, однако чего-то не хватает.

ANM

Интересен тот факт, что реализована поддержка не только скелетной анимации, но и повершинного морфинга. Анимация хранится в виде ключевых состояний покомпонентно.

Описание структуры

meta: id: anm title: Evil Islands, ANM file (bone animation) application: Evil Islands file-extension: anm license: MIT endian: le
doc: Bone animation
seq: - id: rotation_frames_count type: u4 doc: Number of rotation frames - id: rotation_frames type: quat repeat: expr repeat-expr: rotation_frames_count doc: Bone rotations - id: translation_frames_count type: u4 doc: Number of translation frames - id: translation_frames type: vec3 repeat: expr repeat-expr: translation_frames_count doc: Bone translation - id: morphing_frames_count type: u4 doc: Number of morphing frames - id: morphing_vertex_count type: u4 doc: Number of vertices with morphing - id: morphing_frames type: morphing_frame repeat: expr repeat-expr: morphing_frames_count doc: Array of morphing frames
types: vec3: doc: 3d vector seq: - id: x type: f4 doc: x axis - id: y type: f4 doc: y axis - id: z type: f4 doc: z axis quat: doc: quaternion seq: - id: w type: f4 doc: w component - id: x type: f4 doc: x component - id: y type: f4 doc: y component - id: z type: f4 doc: z component morphing_frame: doc: Array of verteces morphing seq: - id: vertex_shift type: vec3 repeat: expr repeat-expr: _parent.morphing_vertex_count doc: Morphing shift per vertex

Всё — теперь у нас есть полноценная модель, можно полюбоваться на свежеотрендеренного ящера-отшельника:

Момент ностальгии

Узнать, что нужно Ящеру

Разговор с ящером в его жилище

Это хорошо. Ящер-Отшельник: Ты пришел, человек.

Зак: Это все, что ты хотел мне сказать?

Я помню твои вопросы и буду на них отвечать. Ящер-Отшельник: Ты опять торопишься. Но я увидел, как они поступили с тобой. Я пришел к людям в железе, чтобы заключить сделку. Ты сдержал слово. Они не держат слова, я перестал им верить. Сделка будет предложена тебе.

Ящерам золото неинтересно. Ящер-Отшельник: Люди любят золото. Этого золота много. Ты выполнишь мое задание, и я дам тебе золото, которое есть у меня.

Зак (задумчиво и без особой заинтересованности): Хм… Золото… Оно, конечно, не помешает…

Ведь ящеры — древний народ, и вы можете это знать! Зак: Было бы лучше, если бы ты помог мне узнать, где живет старый маг, которого я так долго ищу.

Ящеры — древний народ. Ящер-Отшельник: Ты прав. Ты согласен выполнить мое задание? Я могу собрать все, что нам известно про старика.

Считай, что все уже сделано. Зак: О чем разговор!

Ты хочешь меня обмануть? Ящер-Отшельник (серьезно): Уже сделано?

Зак: Вообще-то я хотел пошутить, а то ты уж больно серьезен.

Это шутка. Ящер-Отшельник: Понимаю. Потом. Наверное, я тоже смогу пошутить. Воду украли у нас орки. А сейчас мне надо, чтобы ты вернул воду в Канал.

Увидишь плотину и Канал. Ящер-Отшельник: Иди на юг вдоль воды. Рычагом. Плотину надо поднять. Канал нужно завалить. Я его дам. Камень я не дам. Камнем. Вверх по течению от плотины. Он уже лежит на краю Канала. Когда орки копали, они его поднимали долго. Камень тяжелый. Если ты его толкнешь, обратно он будет падать быстро.

Я расскажу тебе все, что узнаю про старого Мага. Ящер-Отшельник: После этого возвращайся.

Но, кстати, если ты добавишь к рассказу немножко монет, я вовсе не обижусь. Зак: По рукам!

Пройди на самый дальний песчаный остров, третий по счету. Ящер-Отшельник: За монетами отправляйся к моим сородичам, которые живут на отмелях дальше, на юге. Сокровища будут твоими!

Этот человек любит юмор. Ящер-Отшельник (сам себе): Странно. Человек не засмеялся. Я пошутил. Очень странно.

Теперь — самое интересное: как хранится карта.

MP

По несчастливому стечению обстоятельств, расширение совпадает с таковым у файлов сохранения мультиплеера, которые мы рассматривать не будем. Это — заголовочный файл карты.

Сначала нужно дать общую характеристику ландшафту:

  • число "чанков" — кусков карты 32х32 метра;
  • максимальную высоту (так как высота вершин хранится в целочисленной шкале);
  • число тайловых атласов.

Дополнительно идёт описание материалов карты, а также анимированных тайлов — например, воды или лавы.

Описание структуры

meta: id: mp title: Evil Islands, MP file (map header) application: Evil Islands file-extension: mp license: MIT endian: le
doc: Map header
seq: - id: magic contents: [0x72, 0xF6, 0x4A, 0xCE] doc: Magic bytes - id: max_altitude type: f4 doc: Maximal height of terrain - id: x_chunks_count type: u4 doc: Number of sectors by x - id: y_chunks_count type: u4 doc: Number of sectors by y - id: textures_count type: u4 doc: Number of texture files - id: texture_size type: u4 doc: Size of texture in pixels by side - id: tiles_count type: u4 doc: Number of tiles - id: tile_size type: u4 doc: Size of tile in pixels by side - id: materials_count type: u2 doc: Number of materials - id: animated_tiles_count type: u4 doc: Number of animated tiles - id: materials type: material doc: Map materials repeat: expr repeat-expr: materials_count - id: id_array type: u4 doc: Tile type repeat: expr repeat-expr: tiles_count enum: tile_type - id: animated_tiles type: animated_tile doc: Animated tiles repeat: expr repeat-expr: animated_tiles_count
types: material: doc: Material parameters seq: - id: type type: u4 doc: Material type by enum: terrain_type - id: color type: rgba doc: RGBA diffuse color - id: self_illumination type: f4 doc: Self illumination - id: wave_multiplier type: f4 doc: Wave speed multiplier - id: warp_speed type: f4 doc: Warp speed multiplier - id: unknown size: 12 types: rgba: doc: RGBA color seq: - id: r type: f4 doc: Red channel - id: g type: f4 doc: Green channel - id: b type: f4 doc: Blue channel - id: a type: f4 doc: Alpha channel enums: terrain_type: 0: base 1: water_notexture 2: grass 3: water animated_tile: doc: Animated tile parameters seq: - id: start_index type: u2 doc: First tile of animation - id: length type: u2 doc: Animation frames count
enums: tile_type: 0: grass 1: ground 2: stone 3: sand 4: rock 5: field 6: water 7: road 8: empty 9: snow 10: ice 11: drygrass 12: snowballs 13: lava 14: swamp 15: highrock

Список типов ландшафта

terrain type

Тип

0

Базовый ландшафт

1

Вода без текстуры

2

Текстурированная трава

3

Текстурированная вода

Список типов материалов

material type

Тип

0

grass

1

ground

2

stone

3

sand

4

rock

5

field

6

water

7

road

8

(empty)

9

snow

10

ice

11

drygrass

12

snowballs

13

lava

14

swamp

15

highrock

Тип материала должен влиять на проходимость, судя по информации в файле Res/aiinfo.res/tileDesc.reg.

Это интересно: во всех общедоступных описаниях формата, допущена ошибка — поля земли и воды перепутаны по типам.
И опять же: можно спутать эти файлы с сохранениями мультиплеера.

За дело! Теперь мы готовы обработать сами части карты.

SEC

Положение на карте хранится в имени файла, которое имеет вид ZonenameXXXYYY. Файл хранит единичный сектор карты — кусок 32х32 метра.

Описание структуры

meta: id: sec title: Evil Islands, SEC file (map sector) application: Evil Islands file-extension: sec license: MIT endian: le
doc: Map sector
seq: - id: magic contents: [0x74, 0xF7, 0x4B, 0xCF] doc: Magic bytes - id: liquids type: u1 doc: Liquids layer indicator - id: vertexes type: vertex doc: Vertex array 33x33 repeat: expr repeat-expr: 1089 - id: liquid_vertexes type: vertex doc: Vertex array 33x33 if: liquids != 0 repeat: expr repeat-expr: 'liquids != 0 ? 1089 : 0' - id: tiles type: tile doc: Tile array 16x16 repeat: expr repeat-expr: 256 - id: liquid_tiles type: tile doc: Tile array 16x16 if: liquids != 0 repeat: expr repeat-expr: 'liquids != 0 ? 256 : 0' - id: liquid_material type: u2 doc: Index of material if: liquids != 0 repeat: expr repeat-expr: 'liquids != 0 ? 256 : 0'
types: vertex: doc: Vertex data seq: - id: x_shift type: s1 doc: Shift by x axis - id: y_shift type: s1 doc: Shift by y axis - id: altitude type: u2 doc: Height (z position) - id: packed_normal type: normal doc: Packed normal normal: doc: Normal (3d vector) seq: - id: packed type: u4 doc: Normal packed in 4b instances: x: doc: Unpacked x component value: packed >> 11 & 0x7FF y: doc: Unpacked y component value: packed & 0x7FF z: doc: Unpacked z component value: packed >> 22 tile: doc: Tile parameters seq: - id: packed type: u2 doc: Tile information packed in 2b instances: index: doc: Tile index in texture value: packed & 63 texture: doc: Texture index value: packed >> 6 & 255 rotation: doc: Tile rotation (*90 degrees) value: packed >> 14 & 3

Тут разработчики размахнулись на славу — практически все данные хранятся в запакованном виде.

Набор алгоритмов распаковки

Распаковка нормали

10 бит на ось z, по 11 на x и y

unsigned packed_normal; float x = ((float)((packed_normal >> 11) & 0x7FF) - 1000.0f) / 1000.0f;
float y = ((float)(packed_normal & 0x7FF) - 1000.0f) / 1000.0f;
float z = (float)(packed_normal >> 22) / 1000.0f;

Информация о текстуре

6 бит на индекс в атласе, 8 на номер текстуры, 2 на вращение

unsigned short texture; unsigned char tile_index = f & 63;
unsigned char texture_index = (f >> 6) & 255;
unsigned char rotation = (f >> 14) & 3;

Генерация 3d модели

Получение ландшафта

Длина клетки по стороне — 1 условная единица. Вершины идут по 33 элемента в 33 строки, то есть, образуя 32х32 клетки.

Позиция вершины:
x = индекс по x + x_offset / 254
y = индекс по y + y_offset / 254
z = altitude / 65535 * max_altitude (из .mp файла)

Вершины объединяются в полигоны "гребёнкой", при этом четыре вершины образуют два полигона:

0 1 2 *-*-* |/|/| ~
33 *-*-* |/|/| ~
66 *-*-* ~ ~ ~

Длина тайла — 2 условные единицы. Текстура накладывается на сразу четыре таких клетки, то есть, 16х16 тайлов. Тайл может быть повёрнут на угол, кратный 90 градусам.

В таком случае, помимо вершин и текстурной информации, в конце файла указывается ID материала воды, являющийся индексом в таблице материалов из MP файла. Сектор может содержать информацию о жидкостях на уровне.

Это интересно: как и для MP, в описаниях формата допущена ошибка, но здесь она уже гораздо более весомая: указание ID материала считали указанием видимости тайла, из-за чего меш строился бы некорректно.
Также ID разбивает жидкости уровня на несколько групп — подъём воды после применения рычага как раз использует это.

Отлично — теперь у нас есть готовый ландшафт:

Осталось совсем чуть-чуть — добавить на него объекты, а заодно и рассмотреть последний в данной статье формат.

MOB

Причём отличие от формата баз огромно и в хорошую сторону — нет чёткой структуры "по шаблону", зато есть правило составления полей.
Поле может быть либо записью и содержать другие поля, либо быть ключом (хранить значение в конкретном формате). Если вам понравился (или наоборот) формат баз данных, то здесь используется несколько иной, но чисто теоретически схожий принцип: сериализованное дерево записей.

Очень краткое представление:

typedef structure
{ unsigned type_id; unsigned size; byte data[size - 8];
} node;

Графическое представление (неполное, слабонервным не смотреть!)

Описание структуры (опять же, неполное)

meta: id: mob title: Evil Islands, MOB file (map entities) application: Evil Islands file-extension: mob license: MIT endian: le
doc: Map entities tree
seq: - id: root_node type: node doc: Root node
types: node: doc: Entity node seq: - id: type_id type: u4 doc: Node children type ID - id: size type: u4 doc: Node full size - id: data type: node_data size: size - 8 doc: Node stored data node_data: doc: Node data seq: - id: value type: switch-on: _parent.type_id cases: 0xA000: node 0x00001E00: node 0x00001E01: node 0x00001E02: node 0x00001E03: node 0x00001E0B: node 0x00001E0E: node 0x0000A000: node 0x0000AA01: node 0x0000ABD0: node 0x0000B000: node 0x0000B001: node 0x0000CC01: node 0x0000DD01: node 0x0000E000: node 0x0000E001: node 0x0000F000: node 0x0000FF00: node 0x0000FF01: node 0x0000FF02: node 0xBBAB0000: node 0xBBAC0000: node 0xBBBB0000: node 0xBBBC0000: node 0xBBBD0000: node 0xBBBE0000: node 0xBBBF0000: node 0xDDDDDDD1: node _: u1 doc: Node elements repeat: eos

Полный список типов данных

тип данных

размер (обычно)

описание

AiGraph

граф проходимости

AreaArray

Byte

1

1б беззнаковое целое

Diplomacy

4096

32x32 матрица из 2б целых

Dword

4

4б беззнаковое целое

Float

4

4б вещественное

LeverStats

12

параметры рычага

Null

0

пустая нода

Plot

12

3 floats (vec3)

Plot2DArray

Quaternion

16

4 floats (vec4)

Record

>8

контейнер нод

Rectangle

String

строка

StringArray

>4

массив строк

StringEncrypted

>4

зашифрованный скрипт уровня

UnitStats

180

параметры существа

Unknown

Список возможных type_id

type_id

Тип данных

Имя поля

0x00000000

Record

ROOT

0x00001E00

Record

VSS_SECTION

0x00001E01

Record

VSS_TRIGER

0x00001E02

Record

VSS_CHECK

0x00001E03

Record

VSS_PATH

0x00001E04

Dword

VSS_ID

0x00001E05

Rectangle

VSS_RECT

0x00001E06

Dword

VSS_SRC_ID

0x00001E07

Dword

VSS_DST_ID

0x00001E08

String

VSS_TITLE

0x00001E09

String

VSS_COMMANDS

0x00001E0A

Byte

VSS_ISSTART

0x00001E0B

Record

VSS_LINK

0x00001E0C

String

VSS_GROUP

0x00001E0D

Byte

VSS_IS_USE_GROUP

0x00001E0E

Record

VSS_VARIABLE

0x00001E0F

StringArray

VSS_BS_CHECK

0x00001E10

StringArray

VSS_BS_COMMANDS

0x00001E11

String

VSS_CUSTOM_SRIPT

0x0000A000

Record

OBJECTDBFILE

0x0000AA00

Null

LIGHT_SECTION

0x0000AA01

Record

LIGHT

0x0000AA02

Float

LIGHT_RANGE

0x0000AA03

String

LIGHT_NAME

0x0000AA04

Plot

LIGHT_POSITION

0x0000AA05

Dword

LIGHT_ID

0x0000AA06

Byte

LIGHT_SHADOW

0x0000AA07

Plot

LIGHT_COLOR

0x0000AA08

String

LIGHT_COMMENTS

0x0000ABD0

Record

WORLD_SET

0x0000ABD1

Plot

WS_WIND_DIR

0x0000ABD2

Float

WS_WIND_STR

0x0000ABD3

Float

WS_TIME

0x0000ABD4

Float

WS_AMBIENT

0x0000ABD5

Float

WS_SUN_LIGHT

0x0000B000

Record

OBJECTSECTION

0x0000B001

Record

OBJECT

0x0000B002

Dword

NID

0x0000B003

Dword

OBJTYPE

0x0000B004

String

OBJNAME

0x0000B005

Null

OBJINDEX

0x0000B006

String

OBJTEMPLATE

0x0000B007

String

OBJPRIMTXTR

0x0000B008

String

OBJSECTXTR

0x0000B009

Plot

OBJPOSITION

0x0000B00A

Quaternion

OBJROTATION

0x0000B00B

Null

OBJTEXTURE

0x0000B00C

Plot

OBJCOMPLECTION

0x0000B00D

StringArray

OBJBODYPARTS

0x0000B00E

String

PARENTTEMPLATE

0x0000B00F

String

OBJCOMMENTS

0x0000B010

Null

OBJ_DEF_LOGIC

0x0000B011

Byte

OBJ_PLAYER

0x0000B012

Dword

OBJ_PARENT_ID

0x0000B013

Byte

OBJ_USE_IN_SCRIPT

0x0000B014

Byte

OBJ_IS_SHADOW

0x0000B015

Null

OBJ_R

0x0000B016

String

OBJ_QUEST_INFO

0x0000C000

Null

SC_OBJECTDBFILE

0x0000CC00

Null

SOUND_SECTION

0x0000CC01

Record

SOUND

0x0000CC02

Dword

SOUND_ID

0x0000CC03

Plot

SOUND_POSITION

0x0000CC04

Dword

SOUND_RANGE

0x0000CC05

String

SOUND_NAME

0x0000CC06

Dword

SOUND_MIN

0x0000CC07

Dword

SOUND_MAX

0x0000CC08

String

SOUND_COMMENTS

0x0000CC09

Null

SOUND_VOLUME

0x0000CC0A

StringArray

SOUND_RESNAME

0x0000CC0B

Dword

SOUND_RANGE2

0x0000CC0D

Byte

SOUND_AMBIENT

0x0000CC0E

Byte

SOUND_IS_MUSIC

0x0000D000

Null

PR_OBJECTDBFILE

0x0000DD00

Null

PARTICL_SECTION

0x0000DD01

Record

PARTICL

0x0000DD02

Dword

PARTICL_ID

0x0000DD03

Plot

PARTICL_POSITION

0x0000DD04

String

PARTICL_COMMENTS

0x0000DD05

String

PARTICL_NAME

0x0000DD06

Dword

PARTICL_TYPE

0x0000DD07

Float

PARTICL_SCALE

0x0000E000

Record

DIRICTORY

0x0000E001

Record

FOLDER

0x0000E002

String

DIR_NAME

0x0000E003

Dword

DIR_NINST

0x0000E004

Dword

DIR_PARENT_FOLDER

0x0000E005

Byte

DIR_TYPE

0x0000F000

Record

DIRICTORY_ELEMENTS

0x0000FF00

Record

SEC_RANGE

0x0000FF01

Record

MAIN_RANGE

0x0000FF02

Record

RANGE

0x0000FF05

Dword

MIN_ID

0x0000FF06

Dword

MAX_ID

0x31415926

AiGraph

AIGRAPH

0xACCEECCA

String

SS_TEXT_OLD

0xACCEECCB

StringEncrypted

SS_TEXT

0xBBAB0000

Record

MAGIC_TRAP

0xBBAB0001

Dword

MT_DIPLOMACY

0xBBAB0002

String

MT_SPELL

0xBBAB0003

AreaArray

MT_AREAS

0xBBAB0004

Plot2DArray

MT_TARGETS

0xBBAB0005

Dword

MT_CAST_INTERVAL

0xBBAC0000

Record

LEVER

0xBBAC0001

Null

LEVER_SCIENCE_STATS

0xBBAC0002

Byte

LEVER_CUR_STATE

0xBBAC0003

Byte

LEVER_TOTAL_STATE

0xBBAC0004

Byte

LEVER_IS_CYCLED

0xBBAC0005

Byte

LEVER_CAST_ONCE

0xBBAC0006

LeverStats

LEVER_SCIENCE_STATS_NEW

0xBBAC0007

Byte

LEVER_IS_DOOR

0xBBAC0008

Byte

LEVER_RECALC_GRAPH

0xBBBB0000

Record

UNIT

0xBBBB0001

Null

UNIT_R

0xBBBB0002

String

UNIT_PROTOTYPE

0xBBBB0003

Null

UNIT_ITEMS

0xBBBB0004

UnitStats

UNIT_STATS

0xBBBB0005

StringArray

UNIT_QUEST_ITEMS

0xBBBB0006

StringArray

UNIT_QUICK_ITEMS

0xBBBB0007

StringArray

UNIT_SPELLS

0xBBBB0008

StringArray

UNIT_WEAPONS

0xBBBB0009

StringArray

UNIT_ARMORS

0xBBBB000A

Byte

UNIT_NEED_IMPORT

0xBBBC0000

Record

UNIT_LOGIC

0xBBBC0001

Null

UNIT_LOGIC_AGRESSIV

0xBBBC0002

Byte

UNIT_LOGIC_CYCLIC

0xBBBC0003

Dword

UNIT_LOGIC_MODEL

0xBBBC0004

Float

UNIT_LOGIC_GUARD_R

0xBBBC0005

Plot

UNIT_LOGIC_GUARD_PT

0xBBBC0006

Byte

UNIT_LOGIC_NALARM

0xBBBC0007

Byte

UNIT_LOGIC_USE

0xBBBC0008

Null

UNIT_LOGIC_REVENGE

0xBBBC0009

Null

UNIT_LOGIC_FEAR

0xBBBC000A

Float

UNIT_LOGIC_WAIT

0xBBBC000B

Byte

UNIT_LOGIC_ALARM_CONDITION

0xBBBC000C

Float

UNIT_LOGIC_HELP

0xBBBC000D

Byte

UNIT_LOGIC_ALWAYS_ACTIVE

0xBBBC000E

Byte

UNIT_LOGIC_AGRESSION_MODE

0xBBBD0000

Record

GUARD_PT

0xBBBD0001

Plot

GUARD_PT_POSITION

0xBBBD0002

Null

GUARD_PT_ACTION

0xBBBE0000

Record

ACTION_PT

0xBBBE0001

Plot

ACTION_PT_LOOK_PT

0xBBBE0002

Dword

ACTION_PT_WAIT_SEG

0xBBBE0003

Dword

ACTION_PT_TURN_SPEED

0xBBBE0004

Byte

ACTION_PT_FLAGS

0xBBBF0000

Record

TORCH

0xBBBF0001

Float

TORCH_STRENGHT

0xBBBF0002

Plot

TORCH_PTLINK

0xBBBF0003

String

TORCH_SOUND

0xDDDDDDD1

Record

DIPLOMATION

0xDDDDDDD2

Diplomacy

DIPLOMATION_FOF

0xDDDDDDD3

StringArray

DIPLOMATION_PL_NAMES

0xFFFFFFFF

Unknown

UNKNOWN

Этот файл содержит всю информацию об уровне — дипломатию, расположение и параметры юнитов, информацию для легендарного редактора Nival, а самое интересное — скрипт уровня, причём в зашифрованном виде (ключ лежит рядом, не беспокойтесь).

Расшифровка строк

unsigned key; for (size_t i = 0; i < size; i++) { key += (((((key * 13) << 4) + key) << 8) - key) * 4 + 2531011; data[i] ^= key >> 16;
}

В давние времена, одна из команд написала свою утилиту, в которой шифрование производилось одним постоянным ключом, с целью выявления факта использования конкурирующей командой. Это интересно: этот формат очень важен для модмейкеров, однако отреверсить файлы и расшифровать скрипт (а потом зашифровать обратно) было очень непростой задачей.

Тот самый легендарный редактор уровней (взято с форумов, точная дата неизвестна, однако на скриншоте — Windows 98):

Естественно, что его так никому и не дали (кроме как, возможно, разработчикам "Проклятые Земли: Затерянные в Астрале", но информации у меня нет). Это интересно: скриншот редактора несколько раз появлялся на форумах, где его страстно желали заполучить.

Вот теперь, получив всю необходимую нам информацию, мы наконец-то можем сконвертировать всё в более-менее известный формат файлов, например, Collada и сделать финальный рендер на память:

Мы рассмотрели устройство большей части форматов, а особо заинтересованные наверняка уже воспользовались схемами и написали свой собственный конвертер или просмотровщик. Наша краткая экскурсия по файлам Проклятых Земель подошла к концу.

Теперь же шансы сделать что-то новое хоть немного, но возросли — кто-нибудь захочет написать редактор карт, это привлечёт ещё больше заинтересованных людей. Я надеюсь, что эта статья пригодится фанатам игры или привлечёт новых людей в коммьюнити. Эх, мечты-мечты...

На этом я прощаюсь — до встреч на просторах Кании!


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

[Из песочницы] Английский и айтишник: английскую сову на русский глобус?

Люди с техническим складом ума во всем стремятся найти систему. При изучении английского, столь востребованного в IT, многие программисты сталкиваются с тем, что не могут понять, как устроен этот язык, его систему. “Кто виноват?” В чем же проблема? Казалось бы, ...

Читай старьё

Интерес к другим предметам приходил и уходил, а к истории оставался всегда. Всю свою сознательную жизнь я любил историю. Моя любовь к истории даже как-то привела меня на олимпиаду по истории, которую я, по какому-то стечению обстоятельств, выиграл, написав сочинение-рассуждение ...