N64: Neukompiliert ist ein Tool, um N64 -Binärdateien statisch in C -Code umzuwandeln, das für jede Plattform kompiliert werden kann. Dies kann sowohl für Ports oder Tools als auch für die Simulation von Verhaltensweisen erheblich schneller verwendet werden als Dolmetscher oder dynamische Neukompilation. In jedem Kontext, in dem Sie in einer eigenständigen Umgebung einen Teil eines N64 -Binarischen N64 -Binäres betreiben möchten, kann es genauer gesagt werden.
Dies ist nicht das erste Projekt, bei dem statische Neukompilation in Binärdateien bei Spielekonsolen verwendet wird. Ein bekanntes Beispiel ist Jamulator, das auf NES -Binärdateien abzielt. Darüber hinaus ist dies nicht einmal das erste Projekt, das statische Neukompilation auf N64-Projekte anwendet: Die IDO-statische Neukompilation wendet den SGI IRIX-IDO-Compiler für moderne Systeme um, um die entsprechende Dekompilierung von N64-Spielen zu erleichtern. Dieses Projekt funktioniert in gewisser Weise ähnlich wie das IDO Static Recomprojekt, und dieses Projekt war meine Hauptinspiration dafür, dies zu machen.
Der Rekompiler arbeitet mit der Annahme einer Liste von Symbolen und Metadaten neben dem Binärdatum mit dem Ziel, die Input -Binärin in Funktionen aufzuteilen, die jeweils individuell in eine C -Funktion neu kompiliert werden, die gemäß den Metadaten benannt ist.
Die Anweisungen werden nacheinander verarbeitet und der entsprechende C-Code wird nach Verarbeitung von jedem emittiert. Diese Übersetzung ist sehr wörtlich, um die Komplexität niedrig zu halten. Beispielsweise der Anweisungen addiu $r4, $r4, 0x20
, der 0x20
zum 32-Bit-Wert in den niedrigen Bytes von Register $r4
hinzufügt und das erweiterte Schild um 64-Bit-Ergebnis in $r4
speichert, wird in ctx->r4 = ADD32(ctx->r4, 0X20);
Die jal
-Anweisung (Sprung-und-Link) wird direkt in einen Funktionsaufruf umgebaut, und j
oder b
-Anweisungen (bedingungslose Sprünge und Zweige), die als Schwanzberechtigungsoptimierungen identifiziert werden können, werden ebenfalls in Funktionsaufrufe umgebaut. Die Verzögerungsverzögerungsschlitze werden nach Bedarf durch Duplikation von Anweisungen behandelt. Es gibt andere spezifische Verhaltensweisen jr
bestimmte Anweisungen, z. Der Rekompiler wurde hauptsächlich an Binärdateien getestet, die mit alten MIPS -Compilern (z. B. MIPS GCC 2.7.2 und IDO) sowie modernem Clang -Targeting -MIPS gebaut wurden. Moderne MIPS GCC kann den Neukompiler aufgrund bestimmter Optimierungen auflösen, die dies tun kann, aber diese Fälle können wahrscheinlich vermieden werden, indem bestimmte Kompilierungsflags festgelegt werden.
Jede vom Recompiler erstellte Ausgabefunktion wird derzeit in eine eigene Datei emittiert. In Zukunft kann eine Option bereitgestellt werden, um Funktionen in Ausgabedateien zu gruppieren, die dazu beitragen sollten, die Erstellungszeiten der Recompiler -Ausgabe zu verbessern, indem die Datei -E/A im Build -Prozess reduziert wird.
Die Rekompilerausgabe kann mit jedem C -Compiler (mit MSVC, GCC und Clang getestet) kompiliert werden. Es wird erwartet, dass die Ausgabe mit einer Laufzeit verwendet wird, die die erforderlichen Funktionen und Makroimplementierungen bereitstellen kann, um sie auszuführen. Eine Laufzeit ist in N64ModerNruntime bereitgestellt, die im ZEelda 64: Neukompilierten Projekt in Aktion zu sehen sind.
Statisch verknüpfte und verlängerte Overlays können beide von diesem Tool behandelt werden. In beiden Fällen emittiert das Tool Funktions-Lookups für Jump-and-Link-Register (IE-Funktionszeiger oder virtuelle Funktionen), die die bereitgestellte Laufzeit mithilfe einer beliebigen Such-Tabelle implementieren kann. Beispielsweise würde die Anweisung jalr $25
als LOOKUP_FUNC(ctx->r25)(rdram, ctx);
Die Laufzeit kann dann eine Liste der Programmabschnitte verwalten und an welcher Adresse sie sich befinden, um zu bestimmen, welche Funktion ausgeführt werden soll, wenn eine Suche während der Laufzeit ausgelöst wird.
Für lockbare Overlays ändert das Tool unterstützte Anweisungen mit Umzugsdaten ( lui
, addiu
, Laden und Speichern), indem ein zusätzliches Makro ausgestrahlt wird, mit dem die Laufzeit das unmittelbare Feld des sofortigen Wertes der Anweisung verschoben werden kann. Beispielsweise wird die Anweisung lui $24, 0x80C0
in einem Abschnitt, der an der Adresse 0x80BFA100
mit einem Umzug gegen ein Symbol mit einer Adresse von 0x80BFA730
wird, als ctx->r24 = S32(RELOC_HI16(1754, 0X630) << 16);
, wobei 1754 der Index dieses Abschnitts ist. Die Laufzeit kann dann die Makros RELOC_HI16 und RELOC_LO16 implementieren, um die Änderung der unmittelbaren Basis der aktuellen geladenen Adresse des Abschnitts zu verarbeiten.
In Zukunft wird die Unterstützung für Umzüge für die TLB -Mapping vorgestellt, wodurch die Möglichkeit hinzugefügt wird, eine Liste von MIPS32 -Umzügen bereitzustellen, damit die Laufzeit sie auf Ladung verlagern kann. Das Kombinieren mit der Funktionalität, die für lockere Overlays verwendet wird, sollte es ermöglichen, den meisten TLB -zugeordneten Code auszuführen, ohne eine Leistungsstrafe für jeden RAM -Zugriff zu erzielen.
Der Recompiler wird durch Bereitstellung einer TOML -Datei konfiguriert, um das Rekompiler -Verhalten zu konfigurieren. Dies ist das einzige Argument, das dem Recompiler vorgelegt wird. In der TOML geben Sie Eingangs- und Ausgabedateipfade an sowie optional bestimmte Funktionen, die Neukompilierung spezifischer Funktionen überspringen und einzelne Anweisungen im Ziel -Binäranweisungen überspringen. Es gibt auch geplante Funktionen, um Haken in die Recompiler -Ausgabe auszugeben, indem sie den TOML ( [[patches.func]]
und [[patches.hook]]
Abschnitte der verlinkten TOML unten addieren), aber dies ist derzeit derzeit unimplementiert. Die Dokumentation zu jeder Option, die der Recompiler bereitstellt, ist derzeit nicht verfügbar, aber ein Beispiel finden Sie TOML im ZEelda 64: Recompiled -Projekt hier.
Derzeit besteht die einzige Möglichkeit, die erforderlichen Metadaten bereitzustellen, indem sie eine ELF -Datei an dieses Tool weiterleitet. Der einfachste Weg, einen solchen Elfen zu bekommen, besteht darin, eine Demontage oder Dekompilierung des Zielbinärs einzurichten. Es wird jedoch Unterstützung für die Bereitstellung der Metadaten über ein benutzerdefiniertes Format geben, um die Notwendigkeit in Zukunft zu umgehen.
Dieses Tool kann auch so konfiguriert werden, dass im Modus "Einzeldateiausgabe" über eine Option in der Konfiguration TOML neu kompiliert wird. Dadurch wird alle Funktionen in der bereitgestellten ELF in eine einzelne Ausgabedatei ausgestrahlt. Der Zweck dieses Modus ist es, gepatschte Versionen von Funktionen aus dem Ziel binären Kompilieren zu kompilieren.
Dieser Modus kann mit den Funktionen kombiniert werden, die von fast allen Linken (LD, LLD, MSVC -Link.exe usw.) bereitgestellt werden, um Funktionen aus der ursprünglichen Recompiler -Ausgabe durch modifizierte Versionen zu ersetzen. Diese Linker suchen nur nach Symbolen in einer statischen Bibliothek, wenn sie nicht bereits in einer vorherigen Eingabedatei gefunden wurden. Daher führt die Umkompilierung der neu kompilierten Patches dem Linker vor der Bereitstellung der ursprünglichen Recompiler -Ausgabe dazu, dass die Patches Vorrang vor Funktionen mit denselben Namen haben Aus dem ursprünglichen Rekompilerausgang.
Dies spart eine enorme Zeitspanne, während Sie auf Patches für das Ziel -Binärer iteriert, da Sie den Neukompiler auf dem Ziel -Binärdatum umgehen und die ursprüngliche Neukompilierausgabe zusammenstellen können. Ein Beispiel für die Verwendung dieses einzelnen Dateiausgabemodus für diesen Zweck finden Sie im ZEelda 64: Neukompilierten Projekt hier, wobei das entsprechende Makefile verwendet wird, mit dem der ELF für diese Patches hier erstellt wird.
RSP -Mikrocode kann auch mit diesem Tool neu kompiliert werden. Derzeit gibt es keine Unterstützung für die Neukompilierung von RSP -Overlays, aber bei Bedarf kann dies in Zukunft hinzugefügt werden. Die Dokumentation zur Verwendung dieser Funktionalität wird in Kürze erfolgen.
Dieses Projekt kann mit CMake 3.20 oder mehr und einem C ++ - Compiler erstellt werden, der C ++ 20 unterstützt. Dieses Repo verwendet Git-Submodules. Stellen Sie daher sicher, dass klonend ( git clone --recurse-submodules
) oder initialisieren Submodules nach dem Klonen ( git submodule update --init --recursive
) rekursiv). Von dort aus ist das Gebäude mit jedem anderen CMake -Projekt identisch, z. B. cmake
im Zielbauordner und auf die Wurzel dieses Repo richten und dann cmake --build .
Aus diesem Zielordner.