Registrarse
  • ¡Vuelven los concursos de mapas! ¿Quién será el mejor diseñador en esta ocasión? ¿Te ves capaz de demostrar todas tus habilidades en el mapeo, Invitado? ¡Inscríbete ya dándole al botón!

    El periodo de inscripción acaba el día 17 de mayo.

[Decomp-GBA] Tutorial Completo de C orientado a Decompilación

InmortalKaktus

Ludiloco


Tutorial Completo de C orientado a Decompilación

ATENCIÓN: Si eres nuevo en este mundillo, es normal que no entiendas muchas cosas, a pesar de que está todo explicado muy meticulosamente. Si no entiendes cualquier parte NO TENGAS MIEDO de dejar un comentario para preguntarlo. Es más tonto el que se calla para no parecerlo, que el que pregunta para aprender más.

El objetivo de este post es que cualquier persona, incluso aquellas que nunca han programado, salgan de aquí pudiendo leer los archivos de decompilación y se sientan lo suficientemente cómodos porque lo entienda prácticamente todo. Creo que es el paso que falta dar para que la gente se anime con estos proyectos, y el mundo de la creación de fangames en esta base resurja.

A continuación tenéis spoilers con la explicación y ejemplos de todos los conceptos necesarios para aprender a programar, aunque lo que os diferenciará de un buen y un mal programador, dependerá de lo mucho que practiquéis y lo estrictos que seáis con las buenas prácticas que os explique. Si consideráis que ya sabéis en que consiste alguno de los conceptos, podéis sentirlos libres de saltar al que más os interese.

Puede que pienses... Programar no es para mi, no me gustan las mates, o que directamente pienses que es demasiado complicado, pero tengo una grata noticia para ti: En realidad, tú ya sabes programar, y dirás Me voy saliendo del tutorial, que este tío está absolutamente loco., pero nada de eso.

A mi me gusta decir que programar es dar órdenes a un ordenador, y dar órdenes sabemos todos ( ͡° ͜ʖ ͡°). Para que me entendáis mejor, vamos a crear un contexto, y a ejemplificar entorno a ese contexto.

Para el ejemplo, tendremos a Alan y Ada, que viven juntos. Ada trabaja por la mañana, y Alan por la tarde, por lo que Ada le dice a Alan por la mañana Si a las 13:30 no he llegado a casa, haz la comida. ¿Todos entendemos que Alan hará la comida si son las 13:30 y Ada no ha llegado de trabajar verdad?, bien, pues esto mismo se lo podríamos transmitir a un ordenador, y con tan solo dos simples líneas de código, el ordenador podría hacerlo
C:
if(AdaTrabajando && SonLas13_30)
    HacerComida();

Ya veréis como todo tiene su lógica y es super fácil de entender, os doy mi palabra ;)
Puede parecer que no es necesario saber binario (lo de los ceros y los unos) para los proyectos de decompilación, pero en realidad, es bastante necesario entender al menos la base del binario. Visto que luego tengo que explicaros más cosas que se basan en esto.

En primer lugar, ¿qué es el código binario?, bueno, tan solo es una forma de representar los números. Nosotros estamos acostumbrados a representar cualquier número con cualquiera de los 10 dígitos que todos conocemos, es decir, 0, 1, 2, 3, 4, 5, 6, 7, 8 y 9. Y con estos dígitos, podemos formar cualquier número, el 73, el 248, el 1830, cualquiera. Pero... ¿Y si os digo que con sólo unos y ceros, también podemos representar cualquier número?

Bueno, pues voy a explicaros como hacen por lo general los ordenadores para leer un número sin signo (es decir, que con este formato no puedes representar números negativos, pero ya hablaremos de cómo hacerlo en otro momento).

Vamos a empezar por lo básico de lo básico (que al principio os va a parecer obvio, pero os va a servir para entender el binario mejor), y es que nosotros, cuando contamos por lo general, hacemos esto 0, 1, 2, ... 8, 9, 10, cuando llegamos al 9, ya no existe otro dígito, porque solo tenemos 10, así que, tenemos que añadir un número más a la izquierda para poder seguir representando más números. Puede que así lo veáis más claro: 00, 01, 02, ... 08, 09, 10. Si os dais cuenta, cuando contamos, es como si a la izquierda tuviéramos un cero, y cuando llegamos al tope, si queremos seguir contando, aumentamos el valor de ese cero, sólo que como cuando hacemos operaciones, o usamos los números en el día a día, ese cero ahí no tiene sentido, pues no lo usamos, pero estar, está ahí :p. Y lo mismo pasa si seguimos: ... 18, 19, 20..., siempre que llegamos al tope, aumentamos el dígito de la izquierda, y el de la derecha lo ponemos a cero, además, esto se puede expandir al siguiente caso
001, 002, ..., 008, 009, 010, 011, 012 ... 097, 098, 099, 100, 101, 102....

Creo que ya vais pillando por donde van los tiros. Con los números binarios, hacemos exáctamente lo mismo, pero sólo con 0 y 1. A continuación os dejo una lista de los primeros números binarios para que lo veáis más claro.

A la izquierda, tenemos los números como estamos acostumbrado a leerlos (en base decimal), a la derecha, como se escribirían con solo unos y ceros (base binaria).

C:
0 = 0
1 = 1
2 = 10
3 = 11
4 = 100
5 = 101
6 = 110
7 = 111
8 = 1000
9 = 1001
10 = 1010
11 = 1011
12 = 1100
Y al igual que con la explicación de antes, a la izquierda podríamos ponerle tantos 0 como quisieramos, porque no significarían nada (y puede que así lo veáis más claro), pero es exactamente lo mismo

C:
0 = 0000
1 = 0001
2 = 0010
3 = 0011
4 = 0100
5 = 0101
6 = 0110
7 = 0111
8 = 1000
9 = 1001
10 = 1010
11 = 1011
12 = 1100
¡¡Genial!! Ya sabemos como funciona el binario. Parecía tan difícil y ahora es tan fácil... 😌.
Como regalito por haber entendido este primer concepto, os voy a dar un truquito para que podáis saber que valor decimal le corresponde a un número en base binaria.

NOTA: Debéis saber que decir que tenemos 8 bits, es lo mismo que decir que tenemos un 1 byte. Y 16 bits, son 2 bytes. Para que lo tengáis en cuenta si lo digo más adelante.

Si queremos leer un número binario, debemos saber que a cada dígito le corresponde un valor en decimal, y que si los sumamos todos, tendremos el valor decimal del número binario. Dicho así suena muy lioso, pero con esta imagen lo vais a entender super fácil seguro.

1614947080858.png


El valor del bit más a la izquierda en decimal, siempre es 1, el siguiente, 2, el siguiente 4, luego 8, luego 16, 32, 64...
Y tiene todo el sentido, ¿porque este número cuál es?

1614947396583.png


Es el 2.
¿Y este?

1614947596445.png


Exacto, el 4.

Y si sumamos 4 + 2, tenemos 6, que mira por donde, se representa así:

1614947767700.png


Así que, sip, podemos asignar los decimales a cada bit, y sumarlos para obtener el resultado en decimal.
Vamos ahora con un ejemplo más largo, para que veáis que es igual de fácil.

1614948202564.png


Algo importante es que tendréis que sumar sólo los valores cuyo bit esté a 1, si está a 0, no se debe sumar.
Como resultado, obtenemos 128 + 16 + 4 + 1 = 149, vamos a probar a pasar este valor a la calculadora de windows, a ver si estamos en lo cierto.

1614948409884.png


Efectivamente. Ahora somos los putos amos y podemos leer binario de cabeza 😎
Antes de continuar, es importante que que entendáis que cuando programamos, podemos guardarnos valores en algo que llamamos variables. Así, si por ejemplo tenemos que hacer un cálculo de cualquier cosa, podemos guardar su resultado en dichas variables, para poder usarlo más tarde.

Para que me entendáis mejor, si yo le digo al ordenador:
primerNumero = 3, más tarde, si yo quisiera sumar ese valor a un 5 por ejemplo, podría escribir primerNumero + 5, y el ordenador sabrá que lo que tiene que hacer es sumar 3 + 5. Y además, si lo que quisiera es almacenar ese mismo resultado en otra variable, podría hacer segundoNumero = primerNumero + 5. Y en un futuro, cuando usemos la variable segundoNumero, el ordenador lo leerá como si fuera un 8.

Bien, pues este concepto se puede aplicar para todo tipo de operaciones, osea que imaginad cuando multipliquemos 5 cosas entre si, o tengamos una operación complicada, lo útil que nos será.

Además, por lo general, podremos cambiar el valor de la variable, por lo que si hacemos esto primerNumero = 3, y en la siguiente línea ponemos primerNumero = 5, la variable primerNumero valdrá 5.

Con esto claro, podemos seguir.
Debéis saber, que según lo que nos interese, un ordenador nos puede guardar la información de una manera, o de otra. Para que entendáis porqué esto es así, si quisiéramos que el ordenador por ejemplo sumara dos números, pues le podemos decir que haga suma = 5 + 4, por ejemplo, y nos dará como resultado 9. Pero no hay nada que nos impida que le digamos que nos sume sumaImposible = 3 + Hola, por lo que por si cometemos un error de este tipo (que puede parecer muy obvio y que a nadie le va a pasar, pero más adelante, entenderéis que no es así), es importante que el ordenador sepa diferenciar si estamos intentando sumar un número con otro número, o si estamos sumando algo que no tiene sentido, como un número con un texto para que nos pueda dar un error en el que diga ¡Hey, cuidado! estás intentando sumar un número con un texto.. Ese no es el único motivo, también está hecho por cuestión de ahorrar espacio. Para representar por ejemplo un número pequeñito, el ordenador puede necesitar sólo 1 byte (8 bits). Pero para almacenar por ejemplo Bienvenido al Mundo de los Pokémon, se necesitarían unos 35 bytes, imaginad la diferencia que hay. Además, también sirve para tener las cosas más organizadas.

