Registrarse

[Otros] Introducciendo a un binarista en decompilación

StunxFS

Profesional de WaH
Buenos días, tardes o noches, ¿todo bien? ¿todo correcto? y yo que me cago en la pu**, okno xD.

El día de hoy les traigo, lo que se puede llamar, un curso, para aquellos que son parte de el ROMHacking Binario y estén pensando en pasarse al decomp pero no saben cómo, o, aquellos que estén diciendo que el decomp no es para novatos, y no nos muestren pruebas de que esa frase sea cierta.

Quiero decir que al principio esto iba a ser un post hablando sobre la frase ya mencionada y si era verdad o mentira, pero me puse a pensar, ¿para qué? ¿para causar más problemas?, nah, no quiero más peleas, somos una comunidad, por lo tanto debemos estar unidos, ayudándonos los unos a los otros.

En este curso planeo enseñar qué es la decompilación, cómo se compara el binario con decomp, enseñar el C necesario para el decomp, como trabajar con el preprocesador de C, cómo manejar punteros y referencias y, al final de todo, dejar algunos temas que complementen este curso. Comencemos pues.

Nota Importante:
Con tal de que no pierdas el tiempo leyendo, en este tema, cosas que no te interesan, por favor lee lo siguiente:

Sí provienes de binario y tu intención es aprender a programar en C, tengas experiencia haciendo scripts o no, puedes leerte todas las secciones de este tema.

Sí tus intenciones no son las de aprender a programar, sino las de aprender el cómo hacer mapas en decomp, cómo importar sprites o similares, léete la primera y segunda sección de este tema e inmediatamente dirígete a los enlaces complementarios.

Sí eres spriter y buscas saber cómo hacer sprites en decompilación, es lo mismo, no necesitas aprender nada, los sprites que se usan en decompilación son los mismos que se usan en binario.

Sí eres novato y no sabes absolutamente nada, no hay problema, te sugiero que vayas comenzando por el primer enlace complementario que está al final de este tema y regreses, sí lo que te interesa es la programación entonces prosigue, de lo contrario, solo mírate la primera y segunda sección de este tema.
Bien, trataré de ser lo más didáctico posible, imaginemos lo siguiente, tenemos una ROM de X juego de pokémon, obvio, de GBA, entonces queremos desglosar esa ROM en varias partes que nosotros podamos modificar más fácilmente sin tanta viajadera por los offsets, entonces cogemos esa ROM, y, con un destornillador, lo iniciamos a destornillar, cuando ya terminamos, procedemos a separar las 2 partes que conforman la ROM, ¿qué obtenemos? obtenemos al decomp, esto se llama decompilación, el "arte" de desconstruir algo que ya está construido.

Así se puede definir, didácticamente, a la decompilación, el proceso de desconstruir o desarmar algo que ya está construido o armado (como las piezas Lego).

Bien, ya sabemos qué es la decompilación, ahora procedamos a ver las diferencias entre la decompilación y el binario, desde ya quiero dejar claro que cualquier cosa que hayas aprendido en el binario ya no sirve aquí en la decompilación, salvo 1 cosita, la indexación, la cual es lo que mantiene unido a la decompilación con el binario.

No hay mucho que explicar aquí, las diferencias entre la decompilación y el binario quedan en:

En Binario todo se maneja por bytes, con el sistema hexadecimal.

En Decompilación esto es totalmente innecesario, ya que todo está decompilado, no hace falta manejar bytes, sino manejar un lenguaje de programación que está en inglés, como lo es C.

En binario hay que usar muchas herramientas, las cuales, algunas, llegan a no funcionar correctamente por ser muy viejas, construidas sobre un framework antiguo (como el .NET 3.5, vaya recuerdo cuando hice un post hablando sobre esto y Gold me respondió con algo que me dejó en shock, bueno en aquel entonces no era programador aún) o sus autores/creadores ya no los mantienen, o estan obsoleta.

