☰
Textractor 技术指南:Windows/Wine 游戏文本 Hook 工具的原理与实战
2026/10/15 8:30:37 网站建设 项目流程
  • 开发工具
  • 逆向工程
  • 桌面应用

【免费下载链接】Textractor

Extracts text from video games and visual novels. Highly extensible.

项目地址:https://gitcode.com/gh_mirrors/te/Textractor
点击查看免费下载

Textractor(曾用名 NextHooker)是一个运行于 Windows 7 及 Wine 之上的 x86/x64 开源游戏文本提取工具,通过向游戏进程注入 DLL、Hook 文本输出函数并借助命名管道回传数据,实现对视觉小说与各类游戏引擎文本的实时捕获。本篇以项目的俄文 README 为主线,完整覆盖其下载、功能、/H 与 /R 钩子码语法、扩展机制、编译方法与整体架构,并结合作用户指南 docs/TUTORIAL.md,帮助读者既会用工具,也理解其底层实现。

项目概述

Textractor是一个高度可扩展的开源文本 Hook 器,最初脱胎于 ITHVNR(ITH + VNR 引擎项目),用于从视频游戏与视觉小说中提取对话与 UI 文本。它同时支持 32 位与 64 位进程,并可在 Wine 环境下列为可用方案。项目提供多语言 README(含英文、简体中文、俄文等十余种语言),用户也可观看 docs/TUTORIAL.md 中记录的教学视频快速上手。

俄文 README 列出的核心特性如下(与英文版一致,可直接作为功能清单):

  • 高度可扩展:所有后处理逻辑都以扩展(Extension)形式实现;
  • 自动捕获:可从大量游戏引擎自动 Hook 文本,其中包括部分 VNR 并不支持的引擎;
  • /H "hook" 码:通过 Hook 码拦截指定函数的参数或返回值来捕获文本,兼容大多数 AGTH(Another Game Text Hooker)代码;
  • /R "read" 码:不注入 Hook,而是按地址周期性直接读取目标内存,实现"直读"式文本抽取。

获取版本

  • Textractor 的正式发行版可在项目官方 Releases 中下载;
  • 更早的 ITHVNR 版本在俄文 README 中给出了存档链接;
  • 英文 README 还额外提到,带调试信息的实验性构建可以从持续集成平台的 Artifacts 中获取。

下载后解压即可运行Textractor.exe,无需额外安装。若要针对某款特定游戏长期捕获,项目约定游戏根目录下可放置TextractorConfig.txt配置文件(见 include/defs.h 中GAME_CONFIG_FILE的定义),用于保存该游戏的 Hook 码等设置。

钩子码语法:/H hook 码与 /R read 码

钩子码是 Textractor 的核心使用方式:一行代码即可描述"在哪里、按什么格式读多少字节、如何解码"。所有解析逻辑集中在 host/hookcode.cpp,入口函数HookCode::Parse会去掉可选的前缀/与行尾斜杠(兼容 legacy/AGTH 写法),再按首字母分发到ParseHCode或ParseRCode(参见 host/hookcode.cpp)。

/H hook 码

/H 码的一般结构(见ParseHCode实现,host/hookcode.cpp):

