Echelon

Echelon

Not enough ratings
Формат данных игры
By mindego
Описание формата файлов игры. Возможно, используя его кто-то сделает полноценный римейк игры на современном движке.
   
Award
Favorite
Favorited
Unfavorite
Введение
В этом (2022) игре "Шторм" ("Echelon") исполнился 21 год. Являясь представителем довольно редкого жанра фантастического авиасимулятора, игра практически не имеет конкурентов, однако 21 год не пошёл на пользу. Хотя на некоторые скриншоты ещё приятно смотреть, в самой игре "туман" и размытость прямо-таки бросаются в глаза, да и запустить игру на современных ОС является тем ещё квестом. Идеальным решением был бы римейк на современном движке (к примеру, Unity или Unreal Engine), но это требует серьёзной работы.
В тестовых целях была проверена возможность переноса ресурсов в Unity с положительным результатом, но вряд ли автор этого руководства доведёт проект до завершения. Впрочем, это не исключено, однако если кто-то воспользуется этими наработками и самостоятельно сделает такой римейк, автор будет только рад.
Формат карты
Игра "разбивает" карту на несколько файлов в каталоге "Scenes" с различными расширениями.
К сожалению, не все данные удалось опознать, но полученных данных достаточно для восстановления карты
Формат карты: *.hd
Файл описания карты. Этот самый маленький файл (644 байта) из всех файлов описания карты содержит описание карты:
  • uint32 ширинаКарты
  • uint32 высотаКарты
Последующие данные не опознаны.
Высота и ширина карты указаны в блоках по 128 пикселей.
К примеру, в карте Continent (это карта кампании оригинального "Шторма") указаны размеры 2A 00 00 00 и 2F 00 00 00. Числа в файлах игры хранятся в little endian, поэтому меньший байт числа впереди, таким образом размер карты получается 42x47 блоков 128х128 пикселей. Итого размер карты получается 5376 x 6016 вершин. Кстати,именно такое разрешение имеет и bmp файл с картой континента в "Шторм:Солдаты неба"
Формат карты: *.l*
В файлах вида *.l01 сохранены предварительно рассчитанные тени.
Вершины в карте сгруппированы в блоки 16х16 вершин, элементом является:
  • char картаВысот1
  • char картаТеней1
  • char картаВысот2
  • char картаТеней2

Не совсем понятно, почему так (данные в картах высот и картах теней не дублируются), но есть один плюс - эти карты не нужны, так как современные движки позволяют обсчитывать это в реальном времени.
Формат карты: *.bx
Формат полностью соответствует таковому для .l* файлов
Формат карты: *.sq
В файлах .sq содержится основная геометрия карты.

Данные в файле .sq сгруппированы в блоки по 32х32 вершины.
Элементом такой группы является:
  • short картаВысот
  • usort Флаги

Всё карты "чёрно-белые", раскрашены для удобства - у каждого значения свой собственный цвет.

КартаВысот
.png]

Флаги
Флаги точки карты.
В флагах перечислена проходимость этот точки карты, тип поверхности (НЕ текстура)и признак симметричности. Из флага нужный параметр можно извлечь при помощи битовых операций:
  • проходимость (PASS_MASK) XOR 0x1800
  • симметричность SQF_SIMMETRY (1<<15);
  • SQF_GRMASK тип поверхности 0x07

Во флагах указан именно тип поверхности. Соответствие текстуры и типа поверхности можно найти в описании файла .vb
В оригинальном "Шторме" есть всего 8 типов поверхности:
WATER=0,SAND=1,DESERT=2,GROUND=3,GRASS=4,ROCKS=5,SNOW=6,PEAK=7
Формат карты: *.vb
В файле .vb хранятся несколько субкарт с не совсем понятным предназначением.
Данные сгруппированы в блоки по 16х16 вершин, на каждую вершину используется 16 байт таким образом один блок 16х16 вершин содержит информацию о текстурах для 4 блоков вершин геометрии.
Элементом данных является:
  • char неизвестнаяКарта1
  • char неизвестнаяКарта2
  • char неизвестнаяКарта3
  • char неизвестнаяКарта4
  • char неизвестнаяКарта5
  • char неизвестнаяКарта6
  • char неизвестнаяКарта7
  • char неизвестнаяКарта8
  • char картаТекстур
  • char картаТекстур2?
  • char неизвестнаяКарта9
  • char неизвестнаяКарта10
  • char неизвестнаяКарта11
  • char неизвестнаяКарта12
  • char неизвестнаяКарта13
  • char неизвестнаяКарта14

