Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
dconstruct — 一个用于 TLOU2 DC 脚本的反汇编器与实验性反编译器。 | Kitploit
工具/GitHubGitHub/deepquantum/dconstruct
静态分析逆向工程调试器二进制分析固件分析
GitHubdeepquantum/dconstruct

dconstruct

一个用于 TLOU2 DC 脚本的反汇编器与实验性反编译器。

查看仓库
2321719天前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

Version License: CC BY-NC-ND 4.0

dconstruct

完整反汇编与反编译动画

打开源 MP4

dconstruct 是一款用于《最后生还者 第二部》DC-Script 文件的逆向工程工具。它包含反汇编器和反编译器。

工具输出 .asm 文件(包含反汇编出的结构与字节码)以及 .dcpl(DC 伪语言)文件(包含类似 C 语言风格的伪代码)。

你也可以通过命令行对文件进行编辑,包括轻松替换整个结构。这使得创建仅需修改 .bin 文件中少量数值的 mod 变得极其简单。

主要特性

  • 速度优化。反汇编与反编译速度极快。
  • 对原始源代码(尤其是控制流)的精确重构。
  • 对反汇编中所有结构的精确自动解析。
  • 同时反汇编与反编译多个文件。反编译游戏中每一个 .bin 文件仅需几秒钟。
  • 通过 -e 标志进行编辑,生成可用于 mod 的新文件。
  • 加载自定义 sidbase 用于反汇编。

使用方法

首先,建议将解压后的 dconstruct 目录移动到一个安全的位置,例如 C:\Program Files。

为尽可能方便使用 dconstruct,建议将 dconstruct 文件夹内的 .\bin 目录添加到你的 PATH 环境变量中。更多信息请参考这里,或按照以下快速步骤操作:

  • 在 Windows 搜索栏中输入“环境变量”,你会看到“编辑系统环境变量”的选项。
  • 点击该选项后,会弹出一个标题为“系统属性”的窗口。在“确定”、“取消”和“应用”按钮的上方,有一个“环境变量...”按钮。
  • 点击此按钮后,会弹出另一个窗口。在这里,进入第二个表格(标题为“系统变量”),找到变量名为“Path”的条目,双击它。
  • 在此对话框中,选择右侧的“新建”选项。然后粘贴 .\bin 目录的路径。路径应类似这样:

示意图

  • 确保路径以“\bin”结尾,而不是“\dconstruct”。

  • 在所有打开的对话框中点击“确定”。

  • 要验证是否成功,请打开一个新的命令提示符窗口,输入 dconstruct --about。你应看到程序输出一些信息,且没有错误消息。

在命令行中运行如下命令,以生成你的第一个反汇编文件:```shell dconstruct my_bin_file.bin

root@kitploit:~
这将输出一个名为 `my_bin_file.bin.asm` 的文件,位于输入文件所在目录。然后你可以用文本/代码编辑器打开该文件。我推荐使用诸如 VSCode 这类具有高级搜索功能且擅长处理大文件的编辑器。不推荐使用标准 Windows 记事本。

要反编译文件,运行命令时加上 `--decompile` 标志。

# 命令行参数

- `-i` - 输入文件或文件夹。如果第一个参数传递了输入路径,则可以省略。

- `-o` - 输出路径。如果输入路径是文件夹,则此项不能是文件。如果未指定输出,.txt 文件将放在输入文件旁边。如果输入是文件夹且未指定输出,程序将在当前工作目录下创建 "output" 目录,并将所有文件放入其中。

- sidbase 从与可执行文件相邻的 `sidbase.bin` 加载。

- `--no_decompile` - 不将反编译伪代码输出到 .dcpl 文件。该文件将放在 .asm 文件旁边。默认值为 false。

- `--no_optimize` - 不优化和清理 dcpl 代码。包括内联函数调用、移除未使用的变量、将兼容的 for 循环转换为 foreach 循环,以及将某些 if-else 链转换为 match 表达式。

- `--pascal_case` - 将游戏函数名称转换为 dcpl 输出中的帕斯卡命名风格,例如 get-boolean -> GetBoolean。

- `--graphs` - 生成包含所有反编译函数的控制流图的 .svg 文件。每个 .bin 文件拥有自己的文件夹,包含其所有图形。这会**显著**降低反编译速度,因此不建议同时反编译大量文件时使用。

- `--emit_once` - 禁止同一结构在反汇编中出现两次。如果一个结构多次出现,仅第一个实例会被完整输出,其他所有出现位置将被替换为 `ALREADY_EMITTED` 标签。这可以显著减小文件大小。

- `-e` - 进行编辑。更多信息见下方章节。

- `--edit_file` - 提供编辑文件。编辑文件每行包含一个编辑操作。它使用与 -e 标志相同的语法。

# 什么是反汇编器?

[反汇编器](https://en.wikipedia.org/wiki/Disassembler)是一种读取二进制指令(即[字节码](https://en.wikipedia.org/wiki/Bytecode)或[机器码](https://en.wikipedia.org/wiki/Machine_code))并将其逐条转换为人类可读版本(称为[助记符](https://en.wikipedia.org/wiki/Assembly_language#Mnemonics))的工具。反汇编器一般不会尝试解释这些指令的太多含义,只是将它们一对一转换为可读版本。例如,指令:```arm
15 00 00 00
4A 01 01 00
43 31 01 00
1C 00 00 01

