Repository navigation
Releases
v0.1.3
v0.1.2
v0.1.1
v0.1.0
1. 安装与启动
不用改 stellaris.exe。把它交给注入器就行:
- 在游戏根目录(和
stellaris.exe同级)建一个injected_mods文件夹,
把diplo_action_hook.dll放进去; - 双击游戏根目录里的
stellaris_mod_injector.exe。它会启动游戏,并把
injected_mods里的所有 DLL 注入游戏进程。
2. 验证:先看它有没有装上
启动后打开 diplo_action_hook.log(写在 DLL 旁边,也就是 injected_mods\ 里)。实机跑通的完整日志长这样 :
diplo_action_hook attaching
host module: ...\stellaris.exe @ 0x7ff696520000
image base 0x7ff696520000, 7 sections, 134762 functions in .pdata
resolved:
CDiplomaticActionType::CDiplomaticActionType rva 0x004b3f20
CStaticLexer::AddDynamicToken rva 0x01bb5bb0
CStaticLexer singleton accessor rva 0x01bb5650
CDiplomacyView::ShowDiplomaticActions rva 0x01173f90
NDiplomacyUtil::CreateDiplomaticAction rva 0x00ab7400
CreateEmptyAction rva 0x009aa7f0
operator new rva 0x020213e8
CDiplomaticAction::CDiplomaticAction(int) rva 0x00940680
CDiplomaticAction vtable rva 0x023a8b68
action type database pointer variable rva 0x03111898
sizeof(CDiplomaticAction) = 0x50, token field +0x50, type field +0x40
hook 1 installed (stole 5 prologue bytes): ...
hook 2 installed (stole 5 prologue bytes): ...
ready -- define 'action_<your name> = { ... }' ...
catch-up: action database not created yet; the constructor hook will handle every entry as it is loaded
(游戏加载数据需要 50 秒左右,这期间数据库还不存在,属正常)
token space: 18948 static keywords, 47956 dynamic already present
registered keyword 'action_hook_greeting' -> token 66908 (0x1055c) ← 关键
existing keyword 'action_improve_relation' -> token 13597 ← 原版行动沿用原 token
existing keyword 'action_declare_war' -> token 11602
...
catch-up: scanned 69 action types, repaired 0, already registered 1
self-test: OK -- a generic diplomatic action object was built for token 66908 (0x1055c), type confirmed
self-test: 1 of 1 newly registered action(s) verified through the factory the diplomacy view uses
verification complete; monitoring stopped
每一行的意义:
| 日志 | 说明 |
|---|---|
resolved: ... |
所有地址都在运行时从字符串锚点算出来,没有写死 RVA |
hook 1/2 installed |
两个钩子都装上了,各覆盖 5 字节序言 |
registered keyword 'X' -> token N |
新关键字拿到了动态 token——这正是原来做不到的事 |
existing keyword 'X' -> token N |
原版行动沿用原有 token,行为没有被改变 |
catch-up: scanned 69 ... already registered 1 |
回扫数据库:原版 68 个 + 我们的 1 个 = 69,数量对得上 |
self-test: OK ... |
外交界面用的那条工厂调用真的造出了对象,且类型 token 匹配 |
self-test: called the diplomacy view's own check slots (2/2) ... without incident |
直接跑了外交界面在每个行动上调用的那两处虚表槽——这正是之前闪退的那一步 |
self-test: localisation keys ... prefix 'ACTION_HOOK_GREETING' |
界面会按这个前缀去找文本,所以模组里的 ACTION_HOOK_GREETING_TITLE 等键要对得上 |
修好过一次闪退:早期版本用基类
CDiplomaticAction的虚表造对象,而那个基类是抽象类
(虚表+0x70/+0x78是 MSVC 的_purecall桩),一打开外交界面就是
Pure Virtual Function Call崩溃。现在改成在 DLL 里自建一张具体虚表(复制基类虚表,
只把那两个纯虚槽换成实现),并让自检直接调用崩过的那两处。
self-test 那一行走的就是外交界面 ShowDiplomaticActions → CreateDiplomaticAction →
CreateEmptyAction 的同一段代码,所以它通过就说明按钮能拿到对象。
想让 DLL 只报告不动作,就先建一个空的
diplo_action_hook_probe_only.txt(或加 --probe 参数),它会照常解析地址并写日志,
但一个钩子都不装。
3. 卸载
不用卸载:DLL 只在注入后的进程里存在,游戏文件一个字节都没动。
- 直接关掉游戏就干净了;
- 不想再加载它:把
diplo_action_hook.dll从injected_mods\里移走或改名,游戏下次启动就没有钩子了;
4. 写你自己的外交行动
在任意模组的 common/diplomatic_actions/*.txt 里写:
action_my_action = {
icon = "GFX_diplomacy_improve_relation" # 复用现成图标即可
is_listed = yes
auto_accepted = no
action_type = positive
potential = { hidden_trigger = { is_country_type = default } }
possible = { custom_tooltip = { fail_text = "DIPLOMACY_STATUS_WAR" NOT = { is_at_war_with = from } } }
on_accept = { # root = actor, from = recipient
set_country_flag = my_action_fired
}
}
本地化键名是全大写加后缀,例如 action_my_action 对应
ACTION_MY_ACTION_TITLE / _DESC / _COST / _OFFER_DESC / _NOTIFICATION /
_PROPOSAL_FAILED_ACTOR / _PROPOSAL_FAILED_RECIPIENT
(缺的只会在日志里报 warning,不影响功能)。
只有行动名字需要是新的,字段名全部沿用原版已有的键——这一点没变,也不需要有变化。
5. 已知限制
| 限制 | 说明 |
|---|---|
| 没有专用 C++ 子类 | 原版的贸易、宣战、和平、联邦投票等各有一个专门的类,行为不只在脚本里。新行动只能用通用类,也就是纯脚本驱动的那套 |
| AI 默认不会主动提议 | 基类 CDiplomaticAction::ShouldAIPropose() 直接返回 0;只有专用子类才去读脚本里的 should_ai_propose。所以新行动目前是玩家手动发起 |
| 动态 token 号是运行时分配的 | token = 动态数 + 静态数 + 1,取决于注册顺序。存档里记的是 token 号,所以读旧档时注册顺序必须一致;同名行动在同一个版本上按同一顺序加载,实际是稳定的 |
| 多人游戏未验证 | 动态 token 会让词的数值和对方不同,可能与校验和/对端不一致。单机没问题 |
| 只针对 4.5.x | 地址是运行时用字符串锚点解析的,理论上跨小版本可用(已在 4.5.0 / 4.5.1 上分别验证通过);但如果某个版本改了这些日志字符串,解析会失败并安全退出(只写日志,不装钩子) |
| 与其它 DLL 注入工具共存 | 本 DLL 不安装代理 DLL,也不依赖别人;但如果你还用了别的会改 stellaris.exe 内存的工具,理论上可能互相干扰 |
6. 出问题怎么办
钩子对任何意外都是失败即放弃,不会让游戏带伤运行:
FAILED: address resolution stopped at: ...→ 地址没找到(版本变了或字符串改了),
此时没有安装任何钩子,游戏行为与没注入完全相同;WARNING: could not hook the action factory: ...→ 第一步装上了、第二步没装:
新行动会有一个合法 token(脚本里能引用、触发器能判断),但不会出现在外交界面;WARNING: token N: built an action whose type token is M; the game may ignore it
→ 构造出来的对象没对上类型,游戏可能忽略它。
把 diplo_action_hook.log 和 Documents\Paradox Interactive\Stellaris\logs\error.log
一起发出来就能定位。日志里已经打印了所有解析到的 RVA,可以直接和本机版本对照。