неизвестнаяКарта1
Назначение карты неизвестно

неизвестнаяКарта2
Назначение карты неизвестно

неизвестнаяКарта3-8
Назначение карты неизвестно. На картах "подкрашена" область учебного центра. Зачем - неизвестно.

картаТекстур
Карта текстур. Есть всего 32 текстуры:
{ "terr_water1", "GroundDynamic" }, // 0 { "terr_forest1", "GroundDynamic" }, // 1 { "terr_forest2", "GroundDynamic" }, // 2 { "terr_forest3", "GroundDynamic" }, // 3 { "terr_forest5", "GroundDynamic" }, // 4 { "terr_lug1", "GroundDynamic" }, // 5 { "terr_lug2", "GroundDynamic" }, // 6 { "terr_lug3", "GroundDynamic" }, // 7 { "terr_lug4", "GroundDynamic" }, // 8 { "terr_lug5", "GroundDynamic" }, // 9 { "terr_lug6", "GroundDynamic" }, //10 { "terr_rocks1", "GroundDynamic" }, // 1 { "terr_rocks5", "GroundDynamic" }, // 2 { "terr_rocks6", "GroundDynamic" }, // 3 { "terr_snow3", "GroundDynamic" }, // 4 { "terr_snow4", "GroundDynamic" }, // 5 { "terr_ruda", "GroundDynamic" }, // 6 { "terr_mount3", "GroundDynamic" }, // 7 { "terr_mount1", "GroundDynamic" }, // 8 { "terr_mount2", "GroundDynamic" }, // 9 { "terr_rocks4", "GroundDynamic" }, //20 { "terr_snow1", "GroundDynamic" }, // 1 { "terr_snow2", "GroundDynamic" }, // 2 { "terr_sand1", "GroundDynamic" }, // 3 { "terr_sand2", "GroundDynamic" }, // 4 { "CITYGRND", "GroundDynamic" }, // 5 { "forestg250", "GroundDynamic" }, // 6 Forest G250 , есть только в Солдатах Неба, в оригинальном Шторме - зарезервировано и не используется { "BASEGRND", "GroundDynamic" }, // 7 { "forestg200", "GroundDynamic" }, // 8 Forest G200 есть только в Солдатах Неба, в оригинальном Шторме - зарезервировано и не используется { "terr_field4", "GroundDynamic" } // 9

Каждая текстура относится к определённому типу поверхности. Всего типов поверхности 8:
{WATER=0,SAND=1,DESERT=2,GROUND=3,GRASS=4,ROCKS=5,SNOW=6,PEAK=7}
Карта "чёрно-белая", раскрашена следующим образом:
{ { 0, 0, 50 }, 0, WATER}, // 0 { { 0, 50, 0 }, 0, GRASS}, // 1 { { 0, 70, 0 }, 0, GRASS}, // 2 { { 0, 90, 0 }, 0, GRASS}, // 3 { { 0, 110, 0 }, 0, GRASS}, // 4 { { 50, 50, 0 }, 0, GROUND}, // 5 { { 70, 70, 0 }, 0, GROUND}, // 6 { { 90, 90, 0 }, 0, GROUND}, // 7 { { 110, 110, 0 }, 0, SAND}, // 8 { { 130, 130, 0 }, 0, SAND}, // 9 { { 150, 150, 0 }, 0, SAND}, //10 { { 170, 170, 0 }, 0, SAND}, // 1 { { 190, 190, 0 }, 0, SAND}, // 2 { { 110, 0, 0 }, 0, ROCKS}, // 3 { { 130, 0, 0 }, 0, SNOW}, // 4 { { 150, 0, 0 }, 0, PEAK}, // 5 { { 50, 50, 50 }, 0, ROCKS}, // 6 { { 70, 70, 70 }, 0, ROCKS}, // 7 { { 90, 90, 90 }, 0, ROCKS}, // 8 { { 110, 110, 110 }, 0, ROCKS}, // 9 { { 130, 130, 130 }, 0, ROCKS}, //20 { { 150, 150, 150 }, 0, SNOW}, // 1 { { 170, 170, 170 }, 0, SNOW}, // 2 { { 30, 0, 30 }, 0, DESERT}, // 3 { { 50, 0, 50 }, 0, DESERT}, // 4 { { 70, 0, 70 }, 0, DESERT}, // 5 { { 0, 250, 0 }, 0, GRASS}, // 6 Forest G250 { { 110, 0, 110 }, 0, DESERT}, // 7 { { 0, 200, 0 }, 0, GRASS}, // 8 Forest G200 { { 150, 0, 150 }, 0, DESERT} // 9