En Decompilación esto varia un poco, la principal herramienta que necesitarás si o si, es un editor de texto, preferiblemente Visual Studio Code, Atom, CodeBlocks, Sublime Text, Brackets, KDevelop o cualquier otro editor que tenga una extension o plugin, o, incluso, que tenga soporte nativo, que permita usar C fácilmente, estos editores, obviamente, son mantenidos por sus creadores (VSCode por Microsoft, Atom por Github, Brackets por Adobe y KDevelop por KDE) y actualizados casi a diario. Aún no entiendo el por qué recomendar Notepad++, pero ya que. Lo siguiente que sería necesario es el Porymap, que es un cómo un AdvanceMap, pero dulcemente cargado, con un monton de funciones útiles, increíbles y sorprendentes. Esto sería lo más necesario y básico en decompilación, lo demás que queda, que son herramientas creadas por los usuarios, y algunos subidos en Github, Gitlab, etc., como editores de sprites o datos, pueden ser tanto necesario como no.

En binario, si queremos implementar un sistema propio o traído de de algún otro juego de cualquier generación, tenemos 2 opciones: O usamos ASM para hacer realidad ese sistema (el sistema de día y noche, los megas, etc.), o usamos un script para ese sistema (veáse el sistema de banco que creo el usuario Javi4315), entonces si optamos por ASM tendríamos que aprender un lenguaje que es de bajísimo, pero de bajísimo nivel, y encargarnos de escribir una rutina en donde cuidemos cada registro que tiene la GBA, y bla bla bla. Al final, no todos pueden pasar por esto, así que optan por ir a hacer un script, pero, ¡BOOM!, resulta que no se puede por lo limitado que es el scripting de XSE.

En decompilación esto es muy, pero muy fácil, ya que solo tenemos que saber C, crear una cabecera y un archivo de definición para nuestro sistema y ponernos a programar, usando las cabeceras estandard de la GBA fácilmente, y listo, compilamos nuestra ROM para ver si todo ha salido bien y ya, sino pues regresamos a nuestras cabeceras e intentamos arreglar esos errores o bugs que hay en nuestro sistema.

En binario hay una limitación con el espacio que tenemos en la ROM, sabemos que cualquier cosa que hagamos e insertemos en la ROM tendrá un peso, que consumirá cierta cantidad de bytes, reduciendo el santo espacio libre de la ROM que existe, dejándonos en aprietos al final, ya que tendremos que tomar la decisión de sí expandir la ROM de 16MB a 32MB, o cancelar todo.

En decompilación esto ocurre muy distinto, ya que el espacio es más pequeñ... xD, me pregunto que habrás pensado al leer esto. Pero ya en serio, en decompilación esto no pasa ya que estamos manejando el código fuente de la ROM, entonces cada cosa que hagamos si afectará el espacio libre de la ROM, CUANDO ESTA SE COMPILE, es decir, no nos afectará mientras trabajemos, solo cuando compilemos la ROM en binario. En pocas palabras, no tenemos límites de espacio, podemos meter lo que sea :).

En binario tenemos que casi todo el mundo quiere hacer uso de los santos PARCHES. "Ay, mira, un hueco en mi ROM, ¿será que le meto un tubo o un palo? nah, mejor lo parcheo y hago que no lo ví".

Bueno, ese no es el objetivo de los parches (que debería de ser), sino el de agregar cosas que otros ya hayan hecho sin problema alguno, porque sí pues, la flojera de repetir los pasos me llega hasta la cabeza, entonces prefiero hacerlo todo en un abrir y cerrar de ojos, o mejor dicho, prefiero hacer todo en un simple chasquido de dedos porque soy inevitable.

En decompilación no sucede así, pues fácilmente nosotros podemos seguir los pasos, modificando el código fuente, sin temor a que no funcione o nos salga algún problema. En esto la decompilación logra quitarle el guantelete y, antes de chasquear los dedos, dice: Y yo soy Decompilación, Puff.

En binario el ROMHacking está limitado solo a Windows, en Linux (sí, yo uso Linux, para ser más específicos, Linux Mint 20 con el escritorio XFCE) esto queda difícil, ya que hay 2 maneras de trabajar aquí, o instalamos Wine o instalamos una máquina virtual.

En decompilación esto es distinto, ya que en cualquier sistema operativo se puede programar un fangame, Porymap es multiplataforma, los editores ya mencionados son también multiplataforma, los compiladores también son multiplataforma, ¿qué más se puede pedir?

Estas son las 6 diferencias que yo veo entre el binario y la decompilación, sí tienes alguna diferencia más, por favor comentamela.

