Remove planning and acceptance scaffolding while retaining the native Vulkan mission preview, format readers, runtime algorithms, and ordinary Rust tests. Keep the book aligned with the runnable project and validate checked-in shaders without generated tool metadata.
530 lines
27 KiB
Markdown
530 lines
27 KiB
Markdown
# VIII. Устройство оригинальной программы
|
||
|
||
Здесь собраны детали, полезные при чтении оригинальных DLL: экспортируемые
|
||
функции, соглашения вызова, адреса и конфигурационные файлы. Адрес относится
|
||
к конкретной сборке; сравнить её можно по
|
||
[хэшам модулей](../reference/original-binaries.md).
|
||
|
||
## ABI и границы модулей
|
||
|
||
### Базовый binary profile
|
||
|
||
Все исследованные модули -- 32-битные PE для x86, собранные C++-компилятором
|
||
эпохи MSVC6. Публичная граница сочетает именованные exports, фабрики C++-
|
||
объектов, singleton getters и дальнейшие вызовы через vtable.
|
||
|
||
Для binary shim необходимо учитывать:
|
||
|
||
- `__cdecl` и `__stdcall` у свободных функций;
|
||
- `__thiscall` у методов, где `this` передаётся в `ECX`;
|
||
- очистку stack, видимую по `ret N`;
|
||
- точный порядок virtual slots;
|
||
- multiple-interface pointer adjustments;
|
||
- 4-byte alignment и native little-endian types;
|
||
- отсутствие безопасного ABI для STL-контейнеров между современным и старым
|
||
compiler-ом.
|
||
|
||
Внутренний новый движок не обязан использовать этот ABI. Он нужен только
|
||
compatibility layer, который принимает старые DLL-facing interfaces, старый
|
||
порядок slots и старые ownership rules.
|
||
|
||
### Публичная поверхность DLL
|
||
|
||
В 15 DLL обнаружено 313 exports:
|
||
|
||
```text
|
||
AniMesh.dll 2 ArealMap.dll 9
|
||
Behavior.dll 3 Control.dll 5
|
||
Effect.dll 2 Joystick.dll 6
|
||
MisLoad.dll 2 Net.dll 37
|
||
Ngi32.dll 145 Terrain.dll 13
|
||
Wizard.dll 1 World3D.dll 72
|
||
ai.dll 2 iron3d.dll 8
|
||
services.dll 6
|
||
```
|
||
|
||
Демоверсия содержит 1 126 imported function slots, а полные Части 1 и 2 --
|
||
1 134. Они включают Win32 runtime, DirectX и межмодульные связи. Большое число
|
||
exports `Ngi32.dll` состоит из активного объектного API, математических/resource
|
||
functions и legacy compatibility stubs.
|
||
|
||
Compatibility headers должны фиксировать symbol, ordinal, decorated или
|
||
undecorated name и signature конкретной сборки. Смысловое имя недостаточно:
|
||
порядок exports и calling convention входят в бинарный контракт.
|
||
|
||
### Композиционный и сервисный слой
|
||
|
||
`iron3d.dll` экспортирует восемь функций:
|
||
|
||
```text
|
||
createShell deleteShell
|
||
createGame deleteGame
|
||
createSubsystems deleteSubsystems
|
||
getIGame getIShell
|
||
```
|
||
|
||
`services.dll` публикует шесть getters:
|
||
|
||
```text
|
||
getDisplay
|
||
getGUIServer
|
||
getNetManager
|
||
getResManager
|
||
getSoundServer
|
||
getTimer
|
||
```
|
||
|
||
Эти getters возвращают shared interfaces. Caller не должен конструировать
|
||
concrete implementation или уничтожать singleton напрямую. Для совместимости
|
||
важны не только адреса функций, но и порядок startup/shutdown, owner/refcount
|
||
transitions и реакция на failure paths: отсутствие sound device, ошибка display,
|
||
прерванная загрузка миссии и normal shutdown.
|
||
|
||
### Предметные фабрики
|
||
|
||
```text
|
||
AniMesh: LoadAgent, LoadAniMesh
|
||
ArealMap: CreateArealMap, CreateSystemArealMap, GetSystemArealMap,
|
||
CreateHallWay, CreateObjectFromScheme, CreateObjectsForDebug,
|
||
CalcFullResearchCost, Debug_TestSchemeType, ShowDebugVector
|
||
Behavior: CreateBehaviour, CreateDistributor, PressDebugKey
|
||
Control: InitializeSettings, LoadControlSystem, LoadPhysicalModel,
|
||
CreateCollManager, CreateCollObject
|
||
Effect: InitializeSettings, CreateFxManager
|
||
MisLoad: CreateMissionData, LoadResearch
|
||
AI: CreateSuperAI, GetSuperAI
|
||
Wizard: CreateWizard
|
||
Terrain: CreateAtmosphere, CreateLightManager, CreatePrimitives,
|
||
CreatePrimitives2, CreateShader, GetShade, GetWorld,
|
||
LoadCamera, stdGetCurrentCamera2, stdSetCurrentCamera2
|
||
```
|
||
|
||
Фабрика возвращает interface pointer. Конкретный размер объекта и layout
|
||
остаются внутренними; внешнему коду важны vtable, QueryInterface-подобная
|
||
negotiation, lifetime methods и правила владения.
|
||
|
||
### World3D export families
|
||
|
||
72 exports `World3D.dll` группируются по назначению:
|
||
|
||
```text
|
||
lifecycle: stdInitGame, stdCloseGame, stdCalculateGame, stdRenderGame
|
||
objects: CreateObject, AddObjectToGame, AddNewObjectToGame,
|
||
CreateMirrorObject, AddMirrorObjectToGame, AddNewMirrorToGame,
|
||
DeleteGameObject, KillGameObject, CreateQueue, GetQueue
|
||
camera: LoadCamera, stdSetCurrentCamera, stdGetCurrentCamera
|
||
input: UpdateManualEventsList, ClearManualEventsList, stdClearKeyboard,
|
||
converters, scan/string functions, key lock/query, mouse shift
|
||
clock: SetGameTime, PauseGameTime, ResumeGameTime, GetGameTime family
|
||
network: netCreateNetWatcher, GetNetPlayerNum and mirror/player helpers
|
||
resources/render: material, texture, lightmap and end-of-render helpers
|
||
settings/state: CreateGameSettings, SetGameRender, SetStateForGameObjects
|
||
```
|
||
|
||
World3D является главным местом, где внешний ABI превращается в game loop:
|
||
input обновляет manual events, calculation проходит queue/world traversal,
|
||
deferred deletion откладывает фактическое уничтожение объектов, render читает
|
||
подготовленный snapshot, а end-of-render helpers закрывают временные ресурсы.
|
||
|
||
### Net и Joystick
|
||
|
||
`Net.dll` экспортирует создание instance/interface и 33 операции transport
|
||
lifecycle: provider/session enumeration, setup, create/join/close, player
|
||
operations, send/receive, latency, addresses, queue size, lobby и
|
||
`netZipData`/`netUnZipData`.
|
||
|
||
`Joystick.dll` имеет компактную границу:
|
||
|
||
```text
|
||
QueryJoy
|
||
CreateJoy
|
||
ReleaseJoy
|
||
SetJoyRange
|
||
PeekJoyMessage
|
||
GetJoyCaps
|
||
```
|
||
|
||
Эти модули легче всего заменить adapter-ами, потому что их публичная
|
||
поверхность достаточно узкая. Для native interoperability сохраняются исходные
|
||
signatures; modern runtime может использовать внутренние typed interfaces.
|
||
|
||
### Ngi32 export families
|
||
|
||
145 exports `Ngi32.dll` включают:
|
||
|
||
```text
|
||
resource archives: niOpenResFile, niOpenResFileEx, niOpenResInMem,
|
||
niCreateResFile, rsOpenLib, rsFind, rsLoad
|
||
renderer: niGetD3DDriverAmount, niSelectD3DDriver,
|
||
niGetD3DDriverCaps, niGetD3DVideoModeList,
|
||
niCreate3DRender, niGet3DRender, niGetMaxTextureSize
|
||
audio: niCreate3DSound, niGet3DSound, niGet3DSoundCaps,
|
||
niMuteSound, rsLoadWave
|
||
platform: allocation, clocks, fixed-memory helpers
|
||
math/geometry: plane, ray, polygon and volume intersection routines
|
||
CPU dispatch: g_FastProc, niGetProcAddress and feature detection
|
||
legacy ABI: n3d*, vrt*, bsp* compatibility entries
|
||
```
|
||
|
||
Экспорт переменной `g_FastProc` требует особого shim: consumer получает адрес
|
||
таблицы, а не результат функции.
|
||
|
||
### Подтверждённые RVA
|
||
|
||
Адреса указаны как RVA конкретной исследованной сборки:
|
||
|
||
```text
|
||
World3D stdCalculateGame 0x139A0
|
||
World3D stdRenderGame 0x13BD0
|
||
World3D sendEndOfRender 0x13D90
|
||
World3D UpdateManualEvents 0x10E10
|
||
World3D ClearManualEvents 0x11180
|
||
World3D DeleteGameObject 0x087B0
|
||
Ngi32 g_FastProc 0x3A058
|
||
```
|
||
|
||
`iron3d.dll` вызывает calculation около RVA `0x5FA94`, `0x604C1`, `0x6086B`,
|
||
render около `0x60B2F`, а manual-event update находится в Win32 message path
|
||
около `0xA3759`.
|
||
|
||
RVA используются только для сопоставления и трассировки этой версии. Runtime
|
||
implementation не должна встраивать их как постоянные игровые идентификаторы.
|
||
Таблица внутренних RVA хранится по SHA-256 конкретного модуля.
|
||
|
||
Сводка hashes и import/export оригинального движка вынесена в отдельную
|
||
[страницу](../reference/original-binaries.md). Текущее состояние
|
||
реализации и границы live Vulkan path описаны в [справочнике render frame](../reference/render-frame.md)
|
||
и в исходном коде адаптера.
|
||
|
||
Подтверждённые hashes неизменённых DLL:
|
||
|
||
```text
|
||
World3D.dll 17e4a3089b2583a8cf2356c9db0390b1aba138356a09130d79b4e7e4791da61e
|
||
Ngi32.dll bab9840d94f4e4e74ffc26677724fa896cf4823845504d09a9e025f80016edf5
|
||
```
|
||
|
||
Для GOG `World3D.dll` с этим hash RVA export-ов такие: `stdCalculateGame=0x139A0`,
|
||
`stdRenderGame=0x13BD0`,
|
||
`sendEndOfRender=0x13D90`, `stdSetCurrentCamera=0x13E60` и
|
||
`stdGetCurrentCamera=0x13E80`.
|
||
|
||
`stdRenderGame(camera)` сначала вызывает экспорт Terrain
|
||
`stdSetCurrentCamera2(camera)`, затем сохраняет текущий camera pointer в
|
||
глобальном состоянии World3D. После этого виден запрос camera interface через
|
||
selector `6`, запрос связанного service через selector `264`, renderer/world
|
||
boundary slots и traversal render queues. В конце pointer очищается; dispatch
|
||
end-of-render callbacks вынесен также в отдельный `sendEndOfRender`.
|
||
Это доказывает порядок передачи camera и границы frame lifecycle, но не layout
|
||
camera object, не матрицы projection/view и не значения viewport selectors.
|
||
|
||
Отдельная проверка GOG `Terrain.dll` (`AF87D1B2E728A0BE73C52BE3B44CC196AB46DA7799F25A15D40F8C9B0B425EAD`,
|
||
499 712 bytes) уточняет receiver side. `stdSetCurrentCamera2` находится по
|
||
RVA `0x4FD40`: при инициализированном Terrain он требует у переданного объекта
|
||
interface selector `18` и вызывает slot `+12` результата. Он **не** записывает
|
||
переданный pointer в `stdGetCurrentCamera2`. Последний возвращает Terrain global,
|
||
который внутренний initialization path устанавливает результатом selector `8` на
|
||
Terrain object. Следовательно, selector `18`, slot `+12` и global selector `8`
|
||
должны оставаться именованными evidence boundary до dynamic capture; считать
|
||
`stdGetCurrentCamera2` getter-ом переданной camera было бы ошибкой.
|
||
|
||
Статический анализ той же GOG базы уточняет рабочий контракт
|
||
`CBufferingCamera`. Метод Terrain RVA `0x4D740` копирует ровно 64 байта
|
||
(16 dword) в component offset `+0x10`. Frame-preparation метод RVA `0x4D9C0`
|
||
получает viewport rectangle через virtual slot `+0x3C`, выводит width, height,
|
||
centre и aspect, а projection читает через camera interface. Для projection type
|
||
`0` он использует float по component offset `+0x234` в `tan(angle / 2)`; для
|
||
type `2` получает five-float block через slot `+0x70`; иной non-zero type
|
||
завершается `Not supported projection type`. Это доказывает зависимость
|
||
projection от live camera/viewport, но не row/column convention, единицы угла,
|
||
near/far mapping, handedness или initial camera selection. Важно, что строки
|
||
`ICamera::SetTransformMatrix` (RVA `0x4F830`) и
|
||
`ICamera::GetTransformMatrix` (RVA `0x4F850`) ведут только в obsolete-call
|
||
stubs и не дают usable ABI.
|
||
|
||
Live elevated read-only probe впервые подтвердил relocation-aware runtime связь:
|
||
`Terrain.dll` был загружен по `0x02510000`, global `base + 0x7355C` содержал
|
||
non-null `0x0B37DF08`, а первый dword этого объекта был `0x025765B4` — ровно
|
||
relocated `off_100665B4` из `LoadCamera` construction path. Это доказывает
|
||
live camera object с outer vtable, но одновременно исправляет прежнее слишком
|
||
сильное сопоставление offsets: raw read `global + 0x10` не дал finite 4x4 matrix,
|
||
а `+0x234` был zero в данном sample. Receiver static procedures `0x4D740`/
|
||
`0x4D9C0` и exported global ещё не доказаны как один layout без interface
|
||
adjustment; их offsets остаются unassigned до recovery selector relationship.
|
||
|
||
Локальная IDA-база уточняет адреса этой связи: `stdGetCurrentCamera2` — это
|
||
шестибайтный getter по RVA `0x4FD80`, который возвращает `dword` по RVA
|
||
`0x7355C`. Единственный найденный **direct static** initializer этого global — функция Terrain
|
||
по RVA `0x4D4D0`: она запрашивает selector `8` у своего `this` и сохраняет
|
||
полученный interface pointer. Это доказывает адрес хранения и путь заполнения,
|
||
но не разрешает трактовать pointer как конкретный layout камеры либо читать его
|
||
как runtime evidence без доступа к процессу на том же уровне привилегий.
|
||
|
||
Elevated live sampling теперь доказывает, что direct static xref не исчерпывает
|
||
runtime writers: за 25 секунд autoplay global переключился между тремя heap
|
||
pointer, все с relocated outer vtables `0x025765B4`/`0x02576558`. У двух объектов
|
||
paired blocks `+0x2C/+0x3C/+0x4C` и `+0x6C/+0x7C/+0x8C` синхронно несли
|
||
world-like translation, например `(491.562, 761.551, 7.361)`; третий давал
|
||
normalized-looking `(0.098, 0.018, 0.856)` и не совпадал с paired block.
|
||
Наблюдение согласуется с автоматическими camera switches, но не маркирует mode;
|
||
оно запрещает называть `0x4D4D0` единственным runtime writer и требует recovery
|
||
indirect/unanalyzed write path.
|
||
|
||
Outer vtable `off_100665B4` теперь даёт exact transform adjustment для
|
||
world-like sample. Slot `+0x54` вызывает у subobject `outer + 4` slot `+0x20`
|
||
с selector `0`, затем копирует returned `+0x0C/+0x1C/+0x2C`. В live объекте
|
||
selector field `outer + 0x10` был `0xFFFFFFFF`; реализация selector `0` при
|
||
этом возвращает `outer + 0x20`. Значит observed triple
|
||
`outer + 0x2C/+0x3C/+0x4C` — доказанная translation часть active affine
|
||
transform, а не корреляция. Selector `2` возвращает paired block `outer + 0x60`;
|
||
outer slot `+0x70` применяет `atan2` к его axis values, что доказывает
|
||
orientation-angle path. Названия полей, angle order и handedness пока не
|
||
установлены, но raw affine transform и translation можно сохранять как
|
||
backend-neutral camera pose без догадок.
|
||
|
||
### Vtable и interface negotiation
|
||
|
||
Вызовы вида `object->vfunc(offset)` доказывают порядок slots, даже когда имя
|
||
метода неизвестно. Renderer slots около `+0x28`, `+0x30`, `+0x34` окружают
|
||
world traversal; camera и viewport получаются через selector-based interface
|
||
calls; shared objects используют ранний slot как AddRef-подобную операцию.
|
||
|
||
Правила реконструкции:
|
||
|
||
1. Зафиксировать byte offset slot и число аргументов.
|
||
2. Найти все call sites и типы передаваемых значений.
|
||
3. Отделить доказанное поведение от назначенного имени.
|
||
4. Построить C-compatible shim vtable с точным порядком.
|
||
5. Внутри adapter-а перевести вызов в современный typed interface.
|
||
|
||
Нельзя добавлять virtual destructor в начало reconstructed interface: это
|
||
сдвинет все slots.
|
||
|
||
### ABI-матрица Частей 1 и 2
|
||
|
||
Во всех пятнадцати DLL совпадают export names, ordinals и import sets. Общее
|
||
число exports остаётся 313. Обе полные части содержат 1 134 imported function
|
||
slots; значение 1 126 относится к демоверсии и хранится отдельно.
|
||
|
||
Побайтно идентичны девять DLL:
|
||
|
||
```text
|
||
ai.dll
|
||
Behavior.dll
|
||
Joystick.dll
|
||
MisLoad.dll
|
||
Net.dll
|
||
Ngi32.dll
|
||
Terrain.dll
|
||
Wizard.dll
|
||
World3D.dll
|
||
```
|
||
|
||
Пересобраны `AniMesh.dll`, `ArealMap.dll`, `Control.dll`, `Effect.dll`,
|
||
`iron3d.dll`, `services.dll`.
|
||
|
||
Изменение export RVA:
|
||
|
||
```text
|
||
AniMesh 2 / 2
|
||
Control 5 / 5
|
||
iron3d 8 / 8
|
||
services 6 / 6
|
||
ArealMap 0 / 9
|
||
Effect 0 / 2
|
||
```
|
||
|
||
Нулевое изменение export RVA не доказывает идентичность тела функции:
|
||
`ArealMap.dll` и `Effect.dll` имеют изменённый `.text` при прежних адресах
|
||
exports. Compatibility headers фиксируют внешний ABI один раз, но внутренняя
|
||
таблица адресов, тестов и semantic deltas выбирается по build fingerprint.
|
||
|
||
## Файловая поверхность
|
||
|
||
### Каталог как внешний API
|
||
|
||
Оригинальная установка -- не просто набор assets. Имена файлов, относительные
|
||
пути, регистр, конфигурационные ключи и разделение библиотек образуют внешний
|
||
контракт. Совместимый движок должен принимать каталог без переименования и
|
||
предварительной распаковки.
|
||
|
||
Основные root-файлы включают executable и 15 DLL, `Iron_3D.ini`, `Comp.ini`,
|
||
`Behavior.ini`, `ArealMap.ini`, `BuildDat.lst`, input/preload descriptions и
|
||
набор `.rlb/.lib` архивов:
|
||
|
||
```text
|
||
objects.rlb
|
||
system.rlb
|
||
static.rlb
|
||
effects.rlb
|
||
Material.lib
|
||
Textures.lib
|
||
LightMap.lib
|
||
Palettes.lib
|
||
sounds.lib
|
||
voices.lib
|
||
```
|
||
|
||
Parser конфигураций должен сохранять неизвестные keys и секции, поддерживать
|
||
quoted strings, хранить provenance значения и отличать absent key от explicit
|
||
default.
|
||
|
||
### `Iron_3D.ini`
|
||
|
||
Демоверсия содержит секции `[CS]`, `[MULTIPLAYER]`, `[TEMP]` и
|
||
`[LEVEL_RATIO]`.
|
||
|
||
```text
|
||
DISPLAY_WIDTH=640 DISPLAY_HEIGHT=480
|
||
BITDEPTH=16 CURRENT_D3DCARD=0
|
||
WINDOW_MODE=0 FORCE_SOFTWARE_CURSOR=1
|
||
RENDER_QUALITY=2 REFLECTIONS=0
|
||
EMBOSS_BUMP=0 EMBM=0
|
||
PLAY_CD_MUSIC=1 MOUSE_SENS=100
|
||
JOY_SENS=100 MOUSE_REV_Y=0
|
||
JOY_REV_Y=0 JOY_ENABLE=0
|
||
SUBTITLES=1
|
||
```
|
||
|
||
`FORCE_CD_SOUND` хранит строку пути. Multiplayer задаёт default IP, login и
|
||
password. `[TEMP]` содержит normalization и offence/defence ranges,
|
||
`[LEVEL_RATIO]` -- коэффициенты сложности `0.5`, `0.7`, `1.0`.
|
||
|
||
Parser не должен считать имена регистрозависимыми без отдельного
|
||
доказательства. Effective value, raw value и факт присутствия ключа хранятся
|
||
раздельно.
|
||
|
||
### `Comp.ini`: реестр компонентов
|
||
|
||
Формат строки:
|
||
|
||
```text
|
||
<CID> <DLL-name> <Function-name> [comment]
|
||
```
|
||
|
||
Подтверждённая таблица:
|
||
|
||
```text
|
||
0 terrain.dll LoadLandscape
|
||
1 terrain.dll LoadBuilding
|
||
2 terrain.dll LoadCamera
|
||
3 animesh.dll LoadAgent
|
||
4 animesh.dll LoadAgent
|
||
5 terrain.dll CreateAtmosphere
|
||
6 terrain.dll CreateShader
|
||
7 misload.dll LoadResearch
|
||
```
|
||
|
||
World3D использует этот файл как динамический component registry. Standalone
|
||
runtime может сопоставить CID внутренним фабрикам, но compatibility loader
|
||
должен поддерживать исходные DLL/function strings и комментарии `//`.
|
||
|
||
### `Behavior.ini` и `ArealMap.ini`
|
||
|
||
Demo `Behavior.ini` задаёт logging, debug rendering и controller switches:
|
||
|
||
```text
|
||
LogFile=Behavior.log SaveLog=0
|
||
MaxErrorLevel=1 DefErrorLevel=2
|
||
LookBugMode=0 ShowVectors=0
|
||
NoZBuffer=0 LockBehaviour=0
|
||
UseDebugKey=1 GiveDefaultOrder=0
|
||
DefaultOrderPhase=10 DeterminMode=0
|
||
ImmortalHero=0 UseWizard=1
|
||
```
|
||
|
||
Код Behavior также ищет дополнительные `PathFind_*` и network parameters. В
|
||
demo-файле они отсутствуют, следовательно используются compiled defaults или
|
||
другой источник; нельзя приписывать им произвольные значения.
|
||
|
||
`ArealMap.ini` содержит log switches, `ShowAreals`, `Areal_NoZBuffer`,
|
||
`HallWay_NoZBuffer`, `EdgeUp` и `RunBehDebug`.
|
||
|
||
### Миссии, UI и сохранения
|
||
|
||
Типичный каталог миссии содержит:
|
||
|
||
```text
|
||
data.tma
|
||
mission.cfg
|
||
briefing.cfg
|
||
messages.cfg
|
||
```
|
||
|
||
`mission.cfg` -- текстовое описание именованных resource objects. Блок
|
||
начинается `object <name>`, содержит `desc`, `library`, `libtype`, числовой
|
||
`type` и произвольные именованные параметры, затем `end`. В демоверсии через
|
||
него определяются ambient music loops/variations и другие mission services.
|
||
|
||
`briefing.cfg` и `messages.cfg` относятся к пользовательскому представлению и
|
||
текстовым событиям. Binary TMA остаётся источником placement и properties; эти
|
||
файлы дополняют, а не заменяют его.
|
||
|
||
Отдельные поверхности:
|
||
|
||
```text
|
||
MISSIONS/SCRIPTS/*.scr, *.fml, *.trf, varset.var
|
||
MISSIONS/dispatcher.ini
|
||
ui/shell_ctrls.cfg
|
||
ui/menu_resources.cfg
|
||
ui/cursor.cfg
|
||
ui/game_resources.cfg
|
||
ui/hq.cfg
|
||
DATA/TextRes.cfg
|
||
SAVE/saveslots.cfg
|
||
```
|
||
|
||
Dispatcher демоверсии содержит секцию `[COMPLETE]`; полные части расширяют
|
||
campaign state и набор миссионных файлов. UI-config следует читать отдельным
|
||
generic object/config parser-ом, сохраняя порядок блоков и неизвестные fields.
|
||
`TextRes.cfg` связывает ключи с локализованными строками.
|
||
|
||
Save slot list не является полным savegame state. Для полной совместимости
|
||
нужно отдельно восстановить binary save payload, campaign dispatcher и
|
||
serialization world/script/AI/RNG.
|
||
|
||
### Правила файловой совместимости
|
||
|
||
- Поддерживать `/` и `\` во входных legacy paths.
|
||
- Разрешать paths относительно root игры и mission context.
|
||
- Сохранять исходное написание для log и roundtrip.
|
||
- Использовать ASCII case-insensitive lookup внутри архивов.
|
||
- Учитывать CP1251/ANSI строки там, где встречается локализованный текст.
|
||
- Не применять Unicode normalization к фиксированным resource names.
|
||
- Различать физически отсутствующий файл и отсутствующий entry в существующем
|
||
архиве.
|
||
- Не требовать одинакового регистра имени файла на case-sensitive системах:
|
||
resolver строит индекс каталога.
|
||
|
||
Все найденные конфигурации должны иметь schema с defaults, provenance и
|
||
признаком `present`. Это позволяет отличить исходный default от явно заданного
|
||
пользователем значения.
|
||
|
||
### Различия файловой поверхности Частей 1 и 2
|
||
|
||
Часть 2 добавляет `ui_factory.lib` -- NRes с шестью Texm entries.
|
||
`ui/minimap.lib` увеличен примерно с 6,95 до 10,10 МБ. `gamefont.rlb` и
|
||
`sprites.lib` побайтно совпадают между частями.
|
||
|
||
`Iron_3D.ini` Части 2 добавляет ключи `SFX_VOLUME`, `CD_VOLUME`,
|
||
`DEBUG_KEYS_ON`, меняет некоторые defaults (`MOUSE_SENS`, `MAP_ALPHA128`) и
|
||
локализует строки login/password. Это подтверждает правило schema +
|
||
provenance: parser хранит не только effective value, но и признак присутствия
|
||
ключа в конкретной сборке.
|
||
|
||
`BuildDat.lst` Части 2 использует более полные пути под
|
||
`UNITS\BUILDS\AI\...`; category masks при этом остаются логическим контрактом,
|
||
а physical path -- частью content profile.
|
||
|
||
`TextRes.cfg` и `TextRes.dll` значительно расширены. Localized text, resource
|
||
identifier и path normalization должны оставаться разными слоями: локализация
|
||
текста не меняет ASCII-casefold policy имён entries.
|
||
|
||
|
||
## Дальнейшее чтение
|
||
|
||
[Глоссарий](../appendices/glossary.md),
|
||
[открытые вопросы](../appendices/knowledge-boundaries.md),
|
||
[сценарная VM](../appendices/script-vm.md),
|
||
[сохранения и кампания](../appendices/saves-campaign.md),
|
||
[интерфейс игры](../appendices/ui-shell.md).
|