game background image

SKSE插件地址库

作者:meh321最后更新:2020-02-24 19:31:542021.2万2.8MB

上古卷轴5:天际 特别版 SKSE插件地址库-1-奇游Mod大师

Mod简介

包含头文件和数据库,使SKSE DLL插件易于版本独立。
重要的!现在分为两个版本:特别版(1.5.x)和周年纪念版(1.6.x)。这两个版本之间指向地址的ID不一致(游戏可执行文件差异太大,无法匹配,即使匹配,这些函数中的代码也不同)。

描述

对于常规 mod 用户:从文件部分下载并安装“一体化”软件包。您可以使用模组管理器或手动操作。.bin 文件应位于此处:
数据/SKSE/插件/
您无需阅读其余部分。

对于 SKSE DLL 插件作者:
这是一个模组制作资源(头文件)。您可以加载一个存储偏移量的数据库,这样您的 DLL 插件就可以独立于版本,无需重新编译。头文件可以从文件的可选部分下载。周年纪念版的头文件名为 versionlibdb.h,而不是 versiondb.h!如果您使用的是 CommonLib,那么所有这些都已内置,您无需从这里下载任何文件。


如何使用

最快捷的方法:
剧透:
展示


#include "versiondb.h"

void * 我的地址 = NULL;
无符号长整型 MyOffset = 0;

bool 初始化偏移量()
{
// 在堆栈上分配,以便我们退出此函数时它将被卸载。
// 不需要无缘无故地加载整个数据库并占用内存。
版本数据库;

// 使用当前可执行版本加载数据库。
如果(!db.Load())
{
_FATALERROR("无法加载当前可执行文件的版本数据库!");
返回 false;
}
别的
{
//“SkyrimSE.exe”,“1.5.97.0”
_MESSAGE("已加载 %s 版本 %s 的数据库。", db.GetModuleName().c_str(), db.GetLoadedVersionString().c_str());
}

// 该地址已经包含模块的基地址,因此我们可以直接使用该地址。
我的地址 = db.FindAddressById(123);
如果(我的地址 == NULL)
{
_FATALERROR("找不到地址!");
返回 false;
}

// 此偏移量不包含基址。实际地址为 ModuleBase + MyOffset。
如果(!db.FindOffsetById(123,MyOffset))
{
_FATALERROR("无法找到我的东西的偏移量!");
返回 false;
}

// 一切顺利。
返回 true;
}



现在你肯定想知道那个“123”值是什么。这是一个地址的ID。不同版本的数据库会为同一个地址分配相同的ID,但它可能指向不同的值。要获取特定版本的所有ID和值对的列表,请执行以下操作:

剧透:
展示


#include "versiondb.h"

bool DumpSpecificVersion()
{
版本数据库;

// 无论运行的可执行文件版本如何,都尝试加载版本 1.5.62.0 的数据库。
如果(!db.Load(1,5,62,0))
{
_FATALERROR("无法加载 1.5.62.0 数据库!");
返回 false;
}

// 写出一个名为 offsets-1.5.62.0.txt 的文件,其中每一行都是 ID 和偏移量。
db.Dump("偏移量-1.5.62.0.txt");
_MESSAGE("转储 1.5.62.0 的偏移量");
返回 true;
}



不要输入 1, 5, 62, 0,而是输入你正在逆向且熟悉的版本。你必须首先在 /Data/SKSE/Plugins 目录中拥有相应的数据库文件。

调用此命令后,Skyrim 主目录中应该会出现一个名为“offsets-1.5.62.0.txt”的新文件,文件名可以随意设置。该文件的格式如下:
十进制 ID六角偏移

例如,如果您在 1.5.62.0 中有一个地址 142F4DEF8(玩家角色静态指针),并且您想使它与版本无关,您可以这样做:
1. 在偏移文件中查找 2F4DEF8。因为这是没有基数 140000000 的偏移量
2.看到ID是517014(十进制!)
3. 如果您希望在运行时将此地址包含在您的 DLL 中,请执行以下操作:


无效* addressOf142F4DEF8 = db.FindAddressById(517014);


就是这样了。

VersionDb 结构具有以下功能:
剧透:
展示


bool Dump(const std::string& path); // 将当前加载的数据库转储到文件
bool Load(int major, int minor, int revision, int build); // 如果 Data/SKSE/Plugins 目录中存在 db-major-minor-revision-build.bin,则加载特定版本
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; // 如果需要手动迭代,则获取ID到偏移量的映射
void* FindAddressById(unsigned long long id) const; // 通过 ID 查找地址,该 ID 已包含基址且为正确地址。如果未找到,则返回 NULL!
bool FindOffsetById(unsigned long long id, unsigned long long& result) const; // 通过 ID 查找偏移量,这只是不包含基数的偏移量。
bool FindIdByAddress(void* ptr, unsigned long long& result) const; // 通过地址查找 ID,这将尝试反向查找以将地址转换为 ID
bool FindIdByOffset(unsigned long long offset, unsigned long long& result) const; // 通过偏移量查找 ID,这将尝试反向查找以将偏移量转换为 ID