/H {类型标志} [F] [null_length<] [N] [codepage#] [padding+] 偏移 [*间接索引] [:分割偏移 [*分割间接索引]] @地址[:模块[:函数]]

各字段的含义:

字段取值作用
类型标志A大端长度前置按长度前缀读取定长文本
B小端长度前置同上,小端序
SANSI 字符串(直到结尾)
QUnicode 字符串
VUTF-8 字符串
MUnicode + 十六进制转储(HEX dump 模式)
F—完整字符串模式:按参数实际读取整串并补换行
null_length<数字文本长度位于字符串指针前null_length字节处(负偏移写法)
N—不读取调用上下文(NO_CONTEXT)
codepage#数字指定代码页,例如936#为 GBK、65001#为 UTF-8
padding+十六进制读取前跳过的填充字节
偏移十六进制目标寄存器/参数偏移(ITH 与 AGTH 的寄存器偏移相差 4,解析时会自动校正)
*间接索引十六进制二级指针解引用(DATA_INDIRECT)
:分割偏移十六进制从另一寄存器取长度(USING_SPLIT),可再带*表示二级解引用
@地址十六进制要 Hook 的函数地址
:模块文件名地址相对模块基址(如gdi.dll)
:函数符号名地址相对函数入口(如GetTextOutA)

源码中自带的测试用例给出了真实可解析的完整示例(host/hookcode.cpp):

/HQN936#-c*C:C*1C@4AA:gdi.dll:GetTextOutA ; Hook gdi.dll!GetTextOutA,GBK 解码,双级指针 + 双级长度 /HB4@0 ; 长度前置的 ANSI 文本 /RS65001#@44 ; UTF-8 直读码

GenerateHCode还支持反向生成:给定HookParam与进程 ID,它会把绝对地址尝试折算成"模块 + 偏移"形式(通过VirtualQueryEx找到所属模块),使 Hook 码在 ASLR 下更稳定(host/hookcode.cpp)。

/R read 码

/R 码描述一次内存直读,结构为(见 host/hookcode.cpp):

/R {S|Q|V|M} [null_length<] [codepage#] @地址
  • S:ANSI 字符串;Q:Unicode 字符串;V:UTF-8;M:Unicode + 十六进制转储;
  • null_length<:长度前置位置;codepage#:代码页(如/RS65001#@44表示按 UTF-8 直读0x44处的字符串);
  • 非法前缀(如/RW@44)会解析失败被拒绝。

/R 码与 /H 码的差异在于:/H 需要注入一小段指令到目标函数首部,把参数"转发"出来;/R 则不需要 Hook 代码,适合已知剧情文本缓冲区地址的场景。

扩展机制

俄文 README 指出:官方扩展示例项目展示了如何构建扩展,而仓库内的 extensions/ 文件夹本身就是扩展功能的"活文档"。该目录下包含十余个用 C++ 编写的扩展,覆盖了从文本清洗到功能增强:

  • removerepeatchar.cpp/removerepeatphrase.cpp/removerepeatsentence.cpp:去除重复字符、短语与句子(游戏引擎反复重绘对话时的常见问题);
  • regexfilter.cpp/regexreplacer.cpp/replacer.cpp/styler.cpp:基于正则与替换规则的过滤、改写与样式化;
  • extranewlines.cpp、exnewlines/extrawindow.cpp:换行处理与独立文本窗口;
  • copyclipboard.cpp/threadlinker.cpp:剪贴板同步与多线程文本关联;
  • bingtranslate.cpp/googletranslate.cpp/deepltranslate.cpp/translatewrapper.cpp:多种翻译服务的封装;
  • lua.cpp/devtools.cpp:Lua 脚本扩展与开发者调试工具。

从源码结构看,GUI 在显示每一条文本前,会先把它连同元数据分发给所有扩展:GUI/mainwindow.cpp 中调用DispatchSentenceToExtensions(sentence, GetSentenceInfo(thread).data()),若某个扩展返回"跳过",则该句不显示。扩展与宿主之间的元数据接口定义在 extensions/extension.h:SentenceInfo以"名字 → 整型值"的数组形式向扩展暴露线程名、代码页等信息,扩展也可通过抛出自定义的SKIP异常主动丢弃文本。

编译项目

编译 Textractor 的前置条件(俄文 README "Compiling" 一节原文要求):

  1. 安装支持 CMake 的 Visual Studio;
  2. 安装 Qt 5.13。

随后直接用 Visual Studio 打开源码文件夹、选择配置并构建,产物即为Textractor.exe。从 CMakeLists.txt 可以看到这些要求在工程层面的体现:

  • 项目强制 MSVC:非 MSVC 工具链会直接FATAL_ERROR终止配置(CMakeLists.txt);
  • 全局编译选项为 C++17、多核编译/MP,并按目标位数链接x64libs或x86libs(CMakeLists.txt);
  • 通过cmake/QtUtils.cmake注册表搜索定位 Qt 5 的Core、Widgets、WinExtras、WebSockets模块(CMakeLists.txt);
  • 工程由host、texthook、GUI、extensions、test五个子工程组成(CMakeLists.txt),分别对应宿主程序、被注入 DLL、图形界面、扩展与测试。

注意英文版 README 额外提示:从源码构建前需先克隆仓库并初始化子模块(git submodule update --init),因为texthook/minhook/等目录依赖子模块内容。

项目架构

俄文 README 的"项目架构"一节描述了完整的数据通路:**宿主(host)向目标进程注入 texthook.dll,两者通过两条命名管道通信;texthook 等待管道接通后,向文本输出函数(如TextOut、GetGlyphOutline)注入少量指令,把它们的输入参数转发到管道;宿主对收到的文本做初步处理后交回 GUI;GUI 在显示前再把文本分发给扩展。**下面结合源码逐环节拆解。

两条命名管道

管道名与同步对象集中定义在 include/defs.h:

constexpr auto HOOK_PIPE = L"\\\\.\\pipe\\TEXTRACTOR_HOOK"; // texthook → host constexpr auto HOST_PIPE = L"\\\\.\\pipe\\TEXTRACTOR_HOST"; // host → texthook
  • hostPipe(TEXTRACTOR_HOST):宿主写入端,向被注入的 DLL 下发命令;
  • hookPipe(TEXTRACTOR_HOOK):texthook 写入端,回传捕获的文本、控制台日志与钩子事件。

两者都以PIPE_TYPE_MESSAGE(消息型管道)创建,保证每条消息边界完整。

texthook.dll 的启动与管道线程

被注入的 DLL 生命周期入口在 texthook/main.cc 的DllMain:

  1. 用命名互斥量VNR_HOOKMAN_<pid>防止对同一进程重复注入;
  2. 创建内核对象文件映射(即 README 中所说的"文件视图 / section object")VNR_SECTION_<pid>,并把其中一段内存映射为TextHook hooks[MAX_HOOK]数组——宿主进程稍后会只读映射同一块内存,从而不经过管道即可查看当前所有 Hook 的状态(地址、参数、名称等);
  3. 初始化 MinHook 后,创建独立的Pipe线程(texthook/main.cc)。

Pipe线程的工作循环:

  1. 等待事件TEXTRACTOR_PIPE_AVAILABLE,随后以客户端身份连接两条管道(CreateFileW打开HOST_PIPE与HOOK_PIPE,texthook/main.cc);
  2. 先通过 hookPipe 写入自己的进程 ID,宿主据此建立进程记录;
  3. 调用Engine::Hijack()(定义于 texthook/engine/match.cc)启动各引擎的自动匹配/Hook 逻辑;
  4. 循环读取 hostPipe 命令,switch分发四种HostCommandType:
    • HOST_COMMAND_NEW_HOOK:按用户给定的HookParam插入新 Hook(名称形如UserHook1);
    • HOST_COMMAND_REMOVE_HOOK:移除指定地址附近的 Hook;
    • HOST_COMMAND_FIND_HOOK:按SearchParam搜索——给了文本则SearchForText(按已知文本反查地址),否则SearchForHooks(自动搜索候选 Hook);
    • HOST_COMMAND_DETACH:结束循环,DLL 自清理并FreeLibraryAndExitThread。

捕获到的文本经TextOutput打包成"ThreadParam头 + 原文"写入 hookPipe(texthook/main.cc);找到的 Hook 通过NotifyHookFound上报,宿主会据此生成/H码并打印到控制台,方便用户保存为游戏配置。

宿主侧:管道管理、注入与文本整理

宿主逻辑在 host/host.cpp。CreatePipe为每对连接创建两条消息管道:hookPipe 为PIPE_ACCESS_INBOUND,hookPipe 连接成功后先读回 4 字节进程 ID 建立ProcessRecord,再递归调用自身以便支持多进程同时挂接(host/host.cpp)。每个ProcessRecord保存:该进程的 hostPipe 句柄、对VNR_SECTION_<pid>的只读映射视图(README 所说"映射到 TextHook 类引用"的对象),以及保护该视图的互斥量。

读取 hookPipe 的主循环按HostNotificationType分派:FOUND_HOOK(转换编码后回调 GUI 控制台)、RMVHOOK(清理对应线程)、TEXT(控制台输出),其余情况按默认分支视为文本数据——取出ThreadParam头,按"进程 + 地址 + 上下文"哈希定位TextThread,首次出现时触发OnCreate,然后把原始字节Push进线程缓冲(host/host.cpp)。

进程注入实现在Host::InjectProcess(host/host.cpp),是标准的 CreateRemoteThread 注入:

  1. 用VNR_HOOKMAN_<pid>互斥量先占位,已存在则提示"已注入";
  2. OpenProcess(PROCESS_ALL_ACCESS)打开目标,IsWow64Process检查 32/64 位匹配;
  3. VirtualAllocEx+WriteProcessMemory写入texthook.dll的完整路径,再CreateRemoteThread调用LoadLibraryW完成加载。

宿主还内置了两个特殊TextThread:console(系统日志输出)与clipboard(host/host.cpp),后者借助WH_GETMESSAGE钩子监听WM_CLIPBOARDUPDATE,把系统剪贴板里的文本也汇入统一管线——这解释了为何许多扩展能对"复制出来的游戏文本"同样生效。

文本缓冲的整理逻辑在 host/textthread.cpp 的TextThread::Push:

  • 双字节字符集(DBCS)保护:当一次只读到 1 字节且是 DBCS 前导字节时(IsDBCSLeadByteEx),先暂存等下一字节配对,避免 GBK 等编码在消息边界被切断;
  • 编码转换:按HEX_DUMP、USING_UNICODE或codepage分支处理,Unicode 直接转换,ANSI 经StringToWideString按指定代码页(缺省为Host::defaultCodepage)转宽字符;
  • 重复过滤:RemoveRepetition检测"整句被引擎连续重绘 N 遍"的情况并只保留一句(对应英文 README 提到的 issue #40);
  • 每 10 毫秒的定时器队列(CreateTimerQueueTimer)负责把缓冲按时间窗口切分成句子(sentence)向上层输出。

数据流总览

综合以上源码,README 描述的链路可还原为:

游戏进程 └─ texthook.dll(LoadLibrary 注入) ├─ DllMain: 建 VNR_SECTION_<pid> 共享视图 + Pipe 线程 ├─ Pipe 线程: 连接管道 → Engine::Hijack() → 处理 /H、搜索、移除、断开 └─ 文本输出: ThreadParam + 原文 → hookPipe (\\.\pipe\TEXTRACTOR_HOOK) 宿主 host ├─ CreatePipe: 建立双向消息管道,按 PID 维护 ProcessRecord ├─ hostPipe 下发命令(插入/移除/搜索/断开) ├─ 只读映射 VNR_SECTION_<pid> 查看 Hook 状态 └─ TextThread: DBCS 拼接、代码页转换、去重、定时切句 GUI ├─ 显示文本线程 / 控制台 / 剪贴板线程 └─ DispatchSentenceToExtensions: 每句先过扩展,再显示

支持与贡献

俄文 README 给出了明确的协作约定:

  • 反馈渠道:任何 bug、Textractor 无法 Hook 的游戏、功能请求都欢迎提出;若某款游戏无法捕获,作者希望通过免费下载链接或 Steam 礼物获取该游戏用于实测;
  • 贡献方式:关于代码库的问题可邮件联系作者;PR 遵循标准流程(fork → 建分支 → 提交改动 → 向 master 发起 PR);
  • 翻译贡献:所有界面字符串集中在 text.cpp 中,按语言分组维护(俄文HIJACK_ERROR等字符串就在其中),翻译该文件与 README 即可完成多语言贡献;
  • 贡献者名单见 docs/CREDITS.md。

小结

Textractor 以"宿主 + 被注入 DLL + 双管道 + 共享视图"的经典注入架构,把游戏内的文本捕获做成了可编码化(/H、/R 码)、可搜索(自动找 Hook)、可后处理(C++/Lua 扩展管线)的完整工具链。若你要为某款游戏做文本提取,实践路径是:先试自动引擎匹配 → 失败则用"按文本搜索"或手动/H码定位TextOut*/GetGlyphOutline类调用点 → 保存 Hook 码到TextractorConfig.txt→ 用 extensions/ 中的去重、正则扩展清洗输出。仓库中的 include/defs.h、texthook/main.cc、host/host.cpp 与 host/hookcode.cpp 是深入原理时最值得精读的四份文件。

  • 开发工具
  • 逆向工程
  • 桌面应用

【免费下载链接】Textractor

Extracts text from video games and visual novels. Highly extensible.

项目地址:https://gitcode.com/gh_mirrors/te/Textractor
点击查看免费下载

相关推荐

上一篇:5分钟搭建高性能Python FTP服务器:pyftpdlib终极指南 🚀
下一篇:LogicStack-LeetCode 题解精讲:1331. 数组序号转换 —— 排序 + 哈希表模拟的经典模板

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询