From 041b1a6cb3159463fe81f4b2d18cb968d6f3fd87 Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Wed, 11 Feb 2026 21:12:05 +0000 Subject: Добавлены спецификации для сетевой подсистемы, системы звука, загрузки ландшафта, интерфейса пользователя и пайплайна выполнения. Обновлен файл навигации mkdocs.yml для включения новых документов. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/specs/materials-texm.md | 299 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 docs/specs/materials-texm.md (limited to 'docs/specs/materials-texm.md') diff --git a/docs/specs/materials-texm.md b/docs/specs/materials-texm.md new file mode 100644 index 0000000..4c8c8f4 --- /dev/null +++ b/docs/specs/materials-texm.md @@ -0,0 +1,299 @@ +# Materials + Texm + +Документ описывает материалы, текстуры, палитры, блоки `WEAR` / `LIGHTMAPS` и формат `Texm`. + +--- + +## 2.1. Архитектура материальной системы + +Материальная подсистема реализована в `World3D.dll` и включает: + +- **Менеджер материалов** (`LoadMatManager`) — объект размером 0x470 байт (1136), хранящий до 140 таблиц материалов (поле `+572`, `this[143]`). +- **Библиотека палитр** (`SetPalettesLib`) — NRes‑архив с палитрами. +- **Библиотека текстур** (`SetTexturesLib`) — путь к файлу/каталогу текстур. +- **Библиотека материалов** (`SetMaterialLib`) — NRes‑архив с данными материалов. +- **Библиотека lightmap'ов** (`SetLightMapLib`) — опциональная. + +### Загрузка палитр (sub_10002B40) + +Палитры загружаются из NRes‑архива по именам. Система перебирает буквы `'A'`..'Z'` (26 категорий) × 11 суффиксов, формируя имена вида `"A.pal"`. Каждая палитра загружается через `niOpenResFile` → `niReadData` и регистрируется как текстурный объект в графическом движке. + +Максимальное количество палитр: 26 × 11 = **286**. + +## 2.2. Запись материала (76 байт) + +Материал представлен записью размером **76 байт** (19 DWORD). Поля восстановлены из функции интерполяции `sub_10003030` и функций `sub_100031F0` / `sub_10003680`. + +| Смещение | Размер | Тип | Интерполяция | Описание | +|----------|--------|--------|--------------|--------------------------------------| +| 0 | 4 | uint32 | Нет | `flags` — тип/режим материала | +| 4 | 4 | float | Бит 1 (0x02) | Цветовой компонент A — R | +| 8 | 4 | float | Бит 1 (0x02) | Цветовой компонент A — G | +| 12 | 4 | float | Бит 1 (0x02) | Цветовой компонент A — B | +| 16 | 4 | — | Нет | Зарезервировано / паддинг | +| 20 | 4 | float | Бит 0 (0x01) | Цветовой компонент B — R | +| 24 | 4 | float | Бит 0 (0x01) | Цветовой компонент B — G | +| 28 | 4 | float | Бит 0 (0x01) | Цветовой компонент B — B | +| 32 | 4 | float | Бит 4 (0x10) | Скалярный параметр (power / opacity) | +| 36 | 4 | float | Бит 2 (0x04) | Цветовой компонент C — R | +| 40 | 4 | float | Бит 2 (0x04) | Цветовой компонент C — G | +| 44 | 4 | float | Бит 2 (0x04) | Цветовой компонент C — B | +| 48 | 4 | — | Нет | Зарезервировано / паддинг | +| 52 | 4 | float | Бит 3 (0x08) | Цветовой компонент D — R | +| 56 | 4 | float | Бит 3 (0x08) | Цветовой компонент D — G | +| 60 | 4 | float | Бит 3 (0x08) | Цветовой компонент D — B | +| 64 | 4 | — | Нет | Зарезервировано / паддинг | +| 68 | 4 | int32 | Нет | `textureIndex` — индекс текстуры | +| 72 | 4 | int32 | Нет | Дополнительный параметр | + +### Маппинг компонентов на D3D Material (предположительный) + +По аналогии со стандартной структурой `D3DMATERIAL7`: + +| Компонент | Вероятное назначение | Биты интерполяции | +|--------------|----------------------|-------------------| +| A (+4..+12) | Diffuse (RGB) | 0x02 | +| B (+20..+28) | Ambient (RGB) | 0x01 | +| C (+36..+44) | Specular (RGB) | 0x04 | +| D (+52..+60) | Emissive (RGB) | 0x08 | +| (+32) | Specular power | 0x10 | + +### Поле textureIndex (+68) + +- Значение `< 0` означает «нет текстуры» → `texture_ptr = NULL`. +- Значение `≥ 0` используется как индекс в глобальном массиве текстурных объектов: `texture = texture_array[5 * textureIndex]`. + +## 2.3. Алгоритм интерполяции материалов + +Движок поддерживает **анимацию материалов** между ключевыми кадрами. Функция `sub_10003030`: + +``` +Вход: mat_a (исходный), mat_b (целевой), t (фактор 0..1), mask (битовая маска) + +Выход: mat_result + +Для каждого бита mask: + если бит установлен: + mat_result.component = mat_a.component + (mat_b.component - mat_a.component) × t + иначе: + mat_result.component = mat_a.component (без интерполяции) + +mat_result.textureIndex = mat_a.textureIndex (всегда копируется без интерполяции) +``` + +### Режимы анимации материалов + +Материал может иметь несколько фаз (phase) с разными режимами цикличности: + +| Режим (flags & 7) | Описание | +|-------------------|-------------------------------------| +| 0 | Цикл: повтор с начала | +| 1 | Ping‑pong: туда‑обратно | +| 2 | Однократное воспроизведение (clamp) | +| 3 | Случайный кадр (random) | + +## 2.4. Глобальный массив текстур + +Текстуры хранятся в глобальном массиве записей по **20 байт** (5 DWORD): + +```c +struct TextureSlot { // 20 байт + int32_t name_hash; // +0: Хэш/ID имени текстуры (-1 = свободен) + void* texture_object; // +4: Указатель на объект текстуры D3D + int32_t ref_count; // +8: Счётчик ссылок + uint32_t last_release; // +12: Время последнего Release + uint32_t extra; // +16: Дополнительный флаг +}; +``` + +Функция `UnloadAllTextures` обнуляет все слоты, вызывая деструктор для каждого ненулевого `texture_object`. + +## 2.5. Глобальный массив определений материалов + +Определения материалов хранятся в глобальном массиве записей по **368 байт** (92 DWORD): + +```c +struct MaterialDef { // 368 байт (92 DWORD) + int32_t name_hash; // dword_100669F0[92*i]: -1 = свободен + int32_t ref_count; // dword_100669F4[92*i]: Счётчик ссылок + int32_t phase_count; // dword_100669F8[92*i]: Число текстурных фаз + void* record_ptr; // dword_100669FC[92*i]: Указатель на массив записей по 76 байт + int32_t anim_phase_count; // dword_10066A00[92*i]: Число фаз анимации + // +20..+367: данные фаз анимации (до 22 фаз × 16 байт) +}; +``` + +## 2.6. Переключатели рендера (из Ngi32.dll) + +Движок читает настройки из реестра Windows (`HKCU\Software\Nikita\NgiTool`). Подтверждённые ключи: + +| Ключ реестра | Глобальная переменная | Описание | +|--------------------------|-----------------------|---------------------------------| +| `Disable MultiTexturing` | `dword_1003A184` | Отключить мультитекстурирование | +| `DisableMipmap` | `dword_1003A174` | Отключить мипмап‑фильтрацию | +| `Force 16-bit textures` | `dword_1003A180` | Принудительно 16‑бит текстуры | +| `UseFirstCard` | `dword_100340EC` | Использовать первую видеокарту | +| `DisableD3DCalls` | `dword_1003A178` | Отключить вызовы D3D (отладка) | +| `DisableDSound` | `dword_1003A17C` | Отключить DirectSound | +| `ForceCpu` | (комбинированный) | Режим рендера: SW/HW TnL/Mixed | + +### Значения ForceCpu и их влияние на рендер + +| ForceCpu | Force SSE | Force 3DNow | Force FXCH | Force MMX | +|----------|-----------|-------------|------------|-----------| +| 2 | Да | Нет | Нет | Нет | +| 3 | Нет | Да | Нет | Нет | +| 4 | Да | Да | Нет | Нет | +| 5 | Да | Да | Да | Да | +| 6 | Да | Да | Да | Нет | +| 7 | Нет | Нет | Нет | Да | + +### Практические выводы для порта + +Движок спроектирован для работы **без** следующих функций (graceful degradation): + +- Мипмапы. +- Bilinear/trilinear фильтрация. +- Мультитекстурирование (2‑й текстурный слой). +- 32‑битные текстуры (fallback на 16‑бит). +- Аппаратный T&L (software fallback). + +--- + +## 2.7. Текстовый файл WEAR + LIGHTMAPS (World3D.dll) + +`World3D.dll` содержит парсер текстового файла (режим `rt`), который задаёт: + +- список **материалов (wear)**, используемых в сцене/объекте; +- список **лайтмап (lightmaps)**. + +Формат читается через `fgets`/`sscanf`/`fscanf`, поэтому он чувствителен к структуре строк и ключевому слову `LIGHTMAPS`. + +### 2.7.1. Блок WEAR (материалы) + +1) **Первая строка файла** — целое число: + +- `wearCount` (обязательно `> 0`, иначе ошибка `"Illegal wear length."`) + +2) Далее следует `wearCount` строк. Каждая строка имеет вид: + +- ` <пробелы> ` + +Где: + +- `` парсится, но фактически не используется как ключ (движок обрабатывает записи последовательно). +- `` — имя материала, которое движок ищет в менеджере материалов. + - Если материал не найден, пишется `"Material %s not found."` и используется fallback `"DEFAULT"`. + +> Практическая рекомендация для инструментов: считайте `` как необязательный “legacy-id”, а истинным идентификатором материала делайте строку ``. + +### 2.7.2. Блок LIGHTMAPS + +После чтения wear-списка движок последовательно читает токены (`fscanf("%s")`) до тех пор, пока не встретит слово **`LIGHTMAPS`**. + +Затем: + +1) Читается `lightmapCount`: + +- `lightmapCount` (обязательно `> 0`, иначе ошибка `"Illegal lightmaps length."`) + +2) Далее следует `lightmapCount` строк вида: + +- ` <пробелы> ` + +Где: + +- `` парсится, но фактически не используется как ключ (аналогично wear). +- `` — имя лайтмапы; если ресурс не найден, пишется `"LightMap %s not found."`. + +### 2.7.3. Валидация имени лайтмапы (деталь движка) + +Перед загрузкой лайтмапы выполняется проверка имени: + +- в имени должна встречаться точка `.` **в пределах первых 16 символов**, иначе ошибка `"Bad texture name."`; +- далее движок использует подстроку после точки в вычислениях внутренних индексов/кэша (на практике полезно придерживаться шаблона вида `NAME.A1`, `NAME.B2` и т.п.). + +--- +## 2.8. Формат текстурного ассета `Texm` (Ngi32.dll) + +Текстуры из `Textures.lib` хранятся как NRes‑entries типа `0x6D786554` (`"Texm"`). + +### 2.8.1. Заголовок `Texm` (32 байта) + +```c +struct TexmHeader32 { + uint32_t magic; // 0x6D786554 ('Texm') + uint32_t width; // base width + uint32_t height; // base height + uint32_t mipCount; // количество уровней + uint32_t flags4; // наблюдаются 0 или 32 + uint32_t flags5; // наблюдаются 0 или 0x04000000 + uint32_t unk6; // служебное поле (часто 0, иногда ненулевое) + uint32_t format; // код пиксельного формата +}; +``` + +Подтверждённые `format`: + +- `0` — paletted 8-bit (индекс + palette); +- `565`, `556`, `4444` — 16-bit семейство; +- `888`, `8888` — 32-bit семейство. + +### 2.8.2. Layout payload + +После заголовка: + +1) если `format == 0`: palette блок 1024 байта (`256 × 4`); +2) далее mip-chain пикселей; +3) опционально chunk атласа `Page`. + +Размер mip-chain: + +```c +bytesPerPixel = (format == 0 ? 1 : format in {565,556,4444} ? 2 : 4); +pixelBytes = bytesPerPixel * sum_{i=0..mipCount-1}(max(1,width>>i) * max(1,height>>i)); +``` + +Итого «чистый» размер без `Page`: + +```c +sizeCore = 32 + (format == 0 ? 1024 : 0) + pixelBytes; +``` + +### 2.8.3. Опциональный `Page` chunk + +Если после `sizeCore` остаются байты и в этой позиции стоит magic `"Page"` (`0x65676150`), парсер `sub_1000FF60` читает таблицу subrect: + +```c +struct PageChunk { + uint32_t magic; // 'Page' + uint32_t count; + struct Rect16 { + int16_t x; + int16_t w; + int16_t y; + int16_t h; + } rects[count]; +}; +``` + +Для каждого rect рантайм строит: + +- пиксельные границы (`x0,y0,x1,y1`); +- нормализованные UV (`u0,v0,u1,v1`) с делителем `1/(width<