被反汇编成以下人类可读版本:```arm LookupPointer r0, 0 LoadStaticU64Imm r1, 1 Move r49, r1 CallFf r0, r0, 1

root@kitploit:~
字节码中的所有数字均以[十六进制](https://en.wikipedia.org/wiki/hexadecimal)表示。每行中的第一列代表`opcode`,即要执行的指令类型。下一列是目标寄存器,操作的结果将存储在该寄存器中。最后两列是操作数1和2,它们可以是寄存器或字面数值,操作将对这些数值执行。并非所有指令都使用全部4个字节,例如,第一条`LookupPointer`指令仅需要一个操作数。

dconstruct反汇编器还会添加一些额外信息,旨在使指令的阅读更加容易。它还会插入标签(例如`L_0`)以使代码中的分支更易于跟踪。```arm
15 00 00 00   LookupPointer        r0, 0         r0 = ST[0] -> <is-player-abby?>
4A 01 01 00   LoadStaticU64Imm     r1, 1         r1 = ST[1] -> <player>
43 31 01 00   Move                 r49, r1       r49 = player
1C 00 00 01   CallFf               r0, r0, 1     r0 = is-player-abby?(player)
2F 0D 00 00   BranchIfNot          r0, 0xD       IF NOT r0 => L_0

当你希望查看文件的原始内容而不让程序进行过多猜测时,这很有用。但对于大块代码来说,由于完全没有结构,读起来可能会很困难。这时候就需要反编译器了。

什么是反编译器?

反编译器是编译器的逆过程。编译器 是一种接收人类编写的代码(如 C、Java、C++ 等)并生成机器指令的程序。以《最后生还者 第二部》和许多其他顽皮狗游戏为例,所使用的脚本语言称为 'DC',它基本上是编程语言 Racket>) 的一个变体,而“机器”就是游戏本身,在运行游戏时执行这些指令。本质上,程序员编写 DC 代码,然后使用编译器将其转换为随游戏一起发布的 .bin 文件。

反编译器 接收上述反汇编代码,并生成所谓的伪代码。伪代码是_尝试_重建用于生成原始指令的原始源代码。这意味着使代码的理解变得更加容易,然而,生成伪代码的过程相当复杂,因为有许多不同版本的源代码可以生成最终的字节码,此外还有编译过程中发生的优化。

目前,dconstruct 反编译器的输出在语法上_不_类似于原始的 DC。DC(即 Racket)是一种函数式编程语言,具有独特的语法,对于不习惯它的程序员来说简直难以辨认。因此,我目前选择让伪代码更像 C 语言,这样对大多数人来说应该更容易阅读。不过,计划创建更多语法,包括 Racket 以及 Python 版本。

示例

sqrt-sign() 函数