Ahora que ya sabemos porqué podríamos necesitar tener distintos tipos de datos, vamos a explicar todos ellos, aunque primero, os hablaré del tipo de dato como concepto, y luego explicaré como se usan en los proyectos de decompilación.

  • Tipos de datos numéricos: Los tipos de datos numéricos, sirven para almacenar números, como bien dice su nombre, y se dividen en dos tipos
    • Enteros: Se usa para los números enteros, es decir, todos aquellos que no tengan decimales 2, 351, -12
    • Flotantes: Sirven para representar números con decimales: 1.5, 3.1415,1.618033
  • Tipos de datos alfanuméricos: Sirve para almacenar cualquier dato alfanumérico (números, letras y carácteres especiales), y de nuevo, tenemos dos tipos de datos alfanuméricos (NOTA: Debéis saber que para indicar que es un caracter, lo pondremos entre comillas como estas ', y para las cadenas de carácteres, lo pondremos entre comillas dobles como esta ")
    • Carácter: En estos sólo podemos guardar un único carácter: 'a', 'P', '5', '('.
    • Cadena de caracteres: Y para estos, podríamos guardar una cadena de caracteres, es decir, un conjunto de carácteres: "Hola", "¡Estoy aprendiendo a programar!", "Ò.Ó"
  • Tipo Booleano: Este es el más facilito, simplemente sirve para almacenar la opción VERDADERO, o FALSO. Parece una tontería, pero más adelante vais a ver que es super útil (cuando programemos, tendrémos que ponerlo en inglés): TRUE, FALSE.
Hay más tipos, pero por el momento, usaremos estos, que son los más importantes para empezar.

Tal y como hemos visto anteriormente, los datos los podemos almacenar en variables, y aunque no os lo he dicho antes por no liaros demasiado, os he de decir que todas las variables son de un tipo de dato. Es decir, vosotros en lenguaje C (hay algunos lenguajes que si), no podéis hacer variable1 = 5 y luego hacer variable1 = "Texto de prueba". ¿Porqué? Porque cuando usamos una variable por primera vez en C, ya se guarda en la memoria del ordenador como que esa variable sirve para almacenar ese tipo de dato, y si luego le intentas asignar algo de un tipo de dato distinto, adivina que pasa... ¡ERROR!

Y ahora... ¡Vamos a dar el gran paso! Ya empezamos a conocer como funcionan los ordenadores por dentro, pero aún no sabemos como podemos comunicarnos con ellos para pedirles que almacenen estos datos, pero eso va a cambiar dentro de poco 😎. Lo primero será aclarar que hay muchas formas distintas de hacer estas cosas, es decir, existen muchos lenguajes de programación para hacer esto (exactamente igual que tú diciendo Hola y Hello estás diciendo lo mismo pero en idiomas distintos. Prácticamente todos los lenguajes de programación funcionan similar, pero unos lenguajes son más útiles para unas cosas, y otros para otras. En este caso, tenemos el código fuente de los juegos de Pokémon en código C, por lo que nos enfocaremos en como hacerlo en este lenguaje de programación.

NOTA: Es importante declarar los tipos de datos tal y como se especifica aquí, no cambiar las mayúsculas o minúsculas, ni poner espacios donde quieras. Todo tiene su debida forma de escribirse, y hay que ser rigurosos con ello.

NOTA 2: Los tipos numéricos volátiles no se explicarán, porque para empezar no son necesarios.

Podemos declarar hasta 4 tipos distintos de enteros, uno que sólo tenga 8 bits, otro que tenga 16, otro 32, y otro 64. Según cuánto pensemos que podamos necesitar, usaremos un tipo u otro. A continuación os dejo cuál es el valor máximo que podría almacenar cada tipo: 8 bits = 255, 16 bits = 65 535, 32 bits = 4 294 967 295, 64 bits = Unos cuantos cuatrillones.

Por lo general, con el de 16 bits nos sobrará, pero bueno, los otros, ahí están.

Para indicar que queremos crear una variable sin signo (que será la que usemos casi siempre), pondremos una u (de unsigned) delante de uno de los cuatro valores posibles, y luego pondremos el nombre de la variable que queramos usar, es decir u8 para unsigned de 8 bits, u16 para unsigned de 16 bits, u32 para los de 32 bits y u64 para los de 64.

¡ATENCIÓN! El nombre de la variable no puede tener espacios, tan sólo puede tener letras, números, guiones y guiones bajos, y nada más, además, es sensible a las mayúsculas y minúsculas, es decir, no será lo mismo variabledeprueba que variableDePrueba. Cuando queráis poner más de una palabra en el nombre de la variable, podéis hacerlo de varias formas, ya sea separando por como hemos visto antes, como poniendo todas las primeras letras con mayúscula VariableDePrueba, como separandolo por guiones bajos variable_de_prueba, en fin, como más os guste, pero sin espacios ni carácteres especiales.

Bien, llegó el momento de ver el primero código en C, a continuación, vamos a declarar una variable para almacenar un número.

C:
u8 primerNumero = 8;
¡Geniaaal! Ya hemos aprendido a declarar variables, esto es un paso enorme, ni os lo imagináis.

No sé si lo habéis notado, pero después del número hay un punto y coma. El punto y coma sirve para decirle al ordenador que en esa línea ya hemos puesto todo lo que teníamos que poner, osea que tenemos que acostumbrarnos a poner el punto y coma después de las líneas. Hay algunas líneas en las que no tendremos que poner el punto y coma, pero es porque sirven para cosas diferentes. Aún así, tampoco es necesario que os aprendáis de memoria cuando poner el punto y coma, con el tiempo, y viendo el propio código de decompilación, sabréis cuando se pone y cuando no.

Aclarar que si queréis cambiar el valor de una variable que ya habéis usado una vez, no es necesario que pongáis el tipo otra vez, sólo hay que ponerlo la primera vez para que el ordenador sepa de qué tipo es la variable. Es decir, que si primero le damos un valor, y luego le damos otro, tendríamos esto.

Código:
u16 variable = 1;
variable = 9;
Y el ordenador leería variable como 9.

Por tanto, si quisiéramos declarar otro número, pero esta vez de 64 bits por ejemplo, pues
C:
u64 numero65Bits = 11;
Bien, ya entendemos los unsigned. Ahora, para los signed (con signo), es decir, con los que podremos distinguir entre números positivos y negativos, es exactamente lo mismo, pero en lugar de una u, pondremos una s, es decir s8, s16, s32 y s64.

C:
//Número negativo
s16 numeroConSigno = -14;
//Número positivo
s16 numeroConSigno2 = 14;
Como veis, es el mismo concepto. Y como imagino que os preguntaréis, ¿Qué son las //? Pues es lo que se conoce como comentarios.

El ordenador no leerá todo lo que haya después de esa doble barra en esa línea. Esto nos sirve por si tenemos un programa muy largo, y en algún momento queremos dejar escrito un comentario personal de lo que hace cada cosa, para entenderlo si en un futuro volvemos a leer ese código. Por ejemplo, ahora podríamos hacer algo así

C:
//El u8 sirve para declarar números sin signo
u8 num1 = 4; //Si llamo a num1, es como si escribiera un 4
//Ahora declaro un número con signo con s8
s8 num2 = -1; //Si llamo a num2, es como si escribiera un -1

//A continuación, un mal ejemplo de comentario
u16 num3 = //Con esto me cargo el código 5;
//A pesar de poner un 5 y un ; es como si escribiera
//u16 num3 =
//Por lo que recibiría un error
Por último, tenemos los tipos float y double, que se representan como f32 y f64 respectivamente. Ambos sirven para almacenar números con decimales, pero los float tienen 32 bits, y los double tienen 64. Estos prácticamente no los vamos a usar.

C:
//Punto flotante es lo mismo que decir decimal
f32 numeroEnPuntoFlotante = 0.33f;
//Si os fijáis, cuando es decimal, hay que poner una f al final
f32 otroNumeroDecimal = 66.0f;
//A pesar de que no tenga decimales, ponemos el .0 con la f.

f64 loMismoPeroMasBits = 1.13872f;
Aquí realmente seguimos la misma lógica que antes, y ya os he explicado gran parte de lo que tenía que explicaros, así que iremos al grano.
Ejemplos para declarar carácteres sueltos, y cadenas de carácteres

C:
//Declaración de una variable de un carácter
char letraA = 'A';
//Declaración de otra variabl de caracter
char numero5 = '5';
Y ahora, para los textos en C para Decompilación, tenemos una forma un poco especial de hacerlo, no hace falta que lo entendáis, simplemente usadlo así, es un concepto un poco más avanzado que entenderéis más adelante. Usaremos el tipo u8, al final del nombre de la variable pondremos [], y el texto, lo pondremos dentro de las comillas de esta estructura _(" ");

Por lo que tendríamos esto

C:
u8 texto[] = _("¡Hola mundo!");
u8 texto2[] = _("Mi primer texto personalizado");
Pues estos son los más fáciles de todos, la manera en la que declararemos los booleanos será con bool8, también existen bool16 y bool32, pero para el propósito actual, no los usaremos, así que

C:
bool8 miPrimerBool = TRUE;
bool8 MiSegundoBool = FALSE;
Bien, ahora trataremos los tipos de operadores. ¿Y qué es un operador? Pues cualquier cosa que sirva para hacer una operación a dos valores (no tienen porqué ser numéricos). Por ejemplo, una suma es una operación entre dos números.

Irémos por los distintos tipos de operadores, y lógicamente, empezaremos por los que todos conocemos. Además, empezaré a usar la sintaxis de C (es decir, la forma en la que se escribiría en código C) para que nos vayamos familiarizando con el código y le perdamos el miedo.

Los dos primeros, son los de suma + y resta -, con lo que lógicamente, podremos sumar o restar dos números.

C:
u16 sumaNums = 3 + 5;
s8 restaNums = 2 - 6;
También tenemos operador de multiplicación *

C:
u8 mutliplicacion = 3*10;
De división /, con el que en caso de dividir números decimales, recibiremos el número en decimal, pero si lo hacemos con enteros (u8, u16, s8, s16, etc), al dividir dos números obtendremos el cociente.

C:
s16 siete = 7;
s16 seis = 6;
s16 tres = 3;

s16 seisEntreTres = 6/3; //Por lo que seisEntreTres = 2

s16 sieteEntreTres = 7/3; //De nuevo, sieteEntreTres = 2 porque queda 1 de resto.
Vale pero... ¿Y entonces no podremos sacar el resto de las divisiones de enteros? Jejejeje, sabía que me lo preguntaríais. Para guardar el resto en lugar del cociente, no usaremos /, sino que usaremos %.

C:
u16 siete = 7;
u16 tres = 3;

u16 cociente = siete/tres; // Que daría 2 como resultado
u16 resto = siete%tres; //Que daría 1

//Y ya tendríamos cociente y resto :D
Con estos operadores, podremos comparar dos números, pero ahora, en lugar de recibir como resultado otro número, recibiremos como resultado un booleano, es decir, recibiremos o TRUE, o FALSE.

Para agilizar, a partir de ahora explicaré todos los operadores del tipo que tengamos que ver, y luego pondré un código con ejemplos para todos.
  • Operador <: Servirá para comparar si el número de la izquierda es menor que el de la derecha
  • Operador >: Comparará si el número de la izquierda es mayor al número de la derecha
  • Operador <=: Comprobará si el número de la izquierda es menor o igual al de la derecha
  • Operador >=: Comprueba si el número de la izquierda es mayor o igual al de la derecha
  • Operador ==: Comprueba que los dos números sean igual
    • Ojo, no me he equivocado, hay que poner DOS signos de igual, porque cuando pones un signo, estamos asignando y no queremos asignar, queremos comparar
  • Operador !=: Comprobará si los números son diferentes
Y a continuación, los ejemplos y sus respectivos resultados

C:
bool8 menorQue1 = 3 < 5; //TRUE
bool8 menorQue2 = 6 < 5; //FALSE

bool8 mayorQue1 = 3 > 5; //FALSE
bool8 mayorQue2 = 6 > 5; //TRUE

bool8 menorIgualQue1 = 3 <= 5; //TRUE
bool8 menorIgualQue2 = 5 <= 5; //TRUE
bool8 menorIgualQue3 = 6 <= 5; //FALSE

bool8 mayorIgualQue1 = 2 >= 9; //FALSE
bool8 mayorIgualQue2 = 7 >= 7; //TRUE

bool8 igualQue1 = 1 == -3; //FALSE
bool8 igualQue2 = 32 == 32; //TRUE

bool8 distintoQue1 = 1 != 2; //TRUE
bool8 distintoQue2 = 5 != 5; //FALSE
Se empieza a venir la magia de la programación 😌.

Los operadores lógicos no sirven para operar con números, ¡si no con booleanos! (Y sus resultados son booleanos también)

Me gustaría recordaros que los booleanos sólo pueden tener dos valores, verdadero, o falso. Aunque también puedes referirte a ellos como activado y desactivado, apagado y encendido, 1 (verdadero) y 0 (falso) etc.

¿Recordáis lo que os explicaba al principio de que si Ada no ha llegado de trabajar y son la 1:30PM, Alan prepara la comida? Bien, pues eso se lo traduciríamos al ordenador como una operación lógica.

Bien, explicaré que hace cada operador, y luego, ejemplos de uso.
  • Operador &&: Compara si los dos valores booleanos son verdaderos, si lo son, devuelve TRUE, si no, devuelve FALSE
  • Operador ||: Compara si al menos uno de los dos (es decir, o uno de ellos, o los dos) valores booleanos es (o son) TRUE, si es así, devuelve TRUE, si no, devuelve FALSE
  • Operador !: Niega el booleano al que se lo apliquemos, por lo que si se lo aplicamos a TRUE, devolverá FALSE, y si se lo aplicamos a FALSE, devolverá TRUE.
Sencillito, ¿verdad?
Ahora los ejemplos

Código:
bool8 CondicionAND1 = TRUE && TRUE; //TRUE
bool8 CondicionAND2 = TRUE && FALSE; //FALSE
bool8 CondicionAND3 = FALSE && TRUE; //FALSE
bool8 CondicionAND4 = FALSE && FALSE; //FALSE

bool8 CondicionOR1 = TRUE || TRUE; //TRUE
bool8 CondicionOR2 = TRUE || FALSE; //TRUE
bool8 CondicionOR3 = FALSE || TRUE; //TRUE
bool8 CondicionOR4 = FALSE || FALSE; //FALSE

bool8 Negador1 = !TRUE; //FALSE
bool8 Negador2 = !FALSE; //TRUE
He de decir que el primero de ellos, ya os lo he colado y no os habíais dado ni cuenta. ¿Que cuál es?

El operador de asignación básico =, que como bien dice la palabra sirve para asignar. ¿Asignar que? Pues valores a variables. Tal y como hemos venido haciendo hasta ahora

C:
s16 variableX = -2;
Con el =, le decimos al código que ahora la variable variableX, vale -2. Bastante sencillo.

Pero hay más operadores de asignación, que son útiles para resumir código y quedar como unos putos pros de la programación, he de decir que los operadores de asignación que vienen a continuación, funcionarán siempre y cuando la variable ya tenga un valor de antes (con el ejemplo lo entenderéis mejor).
  • Operador +=: Suma el valor de la derecha a la variable. Tenemos dos formas de hacer esto, con variable = variable + 3; como lo harían los noobs, o variable += 3;, como lo haríamos los pros ;). (Pero es lo mismo)
  • Operador -=: Lo mismo, pero en lugar de sumar, resta. Código de noobs: variable = variable - 5;
  • Operador *=: Igual que antes, pero multiplicando
  • Operador /=: Y dividiendo (Se cambia el valor de la variable por el valor del cociente de la divisón)
  • Operador %=: Y finalmente, el resto de la divisón entre la variable y el número de la derecha.
Y ahora, los ejemplos.
C:
u8 numero1 = 3;
numero1 += 7; //Ahora numero1 = 10 (3+7)

u8 numero2 = 5;
numero2 -= 2; //numero2 = 3 (5-2)

u8 numero3 = 3;
numero3 *= 3; //numero3 = 9 (3*3)

u8 numero4 = 5;
numero4 /= 2; //numero4 = 2 (Cociente de 5/2)

u8 numero5 = 5;
numero5 %= 2; //numero5 = 1 (Cociente de 5/2)
Y ya estaría :D
Los operadores unarios simplemente suben para incrementar o decrementar el valor de una variable. Se usarían así

C:
u8 valorAIncrementar = 5;
u8 valorADecrementar = 12;

valorAIncrementar++; //Ahora valorAIncrementar vale 6
valorADecrementar--; //Ahora valorADecrementar vale 11
Cada vez que lo uses sumará, o restará, depende cual uses, en una unidad.
Estos operadores son algo más complejos, y requieren de conocimientos más avanzados. Como no es algo realmente necesario para la base necesaria, os recomiendo que lo dejéis para el final.

Como es bastante más largo, creé un tutorial a parte, que podéis encontrar dando click aquí
Este es un concepto sencillito. Hasta ahora, podréis haber pensado que qué sentido tiene usar variables para darle un nombre a los números, si es más fácil escribirlos directamente. Bueno, pues en realidad, vosotros sólo estáis viendo el momento en el que creamos la variable, pero en un programa de C real, los valores de las variables están cambiando constantemente, y es extremadamente útil que se guarden con un nombre para que podamos saber qué es cada cosa.

¿Pero, y si lo que queremos es darle un nombre a un valor para que sepamos el porqué de ese valor, pero no va a cambiar? Bueno, pues eso se llaman constantes, porque son constantes, y su valor nunca cambia. Esos valores no se deben declarar en el código como las variables. Lo ideal es colocarlos todos al principio del archivo. Para que cuando estemos entre todas las líneas sepamos distinguir entre lo que es una variable o una constante, normalmente los nombres de las constantes se escriben en mayúscula y con guiones bajos.

Por ejemplo, las constantes serían útiles para guardar números concretos que signifiquen algo, así como podría ser el ancho en pixeles de la pantalla del juego.

C:
#define ANCHO_PIXELES_PANTALLA 240
#define ALTO_PIXELES_PANTALLA 160

u8 centroHorizontalPantalla = ANCHO_PIXELES_PANTALLA/2;
u8 centroVerticalPantalla = ALTO_PIXELES_PANTALLA/2;
Y así, podríamos saber qué es el número que estamos dividiendo entre 2, en lugar de poner un 240 o un 160 ahí directamente, que dentro de dos semanas no sabremos porque está ahí.

Como veis, para declarar constantes, usaremos #define NOMBRE_DE_LA_CONSTANTE valor

Y ya somos un poquito más organizados con nuestro código ;)
Bienvenidos a una de las secciones más útiles. Con esto es con lo que le vais a empezar a dar más juego a vuestro código.