Hemos llegado al centro de este tema, el área más importante, el cuerpo de la decompilación, el grandioso y querido lenguaje de programación C. Digo cuerpo porque todo el código fuente está escrito en C. Bien, aquí explicaré detalle por detalle cómo funciona el lenguaje de programación, aprenderemos muchas cosas y al final estaremos listos para usar los proyectos de decompilación en el área de codificación, ya sea con un fangame, o colaborando con alguna modificación o sistema.

Al principio de la era de las computadoras todo funcionaba con el sistema binario, es decir, 0 y 1, donde el 0 representaba lo apagado y 1 lo encendido (de ahí que las flags soporten solo el 0 y 1), pero esto dificultaba el desarrollo de los programas por lo más básico que fuera, y traía muchos errores; entonces alguien pensó en desarrollar un lenguaje de bajo nivel llamado "ensamblador", el cual facilitaba un poco el trabajo, entonces, lo que antes era:

C:
0101010101010101010101 010111101010101010101010101
101010101010101010 00101111010101010101010101010101
Pasó a ser:

Código:
my_function:
    loadreg esx
    cmp esx, 0, 100
    ifeq_goto other

other:
    syscall 6
Pero alguien se dió cuenta de que esto era muy tedioso, el trabajo de escribir comando por comando, cuidando los registros que se usaban, entonces se le ocurrió crear el primer lenguaje de programación de alto nivel que existió, si no mal recuerdo fue Fortran (juzgadme sí esto no es así :c).

A partir de ahí existieron varios lenguajes más, cada uno con su sintaxis propia y su azúcar añadido, hasta que llegamos al lenguaje BCPL, que fue antecesor de C, luego obtuvimos al lenguaje C, que en sus principios, cabe decirlo, era una mierda, pero al pasar de los años se fue mejorando e iniciaron a salir los estándares como lo es el ANSI C, y, desde los años 90, tuvimos varias ediciones hasta la última, que es C18 (junio del 2018).

En pocas palabras, los binaristas quedaron desactualizados al seguir usando el ensamblador para crear sistemas en el ROMHacking, mientras que la decompilación dió un gran avance al permitirle al usuario usar C fácilmente y sin problemas.
Cabe decir que al usar C en decompilación, estamos usando el ensamblador del ARMv7, que es el que se usa en binario para crear sistemas. Es decir, al compilar una ROM en decompilación, el código C se convierte al ensamblador del ARMv7.

Antes de continuar vamos a instalar lo necesario en nuestras computadoras, como sé que este tutorial es para los que provienen del binario, ya sean novatos o avanzados, me imagino que tendrán sus computadoras llenas de mierda, así que vamos a instalar un compilador de C que sea ligero y liviano para hacer pruebas, se trata de TinyC, un compilador pequeño escrito en C, que se compila a si mismo, el cual podremos usar para hacer nuestros ejercicios sin problema alguno.

Código:
Luego edito aquí explicando el proceso de instalación de TinyC.
Bien, solo con ese compilador podemos proceder a aprender el lenguaje C, comencemos con un ejemplo:

C:
#include <tcclib.h>

int main(int argc, char* argv[]) {
    printf("Hola mundo!\n");
    return 0;
}
Copia ese código en un archivo llamado main.c, y, abriendo el terminal (o cmd.exe si estás en Windows) tipea:

Bash:
tcc -run main.c (o tcc.exe -run main.c, si estás en Windows)
Esto generará un ejecutable llamado "main" y en la terminal te debe salir la frase

Código:
Hola mundo!
Si es así, felicidades, has logrado crear tu primer programa en C, ¿ves qué no es nada difícil? prosigamos.

Bien, no borres ese archivo, ya que será el que usaremos durante el curso para aprender el lenguaje C, cuando terminemos el curso te dejaré algunos ejercicios que espero puedas cumplir, poniendo en los comentarios los resultados de cada uno.

Ahora vamos por partes, desglosemos lo que acabamos de hacer en ese archivo:

C:
#include <tcclib.h>
Esto se conoce como una directiva del preprocesador, en este caso, la directiva usada aquí es include, la cual nos permite incluir cabeceras. Pero, ¿qué es un preprocesador? es una utilidad que el compilador de C/C++ corre antes de iniciar el proceso de escaneo, básicamente este preprocesador reemplaza los usos de las símbolos definidos con sus valores respectivos, es decir, si tenemos:

