|
本帖最后由 鼓掌之间 于 2024-11-14 09:26 编辑
转自:https://www.nexusmods.com/skyrimspecialedition/mods/32444
包含头文件和数据库,便于 SKSE DLL 插件的版本独立性。
重要!现在已分为两个版本:特别版 (1.5.x) 和周年纪念版 (1.6.x)。这两个版本之间的地址 ID 不匹配(游戏的可执行文件差别太大,即使地址相同,函数中的代码也不同)。
描述
对于普通 mod 用户:请从文件部分下载并安装“all-in-one”包。您可以使用 mod 管理器或手动安装。.bin 文件应放在以下位置: Data/SKSE/Plugins/ 无需阅读其他内容。
对于 SKSE DLL 插件作者: 这是一个 mod 资源文件(头文件)。您可以加载一个存储偏移的数据库,以便您的 DLL 插件可以独立于版本而无需重新编译。头文件可从文件的可选部分下载。对于周年纪念版,头文件称为 versionlibdb.h,而不是 versiondb.h!如果您使用 CommonLib,则所有内容已集成,无需在此处下载任何内容。
如何使用
最快的方法:
#include "versiondb.h"
void * MyAddress = NULL;
unsigned long long MyOffset = 0;
bool InitializeOffsets()
{
// 在栈上分配,这样在退出此函数时它会被卸载。
// 无需加载整个数据库以节省内存。
VersionDb db;
// 加载当前可执行文件版本的数据库。
if (!db.Load())
{
_FATALERROR("无法加载当前可执行文件的版本数据库!");
return false;
}
else
{
_MESSAGE("已加载 %s 版本的数据库 %s。", db.GetModuleName().c_str(), db.GetLoadedVersionString().c_str());
}
// 该地址已经包含模块的基地址,因此可以直接使用。
MyAddress = db.FindAddressById(123);
if (MyAddress == NULL)
{
_FATALERROR("找不到地址!");
return false;
}
// 此偏移量不包含基地址,实际地址应为 ModuleBase + MyOffset。
if (!db.FindOffsetById(123, MyOffset))
{
_FATALERROR("无法找到项目的偏移量!");
return false;
}
// 成功。
return true;
}
你可能会问那个“123”是什么值。它是地址的 ID。不同版本数据库会为同一地址使用相同的 ID,但指向不同的值。要获取特定版本的所有 ID 和值的列表,可以执行以下操作:
#include "versiondb.h"
bool DumpSpecificVersion()
{
VersionDb db;
// 尝试加载版本 1.5.62.0 的数据库,不论当前运行的可执行版本。
if (!db.Load(1, 5, 62, 0))
{
_FATALERROR("无法加载版本 1.5.62.0 的数据库!");
return false;
}
// 输出名为 offsets-1.5.62.0.txt 的文件,每行是 ID 和偏移量。
db.Dump("offsets-1.5.62.0.txt");
_MESSAGE("已导出版本 1.5.62.0 的偏移量");
return true;
}
将 1, 5, 62, 0 替换为你要调试的版本。确保在 /Data/SKSE/Plugins 目录中有相应的数据库文件。
执行后,Skyrim 目录下应生成一个文件,如“offsets-1.5.62.0.txt”,其中每行为:十进制 ID <tab> 十六进制偏移量 <换行>。
例如,在 1.5.62.0 版本中有一个地址 142F4DEF8(玩家角色静态指针)要使其独立于版本,可以这样操作:
- 在偏移文件中查找 2F4DEF8,因为它是去掉基地址 140000000 的偏移。
- 确认 ID 是 517014(十进制!)
- 如果想在运行时在 DLL 中使用此地址,执行以下操作:
void* addressOf142F4DEF8 = db.FindAddressById(517014);
VersionDb 结构体具有以下功能:显示内容:
要注意的事项:
1、可以将数据库文件与插件一起包含,但可能会显著增加文件大小(约 2.5 MB)。通常建议将此 mod 标记为依赖项。
2、在启动时只加载数据库一次,初始化或缓存所需地址并卸载它即可。卸载只是将 VersionDb 结构体删除或丢弃(如果在栈上分配)。这确保在游戏运行期间不会使用不必要的内存。使用 CommonLib 时,这种情况只会发生一次,而不是每个 DLL 都要加载一次。
3、数据库包含函数、全局变量、RTTI、虚表等的地址,但不包含函数或全局变量中间的地址。如果需要函数中间的地址,应查找函数基地址并自行添加偏移量。它也不包含无用信息,如对齐数据或 rdata 中的编译器生成的 SEH 信息。
4、确保数据库成功加载(Load 返回 true)且查询到的地址有效(非 NULL)。如果加载失败,可能是文件丢失或版本不匹配(例如 SE 头文件用于 AE)。如果查询失败,可能是该版本的地址无效或数据库未能检测到正确地址。若发生这些情况,插件初始化应失败,让 SKSE 知道未正确加载,或手动显示错误信息。
5、在发布 DLL 插件前,确保所有游戏版本中该地址都存在。为此,加载每个版本的数据库文件并在各版本中查询相同的地址 ID。
bool LoadAll(std::vector<VersionDb*>& all)
{
static int versions[] = { 3, 16, 23, 39, 50, 53, 62, 73, 80, 97, -1 };
for (int i = 0; versions >= 0; i++)
{
VersionDb * db = new VersionDb();
if (!db->Load(1, 5, versions, 0))
{
delete db;
return false;
}
all.push_back(db);
}
return true;
}
bool ExistsInAll(std::vector<VersionDb*>& all, unsigned long long id)
{
unsigned long long result = 0;
for (auto db : all)
{
if (!db->FindOffsetById(id, result))
return false;
}
return true;
}
void FreeAll(std::vector<VersionDb*>& all)
{
for (auto db : all)
delete db;
all.clear();
}
bool IsOk()
{
std::vector<VersionDb*> all;
if (!LoadAll(all))
{
_FATALERROR("无法加载一个或多个版本的数据库!");
FreeAll(all);
return false;
}
if (!ExistsInAll(all, 517014))
{
_FATALERROR("517014 在所有版本的数据库中都不存在!");
FreeAll(all);
return false;
}
FreeAll(all);
return true;
}
这样可以确保你的 DLL mod 在所有版本中都能正常工作,或者在某些版本中不工作时可以在 mod 页中说明。
6、有时需要根据运行的游戏版本做不同的操作,可使用以下代码:
int major = 0, minor = 0, revision = 0, build = 0;
if (!db.GetExecutableVersion(major, minor, revision, build))
{
_FATALERROR("出现问题!");
return false;
}
// 运行的游戏版本为 1.5.x,且至少是 1.5.39.0 版本
if (major == 1 && minor == 5 && revision >= 39)
{
// 执行操作
}
7、请注意:若在调试模式下编译 SKSE DLL,加载数据库的时间可能长达 14 秒!在发布模式下大约为 0.2 秒,这是由于标准库容器在调试模式下速度较慢(如 std::map)。
下载地址:
主文件
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有帐号?注册
x
评分
-
1
查看全部评分
-
|