¿Recordáis el ejemplo del principio de Alan y Ada? Pues eso era una estructura condicional. Si se cumple esto, haz esta cosa, y si no se cumple, no la hagas. Aunque hay muchas formas de usar las estructuras condicionales. Pero empezaré por explicarlas, y luego sacaremos conclusiones.

Esta ya la conocemos. Simplemente escribiremos if( ), y dentro de los paréntesis, pondremos la condición que queremos que se cumpla, luego, para indicar que queremos que haga el código si se cumple esa condición, lo pondremos después de los paréntesis, y entre corchetes. Vamos a ver un ejemplo para entenderlo mejor.

C:
bool8 adaTrabajando = TRUE;
bool8 sonLas13_30 = TRUE;
bool8 debemosHacerLaComida = FALSE;

//Como adaTrabajando es TRUE, y sonLas13_30 también, el resultado
//de la operación será TRUE, así que, si que se cumple
//la condición
if (adaTrabajando && sonLas13_30)
{
    debemosHacerLaComida = TRUE;
}
Si analizamos el código, veremos varias cosas. En primer lugar, inicializamos el valor de debemosHacerLaComida a FALSE, por lo que de momento, ese es su valor. Pero cuando llegamos a la condición, vemos que la condición que ponemos, se cumple, por lo que ya cambiamos el valor de debemosHacerLaComida a TRUE. Ahora mismo podéis pensar que esto es inútil, pero en cuanto veamos las estructuras else y else if, os voy a poner un ejemplo más grande, y vais a ver el verdadero potencial de la programación. Prometido ;)
Esta estructura es muy sencilla si ya entendemos la estructura if. En resumen, else signifca En caso contrario, por lo que si estáis algo avispados, ya podréis imaginar para que sirve