C:
#define MISYM "Hola Que tal?"
Y lo usamos en una llamada a función:

C:
myfunc_require_string(MISYM, 2004);
El preprocesador reemplaza MISYM por el valor respectivo:

C:
myfunc_require_string("Hola Que tal?", 2004);
Con el preprocesador obtenemos un regalito llamado "Compilación Condicional", es decir, compila esto si esto otro ocurre, ejemplo:

C:
#if MYSYM
#    define SUPER(x, y) printf("%d\n", (x)+((x)*(y)))
#else
#    error "MYSYM no está definido, por favor defínelo con '-DMYSIM'"
#endif
Esto le indica al preprocesador que, sí MYSYM está definido, defina una macro llamada SUPER, que recibe 2 valores por parámetros, de lo contrario lance un error diciendole al usuario que MYSYM no está definido y que debe definirlo. Hagamos el ejercicio, coloquemos ese contenido dentro de la función main, de manera que quede así:

C:
#include <tcclib.h>

int main(int argc, char* argv[]) {
#if MYSYM
#    define SUPER(x, y) printf("%d\n", (x)+((x)*(y)))
     SUPER(100, 150);
#else
#    error "MYSYM no está definido, por favor defínelo con '-DMYSYM'"
#endif
    return 0;
}
Lo intentamos correr como dijimos más arriba, y veamos lo que obtenemos:

Código:
stunx@stunx-pc:/var/tmp/tcc/bin$ ./tcc -run main.c
main.c:8: error: #error "MYSYM no está definido, por favor defínelo con '-DMYSYM'"
Ups, no tenemos definido el símbolo MYSYM, por ende, el preprocesador solo compila la segunda opción, que era el error, entonces para solucionar esto agregamos un #define arriba del #if:

C:
#include <tcclib.h>

int main(int argc, char* argv[]) {
#define MYSYM 1
#if MYSYM
#    define SUPER(x, y) printf("%d\n", (x)+((x)*(y)))
     SUPER(100, 150);
#else
#    error "MYSYM no está definido, por favor defínelo con '-DMYSIM'"
#endif
    return 0;
}
Volvemos a correr, y esta vez no obtenemos ningún error, sino que obtenemos esto en pantalla:

Código:
stunx@stunx-pc:/var/tmp/tcc/bin$ ./tcc -run main.c
15100
Bien, hemos jugado un poco con el preprocesador, ahora pasemos a explicar cada directiva del preprocesador antes de seguir con nuestro ejemplo:

Este grupo de directivas conforma lo que se conoce como "compilación condicional". La estructura más básica que tenemos de este grupo es:

C:
#if expresión
#endif
Donde expresión es, efectivamente, una expresión que sea de valor booleano (o verdadero/falso), siempre el resultado de la expresión debe ser verdadera para que esto funcione.

Ahora, ¿qué pasa si no se cumple esa condición? bueno, tenemos 2 opciones más: #elif y #else

C:
#if ESTA_DEFINIDO
    puts("Está definido");
#else
    puts("No está definido");
#endif
Si has hecho scripts en XSE entenderás este sistema sin ningún problema. Entonces ¿para qué el "#elif"? para usar otra condición más en caso de que la primera no se cumpla, aprovecho a decir que la directiva #else solo se puede usar una vez, pero la directiva #elif se puede usar cuantas veces se quiera, también aprovecho para decir que #else SIEMPRE VA DEBAJO DE UN #elif, NUNCA ARRIBA:

C:
#if ESTA_DEFINIDO
    puts("Está definido");
#elif ESTA_DEFINIDO_2
    puts("Está definido el segundo símbolo");
#elif ESTA_DEFINIDO_3
    puts("Está definido el tercer símbolo");
#elif ESTA_DEFINIDO_4
    puts("Está definido el cuarto símbolo");
#else
    puts("No está definido ni el primero, ni el segundo, ni el tercero ni el cuarto");
#endif
Esto estaría mal:

C:
#if ESTA_DEFINIDO
    puts("Está definido");
#else
    puts("No está definido ni el primero, ni el segundo, ni el tercero ni el cuarto");
