N64: Recompiled es una herramienta para recompilar estáticamente binarios N64 en un código C que se puede compilar para cualquier plataforma. Esto se puede utilizar para puertos o herramientas, así como para simular comportamientos significativamente más rápido que los intérpretes o la recompilación dinámica. Más ampliamente, se puede usar en cualquier contexto en el que desee ejecutar parte de un binario N64 en un entorno independiente.
Este no es el primer proyecto que utiliza la recompilación estática en los binarios de consola de juegos. Un ejemplo bien conocido es Jamulator, que se dirige a los binarios de NES. Además, este ni siquiera es el primer proyecto en aplicar la recompilación estática a los proyectos relacionados con N64: la recompilación estática IDO recompensa el compilador SGI Irix IDO en los sistemas modernos para facilitar la descompilación coincidente de los juegos N64. Este proyecto funciona de manera similar al proyecto IDO Static Recomp de alguna manera, y ese proyecto fue mi principal inspiración para hacer esto.
El recompilador funciona aceptando una lista de símbolos y metadatos junto con el binario con el objetivo de dividir el binario de entrada en funciones que cada uno se recompilan individualmente en una función C, nombrada de acuerdo con los metadatos.
Las instrucciones se procesan uno por uno y el código C correspondiente se emite a medida que cada una se procesa. Esta traducción es muy literal para mantener baja la complejidad. Por ejemplo, la instrucción addiu $r4, $r4, 0x20
, que agrega 0x20
al valor de 32 bits en los bytes bajos de registro $r4
y almacena el signo extendido de 64 bits en $r4
, se recompina en ctx->r4 = ADD32(ctx->r4, 0X20);
La instrucción jal
(salto y enlace) se recompensa directamente en una llamada de función, y las instrucciones j
o b
(saltos y ramas incondicionales) que pueden identificarse como optimizaciones de llamada de cola también se vuelven a compilar en llamadas de función. Las ranuras de retraso de la rama se manejan duplicando las instrucciones según sea necesario. Existen otros comportamientos específicos para ciertas instrucciones, como el recompilador que intenta convertir una instrucción jr
en una declaración de casos de interruptor si puede decir que se está utilizando con una mesa de salto. El recompilador se ha probado principalmente en binarios construidos con antiguos compiladores MIPS (por ejemplo, MIPS GCC 2.7.2 e IDO), así como los modernos mips de orientación a Clang. MIPS MIPS GCC puede recorrer el recompilador debido a ciertas optimizaciones que puede hacer, pero esos casos probablemente se pueden evitar estableciendo indicadores de compilación específicos.
Cada función de salida creada por el Recompiler actualmente se emite en su propio archivo. Se puede proporcionar una opción en el futuro para agrupar las funciones en archivos de salida, lo que debería ayudar a mejorar los tiempos de compilación de la salida del recompilador al reducir las E/S de archivos en el proceso de compilación.
La salida de recompensa se puede compilar con cualquier compilador C (probado con MSVC, GCC y Clang). Se espera que la salida se use con un tiempo de ejecución que puede proporcionar la funcionalidad necesaria y las implementaciones macro para ejecutarlo. Se proporciona un tiempo de ejecución en N64Modernruntime que se puede ver en acción en el proyecto Zelda 64: Recompiled.
Esta herramienta puede manejar las superposiciones vinculadas y reubicables estágadas por esta herramienta. En ambos casos, la herramienta emite búsquedas de funciones para el registro de salto y enlace (es decir, punteros de función o funciones virtuales) que el tiempo de ejecución proporcionado puede implementar utilizando cualquier tipo de tabla de búsqueda. Por ejemplo, la instrucción jalr $25
se volvería a compilar como LOOKUP_FUNC(ctx->r25)(rdram, ctx);
El tiempo de ejecución puede mantener una lista de las secciones del programa y en qué dirección se encuentran para determinar qué función ejecutar cada vez que se activa una búsqueda durante el tiempo de ejecución.
Para superposiciones reubicables, la herramienta modificará las instrucciones compatibles que poseen datos de reubicación ( lui
, addiu
, Carga y Instrucciones de almacenamiento) emitiendo una macro adicional que permita el tiempo de ejecución para reubicar el campo de valor inmediato de la instrucción. Por ejemplo, la instrucción lui $24, 0x80C0
en una sección que comienza en la dirección 0x80BFA100
con una reubicación contra un símbolo con una dirección de 0x80BFA730
se recompilará como ctx->r24 = S32(RELOC_HI16(1754, 0X630) << 16);
, donde 1754 es el índice de esta sección. El tiempo de ejecución puede implementar las macros RELOC_HI16 y RELOC_LO16 para manejar la modificación de la dirección cargada actual de la sección.
El soporte para las reubicaciones para el mapeo TLB se produce en el futuro, lo que agregará la capacidad de proporcionar una lista de reubicaciones MIPS32 para que el tiempo de ejecución pueda reubicarlos en la carga. La combinación de esto con la funcionalidad utilizada para superposiciones reubicables debería permitir ejecutar la mayoría del código mapeado TLB sin incurrir en una penalización de rendimiento en cada acceso RAM.
El Recompiler se configura proporcionando un archivo TOML para configurar el comportamiento de Recompiler, que es el único argumento proporcionado al Recompiler. El TOML es donde especifica rutas de archivo de entrada y salida, así como opcionalmente, eliminan las funciones específicas, omita la recompilación de funciones específicas e instrucciones individuales en el binario de destino. También hay una funcionalidad planificada para poder emitir ganchos en la salida de recompilador agregándolos a las secciones TomL ( [[patches.func]]
y [[patches.hook]]
del Toml vinculado a continuación), pero actualmente esto es actualmente no implementado. La documentación sobre cada opción que proporciona el Recompiler no está actualmente disponible, pero un ejemplo TOML se puede encontrar en el proyecto Zelda 64: Recompilado aquí.
Actualmente, la única forma de proporcionar los metadatos requeridos es pasar un archivo ELF a esta herramienta. La forma más fácil de obtener dicho elfo es establecer un desmontaje o descompilación del binario objetivo, pero habrá soporte para proporcionar los metadatos a través de un formato personalizado para evitar la necesidad de hacerlo en el futuro.
Esta herramienta también se puede configurar para recompilar en modo "Salida de un solo archivo" a través de una opción en la configuración TOML. Esto emitirá todas las funciones en el ELF proporcionado en un solo archivo de salida. El propósito de este modo es poder compilar versiones parcheadas de funciones del binario de destino.
Este modo se puede combinar con la funcionalidad proporcionada por casi todos los enlazadores (LD, LLD, Link.exe, etc.) para reemplazar las funciones de la salida de Recompiler original con versiones modificadas. Esos enlazadores solo buscan símbolos en una biblioteca estática si no se encontraron en un archivo de entrada anterior, por lo que proporcionar los parches recompilados al enlazador antes de proporcionar la salida de recompilador original dará como resultado que los parches tengan prioridad sobre las funciones con los mismos nombres. Desde la salida original de recompilador.
Esto ahorra una tremenda cantidad de tiempo mientras se itera en los parches para el binario objetivo, ya que puede evitar volver a pasar por alto el recompilador en el binario objetivo, así como compilar la salida de recompilador original. Se puede encontrar un ejemplo del uso de este modo de salida de un solo archivo para ese propósito en el proyecto Zelda 64: Recompilado aquí, con el archivo MakEder correspondiente que se usa para construir el elfo para esos parches aquí.
El microcódigo RSP también se puede recompilar con esta herramienta. Actualmente no hay soporte para recompilar superposiciones de RSP, pero se puede agregar en el futuro si lo desea. La documentación sobre cómo usar esta funcionalidad llegará pronto.
Este proyecto se puede construir con CMake 3.20 o superior y un compilador C ++ que admite C ++ 20. Este repositorio utiliza submódulos Git, así que asegúrese de clonar recursivamente ( git clone --recurse-submodules
) o inicializar submódulos de manera recursiva después de la clonación ( git submodule update --init --recursive
). A partir de ahí, el edificio es idéntico a cualquier otro proyecto de CMake, por ejemplo, ejecuta cmake
en la carpeta de compilación de destino y apunte a la raíz de este repositorio, luego ejecute cmake --build .
Desde esa carpeta objetivo.