картаТекстур2
Назначение карты неизвестно. Содержимое похоже на карту текстур, но "разбавлено" FF. Предположительно - поворот или смешение текстур.

неизвестнаяКарта9-14
Назначение карты неизвестно.
Формат ресурсных файлов
Ресурсные файлы игры можно разделить на три основные группы:
1. Файлы ресурсов
Это собственно файлы ресурсов. Характерным примером может могут быть файлы карты
2. Набор ресурсов
Это файл, содержащий в себе файлы ресурсов
3. База данных
Это файл, содержащий в себе базу данных. Формат свой собственный, кроме собственно данных может содержать набор ресурсов.
Формат ресурсных файлов: Файл ресурса
Описать в одном месте формат файла ресурса неудобно - он кодируется различными способами. Описание каждого формата будет находиться в описании соответстующего набора ресурсов.
Формат ресурсных файлов: Набор ресурсов
Набор ресурсов содержит один или более файлов ресурса. Расширение файла может быть любым, в оригинальном "Шторме" это .dat и .sfx, однако .dat файлом может быть и файл базы данных, поэтому ориентироваться стоит по первым четырём байтам файла.
Первые четыре байта могут быть различными, однако наборы ресурсов кодируются одним способом:
Заголовок
  • String[4] - magic
  • uint32 - resCount
  • uint32 - dataSize
  • uint32 - nameSize

magic
Это "волшебные данные", указывающие на тип набора ресурсов, к примеру DSFX,DATA или TEXS. Особо на этот параметр не стоит обращать внимание, главное чтобы не был EVG1 (это файл базы данных)

resCount
Количество ресурсов в наборе

dataSize
Размер блока данных ресурсов

nameSize
Размер блока имён ресурсов

Таблица размещения ресурсов
  • uint32 resourceID
  • uint32 resourceNameOffset
  • uint32 resourceDataOffset
  • uint32 resourceDataSize
repeat resCount

resourceID
Идентификатор ресурса. Он будет часто использоваться как ссылка на ресурс в других файла. Получается из crc32(resourceName) XOR -1 (хотя использование этого в других ресурсах я не нашёл). Уникален в пределах одного набора ресурсов.

resourceNameOffset
Отступ в блоке списка имён ресурсов (см.далее)

resourceDataOffset
Отступ в блоке данных ресурсов (см.далее)

resourceDataSize
Размер ресурса

Блок данных
  • BYTES[dataSize]
Просто блок идущих подряд байтов.

Блок имен ресурсов
  • BYTES[nameSize]
Просто блок идущих подряд байтов. Имена разделены NULL символом (0x00)
Формат ресурсных файлов: База данных
Файл базы данных содержит в себе собственно базу данных в собственном формате.
Файл БД легко определить по первым четырём символам EVG1
Заголовок
  • String[4] magic
  • uint32 RATSize

magic
Идентификатор файла. Должен быть EVG1

RATSize
Размер таблицы размещения записей

Блок таблицы размещения записей
  • uint32 recordID
  • uint32 recordSize
repeat until <RATSize

recordID
Индекс записи

recordSize
Размер записи

Блок MIKH
Блок MIKH следует за таблицей размещения записей и содержит собственно записи
  • magic
  • dataSize

magic
Идентификатор блока. Должен быть именно MIKH

dataSize
Размер блока данных

Bytes[dataSize]

Сама БД представляет из собой связанный список, начинающийся с 1 элемента. Элемент 0 это NULL элемент.
Запись rec выглядит как:
  • Bytes[4] type
  • uint32 index
type
Тип записи
index
Индекс ресурса со значением