C:
//Como veis, no le estamos dando ningún valor inicial.
//Esto es perfectamente posible siempre y cuando más tarde
//le déis uno
bool8 numeroNegativo;

s16 numeroAComparar = -13;

if (numeroAComparar >= 0)
{
    numeroNegativo = FALSE;
} else
{
    numeroNegativo = TRUE;
}
Bueno, bueno, esto ya no parece tan absurdo, nos sirve para saber si un número es negativo. Efectivamente, el programa primero compara la condición que le damos, que en este caso es que el número sea mayor o igual a 0, si el número es mayor o igual a 0, el valor de la variable numeroNegativo pasa a ser FALSE, y si no es mayor o igual a 0, pues su valor pasa a ser TRUE.

No está mal, pero aún podemos hacer cosas más interesantes, sigamos.
El nombre de esta puede que os confunda, pero es muy sencillita, esta sirve por si tenemos varias posibilidades y las queremos comparar todas, os pongo un ejemplo para que lo veáis de forma más visual, que sé que así lo entendéis mejor.

C:
//Con esto, elegiremos la operación que queremos hacer
char operacion = '+';

//Con estas dos variables, operaremos
u16 x = 9;
u16 y = 3;

//Y aquí, guardaremos el resultado
u16 res;

if(operacion == '+')
{
    res = x + y;
} else if (operacion == '-')
{
    res = x - y;
} else if (operación == '*')
{
    res = x * y;
} else if (operación == '/')
{
    res = x / y;
} else if (operación == '%')
{
    res = x % y;
} else
{
    res = 0;
}
Y con esto, podríamos decir que hemos creado un programa que simula una calculadora simple, así de fácil. Simplemente antes de empezar el programa, tendríamos que cambiar el caracter de operacion al que represente la operación que nos interese, y lo mismo con la variable x y la variable y. Además, hemos hecho que si en operación ponemos cualquier otro caracter que no hayamos tenido en cuenta, el valor de res sea 0.

Va molando, ¿eh? Pues ya veréis cuando lleguemos a las funciones... Vais a flipar.
Esto es prácticamente como todo lo que podemos hacer con las estructuras if, else if y else, pero en una sola estructura. La diferencia de hacerlo con uno o con otro, es que con las estructuras if y else if nos dan mucha más versatilidad, porque en cada condición podemos comparar variables distintas, según nos interese, sin embargo, la estructura switch sirve para comparar una única variable (o el resultado de alguna operación), y según el valor que contenga, se hará una cosa u otra. A continuación el mismo ejemplo de antes, pero con la sentencia switch. Luego del ejemplo explicaré como funciona.

C:
//Con esto, elegiremos la operación que queremos hacer
char operacion = '+';

//Con estas dos variables, operaremos
u16 x = 9;
u16 y = 3;

//Y aquí, guardaremos el resultado
u16 res;

switch(operacion)
{
    case '+':
        res = x + y;
        break;
    case '-':
        res = x - y;
        break;
    case '*':
        res = x * y;
        break;
    case '/':
        res = x / y;
        break;
    case '%':
        res = x % y;
        break;
    default:
        res = 0;
        break;
}
En resumen, dentro de los paréntesis después de switch, ponemos la variable a evaluar, o el resultado de una operación (porque podemos analizar cualquier tipo de datos, y por ejemplo si fueran numéricos, podríamos estar analizando (x*5)/2. Y después, abrimos corchetes, y dentro de los corchetes, ponemos case valor: siendo ese valor, el que queremos que de como resultado para que pase lo que pondrémos a continuación. En las siguientes líneas, pondremos todo lo que queramos que pase, y cuando acabemos con ese caso, escribiremos break; para indicar que ya hemos acabado. Podremos añadir tantos casos como queramos. Y por último, si queremos que pase algo cuando sea cualquier caso que no hemos puesto antes, en lugar de escribir case valor:, simplemente escribiremos default:, porque será lo que pasará por defecto.

Aclarar que tanto este código como el anterior de los if, else if, sirven exactamente para lo mismo. Sólo que como digo, en switch estás trabajando todo el rato sobre el mismo resultado, con los if else if else puedes comprobar un resultado de algo distinto en cada condición.

Y hasta aquí los condicionales, son mucho más útiles de lo que pueden parecer ahora, ya veréis.
Los bucles son pura magia, nos van a facilitar muchísimo muchas operaciones repetitivas, ya veréis por qué.

Para entender el concepto, en este ejemplo conoceremos a Pepe, un humilde trabajador, cuyo trabajo consiste en hacer multiplicaciones para la fábrica en la que trabaja. El encargado de Pepe llega por la mañana y le encomienda el trabajo diario. Pepe, hoy tienes que hacer 200 multiplicaciones, cuando acabes te puedes ir, por lo que Pepe empezará a hacer las multiplicaciones y las irá contando hasta que llegue al límite que su encargado le ha puesto.

Si traducimos esto a programación, los bucles trabajan como Pepe. Le damos un conjunto de tareas a hacer, que la tiene que repetir una cantidad de veces determinada, cuando ya lo ha hecho esa cantidad de veces, o alcanza esa condición, pues puede dejar de hacer operaciones, o lo que es lo mismo, sale del bucle.

Tenemos tres tipos de bucles, empezaré por el que más se parece al ejemplo de Pepe

Usando el caso de Pepe, tenemos que añadir un concepto extra para el bucle for, y es la cantidad de operaciones que ya ha hecho. Puede ser que su encargado le diga que tiene que hacer 500 operaciones en dos días, en lugar de dárselas de forma diaria, por lo que el día dos, no empezará de 0, si no de la cantidad de operaciones que ya hizo ayer.

Para hacer un bucle for, tenemos tres parámetros pues.
  • Parámetro 1 - Valor inicial: Este es el valor desde el que nuestro contador empieza a contar.
  • Parámetro 2 - Condición para seguir en el bucle: Aquí le pondremos la condición que se debe cumplir para que siga haciendo operaciones
  • Parámetro 3 - Operación sobre el contador por cada iteración: Tomando el ejemplo de Pepe, esto es cuanto se le suma al contador cada vez que hace una multiplicación.
Así que, sabiendo esto, para el ejemplo de Pepe, sus parámetros serían (cada parámetro separado por un ;):
contador = 1; contador <= 200; contador++. ¿Fácil, no?

Bien, pues ahora, vamos a poner el ejemplo real. Ahora Pepe va a empezar con un valor inicial. Luego, y ese valor, lo multiplicará por la cantidad de veces que ya ha multiplicado

C:
#define OPERACIONES_DIARIAS 200
u8 contador;
u8 valorACalcular = 3;

for(contador = 1; contador <= OPERACIONES_DIARIAS; contador++)
{
    valorACalcular *= contador;
}
Si empieza con el número a 3, veríamos que los valores del bucle for en cada iteración, serían:
Primera iteración: contador = 1, valorACalcular = 3*1 // 3
Segunda iteración: contador = 2, valorACalcular = 3*2 // 6
Tercera iteración: contador = 3, valorACalcular = 6*3 // 18
Cuarta iteración: contador = 4, valorACalcular = 18*4 // 72
Quinta iteración: contador = 5, valorACalcular = 72*5 // 360

Y así seguiría el pobre Pepe hasta las 200 operaciones. Por suerte tenemos los ordenadores y la programación, que nos lo pueden calcular en menos de un segundo (literalmente).
Este es bastante más sencillo porque usa menos parámetros (no os lo esperabais, ¿eh? ;) ), tranquilos, lo más difícil ya está entendido.

