2024-11-24 21:40:46 +03:00
# Reverse Engineering игры Parkan Железная стратегия 1998
2024-11-24 21:39:50 +03:00
2024-11-26 01:17:35 +03:00
<div align="center">
<img width="300" height="300" src="https://github.com/user-attachments/assets/dcd9ac8f-7d30-491c-ae6c-537267beb7dc" alt="x86 Registers" />
2025-08-20 15:38:44 +03:00
<img width="817" height="376" alt="Image" src="https://github.com/user-attachments/assets/c4959106-9da4-4c78-a2b7-6c94e360a89e" />
2024-11-26 01:17:35 +03:00
</div>
2025-08-20 15:38:44 +03:00
2024-11-24 21:39:50 +03:00
## Сборка проекта
2025-08-23 03:21:03 +03:00
Проект написан на C# под `.NET 9`
2024-11-24 21:39:50 +03:00
Вам должно хватить `dotnet build` для сборки всех проектов отдельно.
Все приложения кросс-платформенные, в том числе UI.
### Состояние проекта
2025-08-18 21:09:31 +03:00
- Поддержка всех `NRes` файлов - звуки, музыка, текстуры, карты и другие файлы. Есть документация.
- Поддержка всех `TEXM` текстур. Есть документация.
- Поддержка файлов миссий `.tma` .
- Поддержка шрифтов TFNT.
- Поддержка файлов скриптов `.scr` .
- Поддержка файлов параметров `.var` .
2025-08-23 03:21:03 +03:00
- Поддержка файлов схем объектов `.dat` .
2024-11-24 21:39:50 +03:00
### Структура проекта
Внимание!
Проект делается как небольшой PET, поэтому тут может не быть
- чёткой структуры
- адекватных названий
- комментариев
Я конечно стараюсь, но ничего не обещаю.
## Для Reverse Engineering-а использую Ghidra
### Наблюдения
- Игра использует множество стандартных библиотек, в частности stl_port, vc++6 и другие. Если хотите что-то изучить в игре, стоит поискать по строкам и сигнатурам, что именно используется в конкретной `dll` .
- Строки в основном используются двух форматов - `char*` и `std::string` . Последняя состоит из 16 байт - `undefined4, char* data, int length, int capacity` .
- В игре очень много `inline` функции, которые повторяются по куче раз в бинарнике.
- Игра загружает и выгружает свои `dll` файлы по несколько раз, так что дебаг с `Memory Map` очень затруднён.
- Игра активно и обильно течёт по памяти, оставляя после чтения файлов их `MapViewOfFile` и подобные штуки.
- Игра нормально не работает на Win10. Мне помог dgVoodoo. Хотя с ним не работает `MisEditor` .
2025-08-23 03:00:14 +03:00
## Как быстро найти текст среди всех файлов игры
```shell
grep -rl --include= "*" "s_tree_05" .
```
## Как быстро найти байты среди всех файлов игры
```shell
grep -rlU $'\x73\x5f\x74\x72\x65\x65\x5f\x30\x35' .
```
## Как работает игра
Главное меню:
Игра сканирует хардкод папку `missions` на наличие файлов миссий. (буквально 01, 02, 03 и т.д.)
Сначала игра читает название миссии из файла `descr` - тут название для меню.
- Одиночные игры - `missions/single.{index}/descr`
- Тренировочные миссии - `missions/tutorial.{index}/descr`
- Кампания - `missions/campaign/campaign.{index1}/descr`
* Далее используются подпапки - `missions/campaign/campaign.{index1}/mission.{index2}/descr`
Как только игра не находит файл `descr` , заканчивается итерация по папкам (понял, т.к. пробуется файл 05 - он не существует).
Загрузка миссии:
Читается файл `ui/game_resources.cfg`
Из этого файла загружаются ресурсы
- `library = "ui\\ui.lib"` - загружается файл `ui.lib`
- `library = "ui\\font.lib"` - загружается файл `font.lib`
- `library = "sounds.lib"` - загружается файл `sounds.lib`
- `library = "voices.lib"` - загружается файл `voices.lib`
Затем игра читает `save/saveslots.cfg` - тут слоты сохранения
Затем `Comp.ini` - тут системные функции, которые используются для загрузки объектов.
2025-08-31 01:07:24 +03:00
```
IComponent ** LoadSomething(undefined4, undefined4, undefined4, undefined4)
```
2025-08-23 03:00:14 +03:00
- `Host.url` - этого файла нет
- `palettes.lib` - тут палитры, но этот NRes пустой
- `system.rlb` - не понятно что
- `Textures.lib` - тут текстуры
- `Material.lib` - тут какие-то материалы - не понятно
- `LightMap.lib` - видимо это карты освещения - не понятно
- `sys.lib` - не понятно
- `ScanCode.dsc` - текстовый файл с мапом клавиш
- `command.dsc` - текстовый файл с мапом клавиш
Тут видимо идёт конфигурация ввода
- `table_1.man` - текстовый файл
- `table_2.man` - текстовый файл
- `hero.man` - текстовый файл
- `addition.man` - текстовый файл
- Снова `table_1.man`
- Снова `table_1.man`
- `M1.tbl` - текстовый файл
- Снова `table_2.man`
- Снова `table_2.man`
- `M2.tbl` - текстовый файл
- Снова `hero.man`
- Снова `hero.man`
- `HERO.TBL`
- Снова `addition.man`
- `ui/hq.cfg`
- Снова `ui/hq.cfg`
Дальше непосредственно читается миссия
- `mission.cfg` - метадата миссии
- `units\\units\\prebld\\scr_pre1.dat` из метаданных `object prebuild` - `cp` файл (грузятся подряд все)
- Опять `ui/hq.cfg`
- `mistips.mis` - описание для игрока (экран F1)
- `scancode.dsc` - хз
- `command.dsc` - хз
- `ui_hero.man` - хз
- `ui_bots.man` - хз
- `ui_hq.man` - хз
- `ui_other.man` - хз
- Цикл чтения курсоров
* `ui/cursor.cfg` - тут настройки курсора.
* `ui/{name}` - курсор
- Снова `mission.cfg` - метадата миссии
- `descr` - название
- `data/textres.cfg` - конфиг текстов
- Снова `mission.cfg` - метадата миссии
- Ещё раз `mission.cfg` - метадата миссии
- `ui/minimap.lib` - NRes с текстурами миникарты.
- `messages.cfg` - Tutorial messages
УРА НАКОНЕЦ-ТО `data.tma`
- Из `.tma` берётся LAND строка (я её так назвал)
- `DATA\\MAPS\\SC_3\\land1.wea`
- `DATA\\MAPS\\SC_3\\land2.wea`
- `BuildDat.lst` - Behaviour will use these schemes to Build Fortification
- `DATA\\MAPS\\SC_3\\land.map`
- `DATA\\MAPS\\SC_3\\land.msh`
- `effects.rlb`
Цикл по кланам из `.tma`
- `MISSIONS\\SCRIPTS\\screampl.scr`
- `varset.var`
- `MISSIONS\\SCRIPTS\\varset.var`
- `MISSIONS\\SCRIPTS\\screampl.fml`
- `missions/single.01/sky.ske`
- `missions/single.01/sky.wea`
Дальше начинаются объекты игры
- `"UNITS\\BUILDS\\BUNKER\\mbunk01.dat"` - cp файл
## Загрузка `cp` файлов
`cp` файл - схема. Он содержит дерево частей объекта.
`cp` файл читается в `ArealMap.dll/CreateObjectFromScheme`
В зависимости от типа объекта внутри схемы (байты 4..8) выбирается функция, с помощью которой загружается схема.
Функция выбирается на основе файла `Comp.ini` .
2025-08-23 03:21:03 +03:00
- Для ClassBuilding (0x80000000) - вызывается функция c классом 3 (по таблице ниже Building).
- Для всех остальных - функция с классом 4 (по таблице ниже Agent).
На основе файла `Comp.ini` и первом вызове внутри функции `World3D.dll/CreateObject` ремаппинг id:
2025-12-04 03:50:29 +03:00
| Logic ID | ClassName | Function |
|:--------:|:------------:|--------------------------------|
| 1 | Landscape | `terrain.dll LoadLandscape` |
| 2 | Agent | `animesh.dll LoadAgent` |
| 3 | Building | `terrain.dll LoadBuilding` |
| 4 | Agent | `animesh.dll LoadAgent` |
| 5 | Camera | `terrain.dll LoadCamera` |
| 7 | Atmosphere | `terrain.dll CreateAtmosphere` |
| 9 | Agent | `animesh.dll LoadAgent` |
2026-05-13 02:53:42 +03:00
| 0xa | Agent | `animesh.dll LoadAgent` |
| 0xb | Research | `misload.dll LoadResearch` |
| 0xc | Agent | `animesh.dll LoadAgent` |
2025-08-23 03:21:03 +03:00
2025-08-23 19:03:03 +03:00
Будет дополняться по мере реверса.
Всем этим функциям передаётся `nres_file_name, nres_entry_name, 0, player_id`
## `fr FORT` файл
Всегда 0x80 байт
Содержит 2 ссылки на файлы:
- `.bas`
- `.ctl` - вызывается `LoadAgent`
2025-08-23 03:00:14 +03:00
## `.msh`
2025-09-03 01:30:54 +03:00
### Описание ниже валидно только для моделей роботов и зданий.
##### Land.msh использует другой формат, хотя 03 файл это всё ещё точки.
2025-08-23 19:03:03 +03:00
Загружается в `AniMesh.dll/LoadAniMesh`
2025-08-31 01:07:24 +03:00
- Тип 01 - заголовок. Он хранит список деталей (submesh) в разных LOD
2025-08-26 04:21:48 +03:00
```
нулевому элементу добавляется флаг 0x1000000
2026-05-09 20:36:43 +03:00
Содержит 2 ссылки на анимационные subentry: AnimMapStart указывает в файл 0x13, FallbackKey указывает в файл 0x08.
2025-08-26 04:21:48 +03:00
Если интерполируется анимация -0.5s короче чем magic1 у файла 13
И у файла есть OffsetIntoFile13
И ushort значение в файле 13 по этому оффсету > IndexInFile08 (это по-моему выполняется всегда)
Тогда вместо IndexInFile08 используется значение из файла 13 по этому оффсету (второй байт)
` ``
2025-08-31 01:07:24 +03:00
- Тип 02 - описание одного LOD Submesh
2025-08-26 04:21:48 +03:00
` ``
Вначале идёт заголовок 0x8C (140) байт
В заголовке:
8 Vector3 (x,y,z) - bounding box
1 Vector4 - center
1 Vector3 - bottom
1 Vector3 - top
1 float - xy_radius
2025-08-26 04:29:30 +03:00
Далее инфа про куски меша
2025-08-26 04:21:48 +03:00
` ``
2025-08-23 03:00:14 +03:00
- Тип 03 - это вершины (vertex)
2025-08-31 01:07:24 +03:00
- Тип 06 - индексы треугольников в файле 03
2025-08-23 03:00:14 +03:00
- Тип 04 - скорее всего какие-то цвета RGBA или типа того
2025-08-26 04:21:48 +03:00
- Тип 08 - меш-анимации (см файл 01)
` ``
Индексируется по IndexInFile08 из файла 01 либо по файлу 13 через OffsetIntoFile13
Структура:
Vector3 position;
float time; // содержит только целые секунды
short rotation_x; // делится на 32767
short rotation_y; // делится на 32767
short rotation_z; // делится на 32767
short rotation_w; // делится на 32767
---
Игра интерполирует анимацию между текущим стейтом и следующим по time.
Если время интерполяции совпадает с исходным time, жёстко берётся первый стейт из 0x13.
Если время интерполяции совпадает с конечным time, жёстко берётся второй стейт из 0x13.
Если ни то и ни другое, тогда t = (time - souce.time) / (dest.time - source.time)
` ``
2025-08-23 03:00:14 +03:00
- Тип 12 - microtexture mapping
2026-05-09 20:36:43 +03:00
- Тип 13 - animation map / карта кадров для выбора ключей из файла 08
2025-08-26 04:21:48 +03:00
` ``
Буквально (hex)
00 01 01 02 ...
` ``
2025-08-31 01:07:24 +03:00
- Тип 0A - ссылка на части меша, не упакованные в текущий меш (например у бункера 4 и 5 части хранятся в parts.rlb)
2025-08-23 19:03:03 +03:00
` ``
2025-08-31 01:07:24 +03:00
Не имеет фиксированной длины. Хранит строки в следующем формате.
2025-08-23 19:03:03 +03:00
Игра обращается по индексу, пропуская суммарную длину и пропуская 4 байта на каждую строку (длина).
т.е. буквально файл выглядит так
00 00 00 00 - пустая строка
03 00 00 00 - длина строки 1
73 74 72 00 - строка "str" + null terminator
.. и повторяется до конца файла
2025-08-31 01:07:24 +03:00
Кол-во элементов из файла 01 должно быть равно кол-ву строк в этом файле, хотя игра это не проверяет.
2025-08-26 04:21:48 +03:00
Если у элемента эта строка равна "central", ему выставляется флаг (flag |= 1)
2025-08-23 19:03:03 +03:00
` ``
## ` .wea`
Загружается в ` World3D.dll/LoadMatManager`
2025-09-03 01:30:54 +03:00
По сути это текстовый файл состоящий из 2 частей:
- Материалы
` ``
{count}
{id} {name}
` ``
- Карты освещения
` ``
LIGHTMAPS
{count}
{id} {name}
` ``
Может как-то анимироваться. Как - пока не понятно.
2026-05-13 02:53:42 +03:00
## ` .bas Basement` файл
Читается в ` Terrain.dll/LoadBuilding/CBuilding::ctor`
Описывает контуры объекта
Формат
` ``
Длина динамическая
(4 байта) Кол-во внутренних контуров -> IC
{
(4 байта) Кол-во сегментов контура -> N
Vector3[N+1] Точки контура
int[N] - непонятно
int[N] - тоже непонятно, но выглядит как порядок вершин (e.g. [0 2 1])
}[IC]
(4 байта) Кол-во внешних контуров -> OC
{
(4 байта) Кол-во сегментов контура -> N
Vector3[N+1] Точки контура
}[OC]
` ``
2025-12-01 07:41:42 +03:00
## Lightmap (TEXM)
У текстур использующихся как карты освещения специальные имена.
` some_name.00` или ` some_name.0`
Первая цифра после ` .` это группа палет (всего 0x11), вторая цифра индекс в этой группе.
` ``
All lightmaps named whatever.0 → they all end up with no DirectDraw palette attached.
` ``
2025-11-30 18:11:16 +03:00
## ` FXID` - файл эффектов
По сути представляет собой последовательный список саб-эффектов идущих друг за другом.
2026-05-09 20:36:43 +03:00
Всего существует 10 (1..10) видов FXID-команд. Field-level смысл payload ещё нужно дополнять по мере реверса.
2025-11-30 18:11:16 +03:00
Выглядит так, словно весь файл это тоже эффект сам по себе.
` ``
0x00-0x04 Type (игра обрезает 1 байт, и поддерживает только значения 1-9)
0x04-0x08 unknown
0x08-0x0C unknown
0x0C-0x10 unknown
0x10-0x14 EffectTemplateFlags
...
0x30-0x34 ScaleX
0x34-0x38 ScaleY
0x38-0x3C ScaleZ
enum EffectTemplateFlags : uint32_t
{
EffectTemplateFlag_RandomizeStrength = 0x0001, // used in UpdateDust
EffectTemplateFlag_RandomOffset = 0x0008, // random worldTransform offset
EffectTemplateFlag_TriangularShape = 0x0020, // post-process strength as 0→1→0
EffectTemplateFlag_OnlyWhenEnvBit0Off = 0x0080, // gating in ComputeEffectStrength
EffectTemplateFlag_OnlyWhenEnvBit0On = 0x0100, // gating in ComputeEffectStrength
EffectTemplateFlag_MultiplyByLife = 0x0200, // multiply strength by life progress
EffectTemplateFlag_EnvFlag2_IfNotSet = 0x0800, // if NOT set → envFlags |= 0x02
EffectTemplateFlag_EnvFlag10_IfSet = 0x1000, // if set → envFlags |= 0x10
EffectTemplateFlag_AlwaysEmitDust = 0x0010, // ignore piece state / flags
EffectTemplateFlag_IgnorePieceState = 0x8000, // treat piece default state as OK
EffectTemplateFlag_AutoDeleteAtFull = 0x0002, // delete when strength >= 1
EffectTemplateFlag_DetachAfterEmit = 0x0004, // detach from attachment after update
};
` ``
# ЕСЛИ ГДЕ-ТО ВИДИШЬ PTR_func_ret1_no_args, ТО ЭТО ShaderConfig
2025-08-23 03:00:14 +03:00
# Внутренняя система ID
2025-11-30 18:11:16 +03:00
- ` 1` - unknown (implemented by CLandscape) видимо ILandscape
- ` 3` - unknown (implemented by CAtmosphere) видимо IAtmosphere
2025-08-23 03:00:14 +03:00
- ` 4` - IShader
- ` 5` - ITerrain
2025-11-30 18:11:16 +03:00
- ` 6` - IGameObject
2025-12-04 03:50:29 +03:00
- ` 7` - ISettings
2025-08-23 03:00:14 +03:00
- ` 8` - ICamera
2025-09-04 02:45:26 +03:00
- ` 9` - IQueue
2025-08-23 03:00:14 +03:00
- ` 10` - IControl
- ` 0xb` - IAnimation
2025-11-30 18:11:16 +03:00
- ` 0xc` - IShadeStatsBuilder (придумал сам implemented by CShade)
2025-08-23 03:00:14 +03:00
- ` 0xd` - IMatManager
2025-08-27 04:03:50 +03:00
- ` 0xe` - ILightManager
2025-11-30 18:11:16 +03:00
- ` 0xf` - IShade
2025-11-27 19:01:41 +03:00
- ` 0x10` - IBehaviour
2025-08-23 03:00:14 +03:00
- ` 0x11` - IBasement
2025-08-31 01:07:24 +03:00
- ` 0x12` - ICamera2 или IBufferingCamera
2025-08-23 03:00:14 +03:00
- ` 0x13` - IEffectManager
- ` 0x14` - IPosition
2025-08-31 01:07:24 +03:00
- ` 0x15` - IAgent
2025-08-23 03:00:14 +03:00
- ` 0x16` - ILifeSystem
2025-08-31 01:07:24 +03:00
- ` 0x17` - IBuilding - точно он, т.к. ArealMap.CreateObject на него проверяет
2025-08-23 03:00:14 +03:00
- ` 0x18` - IMesh2
2025-08-31 01:07:24 +03:00
- ` 0x19` - IManManager
2025-08-23 03:00:14 +03:00
- ` 0x20` - IJointMesh
2025-11-30 18:11:16 +03:00
- ` 0x21` - IShadowProcessor (придумал сам implemented by CShade)
2025-11-27 19:01:41 +03:00
- ` 0x22` - unknown (implement by CLandscape)
2025-12-04 03:50:29 +03:00
- ` 0x23` - IGameSettingsRoot
2025-08-23 03:00:14 +03:00
- ` 0x24` - IGameObject2
2026-05-12 01:19:19 +03:00
- ` 0x25` - ICollisionMesh (придумал сам implemented by CAniMesh)
2026-05-12 16:26:30 +03:00
- ` 0x26` - INetSerializable (придумал сам implemented by CAniMesh and CControl and CWizard)
2025-08-31 01:07:24 +03:00
- ` 0x28` - ICollObject
2025-11-27 19:01:41 +03:00
- ` 0x29` - IPhysicalModel
- ` 0x101` - I3DRender
2025-12-04 03:50:29 +03:00
- ` 0x102` - ITexture (writable)
2025-11-27 19:01:41 +03:00
- ` 0x103` - IColorLookup
- ` 0x104` - IBitmapFont
- ` 0x105` - INResFile
2025-09-03 01:30:54 +03:00
- ` 0x106` - NResFileMetadata
2025-11-27 19:01:41 +03:00
- ` 0x107` - I3DSound
2025-11-30 18:11:16 +03:00
- ` 0x108` - IListenerTransform
2025-11-27 19:01:41 +03:00
- ` 0x109` - ISoundPool
- ` 0x10a` - ISoundBuffer
2025-11-30 18:11:16 +03:00
- ` 0x10c` - ICDPlayer
2025-11-27 19:01:41 +03:00
- ` 0x10d` - IVertexBuffer
2025-08-23 03:00:14 +03:00
- ` 0x201` - IWizard
- ` 0x202` - IItemManager
- ` 0x203` - ICollManager
- ` 0x301` - IArealMap
- ` 0x302` - ISystemArealMap
- ` 0x303` - IHallway
2025-11-27 19:01:41 +03:00
- ` 0x304` - IDistributor
2025-08-23 03:00:14 +03:00
- ` 0x401` - ISuperAI
- ` 0x501` - MissionData
- ` 0x502` - ResTree
2025-11-27 19:01:41 +03:00
- ` 0x700` - INetWatcher
2025-08-23 03:00:14 +03:00
- ` 0x701` - INetworkInterface
2025-11-23 00:04:05 +03:00
## SuperAI = Clan с точки зрения индексации
Т.е. у каждого клана свой SuperAI
2025-08-26 04:21:48 +03:00
## Опции
World3D.dll содержит функцию CreateGameSettings.
Она создаёт объект настроек и далее вызывает методы в соседних библиотеках.
- Terrain.dll - InitializeSettings
- Effect.dll - InitializeSettings
- Control.dll - InitializeSettings
Остальные наверное не трогают настройки.
2025-08-31 01:07:24 +03:00
| Resource ID | wOptionID | Name | Default | Description |
|:-----------:|:---------------:|:--------------------------:|:-------:|--------------------|
| 1 | 100 (0x64) | "Texture detail" | | |
| 2 | 101 (0x65) | "3D Sound" | | |
| 3 | 102 (0x66) | "Mouse sensitivity" | | |
| 4 | 103 (0x67) | "Joystick sensitivity" | | |
| 5 | !not a setting! | "Illegal wOptionID" | | |
| 6 | 104 (0x68) | "Wait for retrace" | | |
| 7 | 105 (0x69) | "Inverse mouse X" | | |
| 8 | 106 (0x6a) | "Inverse mouse Y" | | |
| 9 | 107 (0x6b) | "Inverse joystick X" | | |
| 10 | 108 (0x6c) | "Inverse joystick Y" | | |
| 11 | 109 (0x6d) | "Use BumpMapping" | | |
| 12 | 110 (0x6e) | "3D Sound quality" | | |
| 13 | 90 (0x5a) | "Reverse sound" | | |
| 14 | 91 (0x5b) | "Sound buffer frequency" | | |
| 15 | 92 (0x5c) | "Play sound buffer always" | | |
| 16 | 93 (0x5d) | "Select best sound device" | | |
| ---- | 30 (0x1e) | ShadeConfig | | из файла shade.cfg |
| ---- | (0x8001e) | | | добавляет AniMesh |
2025-08-26 04:21:48 +03:00
2024-11-24 21:39:50 +03:00
## Контакты
2026-05-09 20:36:43 +03:00
Вы можете связаться со мной в [Telegram ](https://t.me/bird_egop ).