#elif ESTA_DEFINIDO_2
    puts("Está definido el segundo símbolo");
#elif ESTA_DEFINIDO_3
    puts("Está definido el tercer símbolo");
#elif ESTA_DEFINIDO_4
    puts("Está definido el cuarto símbolo");
#endif
Ya que el #else le dice al preprocesador que espere la directiva #endif, pero aquí le sigue un #elif, lo que termina en un error.

Podemos anididar varias cadenas de #if-#elif-#else-#endif sin problema alguno:

C:
#if POKEMON_IS_REALITY
#    if POKEMON_IS_MALE || POKEMON_IS_FEMALE
        puts("Tú pokémon es macho o hembra, y es de realidad");
#    endif
#elif POKEMON_IS_VIRTUAL
#    if POKEMON_IS_MALE
        puts("Tú pokémon es virtual y macho");
#    else
        puts("Tú pokémon es hembra y virtual, ¿WTF?");
#    endif
#else
    puts("Vuestro pokémon no es de realidad ni virtualidad");
#endif
Perdónenme el ejemplo anterior, me lo saqué de las semillas xD.

En ese ejemplo hay algo que no he explicado, y es el uso de ||, que quiere decir 'o', también está el &&, que quiere decir 'y', estos 2 son operadores condicionales, los cuales se pueden usar tanto en el preprocesador, como en las típicas comparaciones que se hagan en nuestro código.

Tenemos 3 operadores para ser más exactos:
|| - o
&& - y
! - no

Entonces esto:
C:
#if SABROSURA || ((JAIZU && SAMU) || (ACE10 && LICAON))
Se leería: Si está definido SABROSURA, o si está definido JAIZU y SAMU, o ACE10 y LICAON, entonces...

Quizás te preguntarás: ¿Por qué hay paréntesis ahí? esas paréntesis se usan para decirle, tanto al compilador como el preprocesador, que el resultado de esto se convierta a uno, (básicamente se diría que esto es por la procedencia de cada operador, es decir, hablando en idioma del STAFF, que rango tiene un operador sobre otro), entonces en esa expresión tenemos 2 resultados posibles, aunque a simple vista parezca que son 3, eso se leería así:

SABROSURA = True si está definido, de lo contrario false
(JAIZU && SAMU) || (ACE10 && LICAON) = True si el primer o segundo grupo dan true, de lo contrario, false
Desglosando los 2 grupos serían:
JAIZU && SAMU = True si JAIZU y SAMU están definidos, de lo contrario false
ACE10 && LICAON = True si ambos están definidos, de lo contrario false

Ahora intenta leer estas expresiones:
USER1 && USER2
USER1 && (USER2 || (USER3 && USER4) && (USER5 || (USER6 && USER7 && USER8)))

Si las entendistes bien, ¡COOL!, prosigamos.

Ahora tenemos el operador !, que quiere decir 'no', entonces esto:

C:
#if DEFINED
Que se lee: si DEFINED está definido. Agregandole el ! sería:

C:
#if !DEFINED
Que se lee: si DEFINED no está definido.

El ! siempre va delante de la expresión, indicando que no esperamos que eso ocurra, entonces:

C:
#if !(USER && MARCO)
#endif
Quiere decir que sí USER y MARCO no están definidos, entonces se haga esto.

Para concluir, tenemos otros 2 estilos más para el #if, y son: #ifdef e #ifndef. Ambos funcionan con un solo símbolo, y lucirían de la siguiente manera:

el #ifdef sería:

C:
#ifdef DEFINED // sería lo mismo que: #if DEFINED
Y el #ifndef sería:

C:
#ifndef DEFINED // sería lo mismo que: #if !DEFINED
Yo, en lo personal, sugiero hacer uso de #ifdef e #ifndef para chequear si un solo símbolo está o no definido, y dejar el #if para chequar varios símbolos.

C:
#ifdef WIN32
#    if WIN32_MESALEDELOSBUEBOS && WIN32_DRAKYMINOESTONTO
         puts("El usuario está en estado de idiotez.");
#    endif
#endif

La directiva #define se usa para definir un símbolo en el preprocesador, este símbolo puede contener valores de cualquier tipo.