Este bucle, simplemente seguirá haciendo iteraciones hasta que se cumpla una condición. Aquí un ejemplo.

Código:
u8 varX = 2;

while(varX < 100)
{
    varX += varX;
}
¿Qué hace este código? Mientras que (significado de while en inglés) la variable varX sea menor de 100, la variable varX se suma a si misma (como si la estuviéramos multiplicando por dos, porque varX + varX = 2*varX. He de matizar que si por ejemplo nosotros pusieramos que la variable empieza con el valor 100, o el 101, no se ejecutaría ninguna vez.

Podéis ver el bucle while como un if que se repite hasta que su condición no se cumpla más.

Los más avispados ya os habréis dado cuenta, pero os tengo que avisar de algo. Existe la posibilidad de crear LOS BUCLES INFINITOS :eek: :eek: . Esto nos pasará cuando por muchas iteraciones que tenga el bucle, su condición no cambia, o simplemente es imposible que deje de cumplirse, por lo que estaríamos ante un bucle infinito (y nuestro ordenador o el emulador del juego se nos quedaría pillado y tendríamos que reiniciarlo).

Un ejemplo de bucle infinito (no lo probéis, cabroncetes, que os conozco ¬u¬)

C:
u8 numero = 2;

while(numero > 0)
{
    numero += 9;
}
¿Qué pasa aquí? Que el bucle está sumando todo el rato 9 a la variable numero, pero la condición es que sea mayor que 0, y es que con este código si la variable ya de por si empieza con un valor mayor a 0, nunca saldrá del bucle.
Bueno, este bucle en realidad es igual que el do-while, sólo que con este, el bucle siempre se ejecuta mínimo una vez, no hace la comprobación de si se cumple la condición antes de realizar el bucle, si no después, pero la teoría es la misma.

C:
u8 varX = 2;

do
{
    varX += varX;
} while(varX < 100)

Y bueno, pues ya conocemos las estructuras básicas de programación. ¡¡FELICIDADES!! 🥳🥳
Bienvenidos a las funciones. Esto es lo más útil bajo mi punto de vista en la programación. Sin esto, no podríamos hacer casi nada, y en 10 minutos, habréis aprendido lo más importante para programar. (Con esto ya podréis editar gran parte del código de decompilación entendiendo lo que hacéis). Que ojo, no os vengáis atrás, esto a la par que lo más importante, es también de lo más fácil.

¿Recordáis cuando os enseñé que podemos darle un nombre a una variable? Bien, pues ahora os digo que podemos darle nombre a una secuencia de operaciones. Me explico, imaginad que tenemos que hacer varios cálculos diferentes para obtener un resultado final como este, a partir de tres valor iniciales

C:
//PRO TIP: Podemos declarar variables del mismo tipo en una misma línea
s32 a = 1, b = 3, c = 2;
//Sería lo mismo que hacer
//s32 a = 1;
//s32 b = 3;
//s32 c = 2;

s32 calculo1 = -b * (c + (b/2)) + (a*c);
s32 calculo2 = a * (-1 * c * c * b * b) - (4 * b);

s32 resultadoFinal = (calculo1 - calculo2) * (calculo1 + calculo2);
Como veis es un cálculo muy largo. Pero... ¿Y si os dijera que podemos darle un nombre a todos esos cálculos, como por ejemplo codigoLargo, y que si luego escribiéramos esta línea
C:
s32 resultadoFinal = codigoLargo(1, 3, 2);
conseguiríamos el mismo resultado?

Pues así es, así es como funcionan las funciones. Tenemos varios conceptos a destacar.
  • Podemos crear funciones que simplemente ejecuten código sin devolver un valor, o podemos recibir un valor cuando llamamos una función, tenemos las dos opciones. Si únicamente queremos ejecutar código sin que devuelva nada, empezaremos a declarar la función con void, que es vacío en inglés, ya que no devolverá nada. Si devuelve valor, tendremos que poner de qué tipo de dato es el valor que devuelve. Ya sea un unsigned, un bool, un char, etc.
  • Después del tipo, pondremos el nombre con el que llamaremos a la función (es decir, al conjunto de código), y el nombre tiene los mismo requisitos que las variables: mayúsculas y minúsculas, números, guión alto y bajo, y sin espacios.
  • Después, pondremos unos paréntesis siempre, y dentro de ellos, podremos poner opcionalmente, si queremos que reciba algún valor para usarlo dentro de su código, en ese caso, pondremos el tipo de dato del parámetro, y el nombre que usaremos para referirnos a él dentro de la función, y si queremos pasar más de un parámetro, los separaremos por comas.
  • Luego abrimos corchetes, y dentro pondremos todo nuestro código.
  • Al final del código, justo antes de cerrar los corchetes, si hemos decidido que devuelva un valor cuando la llamemos, pondremos return seguido de el valor a devolver, puede ser tanto un valor concreto escrito directamente, como una variable. Pero sí o sí, deberá ser del tipo de dato que especificamos al principio de la función.
Así quedaría el código anterior dentro de una función.

C:
s32 codigoLargo(s32 a, s32 b, s32 c)
{
    s32 calculo1 = -b * (c + (b/2)) + (a*c);
    s32 calculo2 = a * (-1 * c * c * b * b) - (4 * b);

    s32 resultadoFinal = (calculo1 - calculo2) * (calculo1 + calculo2);

    return resultadoFinal;
}
Y luego, llamaríamos a la función de la manera que vimos antes

C:
s32 resultadoFinal = codigoLargo(1, 3, 2);
De hecho, os he estado medio mintiendo hasta ahora. En realidad cuando estemos en C, siempre escribiremos el código en funciones, no tendremos código fuera de las funciones, pero era demasiado pronto para que lo supierais. Espero que me perdonéis :C

Además, debéis tener en cuenta una cosa muy importante. Las variables que declaréis dentro de una función, sólo la podréis usar DENTRO de esa función, es decir, no podéis hacer esto.

C:
void func1()
{
    u32 x = 5;
}

void func2()
{
    //No lo podríamos hacer, y daría error, porque dentro
    //de esta función no existe ninguna variable con nombre "x"
    u32 y = x*2;
}
Si quisiéramos usar el valor de la primera función en la segunda tenemos varias opciones, la primera y menos recomendable sería declarar lo que se conoce como variables globales, es decir, que se pueden usar en todo el código. (Aunque no es recomendable que hagáis esto, hay mejores formas de hacerlo) ya que si cada vez que las necesitáis, recurrís a ellas, os pueden quedar incluso cientos de ellas, y a parte de que eso hará que nuestro código tienda a tener más errores, se quedará todo mucho más desordenado, por lo que mi obligación es deciros que existen, pero recomendaros encarecidamente que no abuséis de ellas.

C:
s32 variableGlobal = 3;

void funcion1()
{
    variableGlobal = variableGlobal*2; //O variableGlobal *= 2; que queda más bonito.
}

void funcion2()
{
    variableGlobal = variableGlobal/2; //variableGlobal /= 2;
}
Una buena forma de hacerlo sin recurrir a variables globales, sería hacer que fucnion1, devolviera el valor de x, y que llamaramos a funcion1 desde funcion2 para almacenar su valor. Aquí un ejemplo.

Código:
u8 funcion1()
{
    u8 numeroDeFuncion1 = 5;

    return numeroDeFuncion1;
}

void funcion2()
{
    u8 numeroDeFuncion2 = 3 * funcion1();
}

Próximos puntos
  • Punteros y referencias​
  • Visibilidad de funciones​
  • Usos de funciones entre archivos​
  • Estructuras de archivos de código​
(Podéis sugerirme temas a tratar)
 
Última edición:

ACE10

【O】【H】【A】【N】【A】


El objetivo de este post es que cualquier persona, incluso aquellas que nunca han programado, salgan de aquí pudiendo leer los archivos de decompilación y se sientan lo suficientemente cómodos porque lo entienda prácticamente todo. Creo que es el paso que falta dar para que la gente se anime con estos proyectos, y el mundo de la creación de fangames en esta base resurja.

A continuación tenéis spoilers con la explicación y ejemplos de todos los conceptos necesarios para aprender a programar, aunque lo que os diferenciará de un buen y un mal programador, dependerá de lo mucho que practiquéis y lo estrictos que seáis con las buenas prácticas que os explique. Si consideráis que ya sabéis en que consiste alguno de los conceptos, podéis sentirlos libres de saltar al que más os interese.

Puede que pienses... Programar no es para mi, no me gustan las mates, o que directamente pienses que es demasiado complicado, pero tengo una grata noticia para ti: En realidad, tú ya sabes programar, y dirás Me voy saliendo del tutorial, que este tío está absolutamente loco., pero nada de eso.

A mi me gusta decir que programar es dar órdenes a un ordenador, y dar órdenes sabemos todos ( ͡° ͜ʖ ͡°). Para que me entendáis mejor, vamos a crear un contexto, y a ejemplificar entorno a ese contexto.

Para el ejemplo, tendremos a Alan y Ada, que viven juntos. Ada trabaja por la mañana, y Alan por la tarde, por lo que Ada le dice a Alan por la mañana Si a las 13:30 no he llegado a casa, haz la comida. ¿Todos entendemos que Alan hará la comida si son las 13:30 y Ada no ha llegado de trabajar verdad?, bien, pues esto mismo se lo podríamos transmitir a un ordenador, y con tan solo dos simples líneas de código, el ordenador podría hacerlo
C:
if(AdaTrabajando && SonLas13_30)
    HacerComida();

Ya veréis como todo tiene su lógica y es super fácil de entender, os doy mi palabra ;)
Puede parecer que no es necesario saber binario (lo de los ceros y los unos) para los proyectos de decompilación, pero en realidad, es bastante necesario entender al menos la base del binario. Visto que luego tengo que explicaros más cosas que se basan en esto.

