#ffxiv #dll #dll-injection #debugging #windows

bin+lib deucalion

高性能的 Windows 库,用于捕获解码后的 FFXIV 数据包

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网络编程

Download history 99/week @ 2024-06-30 119/week @ 2024-07-07 4/week @ 2024-07-14 45/week @ 2024-07-28 2/week @ 2024-08-04

每月 51 次下载

GPL-3.0 许可证

110KB
2K SLoC

德尤卡洛斯

Test Build GPL-3.0 License Crates.io

高性能的 Windows 库,用于捕获解码后的 FFXIV 数据包。这个库功能相对有限,旨在与其他数据包处理应用程序一起使用。

兼容性

德尤卡洛斯仅支持使用 DX11 的 FFXIV 的 64 位版本。

版本 1.0.0 与 Dawntrail (FFXIV 7.0+) 兼容。较老的 FFXIV 客户端版本应继续使用 0.9.x 版本。

功能

  • 此库可以作为 FFXIV 数据包协议层的嗅探器使用,无需关注底层的 TCP 层。
  • 德尤卡洛斯作为注入的 DLL 运行,并挂钩负责读取解码数据包的函数。因此,这种捕获方法不受基于 libpcap 的捕获限制。
  • 德尤卡洛斯充当广播服务器,将捕获的数据包流式传输到一个或多个订阅者。

构建

  1. 为 Windows 安装 Rust 的夜间版本。

  2. git clone 此仓库。

  3. cddeucalion 并运行 cargo build。DLL 将位于 target/debug/deucalion.dll

ℹ️ 如果 DLL 以调试配置编译,则 DLL 运行时会出现控制台窗口。要隐藏控制台窗口,请在 cargo build 命令中提供 --release 以编译 DLL 的发布配置。

基本用法

初始化时,德尤卡洛斯公开一个 命名管道 服务器,该服务器遵循长度分隔协议来捕获数据包或处理订阅者请求。

  1. 使用您选择的任何方法将 deucalion.dll 注入到 ffxiv_dx11.exe。如果以调试模式构建,则在附加后会出现控制台窗口。
  2. 通过连接到 \\.\pipe\deucalion-{FFXIV PID} 来与 Deucalion 初始化命名管道会话。例如,如果正在运行的 FFXIV 进程的 PID 为 9000,则管道名称为 \\.\pipe\deucalion-9000
  3. Deucalion 将开始向所有订阅者广播接收到的区域数据包。有关广播数据包格式的信息,请参阅 数据格式
  4. 所有订阅者断开连接后,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发送哪些RecvSend数据包。其他如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以使用新签名。

数据格式

使用RecvSend 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[];
}

使用RecvOtherSendOther 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