4 个版本 (1 个稳定版)
1.0.0 | 2024 年 7 月 6 日 |
---|---|
0.9.5 | 2024 年 7 月 6 日 |
0.9.4 | 2023 年 10 月 17 日 |
0.9.3 | 2023 年 4 月 10 日 |
#300 在 网络编程
每月 51 次下载
110KB
2K SLoC
德尤卡洛斯
高性能的 Windows 库,用于捕获解码后的 FFXIV 数据包。这个库功能相对有限,旨在与其他数据包处理应用程序一起使用。
兼容性
德尤卡洛斯仅支持使用 DX11 的 FFXIV 的 64 位版本。
版本 1.0.0 与 Dawntrail (FFXIV 7.0+) 兼容。较老的 FFXIV 客户端版本应继续使用 0.9.x 版本。
功能
- 此库可以作为 FFXIV 数据包协议层的嗅探器使用,无需关注底层的 TCP 层。
- 德尤卡洛斯作为注入的 DLL 运行,并挂钩负责读取解码数据包的函数。因此,这种捕获方法不受基于 libpcap 的捕获限制。
- 德尤卡洛斯充当广播服务器,将捕获的数据包流式传输到一个或多个订阅者。
构建
-
为 Windows 安装 Rust 的夜间版本。
-
git clone
此仓库。 -
cd
到deucalion
并运行cargo build
。DLL 将位于target/debug/deucalion.dll
。
ℹ️ 如果 DLL 以调试配置编译,则 DLL 运行时会出现控制台窗口。要隐藏控制台窗口,请在
cargo build
命令中提供--release
以编译 DLL 的发布配置。
基本用法
初始化时,德尤卡洛斯公开一个 命名管道 服务器,该服务器遵循长度分隔协议来捕获数据包或处理订阅者请求。
- 使用您选择的任何方法将
deucalion.dll
注入到ffxiv_dx11.exe
。如果以调试模式构建,则在附加后会出现控制台窗口。 - 通过连接到
\\.\pipe\deucalion-{FFXIV PID}
来与 Deucalion 初始化命名管道会话。例如,如果正在运行的 FFXIV 进程的 PID 为 9000,则管道名称为\\.\pipe\deucalion-9000
。 - Deucalion 将开始向所有订阅者广播接收到的区域数据包。有关广播数据包格式的信息,请参阅 数据格式。
- 所有订阅者断开连接后,Deucalion 将关闭并卸载自身。
ℹ️ 默认情况下,Deucalion 不会发送除接收到的区域数据包之外的其他数据包。要允许其他数据包类型,请参阅 选项 OP 部分。
有效载荷格式
所有与 Deucalion 的通信都遵循此长度限定协议(范围包含)
字节 [0, 3] | 字节 4 | 字节 [5, 8] | 字节 [9, N] |
---|---|---|---|
LENGTH (u32) | OP | CHANNEL | DATA |
长度
这是整个有效载荷的总长度,包括长度字节。
OP 类型
OP | 名称 | 描述 |
---|---|---|
0 | 调试 | 用于传递调试文本消息。 |
1 | ping | 用于在订阅者和 Deucalion 之间维护连接。当 Deucalion 接收到 ping 时,它将回显相同的有效载荷。 |
2 | 退出 | 用于向 Deucalion 信号从宿主进程卸载自身。在大多数使用场景中,您根本不需要发送此 OP。 |
3 | 接收 | 当从 Deucalion 发送时,包含宿主进程接收到的 FFXIV IPC 数据包。 |
4 | 发送 | 当从 Deucalion 发送时,包含宿主进程发送的 FFXIV IPC 数据包。 |
5 | 选项 | 用于配置每个订阅者的数据包过滤。 |
6 | 接收其他 | 当从 Deucalion 发送时,包含宿主进程接收到的 FFXIV 非IPC段。 |
7 | 发送其他 | 当从 Deucalion 发送时,包含宿主进程发送的 FFXIV 非IPC段。 |
通道
这是用于有效载荷的通道标识符。它用于区分数据流(例如,区域数据包与聊天数据包)。
对于 接收
和 发送
OP,以下 CHANNEL 对应这些数据包类型
CHANNEL | 名称 |
---|---|
0 | 大厅 |
1 | 区域 |
2 | 聊天 |
对于 调试
OP,CHANNEL 9000 表示 HELLO
通道。
数据
对于具有 OP 调试
的有效载荷,有效载荷只是调试日志。
对于具有 OP 接收
或 发送
的有效载荷,数据是宿主进程捕获的 FFXIV IPC 数据包。数据包已经按照正确的顺序排列,但它们仍然需要由您的应用程序进行解码。有关如何处理这些数据的信息,请参阅 数据格式。
具有 OP 接收其他
或 发送其他
的有效载荷将包含从宿主进程捕获的 FFXIV 非IPC段。这也在 数据格式 中进行了讨论。
订阅者协议
Deucalion 服务器能够接收和处理来自订阅者的请求。CHANNEL 字段用于发送请求上下文,Deucalion 将使用具有相同 CHANNEL 的 调试
-OP 有效载荷回复请求的订阅者。
调试 OP
使用除HELLO
以外的CHANNEL发送带有Debug
OP的数据包,将只进行调试记录,并返回OK
响应给请求的订阅者。
对于带有Debug
OP和HELLO
通道的数据包,发送的DATA将设置为当前订阅者的昵称。
以下是对昵称值接受的限制
- DATA中的字节必须是有效的UTF-8。
- 必须只包含ASCII字母数字字符或下划线(
^[A-Za-z0-9_]+$
) - 必须小于或等于30个字符
- 昵称可以与其他订阅者的昵称相同。
例如
Payload { OP: OP.Debug, CHANNEL: 9000, DATA: u8"TEST_CLIENT" }
// Deucalion: Nickname set response
Payload { OP: OP.Debug, CHANNEL: 9000, DATA: u8"CHANGED NICKNAME: TEST_CLIENT (subscriber 0)" }
ping OP
发送给Deucalion的任何Ping
-OP数据包都将被回显给请求的订阅者。
退出OP
Deucalion将立即开始卸载所有钩子,并从宿主进程中清理自己,而无需向订阅者发送响应。
⚠️不需要向Deucalion发送此OP!当所有订阅者断开连接时,Deucalion将安全退出。提前导致Deucalion退出可能会在仍连接的订阅者中引起未定义的行为。
接收OP
在大多数情况下,订阅者不需要发送Recv
OP数据包来初始化钩子。但是,如果钩子未自动初始化,则订阅者可以在Recv
OP数据包中发送UTF-8编码的字符串作为模式签名,以手动初始化钩子。
请参阅示例。有关签名格式的信息,请参阅https://docs.rs/pelite/latest/pelite/pattern/fn.parse.html。
⚠️一旦Recv钩子初始化,所有订阅者都将接收到数据包流,无论他们自己的初始化是否成功。因此,当命名管道连接建立后,立即处理数据并异步处理初始化是明智的。
发送OP
对从订阅者发送的Recv
OP数据包适用的规则也适用于从订阅者发送的Send
OP数据包。
但是,CHANNEL设置为0
的数据包将初始化SendLobbyPacket
钩子。任何其他CHANNEL值的数据包将初始化SendPacket
钩子(处理聊天和区域。)
选项OP
默认情况下,Deucalion为每个订阅者设置了一个过滤器,只允许发送Recv
区域数据包到订阅者。
每个订阅者都可以自定义自己的过滤器,以确定Deucalion发送哪些Recv
或Send
数据包。其他如Debug
之类的数据包OP不受此过滤器的影响。
可以通过发送具有位标志作为CHANNEL的Option
OP来设置过滤器。
标志 | 描述 |
---|---|
1 << 0 | 允许接收到的Lobby数据包。 |
1 << 1 | 允许接收到的区域数据包。 |
1 << 2 | 允许接收到的聊天数据包。 |
1 << 3 | 允许发送的Lobby数据包。 |
1 << 4 | 允许发送的区域数据包。 |
1 << 5 | 允许发送的聊天数据包。 |
1 << 6 | 允许其他数据包类型或通道。 |
例如,要允许接收区域、接收聊天、发送区域和发送聊天数据包,您可以计算过滤器值
54 == (1 << 1 | 1 << 2 | 1 << 4 | 1 << 5)
并发送数据包
Payload { OP: OP.Option, CHANNEL: 54, DATA: <empty>}
// Deucalion: Filter set response
Payload { OP: OP.Debug, CHANNEL: 0, DATA: u8"Packet filters set: 0b00110110" }
错误处理
Deucalion将通过响应包含错误信息的Debug
-OP有效载荷来优雅地处理错误情况。所处理的错误情况包括但不限于:
- 数据无法解码为字符串。
- 字符串无法解析为有效的签名。
- 签名在内存中找不到。
- 钩子已被初始化。
示例
以下是一个Deucalion与订阅者之间的交互示例
连接到Deucalion将自动允许流量开始流向订阅者。
// Deucalion: Connection established message.
Payload { OP: OP.Debug, CHANNEL: 9000, DATA: u8"SERVER HELLO. VERSION: 1.0.0. HOOK STATUS: RECV ON. SEND ON. SEND_LOBBY ON." }
// Deucalion: Data streamed to all subscribers
Payload { OP: OP.Recv, CHANNEL: 1, DATA: deucalion_segment }
...
Deucalion需要签名时的示例
// Deucalion: Connection established message.
Payload { OP: OP.Debug, CHANNEL: 9000, DATA: u8"SERVER HELLO. VERSION: 1.0.0. HOOK STATUS: RECV OFF. SEND OFF. SEND_LOBBY OFF." }
// Subscriber: Request with an invalid sig.
Payload { OP: OP.Recv, CHANNEL: 1, DATA: u8"invalid sig" }
// Deucalion: Response with an invalid sig error.
Payload { OP: OP.Debug, CHANNEL: 1, DATA: u8"Error setting up hook: Invalid signature: \"invalid sig\"..." }
// Subscriber: Request with a valid sig (as of FFXIV global version 6.35).
// Deucalion will gracefully allow subscribers to try again after an error.
Payload { OP: OP.Recv, CHANNEL: 1, DATA: u8"E8 $ { ' } 4C 8B 43 10 41 8B 40 18" }
// Deucalion: OK response
Payload { OP: OP.Debug, CHANNEL: 1, DATA: u8"OK" }
// Deucalion: Data streamed to all subscribers
Payload { OP: OP.Recv, CHANNEL: 1, DATA: deucalion_segment }
尝试重新初始化钩子时的示例
如果接收钩子已经初始化,则可能发生以下情况
// Deucalion: Connection established message.
Payload { OP: OP.Debug, CHANNEL: 9000, DATA: u8"SERVER HELLO. VERSION: 1.0.0. RECV ON. SEND ON. SEND_LOBBY ON." }
// Deucalion: Data streamed to all subscribers
Payload { OP: OP.Recv, CHANNEL: 1, DATA: deucalion_segment }
// Subscriber: Asynchronous task sending request with an valid sig.
Payload { OP: OP.Recv, CHANNEL: 1, DATA: u8"E8 $ { ' } ..." }
// Deucalion: Response with already initialized error.
Payload { OP: OP.Debug, CHANNEL: 1, DATA: u8"Error setting up hook: Detour is already initialized" }
// Deucalion: Data streamed to all subscribers
Payload { OP: OP.Recv, CHANNEL: 1, DATA: deucalion_segment2 }
如果钩子已经部分初始化,则可能需要重新编译Deucalion以使用新签名。
数据格式
使用Recv
或Send
OP广播的数据以Deucalion特定的格式发送给订阅者。
struct DEUCALION_SEGMENT {
uint32_t source_actor;
uint32_t target_actor;
uint64_t timestamp; // milliseconds since UNIX epoch
FFXIVARR_IPC_HEADER ipc_header; // Includes reserved, type (opcode), serverId, etc.
uint8_t packet_data[];
}
使用RecvOther
或SendOther
OP广播的数据以纯FFXIV段的形式发送给订阅者。
struct FFXIV_SEGMENT {
FFXIVARR_PACKET_SEGMENT_HEADER segment_header;
uint8_t segment_data[];
}
有关更多信息,请参阅https://xiv.dev/network/packet-structure。
细节
有很多术语用于描述相同的数据结构,有时可能会令人困惑,因此让我们明确一下我们对FFXIV数据包的定义。
TCP数据以称为段的单个单元到达,这些单元也被称为TCP数据包。
FFXIV数据以被称为多种不同名称的容器到达,但最终这不重要,所以我们在这里将其称为帧。在实际传输中,它们可能分布在多个TCP段中,但这在这里是一个被抽象化的细节。
每个帧包含可能压缩的数据,这些数据通常包括一个或多个FFXIV段。每个段封装了段头信息和数据,这就是我们所说的FFXIV数据包数据。
使用来自https://xiv.dev/network/packet-structure的名称,以下是一个此类结构的图示。Deucalion在解码和解压缩后提取最内层的包数据。
┌───────────────────────────────────────────────┐
│ Frame │
│ ┌───────────────────────────────────────────┐ │
│ │ FFXIVARR_PACKET_HEADER │ │
│ │ │ │
│ │ ... │ │
│ │ uint32_t size; │ │
│ │ uint16_t connectionType; │ │
│ │ uint16_t segmentCount; │ │
│ │ ... │ │
│ │ uint8_t isCompressed; │ │
│ │ ... │ │
│ │ uint32_t decompressedSize; │ │
│ └───────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────┐ │
│ │ Compressed Data │ │
│ │ ┌───────────────────────────────────────┐ │ │
│ │ │ Segment 1 │ │ │
│ │ │ ┌───────────────────────────────────┐ │ │ │
│ │ │ │ FFXIVARR_PACKET_SEGMENT_HEADER │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ uint32_t size; │ │ │ │
│ │ │ │ uint32_t source_actor; │ │ │ │
│ │ │ │ uint32_t target_actor; │ │ │ │
│ │ │ │ uint16_t type; // SEGMENTTYPE_IPC │ │ │ │
│ │ │ │ ... │ │ │ │
│ │ │ ├───────────────────────────────────┤ │ │ │
│ │ │ │ FFXIVARR_IPC_HEADER │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ uint16_t reserved; // 0x0014 │ │ │ │
│ │ │ │ uint16_t type; // Opcode │ │ │ │
│ │ │ │ uint16_t padding; │ │ │ │
│ │ │ │ uint16_t serverId; │ │ │ │
│ │ │ │ uint32_t timestamp; │ │ │ │
│ │ │ │ uint32_t padding1; │ │ │ │
│ │ │ ├───────────────────────────────────┤ │ │ │
│ │ │ │ PACKET_DATA │ │ │ │
│ │ │ │ ... │ │ │ │
│ │ │ └───────────────────────────────────┘ │ │ │
│ │ └───────────────────────────────────────┘ │ │
│ │ ┌───────────────────────────────────────┐ │ │
│ │ │ Segment 2 │ │ │
│ │ │ ... │ │ │
│ │ └───────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────┘ │
└───────────────────────────────────────────────┘
依赖项
~7-20MB
~211K SLoC