原始字节```arm

43 00 31 00 15 01 00 00 43 02 00 00 43 31 02 00 1B 01 01 01 43 02 00 00 40 03 01 00 24 02 02 03 2F 0B 02 00 40 02 02 00 2D 0C 00 00 40 02 03 00 43 03 02 00 15 04 04 00 43 05 01 00 43 31 05 00 1C 04 04 01 07 03 03 04 43 01 03 00 00 01 01 00

root@kitploit:~
由此看来,几乎不可能看出代码在做什么。

## 带有标签和符号表的反汇编代码```arm
sqrt-sign = script-lambda [0x9A8D8] {
    [1 args]
    0000   0x09A928   43 00 31 00   Move                 r0, r49       r0 = arg_0
    0001   0x09A930   15 01 00 00   LookupPointer        r1, 0         r1 = ST[0] -> <absf>
    0002   0x09A938   43 02 00 00   Move                 r2, r0        r2 = arg_0
    0003   0x09A940   43 31 02 00   Move                 r49, r2       r49 = arg_0
    0004   0x09A948   1B 01 01 01   Call                 r1, r1, 1     r1 = absf(arg_0)
    0005   0x09A950   43 02 00 00   Move                 r2, r0        r2 = arg_0
    0006   0x09A958   40 03 01 00   LoadStaticFloatImm   r3, 1         r3 = ST[1] -> <0.000000>
    0007   0x09A960   24 02 02 03   FGreaterThanEqual    r2, r2, r3    r2 = r2 >= r3
    0008   0x09A968   2F 0B 02 00   BranchIfNot          r2, 0xB       IF NOT r2 => L_0
    0009   0x09A970   40 02 02 00   LoadStaticFloatImm   r2, 2         r2 = ST[2] -> <1.000000>
    000A   0x09A978   2D 0C 00 00   Branch               0xC           GOTO => L_1
  L_0:
    000B   0x09A980   40 02 03 00   LoadStaticFloatImm   r2, 3         r2 = ST[3] -> <-1.000000>
  L_1:
    000C   0x09A988   43 03 02 00   Move                 r3, r2        r3 = -1.000000
    000D   0x09A990   15 04 04 00   LookupPointer        r4, 4         r4 = ST[4] -> <sqrt>
    000E   0x09A998   43 05 01 00   Move                 r5, r1        r5 = RET_absf
    000F   0x09A9A0   43 31 05 00   Move                 r49, r5       r49 = RET_absf
    0010   0x09A9A8   1C 04 04 01   CallFf               r4, r4, 1     r4 = sqrt(RET_absf)
    0011   0x09A9B0   07 03 03 04   FMul                 r3, r3, r4    -1.000000 = -1.000000 * RET_sqrt
    0012   0x09A9B8   43 01 03 00   Move                 r1, r3        r1 = -1.000000
    0013   0x09A9C0   00 01 01 00   Return               r1            Return

    SYMBOL TABLE:
    0000   0x09A9C8    function: absf
    0001   0x09A9D0    float: 0.000000
    0002   0x09A9D8    float: 1.000000
    0003   0x09A9E0    float: -1.000000
    0004   0x09A9E8    function: sqrt
}

代码现在可读性大大提高,但即使只有单一分支,对于不习惯阅读汇编的人来说,读起来仍然很烦人。

Control flow graph

这里不过多赘述,控制流图(CFG) 沿各种分支指令将汇编代码分割成"节点"。在我们分析代码以找出程序"流"可能在何处分叉到不同路径时,这至关重要,可能需要我们生成变量、if语句、for循环等。这些图需要在后台生成,但你可以使用 --graphs 程序标志将它们打印成图像。

Final pseudo code```rust

u64? sqrt-sign(f32 arg_0) { f32 var_1; if (arg_0 >= 0.00) { var_1 = 1.00; } else { var_1 = -1.00; } return var_1 * sqrt(absf(arg_0)); }

root@kitploit:~
该函数的目的现在非常明确:我们取参数的绝对值,计算其平方根,然后将结果乘以参数的原始符号。例如,`sqrt-sign(-9) = -3`。

## 优化遍次

dconstruct 会自动对伪代码应用优化遍次。以下是一些示例:

### 函数调用内联

#### 之前```c
u64? set-arrow-explosive-handle-rootvars() {
    u64? var_0 = get-uint64(fx-handle, self);
    u64? var_1 = get-float(kill, self);
    set-effect-float(var_0, killradius, var_1);
    u64? var_2 = get-uint64(fx-handle, self);
    u64? var_3 = get-float(strong, self);
    set-effect-float(var_2, strongradius, var_3);
    u64? var_4 = get-uint64(fx-handle, self);
    u64? var_5 = get-float(weak, self);
    u64? var_6 = set-effect-float(var_4, weakradius, var_5);
    return var_6;
}

之后```c

u64? set-arrow-explosive-handle-rootvars() { set-effect-float(get-uint64(fx-handle, self), killradius, get-float(kill, self)); set-effect-float(get-uint64(fx-handle, self), strongradius, get-float(strong, self)); return set-effect-float(get-uint64(fx-handle, self), weakradius, get-float(weak, self)); }

root@kitploit:~
### Foreach 循环

### 之前```c#
u64? bmm-deactivate-all(u64? arg_0) {
    u64? var_0 = darray-count(arg_0);
    begin-foreach();
    for (u64 i = 0; i < var_0; i++) {
        u64? var_1 = darray-at(arg_0, i);
        u16 var_2;
        if (var_1 && *(u16*)(var_1 + 12) == 7) {
            var_2 = *(u64*)var_1;
        } else if (var_1 && *(u16*)(var_1 + 12) == 5) {
            var_2 = *(u64*)var_1;
        } else if (var_1 && *(u16*)(var_1 + 12) == 4) {
            var_2 = *(u64*)var_1;
        } else {
            var_2 = 0;
        }
        net-send-event-all(deactivate, var_2);
    }
    u64? var_3 = end-foreach();
    return var_3;
}

