- Архив:
Strings.vfs(1 файл:main.dat, 2 116 764 байта). - Загрузчик:
CStringDatabase::LoadStrings(IMMLoadObject *pLoad, unsigned long ulBaseID)—Game/Engine/StringDatabase.cpp:27. - Владеющий интерфейс:
IStringDatabase(загружаетсяCEngineпри старте, запрашивается по всему коду черезGetStringByID(...)).
main.dat — локализованная база текстов для всех строк интерфейса,
реплик NPC, описаний предметов и записей дневника. Строки индексируются
32-битными целочисленными идентификаторами; остальной код передаёт
эти ID (через IStringDatabase) и разрешает их в UTF-16-строку в момент
отображения.
Альфа-сборка содержит ровно один main.dat. Механизм рассчитан на
параллельную установку нескольких языковых пакетов — дополнительные
.dat-файлы загружались бы с ненулевым ulBaseID для разделения
пространств имён.
+----------------------+
| u32 ulCount | number of (id, string) pairs
+----------------------+
| ENTRY[ulCount] | variable size each (see 2.1)
+----------------------+
u32 ulID // идентификатор строки (задаётся вызывающей стороной; произвольный)
CEStringW strString // UTF-16LE-строка с префиксом длины (см. README §Conventions)
CEStringW — широковариантная версия кодировки движка с префиксом длины:
- байт 0 < 0x80 → длина (в единицах
wchar_t, не в байтах) = байт 0. - байт 0 ≥ 0x80 → длина = (байт 0 & 0x7F) | (байт 1 << 7).
- далее
length × 2байт UTF-16LE. Без NUL-терминатора.
Таким образом, строка из N кодпоинтов хранится как префикс длины в 1 или
2 байта плюс 2N байт полезной нагрузки.
CStringReader reader((const char *)pLoad->GetMemoryPointer());
unsigned long ulCount;
reader >> ulCount;
for (unsigned long i = 0; i < ulCount; ++i) {
unsigned long ulID;
CEStringW strString;
reader >> ulID >> strString;
AddString(ulID + ulBaseID, strString);
}(StringDatabase.cpp:27-43)
AddString вставляет в std::map<u32, CEStringW>:
- Первая запись для ID успешна.
- Последующая запись с тем же ID, но другим значением, молча
перезаписывается (в
:20есть баг:r.first->second == str— это выражение сравнения, а не присваивание — читайте как: последующие записи отбрасываются). На практике конвейер сборки гарантирует уникальность ID, поэтому баг никогда не срабатывает.
Поиск (GetStringByID(...)) возвращает L"?????" для отсутствующих ID
(StringDatabase.cpp:117).
Сами ID — непрозрачные целые числа — на диске нет индекса, указывающего, какой диапазон покрывает какую подсистему. По соглашению конвейер сборки группирует ID в диапазоны:
- UI: малые числа (подписи кнопок, пункты меню)
- Предметы: дескрипторы предметов
- Диалоги: блоки на NPC
- Дневник: блоки на запись
Авторам модов, которым нужно найти ID для конкретной строки UI, обычно:
- Патчат
GetStringByID, чтобы логировать каждый промах. - Анализируют существующие
.bin-файлы скриптов (см. bin.md), где ID появляются как непосредственные константы в опкодахPush/PushW, подающихся в вызовы скриптового APIMessageBoxиDiary.
Нет. Файл — сырые записи u32 + CEStringW, идущие друг за другом.
Общий размер (2.1 МБ) — несжатый UTF-16; драматически сжался бы под
zlib, но загружается один раз при старте, и движок не утруждается.
CStringDatabase::LoadStringsFromTextFile
(StringDatabase.cpp:46) принимает в качестве альтернативного источника
UTF-16LE-файл с BOM. Этот путь документирован отдельно в
txt_strings.md и предназначен для рабочих процессов
модификации, когда нет желания поддерживать бинарный .dat.
- Текстовый исходный формат: txt_strings.md.
- Потребители на стороне скриптов: см. [
Game/Engine/CEngine.cpp:699] для пути ранней загрузки (LoadStringsвызывается с содержимымStrings.vfs/main.dat). - Подписи карты мира в map.md ссылаются на ID из этой базы.