从零到发布:FF14 Dalamud 插件开发实战指南
【免费下载链接】DalamudFFXIV plugin framework and API项目地址: https://gitcode.com/GitHub_Trending/da/Dalamud
你在游戏聊天框输入/mytimer 5,小地图旁立刻浮出一个半透明倒计时窗口,归零前一秒,屏幕角落弹出一条通知。这是你刚写完并在游戏里跑起来的第一个插件。它由 Dalamud 驱动——FFXIV(最终幻想14)的插件开发框架,把游戏数据、原生函数钩子和 ImGui 界面渲染打包成一套 C# API,让你能在游戏进程里直接开发功能模块。
认识 Dalamud 在整个插件生态中的位置
你平时用的插件来自 XIVLauncher(游戏启动器,负责下载、更新和注入插件),而真正在进程内承载所有插件代码的是 Dalamud。没有它,任何插件都只是磁盘上的一个文件。
- 技术栈:.NET 10(目标框架
net10.0-windows),UI 基于 ImGui 绑定层Dalamud.Bindings.ImGui - 注入方式:由 XIVLauncher 通过 DLL 注入或重写进程入口点把 .NET 运行时带进游戏
- 与同类方案的取舍:相比直接写内存偏移脚本,它提供带版本的稳定服务接口,插件之间互不引用,避免你手工维护地址表
- 稳定性约定:API 有明确的
DalamudApiLevel,插件清单可声明最低框架版本 - 社区规范:仓库明确不接收 LLM/AI 生成的贡献,插件生态以人工维护为主
- 配套:
Dalamud.Injector(C# 注入器)、Dalamud.Boot(C++ 运行时引导)、DalamudCrashHandler(原生崩溃处理)
看懂能力地图
你打开 Plugin/Services 目录就能看到暴露给插件的服务接口,主要模块如下:
| 能力 | 模块路径 | 一句话说明 |
|---|---|---|
| 游戏数据访问 | Game/ | 客户端状态、任务、聊天、交易行等数据服务 |
| 原生函数钩子 | Hooking/ | 用委托替换游戏函数的执行路径 |
| 签名扫描 | ISigScanner 服务 | 用字节特征码在游戏内存中定位函数地址 |
| 内存读写 | Memory/ | 带权限检查的安全内存访问 |
| 界面渲染 | Interface/ | ImGui 窗口、通知、字体、拖拽、窗口系统 |
| 日志 | Logging/ | 基于 Serilog 的分级日志,按插件隔离 |
| 插件生命周期 | PluginManager | 加载、卸载、更新与清单校验 |
从 0 到 1 完整流程
安装开发环境
Windows 下准备 .NET 10 SDK(global.json要求 10.0.0,允许预览)、CMake 与 Visual Studio。仓库文档指向官方 dalamud.dev 的 building 页,依赖清单以那里为准。
git clone https://gitcode.com/GitHub_Trending/da/Dalamud它把整个框架、绑定层和 C++ 引导组件拉到本地,让你后续可以在游戏进程里加载自己的调试构建。这一步不能跳过:框架依赖注入的运行时组件,手动拼不齐。
创建插件项目骨架
新建插件工程时引用Dalamud程序集,实现 IAsyncDalamudPlugin。框架在加载插件时反射找到构造函数并注入接口:
public sealed class MyPlugin : IAsyncDalamudPlugin { private readonly IDalamudPluginInterface _interface; public MyPlugin(IDalamudPluginInterface pluginInterface) { _interface = pluginInterface; Log.Information("插件构造完成"); } public Task LoadAsync(CancellationToken cancellationToken) => Task.CompletedTask; public ValueTask DisposeAsync() => ValueTask.CompletedTask; }它注册了你的入口类:构造函数由框架注入服务,LoadAsync在插件启用时异步执行。这一步不能跳过:没有实现该接口的程序集不会出现在插件列表里。
挂第一个钩子
钩子 = 在游戏原函数入口改跳你的委托,处理完再跳回原函数。用 IGameInteropProvider 挂到按签名扫出的地址:
private delegate void DrawTextDelegate(IntPtr atkUnitBase, string text, nint arg3); private static nint Address = Service<ISigScanner>.Get().ScanText("48 89 5C 24 ? 48 89 74 24 ? 57 48 83 EC 20"); private static unsafe void Detour(IntPtr atkUnitBase, string text, nint arg3) { Log.Debug("拦截到文本绘制: {Text}", text); new DrawTextDelegate(HookAddress) .Invoke(atkUnitBase, text, arg3); }它让游戏每绘制一次文本就经过你的日志一次。注意委托末尾必须调用原函数(Original)。这一步不能跳过:不调原函数,游戏自己的界面就会停止工作——你会亲手做出一个"游戏只剩一半屏幕"的插件。
给插件加上 UI
订阅UiBuilder.Draw事件,在游戏每一帧绘制时画你的窗口:
_interface.UiBuilder.Draw += () => { ImGui.SetNextWindowSize(new Vector2(260, 90)); if (ImGui.Begin("MyTimer")) { ImGui.Text($"第 {ticks} 帧"); } ImGui.End(); };它在游戏主循环里插入一段自绘界面,窗口可拖动、可缩放。这一步不能跳过:FF14 的界面是即时模式渲染,不进这个绘制回调,你的窗口就永远不会出现。
打包并发布
产物是 DLL + 同名的插件清单 JSON,字段见 PluginManifest:
{ "Name": "MyPlugin", "InternalName": "MyPlugin", "Author": "You", "ApplicableVersion": "any", "DansGuard": false }它告诉框架和 XIVLauncher 这个插件叫什么、兼容哪个游戏版本、由谁维护。这一步不能跳过:IVLauncher 侧的下载、禁用、更新都靠这份清单识别插件,缺了它你的 DLL 只是一段死代码。
案例拆解:采集路线提示
需求:采集点数分散在各地图,你希望进入某张地图时就收到"距离本路线下一个点约 5 分钟路程,今日目标完成 7/20"的提示,进度在重进游戏后不丢失。
涉及模块:IClientState(当前地图、玩家位置)、ReliableFileStorage(按插件隔离的可靠存档)、INotificationManager(游戏内通知)。
实现思路:订阅IClientState的地图初始化事件;进入新地图时从ReliableFileStorage读上次记录的路点,对照路线表算出下一个点的位置;用INotificationManager推一条通知,附带剩余目标数。采集完成或切图前,把进度写回存档。
最终效果:你踏上第二张图的第一秒,右上角就有一条通知写着"下一站:银叶林东段,12 分钟"。崩溃重登也不丢进度——存档在插件目录里,跟游戏数据分离。
常见坑与排错
- 现象:游戏更新后,所有
ScanText返回 null,钩子全失效。原因:更新重编译了二进制,字节特征码过期。解法:把可选项声明为 nullable 字段优雅降级,同时用ISigScanner手动补扫关键地址。 - 现象:挂上钩子后,游戏某块界面文字直接消失。原因:detour 里没有调用
hook.Original跳回原函数。解法:处理逻辑执行完后,用委托显式调回原函数。 - 现象:卸载插件后,聊天命令还能触发旧回调。原因:
DisposeAsync里忘了-= Draw和RemoveHandler。解法:把"订阅"和"取消订阅"写在同一张清单里逐个核对。 - 现象:照抄
Dalamud.CorePlugin写插件,本地一切正常,打包后部分类型找不到。原因:CorePlugin 是可访问内部 API 的调试试验场,源码注释里明确写了"不要拿它当模板"。解法:改用官方 SamplePlugin 仓库作骨架。
性能与稳定性:钩子与崩溃隔离
钩子层的开销发生在被 hook 函数的每次调用上。框架按目标函数结构在 MinHook 与 SafetyHook 后端间自动选择(见 Hook),跳板把原函数入口字节搬到旁路,调用方多走的只是一条跳转。所以原则是:钩子数量决定常驻开销,detour 体决定单次开销——把重计算挪出 detour,别在热路径里Log.Information刷屏。
隔离层面,日志按插件名分通道(IPluginLog),插件的加载与卸载由 PluginManager 内的错误处理器包裹,单个插件的异常会被记入日志而不是直接掀翻整个框架。仓库还带独立的原生进程DalamudCrashHandler,负责托管层之外的内存访问违规类崩溃,先落 dump 再交还给游戏。
接下来去哪挖
API 文档在官方站 dalamud.dev(仓库内 index.md 与docs/指向它),构建流程看 README 里的 building 指南,问题交流和插件讨论在社区 Discord;想抄作业就从官方 SamplePlugin 示例仓库开始。改完名字、重编、进游戏——你的悬浮窗会在小地图旁准时出现,那一刻才算真正开始。
【免费下载链接】DalamudFFXIV plugin framework and API项目地址: https://gitcode.com/GitHub_Trending/da/Dalamud
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考