La directiva se usa tanto para definir símbolos como para definir macros, pero ¿qué son los macros? en C, los macros son plantillas de reemplazo textual que se escriben en el lugar de llamado de una macro, pero antes de continuar con las macros, expliquemos más sobre los símbolos:

C:
#define SYMBOL_STR "Symbol"
En el ejemplo anterior definimos un símbolo con el valor "Symbol", ahora si usamos este símbolo en cualquier parte de nuestro código, el preprocesador se encargará de reemplazar textualmente el símbolo por su valor correspondiente.

C:
#define SYMBOL_STR "Symbol\n"
puts(SYMBOL_STR);

// esto se procesa a:
puts("Symbol\n");
Una cosa, por favor, trata siempre de usar nombres en mayúsculas para los símbolos, así los diferenciarás de las variables normales (las cuales deberías escribir en minúsculas) sin ningún problema.

Cómo añadido al apartado anterior, debido a que los símbolos pueden tener valores, podemos hacer lo siguiente en un #if:
C:
#define VAL1 "Value1"
#define VAL2 "Value1"

#if VAL1 == VAL2
    puts("igualitos");
#endif
Más adelante, cuando lleguemos al tema de las expresiones, explicaré todas las formas posibles que se pueden usar en una comparación, ya sea en el preprocesador o los condicionales normales.

Bien, ahora ya sabemos como funcionan los símbolos, una cosa más antes de continuar, y es que los símbolos se pueden definir sin valor alguno:

C:
#define MI_SIMBOLO_SIN_VALOR
Esto es equivalente a:

C:
#define MI_SIMBOLO_SIN_VALOR 1
OJO: No se pueden usar estos símbolos definidos sin valor para ser reemplazados, solo para hacer comparaciones con el #if, #ifdef e #ifndef.

Ahora hablemos sobre el contrario de #define, el cual es #undefine, #undefine en vez de declarar símbolos, los elimina, por ejemplo:

C:
1 | #define VALUE "Stunx" // se declara el símbolo VALUE con el valor de "Stunx"
2 | puts(VALUE); // se usa el símbolo aquí y, efectivamente, se reemplaza con su valor: puts("Stunx");
3 | #undefine VALUE
// a partir de aquí ya no existe el símbolo VALUE,
4 | // todo intento de uso de este símbolo será ignorado por el preprocesador.
[/CODE]

Vale decir que no se pueden sobreescribir los símbolos, la única manera que tenemos para hacer algo similar es elimnar el símbolo y volverlo a definir con su nuevo valor, ejemplo:

C:
#define XXX "www.whackahack.com/foro"
puts("Visita "XXX);
#undefine XXX
#define XXX "www.whackahack.com/404_page_not_found"
puts(XXX);
Vamos con el tema de las macros, aquí explicaré algunas cosas que tal vez no entiendas, pero conforme vayamos avanzando entenderás muy bien.

Más arriba dijimos que los macros son plantillas de reemplazo textual que se escriben en su lugar de llamado, en otros lenguajes de programación, como Rust, se utiliza un sistema distinto de macros, el cual se basa en agregar nodos al árbol de la sintaxis abstracta (AST, por sus siglas en inglés).

La utilidad que tienen las macros trata de desenvolver cosas que el programador considere que son tediosas de hacer (esto desde mi punto de vista), por ejemplo:

C:
#define MACRO(param1, param2) printf("%s, %s\n", param1, param2);
MACRO("Stunx", "Reality");
// se reemplaza textualmente a:
printf("%s, %s\n", "Stunx", "Reality");
El problema con las macros es que los parámetros que tenga no serán checados por su tipo, solo por su conteo, es decir, si nosotros queremos que los 2 parámetros de MACRO sean tipo string (o cadena), no habrá manera de hacer realidad esto. El preprocesador solo se encargará de que se le hayan pasado la cantidad exacta de parámetros a la macro, pero no sus tipos, así que cuidado al momento de usar las macros.

Las macros pueden recibir parámetros de cantidades variadas, pueden haber macros que no reciban nada de parámetros, como otros que reciban muchos parámetros:

C:
#define MECOMPRASHELADO(respuesta) printf("¿Me comprarás un helado de leche?: %s\n", respuesta)
Estos parámetros se pueden usar dentro del cuerpo de una macro sin problema alguno, ya en el proceso de reemplazo, el preprocesador reemplazará el parámetro por el valor dado a la hora de invocar la macro.

