La recompilación estática es la técnica que está detrás de los ports de juegos de consola que en los últimos años llegaron a la PC sin emulador: Majora’s Mask, Banjo-Kazooie, Sonic Unleashed o los primeros juegos de PS3 de ps3recomp. Consiste en tomar el programa compilado de un juego, traducir todas sus instrucciones a código C una sola vez, antes de jugar, y compilar ese código para la computadora. En esta guía explicamos en qué se diferencia de emular y de descompilar, cómo funciona paso a paso, por qué los resultados corren mejor y se dejan modificar, qué proyectos la usan y qué dice la ley. Y para verlo funcionar, inventamos una CPU de juguete y le escribimos un emulador y un recompilador.
Qué es, en pocas palabras
Un juego de consola es una lista de instrucciones para un procesador concreto: el MIPS de la Nintendo 64, el PowerPC de la Xbox 360, el Cell de la PS3. Una computadora no entiende esas instrucciones. Hay dos formas de resolverlo, y la diferencia se parece a la que hay entre un intérprete simultáneo y un traductor de libros:
- Un emulador es el intérprete simultáneo: mientras juegas, lee cada instrucción del juego, averigua qué significa y la ejecuta. Lo hace millones de veces por segundo, y vuelve a hacerlo cada vez que el juego pasa por el mismo sitio.
- Un recompilador estático es el traductor del libro: traduce el juego entero una sola vez, antes de jugarlo. «Estática» quiere decir eso, que la traducción se hace sin ejecutar el juego. Lo que sale es código en C o C++ que un compilador normal (gcc, Clang, MSVC) convierte en un programa para Windows, Linux o macOS.
El README de N64Recomp, la herramienta que tomaron como modelo XenonRecomp y Unleashed Recompiled, lo resume así: convierte los binarios de la Nintendo 64 «en código C que se puede compilar para cualquier plataforma». Cómo llega luego ese código a una pestaña del navegador lo contamos en cómo llegan los juegos clásicos al navegador; aquí nos centramos en la traducción misma.
Emular, recompilar al vuelo, recompilar antes o descompilar
Las cuatro técnicas resuelven el mismo problema, correr el código de una máquina en otra, y se confunden a menudo:
- Interpretar es lo más sencillo y lo más lento: cada instrucción se decodifica cada vez que se ejecuta.
- Recompilar al vuelo (recompilación dinámica, o JIT, de just-in-time) es lo que hacen los emuladores modernos para ir más rápido: traducen a código nativo los trozos del juego que se van ejecutando y guardan esa traducción para la siguiente vez. El README de ps3recomp lo describe como lo que hacen «brillantemente» emuladores como RPCS3. La ventaja es que el emulador ve el juego mientras corre y sabe en cada momento qué es código y adónde salta.
- Recompilar antes, la recompilación estática, hace toda la traducción por adelantado. No paga ningún costo mientras juegas, pero tiene que adivinar sin ejecutar el juego lo que el JIT simplemente observa. Esa es toda la dificultad, como veremos.
- Descompilar es otra cosa: el objetivo es un código legible por personas, con nombres y estructura. Los equipos de la escena lo hacen función por función, durante años, como contamos en la IA desborda la descompilación de juegos, y qué se pierde por el camino lo explica nuestra guía de descompilación. Un recompilador, en cambio, no intenta que su C se entienda: el de XenonRecomp avisa que su salida «no es muy legible para un humano». Es C escrito para un compilador, no para una persona.
Por eso los ports recompilados existen sin código fuente. El README de Zelda 64: Recompiled lo dice expresamente: la recompilación estática «evita la necesidad de un código fuente descompilado», aunque el proyecto toma encabezados y algunas funciones de la descompilación de Majora’s Mask para hacer sus mejoras.
Y dos de nuestras notas recientes hablan de juegos nativos que no son recompilaciones. nxnative no traduce nada: la Switch y los chips de Apple usan la misma arquitectura, ARM64, así que el código original corre tal cual y solo se retoca donde choca con las reglas de Apple; lo que se rehace es el sistema de la consola y sus gráficos. Y el port de P.T., la demo de Silent Hills, es una reimplementación: su autor reescribió la lógica en C++ sobre un motor propio, a partir de cómo se comporta el original.
Paso a paso: cómo se recompila un juego
Todos los recompiladores de consolas que revisamos siguen, con variaciones, los mismos cuatro pasos:
1. Separar el código de los datos
Un ejecutable mezcla instrucciones con datos: textos, tablas, números, relleno. Para el procesador son todos bytes, y nada en el archivo dice cuáles son cuáles. Si el recompilador trata un dato como si fuera una instrucción, produce basura; si se salta una instrucción, el port se rompe al llegar ahí. La forma habitual de separarlos es recorrer el programa desde su punto de entrada siguiendo cada salto, como quien sigue las flechas de un mapa: lo que se alcanza es código.
Ese recorrido no siempre basta, así que los proyectos reales se apoyan en información extra. N64Recomp pide una lista de símbolos y metadatos, que hoy solo acepta en forma de un archivo ELF y que, según su README, lo más fácil es sacar de un desensamble o una descompilación del juego. XenonAnalyse, la herramienta de análisis de XenonRecomp, lee los límites de muchas funciones de una sección del propio ejecutable de Xbox 360 (.pdata), busca el resto por las instrucciones que las llaman y, cuando falla, deja que una persona escriba a mano en un archivo de configuración dónde empieza y cuánto mide cada función.
2. Traducir cada instrucción a C
Con el código localizado, cada instrucción se convierte en una línea de C. El recompilador no intenta entender qué hace el juego: el estado del procesador de la consola (sus registros) se guarda en una estructura, y cada instrucción se traduce a la operación equivalente sobre ella. N64Recomp pone este ejemplo: la instrucción de MIPS addiu $r4, $r4, 0x20, que suma 0x20 al registro 4, se convierte en ctx->r4 = ADD32(ctx->r4, 0X20);. Su README dice que la traducción es «muy literal» a propósito, para mantener baja la complejidad.
Literal no quiere decir fácil. Cada procesador tiene sus manías y la traducción debe respetarlas todas. El MIPS de la N64 ejecuta la instrucción que va justo después de un salto antes de saltar (el delay slot), y N64Recomp lo resuelve duplicando instrucciones. La Xbox 360 guarda los números en memoria al revés que una PC (es big-endian), así que XenonRecomp invierte el orden de los bytes en cada lectura y cada escritura. Lo que el C traducido no puede hacer, como tocar directamente el hardware de la consola, se convierte en llamadas al runtime, del que hablamos más abajo.
3. El problema de los saltos indirectos
Un salto normal lleva su destino escrito en la propia instrucción: «salta a 0x0024». Ese se traduce sin problema, a un goto o a una llamada a función. El problema es el salto indirecto, cuyo destino está guardado en un registro y solo se conoce al ejecutar el juego. Así se compilan los switch grandes, los punteros a funciones y los métodos virtuales de C++, y los juegos están llenos de ellos. Un emulador no tiene problema, porque lee el registro y salta; un recompilador estático tiene que preparar por adelantado todos los destinos posibles.
Cada proyecto tiene sus trucos. N64Recomp intenta convertir los saltos con tabla en un switch de C y traduce las llamadas por puntero a LOOKUP_FUNC(ctx->r25)(rdram, ctx), una búsqueda que resuelve el runtime mientras el juego corre. XenonRecomp resuelve las llamadas virtuales con una tabla hash «perfecta» que lleva de cada dirección original a su función traducida, y detecta las tablas de saltos por su patrón de instrucciones; su README admite que no hay una solución general y que otros juegos pueden pedir ajustes. En el laboratorio de abajo provocamos este problema a propósito.
4. El código que cambia mientras corre
Hay un caso todavía peor: el código que no existe hasta que el juego lo crea, o que el juego modifica mientras corre (código automodificable). Un recompilador estático no puede traducir lo que no está en el archivo. Uno de los primeros intentos conocidos, que N64Recomp cita como antecedente, terminó en un desastre instructivo. En 2013, Andrew Kelley, que hoy preside la Zig Software Foundation, publicó jamulator, un recompilador estático de juegos de NES, y se topó con todos los trucos de los programadores de ensamblador de los ochenta: Super Mario Bros. salta a la mitad de una instrucción, usa los mismos bytes con dos significados, empuja direcciones a la pila para saltar con un RTS y a veces salta a la RAM. Su solución fue meter un intérprete dentro del programa recompilado para cubrir esos casos, y su conclusión, que la recompilación estática «no tiene una aplicación práctica» para emular videojuegos.
Una de las razones por las que la técnica sí funciona con consolas más modernas está en cómo se hicieron sus juegos: salieron de compiladores, y el código de un compilador sigue patrones mucho más regulares que el ensamblador artesanal de una NES. XenonRecomp lo aprovecha para reconocer las tablas de saltos, que en los ejecutables de Xbox 360 «suelen tener patrones predecibles». N64Recomp dice que se probó sobre todo con binarios hechos con los compiladores MIPS de la época (gcc 2.7.2 e IDO), y XenonRecomp se apoya en que los ejecutables de Xbox 360 siguen unas reglas estables (el ABI) para optimizar. Los casos raros no desaparecen: XenonRecomp todavía no admite las excepciones de C++, y N64Recomp necesita ayuda del runtime para los overlays, trozos de código que el juego carga en distintas direcciones de memoria según lo necesita.
5. El runtime: el hardware convertido en biblioteca
El C traducido solo es la lógica del juego. Todo lo que el juego le pedía a la consola (dibujar, sonar, leer el control, guardar la partida, crear hilos) lo atiende el runtime, una biblioteca que se compila junto con el código y que hace en la PC lo que hacía el hardware original. Es la parte más grande del trabajo y la que distingue un port bueno de uno malo:
- Nintendo 64: N64ModernRuntime reimplementa la biblioteca del sistema de la consola (libultra): hilos, controles, audio, temporizadores y tiempos de video, además de los overlays, las lecturas del cartucho y las partidas. Los gráficos los dibuja RT64, un renderizador moderno.
- Xbox 360: Unleashed Recompiled escribió un renderizador desde cero, y su README explica por qué rinde más: en una recompilación no hace falta emular el chip gráfico de la Xbox 360, así que se pueden saltar sus manías. Los shaders del juego se traducen a HLSL con otra herramienta, XenosRecomp.
- PlayStation 3: ps3recomp reproduce el núcleo del sistema de la consola, con más de 300 llamadas al sistema, y más de un centenar de bibliotecas. Los programas de las seis SPU del Cell se traducen aparte: su memoria local de 256 KB pasa a ser un arreglo, sus transferencias de memoria se vuelven copias con
memcpyy sus canales, colas de mensajes entre hilos. Los gráficos salen por Direct3D 12, Metal o Vulkan.
Laboratorio: recompilamos un programa de una CPU inventada
Para ver todo esto sin tocar ningún juego, inventamos una CPU de juguete, a la que llamamos DC8, con ocho registros y ocho instrucciones. Le escribimos un ensamblador, un intérprete en C (lo que hace un emulador) y un recompilador estático en Python que traduce sus programas a C. Todo es código nuestro, y se ejecutó en un contenedor desechable, sin red y con un usuario normal, con gcc 15.3.0 y Python 3.13.5. Las instrucciones de DC8 son:
LDI rX, número: pone un número en un registro.ADD,SUByMUL rX, rY: suma, resta y multiplica dos registros (con números de 32 bits).JNZ rX, dirección: salta si el registro no vale cero.JMPR rX: salta a la dirección guardada en el registro. Es el salto indirecto.SYS n: le pide algo al «hardware»: 1 escribe en la pantalla, 2 hace sonar un tono y 3 lee el control.HLT: se detiene.
El «juego» tiene dos partes. Un bucle que se repite 200 millones de veces y calcula una suma (el trabajo pesado) y tres rutinas a las que se llega con un salto a una dirección calculada, como lo haría un switch compilado. Entre las rutinas hay bytes de relleno (0xFF) que no son código:
.const VUELTAS 200000000
inicio:
LDI r0, 1 ; x: la semilla
LDI r1, 0 ; suma
LDI r2, VUELTAS ; contador
LDI r3, 1103515245 ; multiplicador
LDI r4, 12345 ; incremento
LDI r5, 1 ; la constante 1
bucle:
MUL r0, r3 ; x = x * 1103515245
ADD r0, r4 ; + 12345
ADD r1, r0 ; suma = suma + x
SUB r2, r5 ; contador = contador - 1
JNZ r2, bucle
LDI r6, 0 ; i: qué rutina toca
LDI r7, 3 ; quedan tres
despacho:
LDI r2, rutinas ; dirección de la primera rutina
LDI r4, 16 ; cada rutina ocupa 16 bytes
MUL r4, r6 ; r4 = i * 16
ADD r2, r4 ; r2 = rutinas + i * 16
JMPR r2 ; salto indirecto: el destino solo se sabe al ejecutar
vuelta:
ADD r6, r5 ; i = i + 1
SUB r7, r5
JNZ r7, despacho
HLT
.relleno 16
rutinas:
LDI r0, 0 ; rutina 0: muestra la suma en pantalla
ADD r0, r1
SYS 1
JNZ r5, vuelta
.relleno 16
LDI r0, 440 ; rutina 1: un pitido de 440 Hz
SYS 2
JNZ r5, vuelta
.relleno 16
SYS 3 ; rutina 2: lee el control y muestra los botones
SYS 1
JNZ r5, vuelta
.relleno 16El intérprete es un bucle con un switch que lee cada instrucción, la decodifica y la ejecuta. Este es su corazón:
switch (p[0]) { /* decodificar... */
case 0x00: if (cuenta) *cuenta = n; return 0; /* HLT */
case 0x01: c->r[p[1]] = lee32(p + 2); pc += 6; break;
case 0x02: c->r[p[1]] += c->r[p[2]]; pc += 3; break;
case 0x03: c->r[p[1]] -= c->r[p[2]]; pc += 3; break;
case 0x04: c->r[p[1]] *= c->r[p[2]]; pc += 3; break;
case 0x05: pc = c->r[p[1]] ? (uint32_t)(p[2] | p[3] << 8) : pc + 4; break;
case 0x06: pc = c->r[p[1]]; break; /* el emulador no tiene problema: lee el registro */
case 0x07: servicio(c, p[1]); pc += 2; break;
default: fprintf(stderr, "error: instrucción 0x%02X inválida en 0x%04X\n", p[0], pc); return 2;
}Con el programa de 144 bytes, el emulador hace esto:
$ ./emulador programa.dc8$ ./emulador programa.dc8 código de salida: 0 [pantalla] 751807232 tono de 440 Hz [control] botones pulsados: 0x05 [pantalla] 5 instrucciones interpretadas: 1000000043
Los servicios del «hardware» ([pantalla], , [control]) están en un archivo aparte, runtime.c, que usan igual el emulador y el programa recompilado: es nuestra versión en miniatura del runtime de los proyectos reales.
Primer intento: leerlo todo como código
Lo más ingenuo es desensamblar de principio a fin, como si todo fuera código. No pasa de la dirección 0x005F, donde empieza el relleno:
salida$ python3 recompilador.py programa.dc8 lineal.c --lineal desensamblado lineal: byte 0xFF en 0x005F: no es una instrucción de DC8 código de salida: 1
Segundo intento: seguir los saltos
El recompilador de verdad recorre el programa desde la dirección 0 siguiendo los saltos. Encuentra el bucle y el despacho, pero se detiene en JMPR r2, porque no puede saber qué valdrá r2. El programa traducido compila sin avisos, corre el bucle y se cae justo al llegar al salto:
salida$ python3 recompilador.py programa.dc8 recomp1.c raíces: 0x0000 instrucciones traducidas: 18 (84 de 144 bytes; 60 sin visitar) aviso: salto indirecto en 0x0052 (JMPR r2): destino desconocido recomp1.c: 37 líneas de C código de salida: 0 (gcc sin avisos) $ ./recomp1 error: salto indirecto a 0x0060, una dirección que no se tradujo código de salida: 1
Tercer intento: darle pistas
Hacemos lo mismo que los archivos de configuración de N64Recomp y XenonRecomp: le decimos al recompilador dónde empiezan las tres rutinas. Ahora traduce 32 instrucciones, y la salida es idéntica a la del emulador. Los tres avisos son otra lección: cada rutina termina con un JNZ r5 que en la práctica siempre salta, porque r5 vale 1, pero el recompilador no lo sabe y comprueba qué hay después. Encuentra relleno y lo deja fuera, con una trampa por si alguna vez se llegara ahí:
salida$ python3 recompilador.py programa.dc8 recomp2.c --entradas 0x0060,0x0070,0x0080 raíces: 0x0000, 0x0060, 0x0070, 0x0080 instrucciones traducidas: 32 (130 de 144 bytes; 14 sin visitar) aviso: byte 0xFF en 0x006F: no es una instrucción de DC8; ese camino se deja sin traducir aviso: byte 0xFF en 0x007C: no es una instrucción de DC8; ese camino se deja sin traducir aviso: byte 0xFF en 0x0088: no es una instrucción de DC8; ese camino se deja sin traducir aviso: salto indirecto en 0x0052 (JMPR r2): destino desconocido recomp2.c: 66 líneas de C código de salida: 0 (gcc sin avisos) $ ./recomp2 código de salida: 0 [pantalla] 751807232 tono de 440 Hz [control] botones pulsados: 0x05 [pantalla] 5 $ diff emu.out rec2.out && echo idénticas idénticas
Este es el bucle de DC8 tal como quedó en C, una línea por instrucción, y el salto indirecto convertido en un switch con todos los destinos conocidos:
L_0024:
ctx->r[0] = ctx->r[0] * ctx->r[3]; /* 0024 MUL r0, r3 */
ctx->r[0] = ctx->r[0] + ctx->r[4]; /* 0027 ADD r0, r4 */
ctx->r[1] = ctx->r[1] + ctx->r[0]; /* 002A ADD r1, r0 */
ctx->r[2] = ctx->r[2] - ctx->r[5]; /* 002D SUB r2, r5 */
if (ctx->r[2] != 0) goto L_0024; /* 0030 JNZ r2, 0x0024 */
…
pc = ctx->r[2]; goto despacho; /* 0052 JMPR r2 */
…
despacho:
switch (pc) {
case 0x0000: goto L_0000;
case 0x0024: goto L_0024;
case 0x0034: goto L_0034;
case 0x0040: goto L_0040;
case 0x0054: goto L_0054;
case 0x005E: goto L_005E;
case 0x0060: goto L_0060;
case 0x0070: goto L_0070;
case 0x0080: goto L_0080;
default:
fprintf(stderr, "error: salto indirecto a 0x%04X, una dirección que no se tradujo\n", pc);
return 1;
}Cuarto intento: el respaldo de jamulator
La otra salida es la de Andrew Kelley en 2013: sin pistas, pero con el programa original y el intérprete metidos en el ejecutable, para que se encargue de lo que no se tradujo. También funciona:
salida$ python3 recompilador.py programa.dc8 recomp3.c --respaldo raíces: 0x0000 instrucciones traducidas: 18 (84 de 144 bytes; 60 sin visitar) aviso: salto indirecto en 0x0052 (JMPR r2): destino desconocido recomp3.c: 38 líneas de C código de salida: 0 (gcc sin avisos) $ ./recomp3 [respaldo] 0x0060 no se tradujo: lo ejecuta el intérprete código de salida: 0 [pantalla] 751807232 tono de 440 Hz [control] botones pulsados: 0x05 [pantalla] 5 idénticas a las del emulador
El precio es que el ejecutable lleva dentro el programa original completo y un emulador, y que todo lo que pase por el respaldo corre a velocidad de intérprete. Aquí no se nota porque el bucle sí se tradujo y las tres rutinas son cortas.
Lo que gana la traducción
Medimos cinco veces cada versión. El emulador tardó entre 1.32 y 2.48 segundos (la mediana, 2.35 s, unos 2.3 nanosegundos por cada una de sus 1,000,000,043 instrucciones interpretadas), y el recompilado, entre 0.175 y 0.177 s: unas 13 veces más rápido.
tiempos con time, en segundosronda 1: emulador 2.476 s · recompilado 0.177 s · recompilado con respaldo 0.175 s ronda 2: emulador 1.322 s · recompilado 0.176 s · recompilado con respaldo 0.178 s ronda 3: emulador 2.331 s · recompilado 0.175 s · recompilado con respaldo 0.177 s ronda 4: emulador 2.376 s · recompilado 0.176 s · recompilado con respaldo 0.178 s ronda 5: emulador 2.346 s · recompilado 0.175 s · recompilado con respaldo 0.175 s
La razón se ve en lo que gcc hizo con el C traducido. Las cinco instrucciones del bucle de DC8 quedaron convertidas en cinco instrucciones de x86, con los registros de DC8 guardados en registros del procesador:
40119c imul $0x41c64e6d,%edx,%edx 4011a2 add $0x3039,%edx 4011a8 add %edx,%ecx 4011aa sub $0x1,%eax 4011ad jne 40119c <programa+0x2c>
El intérprete, en cambio, tiene que leer el byte, saltar a su caso del switch, leer los operandos y avanzar el contador en cada una de las mil millones de instrucciones. Comprobamos además que las dos versiones dan el mismo resultado con otros tamaños del bucle, y lo cotejamos con la misma suma calculada en Python:
emulador, recompilado y PythonVUELTAS=1: emulador «[pantalla] 1103527590» · recompilado «[pantalla] 1103527590» · iguales VUELTAS=7: emulador «[pantalla] 761051411» · recompilado «[pantalla] 761051411» · iguales VUELTAS=1000: emulador «[pantalla] 662615452» · recompilado «[pantalla] 662615452» · iguales VUELTAS=123457: emulador «[pantalla] 1366899654» · recompilado «[pantalla] 1366899654» · iguales Python, VUELTAS=1: 1103527590 Python, VUELTAS=7: 761051411 Python, VUELTAS=1000: 662615452 Python, VUELTAS=123457: 1366899654
Una advertencia: nuestra CPU es mínima y nuestro bucle, el caso ideal. En un juego real la diferencia depende de muchas cosas, y los proyectos no publican mediciones comparables con los emuladores, así que no las damos.
Por qué los ports recompilados corren mejor y se dejan modificar
Lo que el laboratorio muestra en pequeño, los proyectos lo aprovechan en grande. Esto es lo que anuncian en sus repositorios:
- Menos máquina: el juego no se traduce mientras corre, y el compilador optimiza el código traducido para el procesador de la PC. ps3recomp lo presenta como su primera ventaja frente a la emulación: rendimiento nativo y menos requisitos.
- Más cuadros por segundo: Zelda 64: Recompiled se juega a cualquier número de cuadros por segundo, por defecto los que dé el monitor, y su README aclara que cambiarla «no tiene efecto en la jugabilidad». Unleashed Recompiled sube el límite de Sonic Unleashed a 60 FPS y permite más, con muchos de los fallos que aparecen a frecuencias altas ya corregidos.
- Pantalla ancha: los dos admiten pantallas ultrawide, más anchas que 16:9.
- Sin tirones por shaders: Unleashed Recompiled compila los shaders mientras carga cada zona, algo que según su README «nunca sería posible con un emulador», porque exige modificar el juego.
- Mods: como el juego es código C, se puede cambiar una función por otra. N64Recomp tiene un modo pensado para compilar versiones parcheadas de funciones que el enlazador usa en lugar de las originales, y XenonRecomp deja enganchar código propio en direcciones concretas del juego. Zelda 64: Recompiled tiene su propio catálogo de mods.
- Cargas instantáneas y controles modernos: el port de Majora’s Mask añade puntería con giroscopio, cámara con el segundo stick y guardado automático.
A cambio, cada port es un proyecto aparte. Un emulador corre cientos de juegos; una recompilación, uno. Zelda 64: Recompiled solo acepta una ROM concreta, la estadounidense de Majora’s Mask («no es un emulador y no puede correr cualquier ROM»), y cada juego nuevo pide localizar sus funciones, resolver sus saltos, cubrir lo que usa del sistema y arreglar lo que falle. Por eso ps3recomp avanza juego a juego mientras RPCS3, según el README de ps3recomp, ya hace jugable alrededor del 70 % del catálogo de la PS3.
Los proyectos que lo hacen hoy
- N64: Recompiled (MIT): el recompilador de Nintendo 64 de Wiseguy (Mr-Wiseguy en GitHub), con repositorio desde noviembre de 2022. Su README cita como antecedentes jamulator y la recompilación estática del compilador IDO que usan los proyectos de descompilación.
- Zelda 64: Recompiled (GPL 3.0): Majora’s Mask para Windows, Linux y Mac, con la versión 1.0 del 10 de mayo de 2024; Ocarina of Time está en camino.
- Banjo: Recompiled: Banjo-Kazooie con la misma herramienta, versión 1.0 del 24 de enero de 2026. Los dos ports prohíben en sus reglas cualquier contribución hecha con IA generativa.
- XenonRecomp (MIT) y Unleashed Recompiled (GPL 3.0): el recompilador de Xbox 360 y su primer gran resultado, Sonic Unleashed para Windows y Linux, publicado el 1 de marzo de 2025 por el equipo de Skyth y Sajid. XenonRecomp dice inspirarse en N64Recomp y deberle mucho al emulador Xenia.
- ps3recomp (MIT): la herramienta para PS3, en la versión 0.19 desde el 6 de noviembre de 2026. Según su README, The Simpsons Arcade Game ya se juega, incluso en línea, y en el port de la comunidad de Killzone 3 cargan los nueve capítulos y uno se jugó a mano hasta el final. Lo contamos en nuestra nota de ps3recomp.
- Y muchos más: el propio README de ps3recomp cita proyectos con la misma idea para PS2, la primera Xbox y PS5, y el registro de Recomp Haven contó 195 versiones de ports recompilados en diez días de octubre, ya con juegos de Switch. Los ports de PSP y PS2 que llegan al navegador compilan el resultado a WebAssembly, y algunos están en nuestra lista de juegos clásicos en el navegador.
Qué dice la ley: tu copia, su código
Los proyectos serios siguen la misma regla: el repositorio no lleva nada del juego y cada jugador pone su propia copia. Zelda 64: Recompiled y Banjo: Recompiled avisan de que ni el repositorio ni sus versiones contienen recursos del juego y de que hace falta el original para compilarlos o jugarlos; el port de Majora’s Mask solo acepta una ROM concreta, y sus instrucciones de compilación dan hasta su huella SHA-1. Unleashed Recompiled pide volcar los archivos de una copia «adquirida legalmente» desde tu propia Xbox 360. ps3recomp declara que no contiene código, firmware ni claves de Sony, y que los archivos del juego los aporta cada uno.
Eso reduce el riesgo, pero no lo elimina. Para sacar los archivos de una consola a veces hay que saltarse su protección anticopia, y leyes como la DMCA de Estados Unidos lo prohíben aunque el juego sea tuyo, como explicamos con nxnative. Qué permiten las leyes de Estados Unidos, la Unión Europea y México lo resumimos en qué es la ingeniería inversa, y cómo cambia el panorama ahora que la IA abarata estos proyectos, en la ingeniería inversa barata y la ley. Esto es un resumen, no un consejo legal.
Para seguir
Si quieres ver el otro lado de la técnica, empieza por la guía de descompilación y por la guía de Ghidra, la herramienta con la que se estudian estos ejecutables. Para el contexto de la ola de este año, la IA y la descompilación de juegos y cómo llegan los juegos clásicos al navegador.
Fuentes: los README de N64Recomp, N64ModernRuntime, Zelda 64: Recompiled (y su BUILDING.md), Banjo: Recompiled, XenonRecomp, Unleashed Recompiled, ps3recomp y nxnative, con fechas y licencias de la API de GitHub, consultados el 9 de noviembre de 2026; Andrew Kelley, «Statically Recompiling NES Games into Native Executables with LLVM and Go» (7 de junio de 2013). El laboratorio se hizo el 9 de noviembre de 2026 con gcc 15.3.0 y Python 3.13.5 en un contenedor desechable sin red; DC8, su programa, el intérprete y el recompilador son nuestros.
