
Un desensamblador y decompilador experimental para scripts de TLOU2 DC.

dconstruct es una herramienta de ingeniería inversa para los archivos DC-Script utilizados en The Last of Us Part II. Incluye un desensamblador y un descompilador.
Genera archivos .asm que contienen las estructuras desensambladas y bytecode, así como archivos .dcpl (DC Pseudo Language) que contienen código pseudo similar a C.
También puede realizar ediciones en archivos mediante la línea de comandos, incluyendo reemplazar estructuras completas con poco esfuerzo. Esto hace que crear mods que simplemente cambien un par de valores dentro de los archivos .bin sea extremadamente fácil.
-e, creando nuevos archivos que puede usar para modsPrimero, se recomienda que mueva el directorio dconstruct descomprimido a una ubicación segura, como C:\Program Files.
Para que dconstruct sea lo más fácil de usar posible, se recomienda que agregue el directorio .\bin dentro de la carpeta dconstruct a su PATH. Puede obtener más información aquí, o seguir estos pasos rápidos:

Asegúrese de que su ruta termine en "\bin" y NO en "\dconstruct".
Haga clic en 'Aceptar' en todos los cuadros de diálogo abiertos.
Para verificar que funcionó, abra un nuevo símbolo del sistema y escriba dconstruct --about. Debería ver alguna salida del programa y ningún mensaje de error.
Ejecute un comando como este en la línea de comandos para generar su primer archivo desensamblado:```shell dconstruct my_bin_file.bin
Esto generará un archivo llamado `my_bin_file.bin.asm` en el mismo directorio que su archivo de entrada. Luego puede abrir ese archivo usando un editor de texto/código. Recomendaría usar algo como VSCode que ofrece funciones de búsqueda avanzada y es bueno para manejar archivos grandes. No se recomienda el Bloc de notas estándar de Windows.
Para descompilar un archivo, agregue la bandera `--decompile` al ejecutar el comando.
# Argumentos de línea de comandos
- `-i` - archivo o carpeta de entrada. Se puede omitir si se pasa la ruta de entrada como primer argumento.
- `-o` - ruta de salida. Si su ruta de entrada es una carpeta, esto no puede ser un archivo. Si no se especifica salida, el archivo .txt se colocará junto al archivo de entrada. Si la entrada es una carpeta y no se especifica salida, el programa creará un directorio "output" en el directorio de trabajo actual y colocará todos los archivos allí.
- La sidbase se carga desde `sidbase.bin` ubicado junto al ejecutable.
- `--no_decompile` - no emite código pseudo descompilado en un archivo .dcpl. El archivo se colocará junto al archivo .asm. Esto es falso por defecto.
- `--no_optimize` - no optimizar y limpiar el código dcpl. Implica la inserción en línea de llamadas a funciones, la eliminación de variables no utilizadas, la transformación de bucles for compatibles en bucles foreach, y la conversión de algunas cadenas if-else en expresiones match.
- `--pascal_case` - convierte los nombres de las funciones del juego a PascalCase en la salida dcpl, por ejemplo get-boolean -> GetBoolean.
- `--graphs` - genera archivos .svg que contienen gráficos de flujo de control para todas las funciones descompiladas. Cada archivo .bin obtiene su propia carpeta que contiene todos sus gráficos. Esto **ralentiza significativamente** la velocidad de descompilación, por lo que no se recomienda al descompilar un gran número de archivos al mismo tiempo.
- `--emit_once` - prohíbe que la misma estructura sea emitida dos veces en el desensamblado. Si una estructura aparece varias veces, solo la primera instancia se emitirá completamente, y todas las demás ocasiones serán reemplazadas por una etiqueta `ALREADY_EMITTED`. Esto puede reducir significativamente el tamaño del archivo.
- `-e` - realizar una edición. Más información en la sección siguiente.
- `--edit_file` - proporcionar un archivo de edición. Un archivo de edición contiene una edición por línea. Utiliza la misma sintaxis que la bandera -e.
# ¿Qué es un desensamblador?
Un [desensamblador](https://en.wikipedia.org/wiki/Disassembler) es una herramienta que lee instrucciones binarias (también conocido como [bytecode](https://en.wikipedia.org/wiki/Bytecode) o [código máquina](https://en.wikipedia.org/wiki/Machine_code)) y traduce cada una a una versión legible por humanos llamada [mnemónico](https://en.wikipedia.org/wiki/Assembly_language#Mnemonics). Los desensambladores generalmente no intentan interpretar mucho sobre el significado de estas instrucciones y simplemente las transforman 1-1 en sus versiones legibles. Por ejemplo, las instrucciones:```arm
15 00 00 00
4A 01 01 00
43 31 01 00
1C 00 00 01
se desmontan en las siguientes versiones legibles por humanos:```arm LookupPointer r0, 0 LoadStaticU64Imm r1, 1 Move r49, r1 CallFf r0, r0, 1
Todos los números en el bytecode están escritos en [hexadecimal](https://en.wikipedia.org/wiki/hexadecimal). La primera columna de cada fila representa el `opcode`, o el tipo de instrucción a ejecutar. La siguiente columna es el registro de destino, donde se almacenará el resultado de la operación. Las últimas dos columnas son los operandos 1 y 2, que son registros o números literales sobre los cuales se realizará la operación. No todas las instrucciones utilizan los 4 bytes, por ejemplo, la primera instrucción `LookupPointer` solo necesita un operando.
El desensamblador dconstruct también agrega información adicional destinada a facilitar la lectura de las instrucciones. También inserta etiquetas (por ejemplo, `L_0`) para facilitar el seguimiento de las bifurcaciones en el código.```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
Esto es útil cuando quieres ver el contenido bruto del archivo sin que el programa haga demasiadas suposiciones. Pero puede ser difícil de leer para grandes bloques de código, ya que no hay estructura alguna. Aquí es donde entra un descompilador.
Un descompilador es el inverso de un compilador. Un compilador es un programa que toma código escrito por humanos (como C, Java, C++, ...) y produce instrucciones de máquina. En el caso de TLOU2, y muchos otros juegos de ND, el lenguaje de scripting utilizado se llama 'DC', que es básicamente una versión del lenguaje de programación Racket>), y la "máquina" es el propio juego que ejecuta las instrucciones mientras el juego se está ejecutando. Esencialmente, los programadores escriben DC y usan un compilador para convertir ese código en los archivos .bin que se envían con el juego.
Un descompilador toma el código desensamblado de arriba y produce lo que se conoce como pseudocódigo. El pseudocódigo es un intento de reconstruir el código fuente original que se utilizó para generar las instrucciones brutas. Esto pretende hacer que la comprensión del código sea significativamente más fácil, sin embargo, el proceso de generar pseudocódigo es bastante complejo, ya que hay muchas versiones diferentes de código fuente que pueden generar el código de bytes final, así como optimizaciones que tienen lugar durante la compilación.
Actualmente, la salida del descompilador dconstruct no es sintácticamente similar al DC original. DC (es decir, Racket) es un lenguaje de programación funcional con una sintaxis única que es j*damente ilegible para los programadores que no están acostumbrados a ella. Por esta razón, he elegido hacer que el pseudocódigo se parezca más a C por ahora, lo que debería ser más fácil de leer para la mayoría de las personas. Sin embargo, está planeado crear más sintaxis, incluyendo Racket y una versión en Python.
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
A partir de esto, es virtualmente imposible saber qué está haciendo el código.
## Código desensamblado con etiquetas y tabla de símbolos```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
}
El código es ahora mucho más legible, pero incluso la rama única es molesta de leer si no estás acostumbrado a leer ensamblador.
Sin entrar en demasiados detalles aquí, un gráfico de flujo de control (CFG) divide el código ensamblador en "nodos" a lo largo de las diversas instrucciones de bifurcación. Esto es crítico cuando analizamos el código para descubrir dónde el "flujo" del programa puede divergir en diferentes rutas, lo que podría requerir que emitamos variables, if statements, for-loops, etc.. Estos gráficos deben generarse en segundo plano, pero puedes imprimirlos en imágenes usando la bandera --graphs del programa.
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)); }
El propósito de la función ahora es muy claro: tomamos el valor absoluto del argumento, calculamos la raíz cuadrada de ese valor y luego lo multiplicamos por el signo original del argumento. Por ejemplo, `sqrt-sign(-9) = -3`.
## Pasos de optimización
dconstruct aplica automáticamente pasos de optimización al pseudocódigo. Aquí hay algunos ejemplos:
### Inlineado de llamadas a funciones
#### Antes```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;
}
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)); }
### Bucles foreach
### Antes```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;
}
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); } }
### Expresiones de coincidencia
### Antes```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;
}
string #C57EE0A64537AE8F(u16 arg_0) { return match (arg_0) { 0 -> "Militia" 1 -> "Scars" 2 -> "Rattlers" 3 -> "Infected" 4 -> "Max Num Factions" else -> "Invalid" }; }
## Ejemplo de una estructura desensamblada```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
}
}
}
Editando archivos DC usando la bandera -e
Puede usar la bandera -e para aplicar ediciones a archivos DC. Estas ediciones se guardan en una copia nueva del archivo original, dejando el original intacto. Se pueden especificar múltiples banderas -e al mismo tiempo para realizar varias ediciones a la vez.
Alternativamente, puede proporcionar al programa una ruta a un archivo de edición. Un archivo de edición contiene una edición por línea. Usa la misma sintaxis que la bandera -e, pero debería ser un poco más fácil de usar si desea aplicar múltiples ediciones a la vez.
La edición ocurre antes del desensamblado y la descompilación, por lo que la edición aparecerá en los archivos generados.
Cada edición sigue esta sintaxis:```xml
Supón que tienes una estructura como esta:```c++ [4] firearm-gameplay-def [0x11C28] { [0] float 0.7 // might represent the rate of fire, so i want to lower it for my mod ... }
Para reemplazar la primera variable miembro (índice 0) con el valor flotante 0.5, el comando de edición sería:
`-e 0x11C28[0]=0.5`
La estructura que queremos editar está en `0x11C28`, y queremos la primera variable miembro (el 0 a la izquierda del flotante). Luego colocamos el nuevo valor después del `=`, 0.5 en este caso. Si la edición fue exitosa, el programa mostrará un mensaje indicando que el valor cambió de `0.7->0.5`.
Para un archivo de edición, simplemente omita el `-e` y ponga una edición por línea:
### edit_file.txt
0x11C28[0]=0.5
0x11C28[1]=0.2
...
## Tipos de Variables Miembro
Las estructuras pueden tener diferentes tipos de variables miembro:
- `float` - Especifica valores decimales con un punto (ej., 0.5).
- `int` - Especifica valores enteros sin punto (ej., 42).
- `sid` (identificador de cadena) - (más información abajo)
- `string` - no compatible actualmente para reemplazo
- `structure` - reemplazando un puntero (más información abajo)
### Reemplazo de sid por búsqueda de nombre:
`-e 0xABC[5]=ellie`
Esto busca el valor "ellie" en el sidbase actual. Si no existe, se emite una advertencia y no se aplica ninguna edición. Si se encuentra el valor, el valor hash real (un número grande) reemplazará el valor actual en la variable miembro.
### Reemplazo de sid por anulación manual directa de hash:
`-e 0xABC[5]=#XXXXXXXXXXXXXXXX`
El # indica un valor hash bruto, que se aplicará directamente sin búsqueda.
### Reemplazo de estructuras miembro
Si una estructura contiene otra estructura como miembro, puedes reemplazar toda la estructura miembro asignándole la dirección de otra estructura.
Por ejemplo, supón que tienes lo siguiente:```c++
[4] weapon-gameplay-def [0x0C523] {
...
[7] firearm-gameplay-def [0x11C28] {
...
}
}
So weapon-gameplay-def contiene un firearm-gameplay-def.
Para reemplazar el firearm-gameplay-def dentro del weapon-gameplay-def con un firearm-gameplay-def diferente ubicado en la dirección 0x0ABC, la edición será:
-e 0x11C28[7]=0x0ABC
VS Code permite crear extensiones personalizadas para agregar resaltado de sintaxis a lenguajes personalizados. dconstruct incluye un archivo .vsix que agrega este soporte para la extensión de archivo .dcpl. Debido a que es un lenguaje de un solo uso, no destinado realmente a la programación diaria, no estoy subiendo la extensión al marketplace, y en su lugar la distribuyo como un archivo .vsix sin procesar. Para instalar esta extensión en tu VS Code, ejecuta el siguiente comando:```shell code --install-extension <path/to/extension/dcpl-lint-0.0.1.vsix>
o abre la paleta de comandos con CTRL+MAYÚS+P y escribe "Instalar extensión mediante VSIX" y selecciona el archivo .vsix.
Después de esto, tu código .dcpl debería verse algo así:

# Problemas conocidos
El descompilador actualmente no está completo al 100% y por lo tanto se encuentra en un estado "experimental". Si recibes advertencias durante la descompilación, no te preocupes, ya que estas funciones actualmente no son compatibles, pero con suerte lo serán en el futuro. Junto con estas, hay más problemas conocidos en este momento:
- funciones de expresión única con cortocircuito intenso (especialmente aquellas dentro de structs) aún no están implementadas. He comenzado a trabajar en el algoritmo para estas, pero no sé cuánto tiempo llevará terminarlo, aunque esto está en la parte alta de la lista de prioridades
- ciertos tipos son incorrectos, particularmente los tipos de argumentos
- los bloques if vacíos en sentencias if/else pueden causar una sangría extraña. Esto, de nuevo, no siempre es culpa mía, ya que hay ciertas ramas que en realidad no hacen ningún trabajo real, lo cual es difícil de detectar
Junto con estos, hay algunos problemas que probablemente no se solucionarán:
- devolver basura cuando no se sabe si una función es void o no
- toneladas de código redundante
# Funciones planificadas
- un formato de salida completo para Racket y Python
# Agradecimientos especiales
- **icemesh** – por proporcionar las [estructuras subyacentes para los archivos DC](https://github.com/icemesh/dc/tree/main/t2) y [su desensamblador](https://github.com/icemesh/t2-dc-disasm), que sirvió en gran medida como inspiración.
- **Specilizer** – por su DC-Tool, también una inspiración para este programa.
- **uxh** – por sus conocimientos de scripting.
- **bigdragon** y **Wedge** por las pruebas beta
- Toda la comunidad de modding de Discord – por ser amigable y servicial.
## Soporte
Todas mis herramientas y mods serán siempre 100% gratuitos, pero programas como este requieren mucho trabajo.
Si deseas apoyarme, puedes visitar mi Ko-fi:
[](https://ko-fi.com/deepquantum)
## Licencia
Los archivos que crees usando este mod son completamente tuyos y eres libre de hacer con ellos lo que quieras. Se agradecería el crédito, pero no es estrictamente necesario.
El programa en sí está bajo la licencia
[Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License](https://creativecommons.org/licenses/by-nc-nd/4.0/).
Esto significa que puedes compartir el programa con otros si das crédito, pero actualmente no se permite modificarlo ni monetizarlo.