Entonces, sí usamos la macro MECOMPRASHELADO, así:

C:
MECOMPRASHELADO("Sí");
El parámetro "respuesta" será reemplazado por "Sí". También, además de literales, como strings, carácteres y números, podemos usar identificadores:

C:
#define CREAR_UNA_VARIABLE_TIPO_STRING(nombre) char* nombre
CREAR_UNA_VARIABLE_TIPO_STRING(mivariable);
// Se reemplaza a:
char* mivariable;
Cuidado al momento de usar llamadas a macros, cuidense de usar correctamente los parámetros necesarios en sus tipos necesarios. De todas maneras, si se equivocan, el compilador les avisará con un error algo feo y difícil de entender a veces.

Hay una manera de obtener un parámetro stringizado (acción de convertir una expresión a cadena de texto), con la cual es válido cualquier valor que el usuario use:

C:
#define STR_(expr) #expr"\n"
printf(STR_(2 + 2 = 4));
// se reemplazará a:
printf("2 + 2 = 4");
Con esto cualquier expresión, que el usuario pase, será convertida a un string.

Una última cosa, si se usa, al momento de compilar, -D<nombre-de-simbolo> es lo mismo que hacer un #define por medio de la consola o el terminal.
C:
-DUSE_WIN32 es lo mismo que #define USE_WIN32

Bien, ahora toca explicar la directiva #include.
Esta directiva se usa para decirle al preprocesador que incluya un archivo de cabecera o de declaración en el código actual. Aquí procedo a explicar algo que debemos tener en cuenta, el preprocesador no sabe o no retiene en su memoria que archivos de cabecera se han incluido o no, así que, para evitar problemas de duplicación de símbolos, es de suma importancia que en cada archivo de cabecera coloques, lo que es llamado, un guarda.

¿Qué es un guarda? Es simplemente un conjunto de #ifndef-#define-#endif que usa la compilación condicional para evitar que el preprocesador duplique la inclusión. Supongamos que tenemos 2 archivos: cabecera.h y main.c:

C:
// cabecera.h
#ifndef CABECERA_H
#define CABECERA_H

const int anio_de_nacimiento = 2004;

#endif // CABECERA_H
C:
// main.c
#include "cabecera.h"

int main(int argc, char** argv) {
    return 0;
}
Bien, el preprocesador incluye el archivo de cabecera "cabecera.h" y define al mismo tiempo el símbolo CABECERA_H, lo que evita que, sí se vuelve a intentar incluir este archivo, el preprocesador lo incluya, evitando así las duplicaciones de símbolos. Se considera buena práctica el nombrar el símbolo, que se define al principio, con el nombre del archivo de cabecera, es decir, sí el archivo de cabecera se llama text.h, entonces su guarda sería TEXT_H, todo en mayúsculas y reemplazando el punto por un guión bajo.

Entonces, la estructura del #include es: #include <nombre-de-archivo>, el nombre del archivo de cabecera a incluir puede estar declarado de 2 formas:

C:
"nombre-archivo.h" o <nombre-archivo>
La primera forma se usa solo para archivos locales, o, mejor dicho, desarrollados por el usuario, mientras que la segunda forma se usa para los archivos de cabecera estandard, los cuales trae el compilador o la cadena de herramientas (o toolchain) que se use (como DevkitARM), hasta el momento hemos usado un archivo de cabecera estándard, en este caso, se trata de tcclib.h, que viene con el compilador TinyC, el cual nos ofrece algunas funciones útiles, como printf y puts.

Pero ya va, ¿qué es un archivo de cabecera o de declaración? es un archivo cuya extensión es .h (de header = cabecera), el cual se usa para definir prototipos de funciones, estructuras, defines, macros y enumeradores. OJO, veáse la diferencia entre declaración y definición, cuando hablamos de declaración nos referimos a:

C:
int myfunc(const char*, int);
Y al hablar sobre definición nos referimos a:

C:
int myfunc(const char* name, int age) {
    if (age < 18) return 0;
    return 1;
}
Esto en el área de las funciones nada más. Esto sería un archivo de cabecera con su archivo de definición respectivo, de ejemplo:

C:
// people.h
#ifndef PEOPLE_H
#define PEOPLE_H