En primer lugar, ¿qué es el código binario?, bueno, tan solo es una forma de representar los números. Nosotros estamos acostumbrados a representar cualquier número con cualquiera de los 10 dígitos que todos conocemos, es decir, 0, 1, 2, 3, 4, 5, 6, 7, 8 y 9. Y con estos dígitos, podemos formar cualquier número, el 73, el 248, el 1830, cualquiera. Pero... ¿Y si os digo que con sólo unos y ceros, también podemos representar cualquier número?

Bueno, pues voy a explicaros como hacen por lo general los ordenadores para leer un número sin signo (es decir, que con este formato no puedes representar números negativos, pero ya hablaremos de cómo hacerlo en otro momento).

Vamos a empezar por lo básico de lo básico (que al principio os va a parecer obvio, pero os va a servir para entender el binario mejor), y es que nosotros, cuando contamos por lo general, hacemos esto 0, 1, 2, ... 8, 9, 10, cuando llegamos al 9, ya no existe otro dígito, porque solo tenemos 10, así que, tenemos que añadir un número más a la izquierda para poder seguir representando más números. Puede que así lo veáis más claro: 00, 01, 02, ... 08, 09, 10. Si os dais cuenta, cuando contamos, es como si a la izquierda tuviéramos un cero, y cuando llegamos al tope, si queremos seguir contando, aumentamos el valor de ese cero, sólo que como cuando hacemos operaciones, o usamos los números en el día a día, ese cero ahí no tiene sentido, pues no lo usamos, pero estar, está ahí :p. Y lo mismo pasa si seguimos: ... 18, 19, 20..., siempre que llegamos al tope, aumentamos el dígito de la izquierda, y el de la derecha lo ponemos a cero, además, esto se puede expandir al siguiente caso
001, 002, ..., 008, 009, 010, 011, 012 ... 097, 098, 099, 100, 101, 102....

Creo que ya vais pillando por donde van los tiros. Con los números binarios, hacemos exáctamente lo mismo, pero sólo con 0 y 1. A continuación os dejo una lista de los primeros números binarios para que lo veáis más claro.

A la izquierda, tenemos los números como estamos acostumbrado a leerlos (en base decimal), a la derecha, como se escribirían con solo unos y ceros (base binaria).

C:
0 = 0
1 = 1
2 = 10
3 = 11
4 = 100
5 = 101
6 = 110
7 = 111
8 = 1000
9 = 1001
10 = 1010
11 = 1011
12 = 1100
Y al igual que con la explicación de antes, a la izquierda podríamos ponerle tantos 0 como quisieramos, porque no significarían nada (y puede que así lo veáis más claro), pero es exactamente lo mismo

C:
0 = 0000
1 = 0001
2 = 0010
3 = 0011
4 = 0100
5 = 0101
6 = 0110
7 = 0111
8 = 1000
9 = 1001
10 = 1010
11 = 1011
12 = 1100
¡¡Genial!! Ya sabemos como funciona el binario. Parecía tan difícil y ahora es tan fácil... 😌.
Como regalito por haber entendido este primer concepto, os voy a dar un truquito para que podáis saber que valor decimal le corresponde a un número en base binaria.

NOTA: Debéis saber que decir que tenemos 8 bits, es lo mismo que decir que tenemos un 1 byte. Y 16 bits, son 2 bytes. Para que lo tengáis en cuenta si lo digo más adelante.

Si queremos leer un número binario, debemos saber que a cada dígito le corresponde un valor en decimal, y que si los sumamos todos, tendremos el valor decimal del número binario. Dicho así suena muy lioso, pero con esta imagen lo vais a entender super fácil seguro.

Ver el archivo adjunto 4518

El valor del bit más a la izquierda en decimal, siempre es 1, el siguiente, 2, el siguiente 4, luego 8, luego 16, 32, 64...
Y tiene todo el sentido, ¿porque este número cuál es?

Ver el archivo adjunto 4519

Es el 2.
¿Y este?

Ver el archivo adjunto 4520

Exacto, el 4.

Y si sumamos 4 + 2, tenemos 6, que mira por donde, se representa así:

Ver el archivo adjunto 4521

Así que, sip, podemos asignar los decimales a cada bit, y sumarlos para obtener el resultado en decimal.
Vamos ahora con un ejemplo más largo, para que veáis que es igual de fácil.

Ver el archivo adjunto 4522

Algo importante es que tendréis que sumar sólo los valores cuyo bit esté a 1, si está a 0, no se debe subar.
Como resultado, obtenemos 128 + 16 + 4 + 1 = 149, vamos a probar a pasar este valor a la calculadora de windows, a ver si estamos en lo cierto.

Ver el archivo adjunto 4523

Efectivamente. Ahora somos los putos amos y podemos leer binario de cabeza 😎
Antes de continuar, es importante que que entendáis que cuando programamos, podemos guardarnos valores en algo que llamamos variables. Así, si por ejemplo tenemos que hacer un cálculo de cualquier cosa, podemos guardar su resultado en dichas variables, para poder usarlo más tarde.

Para que me entendáis mejor, si yo le digo al ordenador:
primerNumero = 3, más tarde, si yo quisiera sumar ese valor a un 5 por ejemplo, podría escribir primerNumero + 5, y el ordenador sabrá que lo que tiene que hacer es sumar 3 + 5. Y además, si lo que quisiera es almacenar ese mismo resultado en otra variable, podría hacer segundoNumero = primerNumero + 5. Y en un futuro, cuando usemos la variable segundoNumero, el ordenador lo leerá como si fuera un 8.

Bien, pues este concepto se puede aplicar para todo tipo de operaciones, osea que imaginad cuando multipliquemos 5 cosas entre si, o tengamos una operación complicada, lo útil que nos será.

Además, por lo general, podremos cambiar el valor de la variable, por lo que si hacemos esto primerNumero = 3, y en la siguiente línea ponemos primerNumero = 5, la variable primerNumero valdrá 5.

Con esto claro, podemos seguir.
Debéis saber, que según lo que nos interese, un ordenador nos puede guardar la información de una manera, o de otra. Para que entendáis porqué esto es así, si quisiéramos que el ordenador por ejemplo sumara dos números, pues le podemos decir que haga suma = 5 + 4, por ejemplo, y nos dará como resultado 9. Pero no hay nada que nos impida que le digamos que nos sume sumaImposible = 3 + Hola, por lo que por si cometemos un error de este tipo (que puede parecer muy obvio y que a nadie le va a pasar, pero más adelante, entenderéis que no es así), es importante que el ordenador sepa diferenciar si estamos intentando sumar un número con otro número, o si estamos sumando algo que no tiene sentido, como un número con un texto para que nos pueda dar un error en el que diga ¡Hey, cuidado! estás intentando sumar un número con un texto.. Ese no es el único motivo, también está hecho por cuestión de ahorrar espacio. Para representar por ejemplo un número pequeñito, el ordenador puede necesitar sólo 1 byte (8 bits). Pero para almacenar por ejemplo Bienvenido al Mundo de los Pokémon, se necesitarían unos 35 bytes, imaginad la diferencia que hay. Además, también sirve para tener las cosas más organizadas.