Элемент может быть (в зависимости от значения type):
  • Ассоциативным массивом (type A8FA05F7)
  • Простым массивом (type 3F776657)
  • Набором данных (type AA0E7175)
  • Целым числом (type B9969518)
  • Дробным числом (type 1DC7CF3F)
  • Строкой (type AF3C29AF)
  • Вектором (type 5DC6F92A)
  • Данными миссии (type A05F049E)

Ассоциативный массив
  • uint32 recCount
    {
  • rec
  • uint32 nameOffset
    repeat recCount}
  • bytes[recSize-nameOffset] recNames

recCount
Количество элементов в массиве

rec
Элемент массива

nameOffset
Смещение в списке ключей

recNames
Список ключей. Представляет из себя просто строку, разделённую NULL (0x00) символом

Простой массив
Не сильно отличается от ассоциативного
  • uint32 recCount
    {
  • rec
    repeat recCount}
recCount
Количество элементов в массиве

rec
Элемент массива

Набор данных
См. описание формата файлов набора данных
Используется редко - в файле кампании используется в карте дорог

Целое число
  • uint32 value
Целое число (в формате little endian)

Дробное число
  • float32 value
Дробное число

Строка
  • String[recordSize] value
Строка в кодировке CP1251. Встречается, впрочем, и CP866

Вектор
  • float32 x
  • float32 y
  • float32 z
Просто набор из трёх координат в дробных числах

Данные миссии
см. Ассоциативный массив.
В описании миссий юниты указаны в виде 4-байтового идентификатора, не совпадающего ни с чем. Как выяснилось, идентификатором является имя из набора ресурсов gdata.dat, подвергнутого crc32 XOR -1
Пример:
В миссии C1-1 (это первая миссия в ветке пропуска обучения первого Шторма) группа "Альфа" в первом юните содержит значение поля "Name" -2142616657.
Смотрим поле Flag (описание этого поля будет отдельно) - в нём 3907. Смотрим 5-8 биты. В 1 установлен 6-й бит, значит это Craft. Перебираем каждое название (именно название, т.е. из блока Craft("CHORT_V_STUPE") берем "CHORT_V_STUPE") из этого файла, используя следующую функцию crc32(name) XOR -1. Получаем совпадение со строкой "Human_BF1", таким образом в группе "Альфа" первый юнит это Human_BF1 (BFF1-112 Tempest)

Набор ресурсов: Graphics/mesh.dat
Этот набор ресурсов содержит модели. Только модели, без текстур.
Каждая модель состоит из:
  • Файла метаданных
  • 1-4 Файла описания вершин
  • 1-4 Файла описания полигонов

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

Файл метаданных
Файл метаданных выглядит как имя_Im0 (варианта - _Im1,_Im2).
Имеет следующий формат:
  • uint32 header
  • uint32 lod0
  • uint32 lod1
  • uint32 lod2

header
Заголовок, всегда 0.

lod0
Идентификатор (см. описание файлов набора ресурсов) файла уровня детализации 0

lod1
Идентификатор (см. описание файлов набора ресурсов) файла уровня детализации 1

lod2
Идентификатор (см. описание файлов набора ресурсов) файла уровня детализации 2

Значение идентификатора может быть FFFFFFFF. Это означает, что такой уровень детализации у модели отсутствует.

Файл описания вершин
Этот обычно самый большой файл может иметь разное имя (но обычно заканчивается на _Vert00), однако формат его прост:
  • float32 x
  • float32 y
  • float32 z
  • float32 norm_x
  • float32 norm_y
  • float32 norm_z
  • float32 u
  • float32 v

x,y,z
Координаты вершины.

norm_x,norm_y,norm_z
Координаты нормали.

u,v
Текстурные координаты. Хотелось бы отметить, что в некоторых случаях потребуется "переворачивать" либо текстуру либо вертикальную (u) текстурную координату.

