N64: Recompilé est un outil pour recompiler statiquement les binaires N64 dans le code C qui peut être compilé pour n'importe quelle plate-forme. Cela peut être utilisé pour les ports ou les outils ainsi que pour la simulation de comportements considérablement plus rapidement que les interprètes ou la recompilation dynamique. Plus largement, il peut être utilisé dans n'importe quel contexte où vous souhaitez exécuter une partie d'un binaire N64 dans un environnement autonome.
Ce n'est pas le premier projet qui utilise la recompilation statique sur les binaires de console de jeu. Un exemple bien connu est Jamulator, qui cible les binaires NES. De plus, ce n'est même pas le premier projet à appliquer la recompilation statique aux projets liés à N64: la recompilation statique IDO recompile le compilateur IDO SGI IRIX sur les systèmes modernes pour faciliter la décompilation de correspondance des jeux N64. Ce projet fonctionne de la même manière que le projet IDO Static Reomp à certains égards, et ce projet a été ma principale inspiration pour le faire.
Le Recompiler fonctionne en acceptant une liste de symboles et de métadonnées aux côtés du binaire dans le but de diviser le binaire d'entrée en fonctions qui sont chacune recompilées individuellement en une fonction C, nommée selon les métadonnées.
Les instructions sont traitées un par un et le code C correspondant est émis comme chacun est traité. Cette traduction est très littérale afin de maintenir la complexité faible. Par exemple, l'instruction addiu $r4, $r4, 0x20
, qui ajoute 0x20
à la valeur 32 bits dans les octets bas de l'enregistrement $r4
et stocke le panneau étendu 64 bits Résultat en $r4
, se recompile dans ctx->r4 = ADD32(ctx->r4, 0X20);
L'instruction jal
(Jump-and-Link) est recompilée directement dans un appel de fonction, et les instructions j
ou b
(sauts et branches inconditionnels) qui peuvent être identifiés comme des optimisations d'appel de queue sont également recompilées en appels de fonction. Les emplacements de retard de branche sont gérés par des instructions de duplication si nécessaire. Il existe d'autres comportements spécifiques pour certaines instructions, tels que le Recompiler tentant de transformer une instruction jr
en une déclaration de cas de commutateur s'il peut dire qu'il est utilisé avec une table de saut. Le Recompiler a été principalement testé sur des binaires construits avec des compilateurs MIPS anciens (par exemple MIPS GCC 2.7.2 et IDO) ainsi que des MIP ciblant les clang modernes. Le MIPS moderne GCC peut trébucher dans le récompilateur en raison de certaines optimisations qu'il peut faire, mais ces cas peuvent probablement être évités en définissant des drapeaux de compilation spécifiques.
Chaque fonction de sortie créée par le Recompiler est actuellement émise dans son propre fichier. Une option peut être fournie à l'avenir pour regrouper les fonctions dans les fichiers de sortie, ce qui devrait aider à améliorer les temps de construction de la sortie de recompuler en réduisant les E / S de fichiers dans le processus de construction.
La sortie de Recompiler peut être compilée avec n'importe quel compilateur C (testé avec MSVC, GCC et Clang). La sortie devrait être utilisée avec un runtime qui peut fournir les fonctionnalités nécessaires et les implémentations macro pour l'exécuter. Un temps d'exécution est fourni dans N64ModernRuntime qui peut être vu en action dans le projet Zelda 64: Recompilé.
Les superpositions statiquement liées et relocables peuvent toutes deux être gérées par cet outil. Dans les deux cas, l'outil émet des recherches de fonctions pour le saut-et-lien (IE Pointers ou Fonctions Virtual) que le runtime fourni peut implémenter à l'aide de n'importe quel type de table de recherche. Par exemple, l'instruction jalr $25
serait recompilée en tant que LOOKUP_FUNC(ctx->r25)(rdram, ctx);
Le runtime peut ensuite maintenir une liste des sections de programme chargées et à quelle adresse ils se trouvent afin de déterminer la fonction à exécuter chaque fois qu'une recherche est déclenchée pendant l'exécution.
Pour les superpositions relocalisées, l'outil modifiera les instructions prises en charge possédant des données de relocalisation ( lui
, addiu
, CHARGE et STANGER Instructions) en émettant une macro supplémentaire qui permet à l'exécution de déplacer le champ de valeur immédiate de l'instruction. Par exemple, l'instruction lui $24, 0x80C0
dans une section commençant à l'adresse 0x80BFA100
avec une relocalisation contre un symbole avec une adresse de 0x80BFA730
sera recompilée sous le nom ctx->r24 = S32(RELOC_HI16(1754, 0X630) << 16);
, où 1754 est l'indice de cette section. Le runtime peut ensuite implémenter les macros reloc_hi16 et reloc_lo16 afin de gérer la modification de l'immédiat en fonction de l'adresse chargée actuelle de la section.
La prise en charge des relocations pour la cartographie TLB arrive à l'avenir, ce qui ajoutera la possibilité de fournir une liste des délocalisations MIPS32 afin que le temps d'exécution puisse les déplacer sur charge. La combinaison de cela avec les fonctionnalités utilisées pour les superpositions relocatives devrait permettre d'exécuter la plupart des code mappés TLB sans encourir une pénalité de performance sur chaque accès RAM.
Le Recompiler est configuré en fournissant un fichier Toml afin de configurer le comportement de recompilateur, qui est le seul argument fourni au Recompiler. Le TOML est l'endroit où vous spécifiez des chemins de fichier d'entrée et de sortie, ainsi que d'étudier éventuellement des fonctions spécifiques, de sauter la recompilation de fonctions spécifiques et de patcher des instructions uniques dans le binaire cible. Il existe également des fonctionnalités planifiées pour pouvoir émettre des crochets dans la sortie de Recompiler en les ajoutant aux sections Toml ( [[patches.func]]
et [[patches.hook]]
du Toml lié ci-dessous), mais cela est actuellement sans implémentation. La documentation sur toutes les options fournies par le Recompiler n'est pas actuellement disponible, mais un exemple de TOML peut être trouvé dans le projet Zelda 64: Recompilé ici.
Actuellement, la seule façon de fournir les métadonnées requises est de passer un fichier ELF à cet outil. Le moyen le plus simple d'obtenir un tel elfe est de mettre en place un démontage ou une décompilation du binaire cible, mais il y aura un soutien pour fournir les métadonnées via un format personnalisé pour contourner la nécessité de le faire à l'avenir.
Cet outil peut également être configuré pour recompiler dans le mode "SORTIE DE FILE UNE FILE" via une option dans la configuration Toml. Cela émettra toutes les fonctions dans l'ELF fourni dans un seul fichier de sortie. Le but de ce mode est de pouvoir compiler des versions patchées des fonctions du binaire cible.
Ce mode peut être combiné avec les fonctionnalités fournies par presque tous les lieurs (LD, LLD, MSVC Link.exe, etc.) pour remplacer les fonctions de la sortie de récompilation d'origine avec des versions modifiées. Ces linkers ne recherchent que des symboles dans une bibliothèque statique s'ils ne l'étaient pas déjà trouvés dans un fichier d'entrée précédent, donc fournir les correctifs recompilés au linker avant de fournir la sortie de récompilation d'origine entraînera que les correctifs prendront la priorité sur les fonctions avec les mêmes noms de la sortie de récompilation d'origine.
Cela permet d'économiser énormément de temps tout en itérant sur les correctifs pour le binaire cible, comme vous pouvez contourner le rediffusion du Recompiler sur le binaire cible ainsi que la compilation de la sortie de récompilation d'origine. Un exemple d'utilisation de ce mode de sortie de fichier unique à cet effet peut être trouvé dans le projet Zelda 64: Recompilé ici, avec le makefile correspondant qui est utilisé pour construire l'elfe pour ces correctifs ici.
Le microcode RSP peut également être recompilé avec cet outil. Actuellement, il n'y a pas de support pour recompilorer les superpositions RSP, mais elle peut être ajoutée à l'avenir si vous le souhaitez. La documentation sur la façon d'utiliser cette fonctionnalité arrivera bientôt.
Ce projet peut être construit avec CMake 3.20 ou supérieur et un compilateur C ++ qui prend en charge C ++ 20. Ce repo utilise des sous-modules Git, alors assurez-vous de cloner récursivement ( git clone --recurse-submodules
) ou initialiser les sous-modules récursivement après clonage ( git submodule update --init --recursive
). À partir de là, la construction est identique à tout autre projet CMake, par exemple, exécutez cmake
dans le dossier de construction cible et le pointez à la racine de ce repo, puis exécutez cmake --build .
à partir de ce dossier cible.