TajuC/MapleDumper-rs
GitHub: TajuC/MapleDumper-rs
MapleDumper 是一个用 Rust 编写的 MapleStory 客户端跨版本特征码与偏移量分析工具,通过指令感知和重定位感知的特征码生成技术解决客户端补丁更新后特征码失效的问题。
Stars: 6 | Forks: 1
# MapleDumper
用于 MapleStory 客户端的跨版本特征码与偏移量工具包。
[](https://github.com/TajuC/MapleDumper-rs/actions/workflows/ci.yml)
[](LICENSE)


MapleDumper 能够查找、生成并验证那些在客户端补丁更新后依然有效的字节特征码与偏移量。
它会附加到一个正在运行的进程上,使用基于 AVX2 硬件加速的掩码匹配器扫描目标模块,
将匹配结果解析为稳定的模块相对 RVA,并生成可复用的 C/C++ header、Cheat Engine
表格或一份纯文本报告。它的核心亮点功能 **Signature Maker** 可以直接从磁盘读取多个客户端版本,
并生成置信度最高、能在每个版本中解析到相同目标的特征模式。
它以一个无边框的桌面工作区形式发布,会保留每次扫描的本地历史记录,同时还提供了一个
可脚本化的命令行工具。这两个组件都建立在同一个引擎 crate 之上。
## MapleDumper 的与众不同之处
- **设计伊始即为跨版本。** Signature Maker 会对每个候选特征码针对所有提供的版本进行验证,
因此它所生成的特征码是已经在所有版本中都能解析到相同目标的确定结果,
而不是仅凭单个客户端得出的猜测。
- **指令感知与重定位感知。** 特征码使用 `iced-x86` 指令解码和 PE base relocation table 进行掩码处理,
因此易变的操作数和重定位地址会变为通配符,而不是脆弱的固定字节。
- **确定性的输出。** 扫描和生成的特征码会经过排序和去重并保持稳定的顺序,
这使得差异对比和版本比较更具实际意义。
- **完全离线与本地化。** 桌面应用不会发起任何网络请求。Content-Security-Policy 会拦截
所有远程来源的请求,且扫描历史记录保存在本地的 SQLite 数据库中。
## 核心功能亮点
**引擎 (`maple-core`)**
- 读取/扫描流水线。少量读取线程并行将目标内存流式传输到有界 channel 中,
同时 rayon 线程池负责扫描落地的每个数据块,从而实现读取和扫描的重叠执行。
工作单元被切分得很小(256 KiB),因此扫描任务能均匀分布到每个核心上。
- AVX2 掩码匹配器,它利用静态频率表将每个特征模式锚定在其最罕见的固定字节上,
并在运行时自动选择标量回退方案。
- 默认只扫描可执行区域,只需一个开关即可回退到扫描整个模块,
从而大幅减少实时 dump 时读取的字节数。
- 等待并附加。将其指向一个尚未运行的进程,它会持续轮询,并在进程和模块出现的瞬间
完成附加(该过程可取消)。
- 后缀驱动的解析器:RIP 相对寻址和 `rel32` 指针、嵌套调用、struct displacements、
以及 packet-header immediates,均支持对 x64 和 x86 的架构感知。
- 输出为确定性的、已排序且去重的模块 RVA,完全免疫 ASLR。
**桌面工作区 (`maple-app`)**
- 无边框深色仪表盘:包含目标工具栏、按类别分组且带有状态颜色标识的结果表,
以及元数据检查器(RVA、绝对地址、特征码、类型、命中次数、备注)。
- Signature Maker 视图。放入多个客户端版本,即可在不离开应用的情况下生成跨版本特征码:
可通过特征码、地址或两者兼有来指定目标,支持一次性排入多个任务(每行一个),
并针对特征码应该解析到的地址进行交叉验证。添加文件时会自动检查是否被加壳,
选定的结果会直接保存到你的特征码列表中。
- 版本历史记录。每一次扫描都会保存到本地的 SQLite 数据库中,并按版本(根据代码段的
内容哈希值)进行分组,从而让众多客户端版本保持井然有序。完全相同的重复扫描会被去重。
- 跨版本对比。打开任何扫描记录,比较任意两个版本(偏移量的移动、新增或移除),
或者将所有版本排列在矩阵中,以追踪某个偏移量在整个时间线上的变化。点击发生变化的
符号,即可并排查看其字节和 x86/x64 反汇编代码。
- 汇编扫描。通过指令而非字节来查找代码:输入带有通配符的汇编代码行
(`*` 表示零个或多个,`?` 表示一个字符,`^` 表示行首,`$` 表示行尾),它会将目标反汇编,
并列出这些指令连续出现的所有地址。
- 内置特征码管理器(支持添加、编辑、删除和备注)以及一个带有语法高亮的编辑器。
- 隐私遮罩。一键隐藏所有特征码、名称、地址、类别和备注,方便截图。
你可以选择模糊处理,或者选择“展示模式”用逼真的假数据替换原有内容。这仅限于视觉效果;
真实数据不会被更改。
- 支持五种界面语言:英语、日语、中文、韩语和希伯来语(从右至左排版)。
- 完全离线。编辑器和历史数据库均在本地运行,并且 Content-Security-Policy 会拦截
所有远程来源的访问。
**命令行 (`maple-cli`)**
- 每个任务对应一个子命令(`scan`、`lint`、`diff`、`asm`、`mksig`、`profile`),非常适合
脚本编写和 CI。运行 `mapledumper help ` 可查看任何命令的具体参数。
- 无需目标的离线辅助工具:`lint` 用于标记脆弱的特征码,`diff` 用于报告两个 dump 之间
发生了偏移变动的量,而 `profile` 用于将实时扫描分解为读取/扫描/解析的耗时。
- `asm` 通过可选的地址范围,运行与桌面端 Assembly scan 相同的指令扫描。
- `mksig` 从命令行运行 Signature Maker,并提供 `--json` 输出以供工具集成。
- 工作目录下的 `maple.conf`(或通过 `--config ` 指定)可为进程、模块、
架构、特征码文件和输出目录提供默认值;显式指定的命令行参数始终具有最高优先级。
## Signature Maker
当下一次客户端补丁移动了代码或重写了指令时,单版本的特征码就会失效。
Signature Maker 通过跨版本处理来解决这个问题:
1. 向其提供两个或更多的客户端 `.exe` 文件。它会将每个文件作为 PE 映像从磁盘读取,
无需运行中的进程。
2. 通过需要加固的现有 AOB、某个版本中的参考地址 (RVA) 或同时使用两者来选择目标。
3. 它会搜索三种锚点:目标自身的字节(Direct)、对目标的调用或跳转
(`_CALL` / `_JMP`)以及对目标的内存引用(`_PTR`)。
4. 每个候选特征码都会使用指令解码和重定位表进行掩码处理,然后针对每个版本进行验证,
以确保唯一匹配且 callee 在各个版本中保持相似。
5. 每个候选特征码都会基于多个独立维度(唯一性、重编译稳定性、字节熵、
经过验证的语义内容、解析器置信度以及跨版本一致性)进行评分,
并整合为一个单一的 `final_score`,再根据该分数评定从 A 到 F 的等级。
候选特征码会进行确定性排序,并从中选出最佳结果。
桌面版 **Signature Maker** 视图支持以交互方式运行整个流程:在一次运行中排入多个目标
(每行一个特征码或地址),开启 **Cross-validate** 即可将每个特征码与其应该解析到的
地址进行配对并确认它们是否一致,这是检查手写 AOB 是否仍落在预期位置的最快方法。
命令行工具 `mksig` 则为脚本和 CI 提供了相同的生成器。
字母等级是根据 `final_score` 读取的(而不是先定好等级再反向推导):
经过内容验证的锚点(在每次构建中目标均为代码且 callee 保持一致的分支或 RIP 相对引用)
将获得 **A** 级;重定位安全但未经验证的引用(直接匹配,或稳定的数据/导入引用)
大约在 **B** 级;绝对地址、未解析或跨版本不一致的引用则较弱(**C** 级);
加壳输入被限制在 **D** 级;而硬性门槛(固定字节过少、固定字节比例过低、无 opcode 字节、
不支持的重定位)则直接强制判定为 **F**。报告中会展示每一项子分数、`final_score` 及其
背后的原因,以及每个版本的具体证据(匹配 RVA、解析目标、目标种类以及与参考版本相比
callee 指纹的相似度)。
callee 比较是一种实用的*相似度*评估,既不要求绝对相等,也不是反编译器。它融合了
助记符流的保序比较(它们最长公共子序列的 Dice 系数,因此插入或移动一条指令只会
产生一个位置的偏差,而不会导致后续比较错位)、CFG-lite 的代码块/调用/分支/返回
结构特征,以及独特的常量和字符串引用集合。缺失的证据被视为“未知”而不是“匹配”:
如果两个函数恰好都没有引用常量或字符串,则不会被计为完美的常量/字符串匹配。
数值相似度被划分为 High / Medium / Low 区间:High 区间意味着是同一个函数,
Low 区间会触发严重的“callee 在不同版本间出现差异”降级,而 Medium 区间则是较轻缓的
“存在细微差异”提示。这是基于短指令窗口的启发式身份比对,而非完整的控制流或数据流分析。
**字符串锚点**是根据函数引用的只读字符串进行匹配的,因此其评分依据是字符串锚点证据,
而不是将该字符串的字节视作代码字节熵来计算:该字符串必须在每一个必需的版本中
精确解析到同一个函数,它定位该函数的精确程度(解析的唯一性、引用计数,字符串长度仅
作为辅助因素),解析目标的跨版本稳定性,以及其 callee 的相似度。只有在多个版本间
完成验证(单一版本的锚点评分上限低于 A,因为缺乏跨版本证据),它才能获得 A 级;
缺失的字符串不会产生候选结果,而通用的或不一致的字符串则会被降级。
生成过程证明了特征码在提供的版本中是唯一的,但这本身并不能证明它具有高度特异性。
传入不相关模块的负样本库(`--negative` / `--negative-dir`),选定的特征码将会对每个模块
进行扫描。降级的程度取决于其匹配到的不同模块数量(这才是真正的普遍性信号),如果
某一个模块多次匹配到它,降级程度会进一步加深,并且相关证据(扫描的模块、命中的模块、
总匹配次数以及任意单一模块中的最大匹配次数)将记录在原因说明中,并在 JSON 的
`negative_summary` 中暴露。任何匹配都会降低该特征码的唯一性和最终得分,并可能导致其评级下降。
### 在主要版本间进行重定位
当没有任何单一的字节模式能够在所有提供的版本中保留下来时,生成器会回退到多层级的
重编译稳定锚点中,按照最强优先的顺序进行尝试,并在第一次有把握确定该函数时停止:
- **String anchor** — 函数引用的只读字符串(不随版本改变,因此即使重编译移动了所有字节,
它也能保留;请参阅下方实测的跨版本覆盖率)。
- **Import anchor** — 其调用的导入 API 的独特集合。
- **Caller anchor** — 一个以字符串为锚点的 *caller*,目标将被重新确定为身份匹配的 caller 的 callee,
因此自身没有句柄的函数也可以通过拥有句柄的函数来触达。
- **Vtable anchor** — 对于 C++ 虚函数,类的 vtable 会通过其每个槽位指纹的、经过区分度加权的
半全局仿射比对在各版本间进行匹配,这样即使跨大版本转换时插入或删除了方法,也只会
改变匹配位置而不是破坏它;随后即可读回目标的槽位,并跟随任何 adjustor thunk。如果
重构导致整个表偏离了逐槽位匹配器,该表将被**通过安装它的构造函数重新奠定基准**
(构造函数通过一个在版本间保持稳定的类字符串来锚定自身),从而直接恢复 vtable 地址。
- **Encoding and mnemonic fingerprints** — 针对模板实例兄弟项的结构性回退方案。
- **Shortlist** — 当没有任何特征能唯一确定该函数时,会提供一个按版本划分的结构族列表
(它属于该结构族),每一个都附带一个新生成的 AOB,而不是给出一个看似自信实则错误的答案。
跨越较长的版本跨度是通过中间版本,选择置信度最高的**链路**进行过渡,而不是一步完成
低置信度的跨越,并且每一跳都会根据原始目标进行验证,因此该路不会在逐个方法的比对中
产生偏移。
每一个到达的版本都会获得一个全新生成的、操作数掩码处理过的字节 AOB,报告会将这些
合并为**版本覆盖范围**:每个 AOB 保持有效的连续版本区间,并在字节失效的地方自动生成
新的 AOB。一个重定位的特征码可以报告说,例如某一个 AOB 适用于 v83 到 v88,而第二个
AOB 则接替适用于 v91 到 v95。覆盖率在设计上就是部分的:只要置信路径能到达,该函数在
每个版本中都会被锚定,而其余未能到达的版本则会被报告为未触及。重定位支持 x86 / PE32。
## 桌面工作区
启动 `maple-app.exe`。在 Workspace 视图中:
1. 输入目标进程(例如 `MapleStory.exe`)和要扫描的模块。
2. 选择架构。保持开启 **Wait for target** 以在进程启动的瞬间附加,或者切换到
**Find by window class** 以通过类名而不是进程名来定位它。**Code regions only**
(默认开启)仅扫描可执行内存;关闭它可扫描整个模块。
3. 加载或编辑你的特征码列表(Patterns 或 Editor 视图),然后按下 **Start Scan**。
4. 检查任何结果,然后点击 **Export** 导出 `offsets.h`、Cheat Engine 表格或一份纯文本报告。
5. 每一次扫描都会保存到 **History** 中:重新查看它,对比两个版本,打开 **Matrix** 来追踪
某个偏移量在所有版本中的变化,或者点击发生变化的符号查看其字节和反汇编代码。
在分享截图之前,请使用标题栏中的“眼睛”按钮隐藏特征码,可以在设置中选择使用模糊处理
或展示模式下的随机化假数据。
## 命令行
```
mapledumper [options] ( --config is accepted on any command )
scan attach to a process and dump offsets from a pattern file
lint check a pattern file for weak signatures
diff compare two saved dumps and report what moved
asm scan a live process by assembly instructions
mksig build a cross-version signature from client files on disk
profile measure the read/scan/resolve split against a live target
scan / profile share the attach and pattern options:
--process attach by process name (e.g. MapleStory.exe)
--class attach by top-level window class
--pid attach by process id (when several share a name)
--module module to scan (default: process name)
--patterns pattern file (default: patterns.txt)
--arch <32|64> architecture section to load (default: 64)
--no-wait do not wait for the process; fail if it is not running
--timeout give up waiting after this many seconds
--lenient accept malformed pattern lines instead of failing
scan also takes:
--out output directory (default: .)
--ce write update.txt as a Cheat Engine table
--no-offsets do not write offsets.h
--json emit the scan result as JSON on stdout (progress goes to stderr)
lint also takes:
--json emit the lint result as JSON on stdout
asm takes a positional plus --from/--to to clip the address range.
mksig:
--client a client binary (repeat for each version)
--client-dir add every .exe in a folder as a client
--sig target: locate this existing AOB in each client and harden it
--ref --rva target: an address in one reference client
--min-fixed-ratio reject signatures below this fixed-byte ratio (default 0.30)
--negative / --negative-dir unrelated modules the result must not match
--holdout leave-one-out: regenerate per subset and confirm each held-out build matches
--json / --json-out emit the full report as JSON
mapledumper help prints the full options for one command.
```
```
mapledumper scan --process MapleStory.exe --patterns patterns.txt --out .
# 在不附加到任何内容的情况下检查 signature 质量
mapledumper lint --patterns patterns.txt
# 查看两个游戏版本之间哪些 offsets 发生了移动
mapledumper diff old/update.txt new/update.txt
# 通过指令查找代码:每个 push,然后是一个 call,接着是 test eax,eax(每行一条指令)
mapledumper asm --process MapleStory.exe find.asm
# 从多个 client builds 生成一个 cross-version signature
mapledumper mksig --client-dir ./clients --sig "48 8B ?? ?? ?? ?? ?? 48" --json
# 将通用设置保留在 maple.conf 中,然后直接运行 verb
printf 'process = MapleStory.exe\narch = 64\nout = dump\n' > maple.conf
mapledumper scan
```
### 退出代码
CLI 会返回稳定且具体的退出代码,这样脚本就可以根据结果进行分支处理,而不是将所有
非零结果一视同仁:
| 代码 | 含义 |
|------|---------|
| `0` | 成功,没有任何需要标记的警告 |
| `1` | 内部错误(意外) |
| `2` | 成功但带有警告(`lint` 标记了脆弱的特征码;`mksig` 匹配到了负样本库) |
| `3` | 扫描已运行,但部分模式未找到或匹配到了却无法解析 |
| `4` | 扫描已运行,但至少有一个模式在多个位置匹配到(存在歧义) |
| `5` | 无效输入:错误的参数、错误的配置、错误或空的特征码,或者无法定位目标 |
| `6` | 打开目标进程时被拒绝访问(请尝试以管理员身份运行) |
如果将 `--arch` 设置为与目标模块位数不匹配的错误架构,扫描会直接以架构不匹配的
错误(退出代码 `5`)快速失败,而不是静默地将所有结果报告为“未找到”;并且,如果读取
到的区域长度不足,将会报告为“部分读取”警告,这样就不会把因为内存不可读而导致的
“未找到”误认为是确信的不存在。在 `--json` 模式下,每个发现结果都会携带结构化的解析
追踪信息(解析器类型、指令偏移量、操作数、计算出的目标、区段以及任何失败原因)。
## 快速开始
1. 构建工作区:`cargo build --release`。
2. 桌面端:运行 `target/release/maple-app.exe`,设置目标进程,按下 Start Scan。
3. CLI:运行 `target/release/mapledumper.exe scan --process --patterns patterns.txt`。
4. 请以管理员权限运行,以确保针对受保护目标的 `OpenProcess` 和 `SeDebugPrivilege` 能够成功执行。
## 构建
需要稳定的 Rust 工具链 (MSVC) 和 Windows SDK。桌面应用在运行时需要
[WebView2 runtime](https://developer.microsoft.com/microsoft-edge/webview2/),
该运行时已随当前版本的 Windows 系统一起发布。
```
cargo build --release
```
- 桌面应用: `target/release/maple-app.exe`
- CLI: `target/release/mapledumper.exe`
## 特征码语法
每个非空行定义一个特征码。可接受的格式:
```
Name = AA BB ?? CC
Name: 0xAA ?? CC
Name AA ?? CC
```
- 通配符:`?` 或 `??`。允许在字节之间使用逗号。
- 备注和注释:`;` 或 `#` 之后的文本将被捕获作为该符号的备注(并显示在应用程序中);
以 `#` 开头的行是注释。
- 架构区段:`#32BIT` 和 `#64BIT` 标题用于选择要加载的区块。在任何区段标题之前的特征码
对两者都适用。
- 分类区段:`[name]` 设置以下符号在 `offsets.h` 中使用的 namespace
(默认为 `globals`)。
通过名称后缀来选择匹配结果的解析方式。这是一种为了向下兼容而保留的格式,以确保现有的
特征码文件能继续正常工作:
| 后缀 | 含义 |
|----------|--------------------------------------------------------------------------|
| `_PTR` | 解析 RIP 相对加载(`mov`/`lea`/`cmp`/SSE)或 `rel32` 的 jmp/call 指令。 |
| `_CALL` | 将匹配结果视为一个 call,并解析(嵌套的)call 目标。 |
| `_OFF` | 提取 struct 成员 displacement(作为原始 offset 输出)。 |
| `_HDR` | 提取立即操作数,例如 packet header opcode。 |
| (无) | 直接输出匹配地址本身。 |
如果需要显式且类型化的计划,可以附加 `@key=value` 指令,而不是依赖名称。`@kind` 会以
值的形式直接指定解析器,而不是从后缀中解析,并且严格加载器会将每个指令解析并验证为
特征码的类型化计划,如果遇到未知的 key 或 value 则会连同行号一起拒绝:
```
CUserLocal = 48 8B 0D ?? ?? ?? ?? @kind=ptr @section=code @hits=unique
```
| 指令 | 可选值 | 含义 |
|-------------|----------------------------------------|------|
| `@kind` | `ptr`, `call`, `off`, `hdr`, `direct` | 解析器类型,覆盖任何后缀。决定解析方式。 |
| `@section` | `code`, `data`, `rodata`, `import` | 解析目标必须落入的区段。在实时扫描中强制执行:如果目标落入了错误类型的内存中,将被报告为失败,而不是成功的匹配。 |
| `@hits` | `unique`, `any`, `>=N` | 该特征码预期应产生的匹配数量。 |
| `@instr` | 数字 | 在匹配窗口中,从哪一条解码后的指令开始解析。 |
| `@operand` | 数字 | 读取该指令的哪一个操作数。 |
有关具体的示例,请参阅 [patterns.sample.txt](patterns.sample.txt)。
实时进程会暴露页保护属性,但不会暴露其在磁盘上的区段表,因此 `@section` 的强制检查
粒度取决于内存保护属性所允许的范围:`code` 要求解析目标必须落入可执行区域,
而 `data`、`rodata` 和 `import` 则要求其落入可执行区域之外。当目标未能落入预期的区段时,
该特征码将被报告为 `out of expected section`(并且永远不会被导出),
这就是特征码匹配到了错误指令的信号。
**String-anchored 特征码。** 除了使用字节,特征码还可以直接命名目标函数引用的一个
只读字符串。该字符串能够在移动了周围字节的重编译中幸存,因此当字节特征码失效时,
它可以跨客户端版本定位到同一个函数:
```
StatWindow = @string=UI/UIWindow2.img/Stat
```
如果没有单个字符串是该函数唯一的,可以使用 `@also` 添加第二个字符串;目标函数是同时
引用了这两个字符串的那一个:
```
StatWindow = @string=UI/UIWindow2.img/Stat @also=UI/UIWindow2.img/Stat/main
```
引擎会在数据段中找到该字符串,顺着唯一的代码引用追溯到它,并解析出包含该引用的
函数入口。
**实测的跨版本覆盖率。** `--ignored` 测试套件
`cross_version_relocation_coverage_and_false_positive_sweep`(位于 `crates/maple-core/src/sigmaker/`)
会扫描 GMS v83 到 v95.1 的血脉版本,并报告每种锚点能重定位多少个函数,以及错误地址率是多少。
误判是通过独立于受测锚点的检查(反向往返验证、第二个被引用字符串的佐证以及重编译后的
身份一致性)来判定的。在本地的客户端测试集中,字符串锚点在 100% 的相邻版本
(v83 到 v91)中都能解析到同一个函数,在跨越 v95 阶级重构时的解析率也达到了 71%
(基于 v83 中可应用字符串锚点的函数群体),并且 Import、Caller 和 Vtable 锚点均做到了
零确认的错误地址命中。相比之下,普通的字节特征码几乎无法在移动了操作数和重排了代码的
重编译中幸存,这正是为什么要存在这些重编译稳定锚点的原因。利用该测试套件针对你自己的
客户端版本即可复现这些数据(真正的客户端受版权保护且不可分发,因此本代码库不附带任何
测试集)。
## 架构一览
| Crate | 职责 |
|--------------|--------------------------------------------------------------------------------|
| `maple-core` | 核心引擎:特征码解析、SIMD 扫描器、进程内存访问、解析器、扫描流水线、Signature Maker、PE 磁盘读取器以及输出编写器。 |
| `maple-app` | 桌面工作区:一个带有嵌入式 Web UI(Tauri v2)的 Rust 后端。 |
| `maple-cli` | 命令行前端。 |
## 性能表现
匹配器会将每个特征模式锚定在其最罕见的固定字节(基于静态频率表)上,而不是第一个字节,
因此像 `0x48` (REX.W) 这样的常见字节就不会使预过滤器不堪重负。它使用通过
`is_x86_feature_detected!` 在运行时选择的 AVX2 路径,并配有标量回退方案。对于大型特征码
集合,它会切换到单次遍历的多模式索引,因此成本会随着缓冲区大小加上匹配数量而增长,
而不是缓冲区大小乘以特征码数量。
合成吞吐量(基于 criterion 的 `cargo bench`,使用 8 MiB 模拟代码的缓冲区)在作者硬件上的
表现为:采用最罕字节锚定的单模式 AVX2 路径扫描速度约为 29 GiB/s,相比之下,如果强制
使用像 `0x48` 这样的常见字节,速度大约只有 0.8 GiB/s,相差了约 37 倍,这也正是为什么要
引入锚点启发式算法的原因。(多模式索引路径是基于标量的,因此它是用单字节扫描速度换取了
O(buffer + matches) 而非 O(buffer x patterns) 的复杂度;这里的 29 GiB/s 数据仅针对单模式情况。)
(`cargo run --release --example throughput` 是一个依赖更少的等效实现。)这些数据是合成的,
且取决于硬件配置;请在本地使用 `cargo bench` 进行复现。
针对实时进程,`--profile` 会将运行时间拆分为读取、扫描和解析阶段
(并尝试不同的工作单元大小),从而使性能调优建立在测量的基础上,而不是凭空猜测。
## 许可证
MIT。详情请见 [LICENSE](LICENSE)。
标签:PE解析, Rust, 内存扫描, 可视化界面, 外挂开发, 游戏逆向, 特征码生成, 网络流量审计, 通知系统