Файл описания полигонов
Этот файл содержит нужные для создания модели данные.
Формат по сравнению с остальными сложен
  • uint32 header
  • uint32 blk_cnt
  • uint32 blk_cnt2
  • uint32 blk_num?
  • uint32 boundSphereX
  • uint32 boundSphereY
  • uint32 boundSphereZ
  • uint32 boundSphereRadius
    {
  • uint32 headerTexs
  • uint32 textureID
  • uint32 materialID
  • uint32 headerTexs
    } repeat blk_cnt
    {
  • uint32 headerGroupVert
  • uint32 groupNum
  • uint16 groupVertOffset
  • uint16 groupVertCount
  • uint32 indicesCount
    } repeat blk_cnt
  • uint32 headerVert
  • uint32 vertexCount
  • uint32 vertexfileID
  • uint16[sum([indicesCount)] indices

header
Заголовок, всегда 0

blk_cnt,blk_cnt2
Количество групп блоков данных в модели (число субмоделей)

blk_num
Назначение неизвестно.

unknown
Назначение неизвестно. Предположительно - изменение размеров или поворот

objectID
Идентификатор из соответствующего object файла. Назначение неясно

headerTexs
Заголовок блока материала. всегда 0

textureID
Идентификатор текстуры из набора ресурсов textures.dat

materialID
Идентификатор материала из набора ресурсов materials.dat

headerGroupVert
Заголовок блока описания вершин группы (04 00 00 00).

groupNum
Номер группы.

groupVertOffset
Смещение индекса вершины для текущей группы.

groupVertCount
Количество вершин в группе

indicesCount
Количество индексов в группе

headerVert
Заголовок блока описания вершин (12 01 00 00)

vertexCount
Общее число вершин в файле вершин

vertexfileID
Идентификатор ресурса вершин в наборе ресурсов mesh.dat

indices
Индексы

Небольшое пояснение. Под "Группой" в этом разделе подразумевается "Субмодель". К примеру, в грузовике есть две субмодели - сам грузовик и его водители. Вершины модели идут сплошным потоком - для примера a b c d e f G H I (большими буквами - вершины, относящиеся к водителям). В этом случае blk_count в файле будет 2 (т.е две субмодели), для группы 0 (сам грузовик) смещение будет 0, для группы 1 (водители) смещение будет 6
Таким образом, при генерации модели для группы 0 ничего смещать не надо, для группы 1 к полученному индексу потребуется добавить groupVertOffset. Впрочем, можно генерировать модель грузовика отдельно от модели водителей - в этому случае смещение можно не использовать.
Набор ресурсов: Graphics/objects2.dat
Этот набор ресурсов содержит иерархию отдельного юнита. В терминах игры "юнитом" является любой объект на карте, к примеру - крафт игрока, танк, мост или статуя Владычицы Озера (серьёзно, кто ещё может быть женского пола в Новом Авалоне? Моргана?).
Иерархия блоков представляет из себя связный список. Каждый следующий блок в списке потомков "родительского" блока прописан в предпоследнем dword Mesh_block, последний dword указывает на блок-потомок текущего блока.
Файл иерархии состоит из следующих элементов:
Блок слота. Слотом является место "крепления" другого юнита к текущему юниту. Характерным примером является слот оружия на крафте, турель на авианосце или выхлоп из дюзы.
Имеет следующий формат:
    Slot_Block 0x30 bytes
  • u4le unknown
  • u4le blockId
  • u4le blockIndex
  • Vec3 localPos
  • 6*4 bytes unknown
Блок описания субмодели юнита. Собственно, отдельная видимая часть юнита.
Имеет следующий формат:
    MeshData_block 0x24 bytes
  • u4le meshId //идентификатор модели. StormCRC32 от имени файла.
  • u4le meshId
  • 7*4 bytes unknown
Блок элемента юнита. Собственно, отдельная видимая часть юнита. Именно к ней "привязаны" слоты. Блок _обязательно_ содержит 4 блока MeshData_block - это "варианты" отображения блока. В основном это варианты вида "блок целый", "блок повреждённый", "блок разбитый", четвёртый вариант встречается редко - в основном это "детальные" (ну, тогда детальные - текущий момент детальность так себе) внутренние виды кабины.
Имеет следующий формат:
    Mesh_block 0xC8 bytes
  • u4le unknown
  • u4le blockId
  • u4le slotCount //количество слотов на текущеё субмодели
  • Vec3 localPos
  • 6*4 bytes unknown
  • 4xMeshData_block
  • u4le next_Mesh_block_offset //относительное смещение
  • u4le child_mesh_block_offset //относительное смещение
Структура самого файла проста:
  • 0xC8 bytes Mesh_Block
  • 0x30*slotCount Slot_Block
  • repeat until EOF
1 Comments
ScorpyX 30 Apr, 2024 @ 2:46pm 
Спасибо за подробный разбор. Это отлично :mhwgood: