# Документация формата MSH Формат `.msh` используется игрой Parkan: Железная стратегия (1998) для хранения 3D-мешей. MSH файлы — это NRes архивы, содержащие несколько типизированных компонентов. ## Обзор Существует **два варианта** формата MSH: | Вариант | Применение | Ключевые компоненты | Хранение треугольников | |---------|------------|---------------------|------------------------| | **Модель** | Роботы, здания, объекты | 06, 0D, 07 | Индексированные треугольники | | **Ландшафт** | Террейн | 0B, 15 | Прямые треугольники | ### Автоопределение типа ``` Модель: Есть компонент 06 (индексы) И 0D (батчи) Ландшафт: Есть компонент 0B (материалы) И НЕТ компонента 06 ``` --- ## Сводка компонентов | Тип | Название | Размер элемента | Описание | |:---:|----------|:---------------:|----------| | 01 | Node table | 38 (0x26), редко 24 | Узлы модели / тайлы; старое имя: Pieces | | 02 | Header + slots | 0x8C + n*68 | Общий заголовок и slot records; старое имя: Submeshes | | 03 | Positions | 12 (0x0C) | Позиции вершин (Vector3); старое имя: Vertices | | 04 | PackedNormals | 4 | `int8[4]`, normal = clamp(component / 127.0, -1..1) | | 05 | PackedUV0 | 4 | `int16[2]`, uv = component / 1024.0 | | 06 | Index buffer | 2 | Индексы вершин треугольников | | 07 | Tri descriptors | 16 | Описатели треугольников для коллизии/пикинга | | 08 | AnimKeyPool | 24 | Кейфреймы анимации меша | | 0A | Node strings | переменный | Строки узлов; старое имя: ExternalRefs | | 0B | неизвестно | 4 | неизвестно (только Ландшафт) | | 0D | Batch table | 20 (0x14) | Батчи рендера; FParkan Res13 decimal | | 0E | неизвестно | 4 | неизвестно (только Ландшафт) | | 12 | MicrotextureMap | 4 | неизвестно | | 13 | AnimMap | 2 | Карта кадров анимации, на нее указывает `AnimMapStart` из 0x01 | | 15 | TerrainTriangle table | 28 (0x1C) | Terrain-гипотеза | --- ## Поток данных ### Модель (роботы, здания) ``` Компонент 01 (Pieces - части) │ └─► Lod[n] ──► Компонент 02 (индекс сабмеша) │ ├─► TriStart:TriCount ──► Компонент 07 (данные на треугольник) │ └─► BatchStart:BatchCount ──► Компонент 0D (батчи) │ ├─► IndexStart:IndexCount ──► Компонент 06 (индексы) │ │ │ └─► Компонент 03 (вершины) │ └─► BaseVertex (базовое смещение вершины) ``` ### Ландшафт (террейн) ``` Компонент 01 (Тайлы, обычно 16×16 = 256) │ └─► Lod[n] ──► Компонент 02 (индекс сабмеша) │ └─► TriStart:TriCount ──► Компонент 15 (треугольники) │ └─► Vertex1/2/3Index ──► Компонент 03 (вершины) └─► TriStart:TriCount ──► Компонент 0B (материалы, параллельно 15) ``` **Важно:** В ландшафтных мешах поля `TriStart` и `TriCount` в Компоненте 02 используются для индексации в Компонент 15 (треугольники), а не в Компонент 07. --- ## Структуры компонентов ### Компонент 0x01 - Node table (0x26 = 38 байт) Определяет узлы модели или тайлы terrain. Старое локальное имя: Pieces / SubMesh. | Смещение | Размер | Тип | Поле | Описание | |:--------:|:------:|:---:|------|----------| | 0x00 | 2 | uint16 | Header0 | Заголовочное слово узла; старые имена: Type1 + Type2 | | 0x02 | 2 | uint16 | ParentOrLink | Индекс родителя/ссылка; старый локальный тип int16 показывал 0xFFFF как -1 | | 0x04 | 2 | uint16 | AnimMapStart | Начало блока в 0x13 или 0xFFFF; старое имя: OffsetIntoFile13 | | 0x06 | 2 | uint16 | FallbackKey | Индекс fallback-ключа в 0x08; старое имя: IndexInFile08 | | 0x08 | 30 | ushort[15] | SlotIndex | Индексы slot в 0x02 по формуле `lod * 5 + group`; старое имя: Lod | **Ландшафт:** 256 тайлов в сетке 16×16. Каждый тайл имеет 2 LOD (индексы 0-255 и 256-511). --- ### Компонент 0x02 - Header + slots (Заголовок: 0x8C = 140 байт, slot: 0x44 = 68 байт) #### Заголовок (140 байт) | Смещение | Размер | Тип | Поле | Описание | |:--------:|:------:|:---:|------|----------| | 0x00 | 96 | Vector3[8] | BoundingBox | 8-точечный баундинг-бокс | | 0x60 | 12 | Vector3 | Center | Центральная точка | | 0x6C | 4 | float | CenterW | W-компонента | | 0x70 | 12 | Vector3 | Bottom | Нижняя точка | | 0x7C | 12 | Vector3 | Top | Верхняя точка | | 0x88 | 4 | float | XYRadius | Радиус в плоскости XY | #### Элемент (68 байт) | Смещение | Размер | Тип | Поле | Описание | |:--------:|:------:|:---:|------|----------| | 0x00 | 2 | ushort | TriStart | Начальный индекс в Компоненте 07; в landscape-tooling может указывать в 15 | | 0x02 | 2 | ushort | TriCount | Количество записей в Компоненте 07; в landscape-tooling может быть count для 15 | | 0x04 | 2 | ushort | BatchStart | Начальное смещение в Компоненте 0D (только Модель) | | 0x06 | 2 | ushort | BatchCount | Количество батчей в Компоненте 0D (только Модель) | | 0x08 | 12 | Vector3 | LocalMinimum | Минимум локального баундинг-бокса | | 0x14 | 12 | Vector3 | LocalMaximum | Максимум локального баундинг-бокса | | 0x20 | 12 | Vector3 | Center | Центр сабмеша | | 0x2C | 4 | float | SphereRadius | Радиус bounding sphere; старый `Vector4` был overlay-гипотезой | | 0x30 | 20 | uint32[5] | Opaque | Непонятый tail, сохранять 1:1; старый `Vector5` был overlay-гипотезой | --- ### Компонент 03 - Vertices (0x0C = 12 байт) | Смещение | Размер | Тип | Поле | Описание | |:--------:|:------:|:---:|------|----------| | 0x00 | 4 | float | X | Координата X | | 0x04 | 4 | float | Y | Координата Y | | 0x08 | 4 | float | Z | Координата Z | --- ### Компонент 06 - Indices (2 байта) - Только Модель Массив `ushort` значений — индексы вершин треугольников. Используются группами по 3 для каждого треугольника. Ссылки через батчи Компонента 0D. --- ### Компонент 0x07 - Tri descriptors (0x10 = 16 байт) Описатели треугольников для коллизии/пикинга. | Смещение | Размер | Тип | Поле | Описание | |:--------:|:------:|:---:|------|----------| | 0x00 | 2 | ushort | TriFlags | Флаги треугольника; старое имя: Flags | | 0x02 | 2 | ushort | Link0 | Связь/opaque поле 0; старое имя: Magic02 | | 0x04 | 2 | ushort | Link1 | Связь/opaque поле 1; старое имя: Magic04 | | 0x06 | 2 | ushort | Link2 | Связь/opaque поле 2; старое имя: Magic06 | | 0x08 | 2 | int16 | NormalX | Упакованная X-компонента нормали; старое имя: OffsetX | | 0x0A | 2 | int16 | NormalY | Упакованная Y-компонента нормали; старое имя: OffsetY | | 0x0C | 2 | int16 | NormalZ | Упакованная Z-компонента нормали; старое имя: OffsetZ | | 0x0E | 2 | ushort | SelectorPacked | Три 2-битных селектора; `3` трактуется как `0xFFFF`; старое имя: Magic14 | --- ### Компонент 0B - Material Data (4 байта) - Только Ландшафт Информация о материале/текстуре на каждый треугольник. Параллельный массив к Компоненту 15. | Смещение | Размер | Тип | Поле | Описание | |:--------:|:------:|:---:|------|----------| | 0x00 | 2 | ushort | HighWord | Индекс материала/текстуры | | 0x02 | 2 | ushort | LowWord | Индекс треугольника (последовательный) | --- ### Компонент 0x0D - Batch table (0x14 = 20 байт) Определяет батчи вызовов отрисовки. В терминах FParkan это Res13 decimal. | Смещение | Размер | Тип | Поле | Описание | |:--------:|:------:|:---:|------|----------| | 0x00 | 2 | ushort | BatchFlags / Flags.low | Флаги батча | | 0x02 | 2 | ushort | MaterialIndex / Flags.high | Индекс material slot | | 0x04 | 2 | ushort | Opaque4 | Opaque, старое имя `TriangleCount` не подтверждено | | 0x06 | 2 | ushort | Opaque6 | Opaque | | 0x08 | 2 | ushort | IndexCount | Количество индексов для отрисовки в 0x06 | | 0x0A | 4 | uint32 | IndexStart | Начальный индекс в Компоненте 06 | | 0x0E | 2 | ushort | Opaque14 | Opaque, старое имя `CountOf03` не подтверждено | | 0x10 | 4 | uint32 | BaseVertex | Базовое смещение вершины в Компоненте 03 | --- ### Компонент 0x15 - TerrainTriangle table (0x1C = 28 байт) Прямые определения terrain-треугольников. Это hex-компонент 0x15 проекта, не FParkan Res15 decimal. | Смещение | Размер | Тип | Поле | Описание | |:--------:|:------:|:---:|------|----------| | 0x00 | 4 | uint32 | Flags | Флаги треугольника (0x20000 = коллизия) | | 0x04 | 4 | uint32 | MaterialData | Данные материала; старое имя: Magic04 | | 0x08 | 2 | ushort | Vertex1Index | Индекс первой вершины | | 0x0A | 2 | ushort | Vertex2Index | Индекс второй вершины | | 0x0C | 2 | ushort | Vertex3Index | Индекс третьей вершины | | 0x0E | 4 | uint32 | Opaque0E | Opaque; старое имя: Magic0E | | 0x12 | 4 | uint32 | Opaque12 | Opaque; старое имя: Magic12 | | 0x16 | 4 | uint32 | Opaque16 | Opaque; старое имя: Magic16 | | 0x1A | 2 | ushort | Opaque1A | Opaque; старое имя: Magic1A | #### MaterialData (0x04) - Структура материала ``` MaterialData = 0xFFFF_SSPP │ │└─ PP: Основной материал (byte 0) │ └─── SS: Вторичный материал для блендинга (byte 1) └────── Всегда 0xFFFF (байты 2-3) ``` | Значение SS | Описание | |:-----------:|----------| | 0xFF | Сплошной материал (без блендинга) | | 0x01-0xFE | Индекс вторичного материала для блендинга | Примеры: - `0xFFFFFF01` = Сплошной материал 1 - `0xFFFF0203` = Материал 3 с блендингом в материал 2 --- ### Компонент 0A - External References (переменный размер) Таблица строк для внешних ссылок на части меша. Формат: ``` [4 байта: длина] [байты строки] [null-терминатор] ...повтор... ``` Длина 0 означает пустую запись. Строки типа `"central"` имеют особое значение (flag |= 1). --- ## Пример: Ландшафт SC_1 ``` Land.msh (SC_1): ├── 01: 256 тайлов (сетка 16×16) ├── 02: 512 сабмешей (256 LOD0 + 256 LOD1) ├── 03: 10 530 вершин ├── 04: 10 530 данных на вершину ├── 05: 10 530 данных на вершину ├── 0B: 7 882 записи материалов ├── 0E: 10 530 данных на вершину ├── 12: 10 530 микротекстурный маппинг └── 15: 7 882 треугольника ├── LOD 0: 4 993 треугольника (тайлы 0-255 → сабмеши 0-255) └── LOD 1: 2 889 треугольников (тайлы 0-255 → сабмеши 256-511) ``` --- ## Использование ```csharp var converter = new MshConverter(); // Автоопределение типа и конвертация в OBJ converter.Convert("Land.msh", "terrain.obj", lodLevel: 0); converter.Convert("robot.msh", "robot.obj", lodLevel: 0); // Ручное определение типа var archive = NResParser.ReadFile("mesh.msh").Archive; var type = MshConverter.DetectMeshType(archive); // Возвращает: MshType.Model или MshType.Landscape ``` --- ## Формат WEA - Файлы материалов ландшафта Файлы `.wea` — текстовые файлы, определяющие таблицу материалов для ландшафта. ### Формат ``` {count} {index} {material_name} {index} {material_name} ... ``` ### Связь с Land.msh Каждая карта имеет два файла материалов: | Файл | Используется для | Треугольники в Comp15 | |------|------------------|----------------------| | `Land1.wea` | LOD0 (высокая детализация) | Первые N (сумма TriCount для LOD0) | | `Land2.wea` | LOD1 (низкая детализация) | Остальные | ### Пример (SC_1) **Land1.wea:** ``` 4 0 B_S0 1 L04 2 L02 3 L00 ``` **Land2.wea:** ``` 4 0 DEFAULT 1 L05 2 L03 3 L01 ``` ### Маппинг материалов Индекс материала в `Comp15.MaterialData & 0xFF` → строка в `.wea` файле. ``` Треугольник с MaterialData = 0xFFFF0102 └─ Основной материал = 02 → Land1.wea[2] = "L02" └─ Блендинг с материалом = 01 → Land1.wea[1] = "L04" ``` ### Типичные имена материалов | Префикс | Назначение | |---------|------------| | L00-L05 | Текстуры ландшафта (grass, dirt, etc.) | | B_S0 | Базовая текстура | | DEFAULT | Фолбэк для LOD1 | | WATER | Вода (поверхность) | | WATER_BOT | Вода (дно) | | WATER_M | Вода LOD1 | --- ## Источники - Реверс-инжиниринг `Terrain.dll` (класс CLandscape) - Декомпиляция Ghidra: `CLandscape::ctor` и `IMesh2_of_CLandscape::Render`