news 2026/10/23 2:34:25

从零到发布:FF14 Dalamud 插件开发实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零到发布:FF14 Dalamud 插件开发实战指南

从零到发布: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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/23 2:32:37

PCL2启动器:终极Minecraft启动器完整使用指南

PCL2启动器&#xff1a;终极Minecraft启动器完整使用指南 Plain Craft Launcher 2&#xff08;简称PCL2启动器&#xff09;是一款功能强大的免费开源Minecraft启动器&#xff0c;专为简化游戏环境配置和版本管理而设计。无论你是Minecraft新手还是资深玩家&#xff0c;PCL2启动…

作者头像 李华
网站建设 2026/10/23 2:32:25

RISC-V IDE MRS进阶分享(五):代码密度和运行速度的优化方法

一、引言MRS(第二代)为MounRiver Studio的换代版本&#xff0c;从V2.1开始&#xff0c;框架更换至更现代的VSCode&#xff0c;并深度定制开发。在工程管理、代码编辑、编译、调试等方面均兼容之前版本&#xff0c;并在效率和功能等方面进行提升&#xff0c;着力将MRS打造为更加…

作者头像 李华