typedef struct {
    char*     name;
    int        age;
} People;

People People_new(const char* name, int age);
int People_is_children(People* people);
int People_is_older(People* people);
#endif // PEOPLE_H
C:
// people.c
#include "people.h"

People People_new(const char* name, int age) {
    return (People){
        .name = name,
        .age = age
    };
}

int People_is_children(People* people) {
    return people->age < 13;
}

int People_is_older(People* people) {
    return people->age > 55;
}

Bien, hemos visto lo suficiente, aunque hay más directivas, creo que con eso ya es más que suficiente para decompilación. Ya que en el código fuente de cualquiera de los juegos decompilados hallarás muchos archivos de cabecera, definición y mucho uso del preprocesador, ya sea para macros o defines (o constantes, como son llamados a veces).

Sigamos con el ejemplo con el que iniciamos, ahora nos toca explicar que es lo que venía luego del #include que vimos, a partir de aquí ya no explicaré línea por línea, en vez de eso, arrancaré inmediatamente a explicar todo el lenguaje C. Ya sabes cómo usar el preprocesador, lo más fácil que hay en C (OJO, esto no quiere decir que C sea difícil, no lo es, para nada en absoluto), ahora procedamos con la buena vibra.

En progreso...

Enlaces complementarios:
¿Qué es Decomp?¿Qué plataforma debería escoger? (de Samu)
Guía básica de Decompilación (de Samu)

Esto aún se encuentra en progreso, poco a poco lo terminaré, saludos.
Att: Stunx
 
Última edición por un moderador:

kakarotto

Leyenda de WaH
Te agradezco en nombre de los decompers que intentes argumentar porque decomp es mejor que binario pero precisamente el mega texto que has puesto no ayuda y has sido muy técnico en ciertos puntos. Creo que hubieras aportado un poco más promocionando los tutoriales de Samu, que esta más enfocado a la gente que empieza desde cero, pero igualmente buen trabajo por tu intención.
 

Jaizu

Usuario mítico
No gracias, estoy bien donde estoy. Saludos, y buen día.
Teniendo en cuenta que cancelaste tu hack y luego lo "descancelaste" porque la gente lo criticó y teniendo en cuenta que te ibas a pasar a rpg maker xp porque te flipaba essentials no sé yo si "estar bien" es lo que mejor te describe en el munfo del fangaming, yo creo que para llenar los temas de tonterías e ignorancia cuando lo único que haces es quedar en evidencia es mejor que te quedes callado.

En cuanto al tema, espero que a la gente le sirva, con esto y el tema de Samu deberían ser motivos suficientes. El único problema que veo es la falta de público/interés general, y más cuando los rom hackers nuevos que van llegando solo quieren replicar todo lo de las últimas generaciones y poco más.
 

Tohru

Baneado
Seria bueno que pusieras referencias con fotos , y que en un futuro saquen un tutorial que muestre como crear interfaces o por lo menos darnos la bases
 

Jaizu

Usuario mítico
Explica como crear interfaces por que aún no he visto un video con ese titulo , y si existe me lo podrias pasar ?
Literalmente te acabo de decir que Samu tiene tutoriales, podrías haberte molestado en ir a su perfil y buscar sus temas.
Igualmente el concepto "Crear interfaces" es muy amplio y poco preciso.
 

StunxFS

Profesional de WaH
Buenas gente, he actualizado el tema principal con más info, esta vez relacionada al preprocesador del lenguaje C, para ser más específicos, la compilación condicional, también he mejorado la estructura de las diferencias entre el binario y decomp, y he centrado el tema en el lenguaje de programación C, ya que Samu tiene un tema en donde enseña otras cosas básicas, el cual está enlazado al final.
 

StunxFS

Profesional de WaH
De nuevo he actualizado el tema, está vez he hecho más añadidos y cambios:
  • He añadido una nota al principio explicándole al novato o avanzado que debe hacer para iniciar en decompilación.
  • He terminado la sección que correspondía al preprocesador de C.
  • He añadido más explicaciones al comienzo de la sección del lenguaje C, explicando cómo funcionaba todo al principio de la era de las computadoras.
Ahora toca la parte del lenguaje C, poco a poco iré terminando este curso. Saludos!
 
Arriba