您应该了解并牢记的事情:

1. 您可以在插件中包含任意(或全部)数据库文件,但这可能会显著增加文件大小(约 2.5 MB)。目前,通常将此模块标记为依赖项。

2. 您应该始终在启动时只加载一次数据库,初始化/缓存所需的地址,然后让它卸载。卸载仅意味着 VersionDb 结构体会被删除或丢失(如果您在堆栈上分配了内存)。这将确保您在游戏运行时不会使用不必要的内存。游戏过程中无需保持数据库加载状态。如果您使用 CommonLib,这一点就毫无意义了,因为它只会加载一次,而不是为每个 DLL 加载一次。

3. 该数据库包含函数、全局变量、RTTI、vtable 以及任何其他可能引用它的对象的地址。它不包含函数中间或全局变量中间的地址。如果需要函数中间的地址,您应该查找函数基址并自行添加额外的偏移量。它也不包含无用的内容,例如函数周围的对齐(在 rdata 中引用),pdata 段会被丢弃,一些编译器从 rdata 生成的 SEH 信息也会被丢弃。

4. 您应该始终检查结果,以确保数据库已成功加载(bool Load 返回 true),并且查询的地址确实返回了有效结果(非 NULL)。如果加载失败,则意味着文件缺失或版本错误(例如,尝试在 AE 中使用 SE 标头)。如果查询失败,则意味着在该版本中找不到该地址。这可能意味着游戏代码发生了足够大的更改,导致该地址对该版本不再有效,或者数据库本身无法检测到正确的地址。如果发生上述任何一种情况,您应该使插件初始化失败,以便让 SKSE 知道您未正确加载。或者手动显示错误消息。

5. 在发布你的 DLL 插件之前,最好检查一下该地址在所有版本的游戏中都存在。为此,请加载每个版本的数据库文件,并在每个版本中查询相同的地址 ID,以确保它存在:
剧透:
展示


bool LoadAll(std::vector& 全部)
{
静态 int 版本[] = { 3, 16, 23, 39, 50, 53, 62, 73, 80, 97, -1 };
对于(int i = 0;版本[i]> = 0; i ++)
{
版本数据库 * db = 新的版本数据库();
如果(!db->Load(1,5,版本[i],0))
{
删除数据库;
返回 false;
}
全部.push_back(db);
}
返回 true;
}

bool ExistsInAll(std::vector&全部,无符号长整型 id)
{
无符号长整型结果 = 0;
对于(自动数据库:全部)
{
如果(!db->FindOffsetById(id,结果))
返回 false;
}
返回 true;
}

无效的FreeAll(std ::向量& 全部)
{
对于(自动数据库:全部)
删除数据库;
全部.清除();
}

bool IsOk()
{
std::vector全部;
如果(!LoadAll(全部))
{
_FATALERROR("无法为当前可执行文件加载一个或多个版本数据库!");
免费全部(全部);
返回 false;
}

如果(!ExistsInAll(全部,517014))
{
_FATALERROR("517014 在所有版本的数据库中都不存在!");
免费全部(全部);
返回 false;
}

免费全部(全部);
// 好的!
返回 true;
}



这样,您可以确保您的 DLL mod 在所有版本中都能工作,或者如果它在某些版本中不起作用,您可以在 mod 页面上写明。

6. 有时您需要根据正在运行的游戏版本执行不同的操作。您可以使用以下代码片段来实现:
剧透:
展示


int major = 0, minor = 0, revision = 0, build = 0;
如果(!db.GetExecutableVersion(主要,次要,修订,构建))
{
_FATALERROR("出现错误!");
返回 false;
}

// 正在运行的游戏是 1.5.x 并且至少是版本 1.5.39.0
if (主要 == 1 && 次要 == 5 && 修订 >= 39)
{
// 东西 ... ?
}



7. 请注意:如果您在调试模式下编译 SKSE DLL,数据库加载时间可能约为 14 秒!在发布模式下,加载时间约为 0.2 秒。这是因为标准库容器在该模式下(std map)运行速度非常慢。


权限

做任何你想做的事。
本工具由三方[bufftool]提供Attention Nomal
下载

立即下载模组

下载客户端搜索上古卷轴5:天际 特别版后使用Mod大师工具。