Содержит файл заголовка и базу данных, чтобы сделать версию плагинов SKSE DLL независимой.
ВАЖНЫЙ!Теперь игра разделена на две версии: Special Edition (1.5.x) и Anniversary Edition (1.6.x). Идентификаторы, указывающие на адреса, в этих двух версиях не будут совпадать (исполняемый файл игры слишком отличается для сопоставления, и даже если бы они совпадали, код внутри этих функций всё равно различается).ОписаниеДля постоянных пользователей мода:Скачайте и установите пакет «всё в одном» из раздела «Файлы». Вы можете использовать менеджер модов или сделать это вручную. Файлы .bin должны располагаться здесь:
Данные/SKSE/Плагины/Вам нет необходимости читать остальную часть этого.
Для авторов плагинов SKSE DLL:Это ресурс для моддеров (заголовочный файл). Вы можете загрузить базу данных, хранящую смещения, чтобы ваш плагин DLL стал независимым от версии без необходимости перекомпиляции. Заголовочный файл можно загрузить из раздела «Дополнительные файлы». В Anniversary Edition заголовочный файл называется versionlibdb.h, а не versiondb.h! Если вы используете CommonLib, то всё это уже встроено, и вам ничего отсюда не нужно.
Как использоватьСамый быстрый способ:
Спойлер:
Показывать
#include "versiondb.h"
void * MyAddress = NULL;
беззнаковый длинный длинный MyOffset = 0;
bool InitializeOffsets()
{
// Размещаем в стеке, чтобы он был выгружен при выходе из этой функции.
// Нет необходимости загружать всю базу данных и использовать память без причины.
VersionDb db;
// Загрузить базу данных с текущей версией исполняемого файла.
если (!db.Load())
{
_FATALERROR("Не удалось загрузить базу данных версий для текущего исполняемого файла!");
вернуть false;
}
еще
{
// "SkyrimSE.exe", "1.5.97.0"
_MESSAGE("Загружена база данных для %s версии %s.", db.GetModuleName().c_str(), db.GetLoadedVersionString().c_str());
}
// Этот адрес уже включает базовый адрес модуля, поэтому мы можем использовать адрес напрямую.
МойАдрес = db.FindAddressById(123);
если (MyAddress == NULL)
{
_FATALERROR("Не удалось найти адрес!");
вернуть false;
}
// Это смещение не включает базовый адрес. Фактический адрес будет равен ModuleBase + MyOffset.
если (!db.FindOffsetById(123, MyOffset))
{
_FATALERROR("Не удалось найти смещение для моей вещи!");
вернуть false;
}
// Всё прошло успешно.
вернуть истину;
}
Теперь вам интересно, что это за значение "123". Это идентификатор адреса. В базах данных разных версий идентификатор адреса будет одинаковым, но он может указывать на разные значения. Чтобы получить список всех пар идентификатор-значение для конкретной версии, выполните следующее:
Спойлер:
Показывать
#include "versiondb.h"
bool DumpSpecificVersion()
{
VersionDb db;
// Попытаемся загрузить базу данных версии 1.5.62.0 независимо от запущенной версии исполняемого файла.
если (!db.Load(1, 5, 62, 0))
{
_FATALERROR("Не удалось загрузить базу данных для 1.5.62.0!");
вернуть false;
}
// Записываем файл с именем offsets-1.5.62.0.txt, где каждая строка — это идентификатор и смещение.
db.Dump("offsets-1.5.62.0.txt");
_MESSAGE("Сброшенные смещения для 1.5.62.0");
вернуть истину;
}
Вместо 1, 5, 62, 0 укажите версию, с которой вы работаете и с которой знакомы. Для этого сначала необходимо иметь соответствующий файл базы данных в каталоге /Data/SKSE/Plugins.
После этого в корневом каталоге Skyrim должен появиться новый файл с именем "offsets-1.5.62.0.txt" или любым другим именем, которое вы укажете. Формат файла будет следующим:
Десятичный идентификатор
Смещение шестигранника
Например, если у вас есть адрес 142F4DEF8 (статический указатель персонажа игрока) в версии 1.5.62.0, который вы хотите сделать независимым от версии, то вам нужно сделать следующее:
1. Найдите значение 2F4DEF8 в файле смещений. Это смещение без базы 140000000.
2. Убедитесь, что идентификатор — 517014 (десятичный!)
3. Если вы хотите, чтобы этот адрес был в вашей DLL во время выполнения, сделайте следующее:
void* addressOf142F4DEF8 = db.FindAddressById(517014);
И вот вам результат.
Структура VersionDb имеет следующие функции:
Спойлер:
Показывать
bool Dump(const std::string& path); // Сохраняет текущую загруженную базу данных в файл
bool Load(int major, int minor, int revision, int build); // Загрузить конкретную версию, если файл db-major-minor-revision-build.bin существует в каталоге Data/SKSE/Plugins
bool Load(); // Загрузить версию для текущего приложения
void Clear(); // Очистить текущую загруженную базу данных
void GetLoadedVersion(int& major, int& minor, int& revision, int& build) const; // Получить версию файла базы данных, которую мы загрузили прямо сейчас
bool GetExecutableVersion(int& major, int& minor, int& revision, int& build) const; // Получить версию текущего выполняемого приложения
const std::string& GetModuleName() const; // Получить имя текущего загруженного модуля базы данных, должно быть "SkyrimSE.exe"
const std::string& GetLoadedVersionString() const; // Получить текущую загруженную версию в виде строки, например "1.5.62.0"
const std::map& GetOffsetMap() const; // Получить карту идентификатора для смещения, если вам нужно выполнить итерацию вручную
void* FindAddressById(unsigned long long id) const; // Найти адрес по идентификатору. Это уже будет включать базовый адрес и будет правильным адресом. Возвращает NULL, если адрес не найден!
bool FindOffsetById(unsigned long long id, unsigned long long& result) const; // Найти смещение по ID, это будет просто смещение без учета базы.
bool FindIdByAddress(void* ptr, unsigned long long& result) const; // Найти идентификатор по адресу, это попытается выполнить обратный поиск для преобразования адреса в идентификатор
bool FindIdByOffset(unsigned long long offset, unsigned long long& result) const; // Найти идентификатор по смещению, это попытается выполнить обратный поиск для преобразования смещения в идентификатор
Что вам следует знать и помнить:
1. Вы можете включить любой (или все) файлы базы данных в свой плагин, но это может значительно увеличить размер файла (примерно на 2,5 МБ). До сих пор этот мод обычно отмечался как зависимость.
2. Базу данных ВСЕГДА следует загружать только один раз при запуске, инициализировать/кэшировать необходимые адреса и дать ей выгрузиться. Выгрузка просто означает удаление или потерю структуры VersionDb (если память выделена в стеке). Это позволит избежать лишнего использования памяти во время игры. Нет необходимости держать базу данных загруженной во время игры. Это спорный вопрос, если вы используете CommonLib, так как она загружается только один раз, а не для каждой DLL.
3. База данных содержит адреса функций, глобальных переменных, RTTI, виртуальных таблиц и всего остального, что может ссылаться на неё. Она не содержит адресов, находящихся в середине функций или глобальных переменных. Если вам нужен адрес в середине функции, вам следует найти базовый адрес функции и самостоятельно добавить дополнительное смещение. Она также не содержит бесполезных данных, таких как выравнивание вокруг функций (на которые есть ссылки в rdata), раздел pdata отбрасывается, и часть SEH-информации, сгенерированной компилятором из rdata, отбрасывается.
4. Всегда проверяйте результат, чтобы убедиться, что база данных успешно загружена (функция bool Load вернула true) и что запрошенные адреса действительно вернули корректный результат (не NULL). Если загрузка не удалась, это означает, что файл, скорее всего, отсутствует или имеет неправильную версию (например, при попытке использовать заголовок SE в AE). Если запрос не удался, это означает, что адрес не найден в этой версии. Это может означать, что либо код игры изменился настолько, что адрес больше недействителен для этой версии, либо сама база данных не смогла определить корректный адрес. В любом из этих случаев следует сбить инициализацию плагина, чтобы SKSE понял, что загрузка прошла неправильно. Или вручную вывести сообщение об ошибке.
5. Также рекомендуется проверить, существует ли адрес во всех версиях игры, прежде чем публиковать DLL-плагин. Для этого загрузите каждую версию файла базы данных и запросите один и тот же идентификатор адреса в каждой из них, чтобы убедиться в его существовании:
Спойлер:
Показывать
bool LoadAll(std::vector& все)
{
статические int-версии[] = { 3, 16, 23, 39, 50, 53, 62, 73, 80, 97, -1 };
для (int i = 0; версии[i] >= 0; i++)
{
VersionDb * db = new VersionDb();
если (!db->Load(1, 5, версии[i], 0))
{
удалить базу данных;
вернуть false;
}
все.push_back(db);
}
вернуть истину;
}
bool ExistsInAll(std::vector& все, беззнаковый длинный длинный идентификатор)
{
беззнаковый длинный длинный результат = 0;
для (auto db : all)
{
если (!db->FindOffsetById(id, result))
вернуть false;
}
вернуть истину;
}
void FreeAll(std::vector& все)
{
для (auto db : all)
удалить базу данных;
все.очистить();
}
bool IsOk()
{
std::vectorвсе;
если (!LoadAll(все))
{
_FATALERROR("Не удалось загрузить одну или несколько баз данных версий для текущего исполняемого файла!");
FreeAll(все);
вернуть false;
}
если (!ExistsInAll(все, 517014))
{
_FATALERROR("517014 не существует во всех версиях базы данных!");
FreeAll(все);
вернуть false;
}
FreeAll(все);
// Хорошо!
вернуть истину;
}
Таким образом, вы можете быть уверены, что ваш DLL-мод будет работать во всех версиях, или, если он не работает в некоторых версиях, вы можете написать об этом на странице вашего мода.
6. Иногда вам может потребоваться что-то другое в зависимости от текущей версии игры. Это можно сделать с помощью этого фрагмента кода:
Спойлер:
Показывать
int major = 0, minor = 0, revision = 0, build = 0;
если (!db.GetExecutableVersion(основная, второстепенная, ревизия, сборка))
{
_FATALERROR("Что-то пошло не так!");
вернуть false;
}
// Текущая версия игры — 1.5.x и не ниже 1.5.39.0
если (основной == 1 && второстепенный == 5 && пересмотр >= 39)
{
// Вещи ... ?
}
7. Имейте в виду: при компиляции DLL-библиотеки SKSE в режиме отладки время загрузки базы данных может составлять около 14 секунд! В режиме выпуска это время составляет около 0,2 секунды. Это связано с тем, что контейнеры стандартной библиотеки (std map) работают в этом режиме очень медленно.
Разрешения
Делай, что хочешь.