Ahora que ya sabemos porqué podríamos necesitar tener distintos tipos de datos, vamos a explicar todos ellos, aunque primero, os hablaré del tipo de dato como concepto, y luego explicaré como se usan en los proyectos de decompilación.

  • Tipos de datos numéricos: Los tipos de datos numéricos, sirven para almacenar números, como bien dice su nombre, y se dividen en dos tipos
    • Enteros: Se usa para los números enteros, es decir, todos aquellos que no tengan decimales 2, 351, -12
    • Flotantes: Sirven para representar números con decimales: 1.5, 3.1415,1.618033
  • Tipos de datos alfanuméricos: Sirve para almacenar cualquier dato alfanumérico (números, letras y carácteres especiales), y de nuevo, tenemos dos tipos de datos alfanuméricos (NOTA: Debéis saber que para indicar que es un caracter, lo pondremos entre comillas como estas ', y para las cadenas de carácteres, lo pondremos entre comillas dobles como esta ")
    • Carácter: En estos sólo podemos guardar un único carácter: 'a', 'P', '5', '('.
    • Cadena de caracteres: Y para estos, podríamos guardar una cadena de caracteres, es decir, un conjunto de carácteres: "Hola", "¡Estoy aprendiendo a programar!", "Ò.Ó"
  • Tipo Booleano: Este es el más facilito, simplemente sirve para almacenar la opción VERDADERO, o FALSO. Parece una tontería, pero más adelante vais a ver que es super útil (cuando programemos, tendrémos que ponerlo en inglés): TRUE, FALSE.
Hay más tipos, pero por el momento, usaremos estos, que son los más importantes para empezar.

Tal y como hemos visto anteriormente, los datos los podemos almacenar en variables, y aunque no os lo he dicho antes por no liaros demasiado, os he de decir que todas las variables son de un tipo de dato. Es decir, vosotros en lenguaje C (hay algunos lenguajes que si), no podéis hacer variable1 = 5 y luego hacer variable1 = "Texto de prueba". ¿Porqué? Porque cuando usamos una variable por primera vez en C, ya se guarda en la memoria del ordenador como que esa variable sirve para almacenar ese tipo de dato, y si luego le intentas asignar algo de un tipo de dato distinto, adivina que pasa... ¡ERROR!

Y ahora... ¡Vamos a dar el gran paso! Ya empezamos a conocer como funcionan los ordenadores por dentro, pero aún no sabemos como podemos comunicarnos con ellos para pedirles que almacenen estos datos, pero eso va a cambiar dentro de poco 😎. Lo primero será aclarar que hay muchas formas distintas de hacer estas cosas, es decir, existen muchos lenguajes de programación para hacer esto (exactamente igual que tú diciendo Hola y Hello estás diciendo lo mismo pero en idiomas distintos. Prácticamente todos los lenguajes de programación funcionan similar, pero unos lenguajes son más útiles para unas cosas, y otros para otras. En este caso, tenemos el código fuente de los juegos de Pokémon en código C, por lo que nos enfocaremos en como hacerlo en este lenguaje de programación.

NOTA: Es importante declarar los tipos de datos tal y como se especifica aquí, no cambiar las mayúsculas o minúsculas, ni poner espacios donde quieras. Todo tiene su debida forma de escribirse, y hay que ser rigurosos con ello.

NOTA 2: Los tipos numéricos volátiles no se explicarán, porque para empezar no son necesarios.

Podemos declarar hasta 4 tipos distintos de enteros, uno que sólo tenga 8 bits, otro que tenga 16, otro 32, y otro 64. Según cuánto pensemos que podamos necesitar, usaremos un tipo u otro. A continuación os dejo cuál es el valor máximo que podría almacenar cada tipo: 8 bits = 255, 16 bits = 65 535, 32 bits = 4 294 967 295, 64 bits = Unos cuantos cuatrillones.

Por lo general, con el de 16 bits nos sobrará, pero bueno, los otros, ahí están.

Para indicar que queremos crear una variable sin signo (que será la que usemos casi siempre), pondremos una u (de unsigned) delante de uno de los cuatro valores posibles, y luego pondremos el nombre de la variable que queramos usar, es decir u8 para unsigned de 8 bits, u16 para unsigned de 16 bits, u32 para los de 32 bits y u64 para los de 64.

¡ATENCIÓN! El nombre de la variable no puede tener espacios, tan sólo puede tener letras, números, guiones y guiones bajos, y nada más, además, es sensible a las mayúsculas y minúsculas, es decir, no será lo mismo variabledeprueba que variableDePrueba. Cuando queráis poner más de una palabra en el nombre de la variable, podéis hacerlo de varias formas, ya sea separando por como hemos visto antes, como poniendo todas las primeras letras con mayúscula VariableDePrueba, como separandolo por guiones bajos variable_de_prueba, en fin, como más os guste, pero sin espacios ni carácteres especiales.

Bien, llegó el momento de ver el primero código en C, a continuación, vamos a declarar una variable para almacenar un número.

C:
u8 primerNumero = 8;
¡Geniaaal! Ya hemos aprendido a declarar variables, esto es un paso enorme, ni os lo imagináis.

No sé si lo habéis notado, pero después del número hay un punto y coma. El punto y coma sirve para decirle al ordenador que en esa línea ya hemos puesto todo lo que teníamos que poner, osea que tenemos que acostumbrarnos a poner el punto y coma después de las líneas. Hay algunas líneas en las que no tendremos que poner el punto y coma, pero es porque sirven para cosas diferentes. Aún así, tampoco es necesario que os aprendáis de memoria cuando poner el punto y coma, con el tiempo, y viendo el propio código de decompilación, sabréis cuando se pone y cuando no.

Por tanto, si quisiéramos declarar otro número, pero esta vez de 64 bits por ejemplo, pues
C:
u64 numero65Bits = 11;
Bien, ya entendemos los unsigned. Ahora, para los signed (con signo), es decir, con los que podremos distinguir entre números positivos y negativos, es exactamente lo mismo, pero en lugar de una u, pondremos una s, es decir s8, s16, s32 y s64.

C:
//Número negativo
s16 numeroConSigno = -14;
//Número positivo
s16 numeroConSigno2 = 14;
Como veis, es el mismo concepto. Y como imagino que os preguntaréis, ¿Qué son las //? Pues es lo que se conoce como comentarios.

El ordenador no leerá todo lo que haya después de esa doble barra en esa línea. Esto nos sirve por si tenemos un programa muy largo, y en algún momento queremos dejar escrito un comentario personal de lo que hace cada cosa, para entenderlo si en un futuro volvemos a leer ese código. Por ejemplo, ahora podríamos hacer algo así

C:
//El u8 sirve para declarar números sin signo
u8 num1 = 4; //Si llamo a num1, es como si escribiera un 4
//Ahora declaro un número con signo con s8
s8 num2 = -1; //Si llamo a num2, es como si escribiera un -1

//A continuación, un mal ejemplo de comentario
u16 num3 = //Con esto me cargo el código 5;
//A pesar de poner un 5 y un ; es como si escribiera
//u16 num3 =
//Por lo que recibiría un error
Aquí realmente seguimos la misma lógica que antes, y ya os he explicado gran parte de lo que tenía que explicaros, así que iremos al grano.
Ejemplos para declarar carácteres sueltos, y cadenas de carácteres

C:
//Declaración de una variable de un carácter
char letraA = 'A';
//Declaración de otra variabl de caracter
char numero5 = '5';
Y ahora, para los textos en C para Decompilación, tenemos una forma un poco especial de hacerlo, no hace falta que lo entendáis, simplemente usadlo así, es un concepto un poco más avanzado que entenderéis más adelante. Usaremos el tipo u8, al final del nombre de la variable pondremos [], y el texto, lo pondremos dentro de las comillas de esta estructura _(" ");

Por lo que tendríamos esto

C:
u8 texto[] = _("¡Hola mundo!");
u8 texto2[] = _("Mi primer texto personalizado");
Pues estos son los más fáciles de todos, la manera en la que declararemos los booleanos será con bool8, también existen bool16 y bool32, pero para el propósito actual, no los usaremos, así que

Código:
bool8 miPrimerBool = TRUE;
bool8 MiSegundoBool = FALSE;

En construcción...
Próximos puntos
  • Tipos de operadores​
    • Operadores aritméticos​
    • Operadores relacionales​
    • Operadores lógicos​
    • Operadores Bit a Bit​
    • Operadores de Asignación​
    • Operadores unarios​

  • Constantes​
  • Estructuras condicionales​
    • Estructura if​
    • Estructura else​
    • Estructura else if​
    • Estructura switch​

  • Bucles​
    • Bucle while​
    • Bucle for​

  • Funciones​
  • Visibilidad de funciones​
  • Estructuras de archivos de código​
(Podéis sugerirme temas a tratar)
wow será mas fácil de entender con este tutorial bien explicado
Además le ayudaras a muchas personas y con esto ya podremos aconsejarles bien que quien quiera use decomp
 

Juanjo

Hacker del pasado... Compilador del presente
IKsito, excelente guía y un enorme aporte a toda esta comunidad.

A ver si de una vez por todas dejamos esa programofobia y comenzamos de una vez a actualizarnos.

La decompilación mantiene la misma escencia del RH, de hecho te muestra exactamente como está hecho el ROM original y te deja hacer más cosas para actualizarlo. No existe ninguna excusa para no trabajar en decomp, a menos que tengas un proyecto ya muy avanzado. Allí es super comprensible.

Felicitaciones por tan buen aporte y espero que sigas aportando y añadiendo los nuevos puntos.
 

Acimut

Aprendiz de Saltamontes
¡Genial!
Me parece estupendo que se realicen ente tipo de aportes que disminuyen la brecha hacia el uso de proyectos de decomp, algo que siempre pensé hacía falta, así que se agradece y mucho. Por mi parte pienso hacer una guía similar (no la he hecho porque sufro de amnisedad), pero orientado al rom hacking en general, así que esta guía será una gran referencia.

Por otro lado, aclarar que C es un lenguaje que pertenece al paradigma de programación estructurada, es importante explicar el uso de las estructuras, así como de punteros.

Nota: En "Tipos de datos", al final de "Declarando valores numéricos", creo que en la línea
u16 num3 = //Con esto me cargo el código 5;
querías poner
u16 num3 = //5;
//Con esto me cargo el código 5;

C:
//El u8 sirve para declarar números sin signo
u8 num1 = 4; //Si llamo a num1, es como si escribiera un 4
//Ahora declaro un número con signo con s8
s8 num2 = -1; //Si llamo a num2, es como si escribiera un -1

//A continuación, un mal ejemplo de comentario
u16 num3 = //Con esto me cargo el código 5;
//A pesar de poner un 5 y un ; es como si escribiera
//u16 num3 =
//Por lo que recibiría un error
 

InmortalKaktus

Ludiloco
¡Genial!
Me parece estupendo que se realicen ente tipo de aportes que disminuyen la brecha hacia el uso de proyectos de decomp, algo que siempre pensé hacía falta, así que se agradece y mucho. Por mi parte pienso hacer una guía similar (no la he hecho porque sufro de amnisedad), pero orientado al rom hacking en general, así que esta guía será una gran referencia.

Por otro lado, aclarar que C es un lenguaje que pertenece al paradigma de programación estructurada, es importante explicar el uso de las estructuras, así como de punteros.

Nota: En "Tipos de datos", al final de "Declarando valores numéricos", creo que en la línea
u16 num3 = //Con esto me cargo el código 5;
querías poner
u16 num3 = //5;
//Con esto me cargo el código 5;
Nop, lo quería poner así, es un poco lioso, pero era para que se viera que el // se pone después del punto y coma

Y sip, se me olvidaban los punteros y referencias, los añadiré también, graciass 😁😁
 

InmortalKaktus

Ludiloco
EL POST SIGUE A PARTIR DE AQUÍ
(El post principal no me permite más caracteres, así que, tengo que seguir a partir de aquí)

En este apartado, os iré poniendo fragmentos de código, para que vayáis leyéndolos e intentando entender que hace cada función. Después del código, encontraréis la solución de lo que hace en un spoiler, pero si de verdad queréis sacarle provecho a este apartado, es importante que intentéis comprender cada código y luego veáis la solución

C:
u8 returnMaxValue(u8 a, u8 b)
{
    if(a > b)
        return a;
    else
        return b;
}
Efectivamente, este por el nombre de la función es muy sencillito, pero aún así lo desglosaremos. En primer lugar, le decimos que tiene que devolver un u8, y que tiene dos parámetros, a y b, de este mismo tipo. Dentro, comprobamos si a es mayor que b, y en tal caso, devolvemos a. En caso contrario, devolvemos b. Muy sencillito, una función para recibir el valor mayor.

Como curiosidad, este código se puede resumir todavía más, para dejarlo más elegante, y quedaría de la siguiente forma

C:
u8 returnMaxValue(u8 a, u8 b)
{
    if(a > b)
        return a;
    return b;
}
Es importante comprender que una vez la función ha devuelto un valor con el return, las líneas que quedan en esa función ya no se ejecutan. Porque total, si ya ha cumplido su cometido y ha devuelto un valor, ¿para que iba a seguir ejecutándose?

Pues gracias a esta regla, se da la condición de que si a es mayor que b, devolverá el valor de a, y se saldrá del código, pero si a no es mayor que b, ese primer return no se ejecutará, y se ejecutará el siguiente ;)

Ahora vamos con una menos intuitiva (pero facilita también :D)

C:
u8 funcion1(u8 value)
{
    u8 auxiliarValue = value;
    
    if((value % 2) != 0)
        auxiliarValue++;

    return auxiliarValue;
}
Sin el nombre en la función, cuesta un poco más, ehh? Por eso es importante ser descriptivos con los nombres de nuestras funciones, así cuando necesitemos saber que hace una que escribimos hace tiempo, tendremos una gran pista inicial y será mucho más fácil entender el código.
En esta ocasión, también estamos devolviendo un u8, y pasando otro más de ellos como valor. Además, creamos una variable auxiliar para almacenar el valor del parámetro. ¿Y esto porqué? porque si queremos modificar ese valor, no podemos modificar el parámetro, visto que el parámetro sirve como dato de entrada, no como una variable, entonces no se puede/debe cambiar. Más tarde (como aún así el parámetro sigue siendo un dato, lo podemos usar para las comparaciones en los if y esas cosas, aunque hubiera sido igual de valido usar la variableAuxiliar dentro del if), comparamos si el resto de dividir el valor entre dos es distinto de 0, y si lo es le sumamos uno a la variable auxiliar. Un momento... ¿Os habíais dado cuenta de que este código sirve para comprobar si un número es par, y que si no lo es, lo convierta en par sumándole uno?

Así es, esta función sirve para que si le pasamos un número impar, lo transforme en par sumándole uno, y que si ya es par, lo deje como está.

Y ahora, un poquito de todo.

C:
#define NUMEROS_GENERADOS 100
//Suponemos que podemos usar la funcion1 de antes

u8 funcion2()
{
    u8 i = 0, contador = 0;
    u8 rand;
    for( i = 0; i < NUMEROS_GENERADOS; i++){
        //Supongamos que tenemos otra variable de nombre
        //generateRandomNumber que devuelve un u8 random
        rand = generateRandomNumber();

        if(rand != (funcion1(rand)))
            contador++;
    }

    return contador;
}
Si al menos habéis intentado comprender este código, he de felicitaros, lo estáis haciendo de fábula. En esta función, en primer lugar definimos una constante, que servirá para definir cuantos números random vamos a generar. Además, tenemos que tener en cuenta que en esta función podremos usar la función del ejercicio anterior, y empezamos.

La función devuelve un u8, y no tiene parámetros, definimos una variable para el for, un contador, que luego veremos para que sirve, y una variable para almacenar los números random que generemos.

Creamos un bucle que va desde 0 hasta NUMEROS_GENERADOS - 1 (el - 1, porque la condición es que sea menor, por lo que el valor máximo que tendrá i será 99, pero como va desde 0 a 99, en realidad está haciendo NUMEROS_GENERADOS iteraciones, que en este caso son 100). Dentro del bucle, generamos un número random con una función imaginaria (esto cuando estemos programando de verdad, no lo podremos hacer, lógicamente tendríamos que crear esa función, pero es por agilizar :ROFLMAO:), y comprueba si el número generado, es distinto al valor que devolvería nuestra funcion1 si le pasamos este mismo valor. En caso de que sean distintos, suma uno a nuestro contador. Finalmente, tras acabar el bucle, devuelve el valor de contador.

Si os fijáis, en resumen, lo que hace este código es generar 100 números random, y contar cuantos de estos eran impares. Por lo que devuelve la cantidad de números impares generados. (Interesante lo que pueden llegar a hacer tan pocas líneas de código, ¿eh?)

Y ahora, señores, os merecéis que os felicite, porque... Si echáis la vista atrás a antes de saber programar, ¿esperaríais que algún día comprenderíais la programación tan bien? Habéis tenido un par de 🥚 y lo habéis hecho de maravilla, a partir de este punto del tutorial, ya podéis navegar entre los archivos de los proyectos decompilados, y aunque al principio tendréis que ir muy poquito a poco, empezaréis a pillarle el truquillo con la práctica, y luego haréis las cosas sin pensarlas.

Lo que queda del tutorial son conceptos para acabar de entender bien todo lo que podáis llegar a ver dentro de los archivos, pero no es fundamental, ojo, eso no quiere decir que no lo tengáis que leer, pero ya podéis sentiros tranquilos, porque las bases, ya las tenéis ;)
 