之后```c#

u64? bmm-deactivate-all(u64? arg_0) { foreach (u64? var_1 : arg_0) { u16 var_2; if (var_1 && (u16)(var_1 + 12) == 7) { var_2 = (u64)var_1; } else if (var_1 && (u16)(var_1 + 12) == 5) { var_2 = (u64)var_1; } else if (var_1 && (u16)(var_1 + 12) == 4) { var_2 = (u64)var_1; } else { var_2 = 0; } net-send-event-all(deactivate, var_2); } }

root@kitploit:~
### 匹配表达式

### 之前```scala
string #C57EE0A64537AE8F(u16 arg_0) {
    string var_0;
    if (arg_0 == 0) {
        var_0 = "Militia";
    } else if (arg_0 == 1) {
        var_0 = "Scars";
    } else if (arg_0 == 2) {
        var_0 = "Rattlers";
    } else if (arg_0 == 3) {
        var_0 = "Infected";
    } else if (arg_0 == 4) {
        var_0 = "Max Num Factions";
    } else {
        var_0 = "Invalid";
    }
    return var_0;
}

之后```scala

string #C57EE0A64537AE8F(u16 arg_0) { return match (arg_0) { 0 -> "Militia" 1 -> "Scars" 2 -> "Rattlers" 3 -> "Infected" 4 -> "Max Num Factions" else -> "Invalid" }; }

root@kitploit:~
## 反汇编结构示例```c
*ellie-weapons* = symbol-array [0x00190] {
  [0] int: 6
  [1] int: 0
  [2] array [0x198] {size: 6} {
    [0] anonymous struct [0x780] {
      [0] sid: pistol-beretta
    }
    [1] anonymous struct [0x788] {
      [0] sid: pistol-revolver-taurus
    }
    [2] anonymous struct [0x790] {
      [0] sid: rifle-remington-bolt
    }
    [3] anonymous struct [0x798] {
      [0] sid: bow-ellie
    }
    [4] anonymous struct [0x7a0] {
      [0] sid: shotgun-remington-pump
    }
    [5] anonymous struct [0x7a8] {
      [0] sid: rifle-mpx5
    }
  }
}

Editing

使用 -e 标志编辑 DC 文件

你可以使用 -e 标志对 DC 文件进行编辑。这些编辑会保存到原文件的新副本中,原文件保持不变。可以同时指定多个 -e 标志以一次性进行多项编辑。

或者,你也可以向程序提供一个编辑文件的路径。编辑文件中每行包含一个编辑项,使用与 -e 标志相同的语法,但在需要同时应用多项编辑时应该更方便使用。

编辑发生在_反汇编和反编译之前_,因此生成的文件中会显示编辑结果。

编辑语法

每项编辑遵循以下语法:```xml

[]= ``` - ``: 要编辑的结构体的内存地址(十六进制,以`0x`开头。最简单的方法是从要编辑的文件的反汇编版本中复制粘贴)。 - ``: 结构体内部成员变量的索引。相当于您可以在成员左侧看到的数字。 - ``: 要分配给该成员的新值。必须大小相同。(整数和浮点数大小为4,sids/结构体大小为8)。程序不会检查结构体是否类型相同。

示例

假设您有以下结构体:

root@kitploit:~
[4] firearm-gameplay-def [0x11C28] {
    [0] float 0.7  // might represent the rate of fire, so i want to lower it for my mod
    ...
}
```
要将第一个成员变量(索引0)替换为浮点数值0.5,编辑命令应为:

`-e 0x11C28[0]=0.5`

我们想要编辑的结构位于 `0x11C28`,并且我们想要第一个成员变量(浮点数左侧的0)。然后我们将新值放在 `=` 之后,在本例中是0.5。如果编辑成功,程序将显示一条消息,指示该值已从 `0.7->0.5` 更改。

对于编辑文件,只需去掉 `-e`,每行放一个编辑条目:

### edit_file.txt

0x11C28[0]=0.5

0x11C28[1]=0.2

...

## 成员变量的类型

结构体可以有不同类型的成员变量:

- `float` - 使用小数点指定十进制值(例如 0.5)。

- `int` - 使用不带小数点的整数(例如 42)。

- `sid`(字符串标识符) - (更多信息见下文)

- `string` - 当前不支持替换

- `structure` - 通过替换指针(更多信息见下文)

### 通过名称查找替换 sid:

`-e 0xABC[5]=ellie`

这将查找当前 sidbase 中是否存在值 "ellie"。如果不存在,则发出警告且不应用编辑。如果找到该值,实际的哈希值(一个大数字)将替换成员变量处的当前值。

### 通过直接手动哈希覆盖替换 sid:

`-e 0xABC[5]=#XXXXXXXXXXXXXXXX`

# 表示原始哈希值,将直接应用而无需查找。

### 替换成员结构体

如果一个结构体包含另一个结构体作为成员,你可以通过为它分配另一个结构体的地址来替换整个成员结构体。

例如,假设你有以下内容:```c++
[4] weapon-gameplay-def [0x0C523] {
    ...
    [7] firearm-gameplay-def [0x11C28] {
        ...
    }
}
```
所以 `weapon-gameplay-def` 包含一个 `firearm-gameplay-def`。
要将位于地址 `0x0ABC` 的不同 `firearm-gameplay-def` 替换 `weapon-gameplay-def` 内部的 `firearm-gameplay-def`,编辑操作如下:

`-e 0x11C28[7]=0x0ABC`

# DCPL language VS Code extension

VS Code 支持创建自定义扩展,为自定义语言添加语法高亮。dconstruct 附带一个 .vsix 文件,为 .dcpl 文件扩展名添加此支持。由于它是一种单一用途的语言,并非用于日常编程,因此我不会将该扩展上传到市场,而是以原始 .vsix 文件的形式提供。要在你的 VS Code 中安装此扩展,请运行以下命令:```shell
code --install-extension <path/to/extension/dcpl-lint-0.0.1.vsix>
```
或按 `CTRL+SHIFT+P` 打开命令面板,输入“Install extension via VSIX”并选择 `.vsix` 文件。

之后,你的 `.dcpl` 代码看起来应该类似这样:

![dcpl_code](https://assets.kitploit.com/production/public/readmes/13048/8b4ab26ab7952a91a015d1376d3101bcad17c65602ed9c2b5c1587cb1d1a9f3c.png)

# 已知问题

当前反编译器尚未完全完善,因此处于“实验性”状态。如果在反编译过程中看到警告,不必担心,这些函数目前尚未支持,但希望未来能够支持。除此之外,目前还有以下已知问题:

- 具有大量短路求值的单表达式函数(尤其是结构体内的函数)尚未实现。我已经开始为此类算法编写代码,但不确定需要多长时间完成,不过这属于高优先级项目。
- 某些类型不正确,尤其是参数类型。
- if/else 语句中的空 if 块可能导致缩进异常。这同样不完全是我们的问题,因为某些分支实际上并未执行实际操作,难以检测。

此外,还有一些可能无法修复的问题:

- 当不知道函数是否为 void 时返回垃圾值。
- 存在大量冗余代码。

# 计划功能

- 完整的 Racket 和 Python 输出格式

# 特别感谢

- **icemesh** – 提供了 [DC 文件底层结构](https://github.com/icemesh/dc/tree/main/t2) 和 [他的反汇编器](https://github.com/icemesh/t2-dc-disasm),本项目很大程度上受其启发。

- **Specilizer** – 感谢他的 DC-Tool,同样启发了本程序。

- **uxh** – 感谢脚本知识支持。

- **bigdragon** 和 **Wedge** – 感谢 Beta 测试。

- 整个模组 Discord 社区 – 感谢你们的友好与帮助。

## 支持

我所有的工具和模组将永远保持 100% 免费,但像这样的程序需要大量工作。

如果你想支持我,可以访问我的 Ko-fi:

[![Ko-fi](https://img.shields.io/badge/Donate-Ko--fi-ff5f5f.svg)](https://ko-fi.com/deepquantum)

## 许可证

你使用此模组创建的文件完全属于你,你可以随意处理它们。如果可以,建议注明出处,但并非强制要求。

程序本身采用
[Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License](https://creativecommons.org/licenses/by-nc-nd/4.0/) 许可证。

这意味着你可以与他人分享本程序,但必须注明出处;目前不允许修改或将其用于商业目的。
下载工具