Skip to content

Releases

v0.1.3

Choose a tag to compare

@MyresMkG MyresMkG released this 03 Oct 15:49
b859cd1

提升跨版本兼容性,已离线验证4.2.4与3.14.1592653
已知问题:3.14不支持AI_acceptance_base_value

v0.1.2

Choose a tag to compare

@MyresMkG MyresMkG released this 28 Sep 08:37
2ebb38f

现在AI_acceptance_base_value对于脚本新增的外交行动而言也可用了。
其它不变。

v0.1.1

v0.1.1 Pre-release
Pre-release

Choose a tag to compare

@MyresMkG MyresMkG released this 28 Sep 07:38
46b515f

实现ScriptedShouldAIPropose,即AI现在可以通过should_ai_propose(weight field)判断是否要主动提出脚本新增的外交行动。
其余不变

v0.1.0

v0.1.0 Pre-release
Pre-release

Choose a tag to compare

@MyresMkG MyresMkG released this 26 Sep 04:51
0a1d190

1. 安装与启动

不用改 stellaris.exe。把它交给注入器就行:

  1. 在游戏根目录(和 stellaris.exe 同级)建一个 injected_mods 文件夹,
    把 diplo_action_hook.dll 放进去;
  2. 双击游戏根目录里的 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,可以直接和本机版本对照。