Última edición:

Xiros

Creador de Pokémon Omega (2007)
Miembro de honor
Excelente tutorial! Aún no leí a fondo, pero parece estar bastante bien explicada para aquellos que no sepan programar. Y para los que sabemos, seguro hay detalles de C que irán apareciendo que este bueno saber.
 

InmortalKaktus

Ludiloco
¡Actualizado con una nueva sección super importante, Funciones! Espero que lo disfrutéis, a partir de Funciones ya podréis ser capaces de entender gran parte del código de decomp, aunque aún tenemos que conocer conceptos más avanzados de C.

Esta noche actualizaré con unos cuantos ejemplos donde implementaremos gran parte de las cosas que hemos visto.
 

InmortalKaktus

Ludiloco
MUY GRANDIOSO APORTE para personas como yo que recientemente se esta pasando a Decomp
¡Me alegra que te haya gustado el aporte! (Por cierto, intenta evitar postear dos veces seguidas, hay una opción para editar el mensaje por si quieres añadir algo más y todavía nadie comentó, así está todo más organizado y hay comentarios de más gente en cada página ^^)

buena explicación de como se usaron las variables me gusto mucho aprender de C y me voy a probar decomp
Y también me alegra mucho que se entienda bien lo que hay explicado, de verdad :D

Venía de paso a notificar que acabo de intentar publicar la nueva sección de ejemplos de funciones, pero cuando le he dado a publicar, me ha saltado el aviso de los 50000 carácteres y me he comido una :poop:. Visto que tenía pensado complementar este post con videotutoriales para cada sección las secciones faltantes tendrán tan solo videotutorial.

EDIT: Tras tener 3 vídeos grabados me he dado cuenta de que hay que expresar demasiada información importante en poco tiempo para que un vídeo no se extienda, y sea fácil de consumir, me cuesta bastante expresar todas las cosas tan bien que de forma escrita, y aún así, se van a quedar vídeos muy largos, por lo que creo que mantendré el formato escrito.

Además, como de momento ya contamos con lo más básico, hasta que no pueda postear estos tutos en la web, de momento no avanzaré, así tampoco os agobiáis demasiado con tanto contenido.
 
Última edición:

InmortalKaktus

Ludiloco
He actualizado con ejemplos de texto, y reestructurado el post para poder seguir en otro mensaje debido al límite de caracteres. También he traído el siguiente punto. Ejemplos de cosas que podemos hacer con todo lo aprendido
 

EaasGame.

Usuario de Oro
La verdad no le hice incapie al tuto pq sabes wue no es lo que trabajo.
Pero acabo de dignarme a leerlo.

Hasta yo lo entendi bro, sinceramente aprecio el trabajo que has estado haciendl encuanto a darle una buena documentacion a la comunidad(al final hasta yo te lo tengo q agradecer.
 

Axel

Usuario de Platino
Felicidades por este tutorial.
En sólo un post has conseguido explicar la programación base que tardaron casi 3 meses en enserñarnos en clase.

Todo está muy bien explicado, y aunque ya se sepa nunca está de más echarle un vistazo, ya que en cada programación hay cosillas que cambian. Espero más actualizaciones!

Felicidades de nuevo ^^
 
Arriba