Hack Age of Empires III, um die Einstellungen für die Shader-Qualität zu ändern

Anfang Mai 2020 - Wenn Sie wie ich sind, haben Sie durch Quarantäne Spiele wiederholt, die seit vielen Jahren nicht mehr gestartet wurden.

Und wenn Sie mir noch ähnlicher sind, liegt vielleicht irgendwo eine Diskette mit Age of Empires 3 herum. Vielleicht spielen Sie auf einem Mac, vielleicht haben Sie noch kein Upgrade auf Catalina durchgeführt und möchten Morgan Black befehlen.

Sie starten das Spiel, rufen das Hauptmenü auf und bemerken sofort, dass etwas nicht stimmt ... Das Menü sieht ekelhaft aus .


Wenn Sie sich fragen, was genau „ekelhaft“ ist, achten Sie auf das Wasser. Alles andere ist auch schrecklich, aber es ist weniger offensichtlich.


Also, Sie gehen in die Optionen, erhöhen alle Parameter auf das Maximum ... Aber das Spiel ist immer noch hässlich.

Sie bemerken, dass die Optionen " Shader-Qualität " verdächtig auf " Niedrig " gesperrt sind .

Versuch Nr. 1 - Hack-Parameter


An diesem Punkt beginnen Sie mit der Suche nach dem Ordner des Spiels, der angeblich irgendwo im Ordner "Dokumente" erstellt wurde, da das Spiel im Jahr 2005 war, und dann haben es alle getan.

Was suchen wir? Natürlich die Datei, in der die Parameter gespeichert sind. Die Menüoberfläche erlaubt es uns nicht, die Einstellungen zu ändern, aber wir sind schwierig, oder?

Wir finden das erforderliche XML, da das Spiel 2005 ist und es natürlich XML ist, wo wir auf die Option " optiongrfxshaderquality " stoßen , die auf 0 gesetzt ist. Es scheint, dass wir danach gesucht haben, also erhöhen wir den Wert auf 100, weil es nicht viel Qualität gibt. Hier stimme ich Ihnen zu.


Möglicherweise stellen Sie auch fest, dass dies eine schreckliche Verwendung von XML ist. Zum Glück hat er keine guten Verwendungsmöglichkeiten.

Mit uns zufrieden starten wir das Spiel. Leider hat sich nichts geändert. Wenn wir uns die XML-Datei noch einmal ansehen, sehen wir, dass der Parameter wieder auf 0 gesetzt ist. Ensemble Studios (möge es in Ruhe ruhen) sieht Ihren Einfallsreichtum mit Verachtung an.

Danach könnten wir aufgeben . Dies ist ein Mac, daher ist es unwahrscheinlich, dass die von Benutzern geschriebenen Patches gefunden werden (tatsächlich habe ich überprüft - es gibt alle möglichen zweifelhaften Lösungen, die möglicherweise funktionieren). Es dreht sich alles um die Grafik. Es scheint schwierig zu sein, das Problem zu beheben.

Aber wir werden nicht aufgeben. Weil wir uns daran erinnern, wie Alter 3 aussehen sollte. Wir erinnern uns, wie wir dieses schöne gewellte Wasser bewunderten. Diese Partikeleffekte. Diese riesigen Schiffe, die einfach nicht in den Rahmen passen. Und es ist nur so, dass die Kamera vergrößert wurde. Was habe ich gerade gedacht?

Versuch Nr. 1 - Daten hacken


Ganz am Anfang des Ordners in Dokumente befindet sich eine Protokolldatei. Das Spiel schrieb dort eine Grafikkarte und berichtete, dass es die Einstellung „generisches dx7“ gewählt hatte. Es heißt, dass Sie Intel GMA 950 installiert haben, das wirklich auf dem vorherigen Computer war.

Aber hier haben Sie Intel Iris Graphics 6100. Irgendwas stimmt wirklich nicht. Sie gehen davon aus, dass das Spiel die Grafikfunktionen des Computers ermittelt und diese mit einer Datenbank mit Grafikkarten vergleicht, anstatt die Funktionen der Karte selbst zu überprüfen. Denn genau das tun AAA-Entwickler, und wenn Sie an Open-Source-RTS gearbeitet haben , wissen Sie, wie dies geschieht.


Der Inhalt der Protokolldatei. Wenn Sie es lustig finden, dass Intel als 0x8086 bezeichnet wird, haben Sie den entsprechenden Artikel ausgewählt.

Durch Zufall finden Sie alte Patchnotizen , die Ihre Bedenken bestätigen. Sie schauen in den GameData-Ordner, aber es gibt nur .bar- und .xmb-Dateien , die größtenteils nicht lesbar sind. Sie suchen nach grep " Intel " und " dx7 ". Es passiert nichts. Mehrere Dateien löschen ... Es passiert nichts. Aber Sie wissen, dass AAA-Spiele einige Ressourcen an seltsamen Orten speichern können, und dies ist der Port von Windows-Spielen auf dem Mac, sodass hier alles sehr bizarr sein kann.

Letztendlich finden Sie die Datei "render.bar". Wenn Sie sie verschieben, stürzt das Spiel beim Start ab. Es ist nicht sehr klar, aber nicht schlecht. Wir öffnen die Datei in Hex Fiend , da Sie mit diesem Programm normalerweise etwas über Binärdateien lernen können. Und so stellt sich heraus. Ein Teil dieser Daten ist lesbarer Text. Einige von ihnen sind Shader. Aber die meisten Daten sind seltsamerweise durch Leerzeichen unterteilt ...


Hex Fiend-Schnittstelle Es ist minimalistisch, aber es funktioniert. Die ersten Bytes sind die Buchstaben "ESPN", bei denen es sich höchstwahrscheinlich um den .bar-Header handelt.

Sie rufen aus: "Offensichtlich ist dies UTF-16!" Das Spiel wurde für Windows erstellt, daher ist es logisch, dass es Text in UTF-16 speichert und fast die Hälfte einer 30-Megabyte-Datendatei verschwendet. Immerhin liebte Windows UTF-16 (obwohl dies nicht der Fall sein sollte ). Warum also nicht diese Codierung und ASCII-Shader in einer Datei verwenden? Es ist eine logische Idee!

(Eigentlich habe ich ungefähr einen Tag gebraucht, um es herauszufinden.)

Hex Fiend hat die Möglichkeit, Bytes wie UTF-16 zu analysieren, also suchen wir wieder nach dem Grep-String dx7. Es gibt mehrere Einträge. Wir ersetzen sie durch die dx9-Zeile, die auch in den Daten enthalten ist, speichern die Datei und starten das Spiel.

Ja Protokolle zeigen wieder nicht "dx9" an. Dies ist wahrscheinlich woanders eingestellt.

Versuchen wir etwas anderes. Angenommen, das Spiel erkennt Grafikkarten und ihre hexadezimalen Kennungen werden in den Protokollen gespeichert (in meinem Fall ist 0x8086 Intel und auch 0x2773). Wir suchen in Hex Fiend nach „8086“, wir finden etwas und ein Teil des Textes sieht vielversprechend aus. Die Intel 9-Serie ist die GMA 950-Serie, und Age 3 hat wahrscheinlich nicht sehr gut daran funktioniert.


Einige Nummern sind wie Kartenkennungen (2582, 2592); Wenn Sie sie ersetzen, ändert sich möglicherweise etwas. Wir haben trotzdem eine Kopie der Datei erstellt, damit Sie es versuchen können.

Leider ist dies auch eine Sackgasse. Tatsächlich untersuchen wir die falsche Datei. Zum Glück kann ich Ihnen das sagen, weil ich viel Zeit damit verbracht und zu viel versucht habe . Dieses verdammte Ding hat ungefähr 20 Stunden gedauert. Und dies zählt nicht die Zeit für alle Screenshots ... Die

gewünschte Datei heißt "DataP.bar", und wenn wir die ID mit Bedacht ersetzen, sehen wir etwas völlig Neues in den Protokollen:


Ich verstehe nicht ganz, warum dieses "generische dx7" nicht dasselbe ist wie im obigen Screenshot.

Natürlich sind wir im Spiel immer noch durch die "Niedrig" -Einstellungen eingeschränkt, das heißt, die Protokolle lügen nicht. Wir hatten auf ein Medium gehofft, aber leider .

Es scheint, dass mehr aus dem Hacken von Daten nicht erreicht werden kann. Es ist Zeit, die Werkzeuge ernst zu nehmen.

Versuch Nr. 2 - Hacken


Wenn also nichts gelingt, haben wir noch eine letzte Sache: die ausführbare Datei selbst zu ändern (das heißt, wie sie 2005 sagten, sie zu „knacken“). Es scheint, dass es heute einfach als Hacking bezeichnet wird, aber dieser Begriff wurde im Bereich der Wildpiraterie beibehalten (natürlich habe ich das persönlich nie getan).

Wir kommen zum interessantesten Teil des Artikels (ja, Sie haben bereits tausend Wörter gelesen, aber war es nicht neugierig?).

Disassembler Hopper


Zu diesem Zeitpunkt wird unser Tool ein Disassembler sein: eine Anwendung, die den Binärcode der ausführbaren Datei empfängt und in lesbaren (ha ha) Assembler-Code umwandelt. Das beliebteste davon ist IDA Pro, aber es ist wahnsinnig teuer und ich bin mir nicht sicher, ob es auf einem Mac funktioniert, also werde ich Hopper verwenden . Es ist nicht kostenlos (kostet 90 US-Dollar), kann aber mit fast allen erforderlichen Funktionen 30 Minuten lang frei verwendet werden.

Ich werde die Benutzeroberfläche nicht im Detail erklären, da im Hopper-Tutorial alles gut beschrieben ist, aber ich werde Ihnen sagen, was ich tun werde, und versuchen, genügend Screenshots zu machen, damit Sie etwas lernen können.

Zuerst müssen Sie sagen: Ich habe fünf Tage gebraucht, um das Problem zu lösen, und es wurden viele erfolglose Versuche unternommen. Grundsätzlich werde ich über erfolgreiche Schritte sprechen, also wird es magisch erscheinen. Aber es war schwierig . Machen Sie sich keine Sorgen, wenn Sie Probleme haben.

Und noch ein Hinweis: „Cracken“ ist meistens illegal und / oder wird für illegale Zwecke durchgeführt. Ich zeige ein eher seltenes Beispiel für eine ziemlich legitime Verwendung, die gleichzeitig völlig „weißer Hut“ ist, also ist hier alles in Ordnung. Ich gehe davon aus, dass dies streng genommen illegal ist / gegen die Endbenutzervereinbarung verstößt, aber offensichtlich ein Fall höherer Gewalt ist . Wie auch immer, zahlen Sie für das, was Sie mögen, und nur dafür, denn Anti-Konsumismus ist cool.




Ziehen Sie die Age 3-App, um sie zu öffnen. Wählen Sie einfach "x86" anstelle von powerPC, denn das Spiel ist etwas 2005.

Stufe 1 - Suche nach dem gewünschten Fragment


Überraschenderweise kann dies am schwierigsten sein. Wir haben einen zerlegten Code, aber wir haben keine Ahnung, wo wir suchen sollen. Ja, einige Spiele haben Debugging-Symbole, sodass Sie die Funktionsnamen lesen können, in unserem Fall jedoch nicht. Hopper gibt uns Namen wie "sub_a8cbb6", mit denen wir uns selbst befassen müssen.

Zum Glück haben wir einen Vorsprung: unsere Protokolldatei . Es ist sehr wahrscheinlich, dass beim Schreiben in das Protokoll Zeichenfolgenliterale verwendet werdenDas heißt, ASCII werden in der ausführbaren Datei geflasht. Wir können sie mit grep suchen, da wir sie durch keine Kompilierung entfernen können (sie können jedoch durch Verschleierung des Codes ausgeblendet werden. Glücklicherweise ist dies nicht geschehen.) Das Auffinden von Grep-String-Literalen ist normalerweise das erste, was sie tun, wenn sie ein Programm zerlegen. Geben Sie also "gefundener Anbieter" in das Suchfeld "Hopper" ein und es wird etwas gefunden:


Oh ja, String-Literale, ich verehre sie.

Dies an sich hat uns nicht so sehr vorangebracht. Da die Adresse dieser "Zeichenfolge von Literalen" fest codiert ist, können wir Links dazu finden. Hopper hat sie rechts grün hervorgehoben: "DATA XREF = sub_1d5908 + 1252." Durch Doppelklicken auf die Zeile gelangen wir zu unserer Helden- Sub_1d5908- Prozedur .


Hier finden wir den Assembler-Code. Es wird angenommen, dass es schwer zu lesen ist, aber es ist nicht. Das Schwierigste ist, es zu verstehen.

Standardmäßig verwendet Hopper die "Intel-Syntax", dh der erste Operand ist der Empfänger und der zweite die Quelle. In der hervorgehobenen Zeile sehen wir mov dword [esp+0x1DC8+var_1DC4], aXmlRenderConfi. Lass es uns ausmachen.

Unsere erste Zeile im Assembler


movIst ein MOV - Team für seinen auf x86 bekannt Turing-complete . Es verschiebt (tatsächlich kopiert) die Daten von A nach B, was im Wesentlichen bedeutet, A zu lesen und in B zu schreiben. Basierend auf dem Vorstehenden ist aXmlRenderConfidies A und dword [esp+0x1DC8+var_1DC4]dies ist B, der Empfänger. Schauen wir uns das genauer an.

aXmlRenderConfi- das ist der Wert, den Hopper uns hilft. Dies ist eigentlich ein Alias ​​für die Speicheradresse des String-Literal, auf das wir vor einigen Sekunden geklickt haben. Wenn Sie das Fenster teilen und den Code im Hex-Modus (der bevorzugte Modus für Hacker) betrachten, werden wir dort sehen 88 18 83 00.


Hopper hebt bequem die gleichen ausgewählten Fragmente in beiden Fenstern hervor.

Wenn der Flip-Wert korrekt ist, erhalten wir 0x00831888 - die Speicheradresse des String-Literal (in einem der oben gezeigten Screenshots wird sie sogar gelb hervorgehoben). Ein Rätsel ist also gelöst: Der Code führt MOV aus, dh er schreibt die Adresse 0x00831888.

Warum ist es so geschrieben 88 18 83 00, nicht wie 00 83 18 88? Dies geschah aufgrund der Bytereihenfolge - ein ziemlich verwirrendes Thema, das normalerweise wenig Einfluss hat. Dies ist eines der Dinge, die die Leute dazu bringen zu sagen, dass „Computer nicht funktionieren“. Für uns bedeutet dies, dass dieses Problem in allen Zahlen auftritt, mit denen wir heute arbeiten werden.

Sie haben vielleicht bemerkt, dass ich "Adresse aufschreiben" gesagt habe, und das stimmt: Wir kümmern uns nicht wirklich um den Inhalt, der sich als String-Literal mit einem Null-Byte am Ende herausstellte. Wir schreiben die Adresse auf, da sich der Code später auf diese Adresse bezieht. Dies ist ein Zeiger.

Was ist mit dword [esp+0x1DC8+var_1DC4]? Hier ist alles komplizierter. dwordbedeutet, dass wir mit "Doppelwörtern (Doppelwörtern)" arbeiten, dh mit 16 * 2 Bits. Das heißt, wir kopieren 32 Bit. Rückruf: Unsere Adresse ist0x00831888das heißt, 8 Hexadezimalzeichen, und jedes Hexadezimalzeichen kann 16 Werte haben, d. h. 4 Datenbits. Wir erhalten also 8 * 4 = 32. Von hier stammt übrigens die Notation „32-Bit“ für Computersysteme. Wenn wir 64 Bit verwenden würden, würde die Speicheradresse als 0x001122334455667788 geschrieben, wäre doppelt so lang und 2 ^ 32 mal größer, und wir müssten 16 Bytes kopieren. Wir hätten also viel mehr Speicher, aber das Kopieren des Zeigers würde doppelt so viel Arbeit erfordern, und daher sind 64 Bit tatsächlich nicht doppelt so schnell wie 32 Bit. Eine ausführlichere Erklärung des Begriffs „Wort“ finden Sie hier . Weil es natürlich komplizierter ist als meine Erklärung.

Was ist also mit dem Teil in eckigen Klammern? Die Klammern bedeuten, dass wir berechnen, was sich darin befindet, und es als Zeiger betrachten. Daher schreibt der MOV-Befehl in die aus den Berechnungen erhaltene Speicheradresse. Schauen wir uns das noch einmal genauer an (und keine Sorge, wenn Sie damit fertig sind, wissen Sie im Wesentlichen, wie man Assembler-Code mit Intel-Syntax liest).

espEs ist ein Register , das im Assembler einer Variablen am nächsten kommt. In der x86-Architektur gibt es viele Register, und verschiedene Register haben unterschiedliche Anwendungsmöglichkeiten, aber das ist uns eigentlich egal. Erfahren Sie hier mehr darüber .. Beachten Sie jedoch, dass es spezielle Register für Gleitkommazahlen sowie „Flags“ gibt, die für Vergleiche und ähnliche Operationen verwendet werden. Alles andere sind nur Hexadezimalzahlen, die Hopper ein wenig überarbeitet hat, um uns zu helfen. var_1DC4- Dies -0x1DC4können wir zu Beginn des Verfahrens oder im rechten Bereich sehen. Dies bedeutet , dass das Ergebnis der Berechnung [esp+0x1DC8+var_1DC4]wird [esp + 0x1DC8 — 0x1DC4]das entspricht [esp + 4]. Im Wesentlichen bedeutet dies "Nehmen Sie den 32-Bit-Wert des ESP-Registers, addieren Sie 4 und interpretieren Sie das Ergebnis als Speicheradresse."

Also wiederholen wir: Wir schreiben die Adresse des String-Literal in „esp + 4“. Dies sind korrekte Informationen, aber sie sind völlig nutzlos. Was bedeutet das?

Dies ist die Schwierigkeit beim Lesen von Assembler-Code: beim Parsen, was er tun soll. Hier haben wir einen Vorsprung: Wir wissen, dass er höchstwahrscheinlich etwas in das Protokoll schreibt. Daher können wir einen Aufruf einer Funktion erwarten, die in das Protokoll schreibt. Lass uns nach ihr suchen.


Im obigen Screenshot gibt es mehrere ähnliche Aufrufe sowie den Befehl call, der die Prozedur aufruft. Hopper spricht normalerweise darüber (ich weiß nicht, warum er es hier nicht getan hat), aber im Wesentlichen geben wir Argumente für den Aufruf der Funktion. Wenn Sie auf verschiedene Unterprogrammaufrufe klicken, finden wir etwas namens imp___jump_table___Z22StringVPrintfExWorkerAPcmmPS_PmmPKcS_. Dies ist der dekodierte Name der Funktion, und der Teil „Printf“ reicht aus, um zu verstehen, dass er wirklich etwas ausgibt. Es ist uns egal, wie sie das macht.

Geh einen Schritt zurück


Zu diesem Zeitpunkt haben wir uns völlig von dem entfernt, was wir getan haben: Wir haben versucht, das Spiel davon zu überzeugen, dass unsere Grafikkarte von 2015 leistungsstark genug ist, um das Spiel von 2005 zu starten. Fassen wir zusammen. Wir haben den Ort gefunden, an dem das Protokoll aufgezeichnet wird. Wenn wir Glück haben, finden Sie hier den Code, der die Parameter und Funktionen von Grafikkarten überprüft. Zum Glück hatten wir wirklich Glück (es besteht eine hohe Wahrscheinlichkeit, dass dieser Artikel sonst nicht gewesen wäre).

Um das Gesamtbild zu erhalten, verwenden wir die Hopper Control Flow Graph-Funktion, die den Code in kleine Blöcke aufteilt und Pfeile zeichnet, die verschiedene Übergänge anzeigen. Dies ist viel einfacher zu verstehen als bei gewöhnlichen Assemblern, bei denen oft alles durcheinander ist.


Der Aufruf des Protokolleintrags befindet sich mitten in einer furchtbar langen Funktion, da dies wiederum normalerweise in AAA-Spielen geschieht. Hier sollten wir nach anderen Hopper-String-Literalen und "Hinweisen" suchen, in der Hoffnung, etwas zu finden. Zu Beginn des Vorgangs befindet sich der Rest der Protokollierung. Im Folgenden finden Sie interessante Abschnitte zum Erzwingen der Parameter "dx7" und "Sehr hoch" sowie ein Problem mit der Version der Pixel-Shader, die wir haben.

Das Protokoll unserer "gehackten" Daten ergab, dass wir Pixel-Shader der Version 0.0 hatten und diese nicht mit den "High" -Parametern kompatibel sind. Dieser Code befindet sich in der unteren linken Ecke unserer Funktion. Wenn Sie genau hinschauen, können Sie etwas Merkwürdiges sehen (hervorgehoben von Ihrem bescheidenen Diener): Dies ist derselbe Code, der viermal für die Parameter "Sehr hoch", "Hoch", "Mittel" und "Niedrig" wiederholt wird. Und ja, ich habe mehrere Stunden gebraucht, um das zu finden.


Ich habe den Block, in dem "Pixel Shader Version 0.0 High" im Protokoll angezeigt wird, blau hervorgehoben. Ich habe ähnliche Teile mit anderen schönen Farben hervorgehoben.

Die einzigen Unterschiede sind, dass wir an verschiedenen Stellen „0x0“, „0x1“, „0x2“ und „0x3“ schreiben. Dies ähnelt verdächtig der oben untersuchten Parameterdatei und dem Parameter optiongrfxshaderquality (Sie verstehen dies möglicherweise noch nicht. Aber glauben Sie mir).

In diesem Stadium können wir versuchen, auf verschiedene Arten vorzugehen, was ich auch getan habe. Aber ich werde mich kurz fassen - wir werden das Notwendigste tun: Finden Sie heraus, warum die Pixel-Shader-Überprüfung fehlschlägt. Suchen wir nach einem Zweig. Der Ausgabeblock für das Protokoll ist linear, was bedeutet, dass er irgendwo darüber liegen sollte.


SEMVER-Fans sehen ein erweitertes Versionsformat: Gleitkommazahlen.

Und tatsächlich gibt es direkt darüber jbeinen Übergangsbefehl (denken Sie an „goto“). Dies ist ein " bedingter Sprung " und die Bedingung ist " wenn weniger ". Assembler-Ifs arbeiten mit Registerflags , über die ich oben kurz gesprochen habe. Es reicht aus, nur zu wissen, dass wir uns den obigen Befehl ansehen müssen: Wahrscheinlich setzt er das Flag. Oft ist dies ein Befehl cmpoder test, aber hier verwendet ucomiss. Das ist Barbarei. Aber sie googelt leicht: ucomiss . Dies ist ein Gleitkomma-Vergleichsbefehl, und dies erklärt, warum der Code hatxmm0: Dies ist ein Register von Gleitkommazahlen. Dies ist logisch: Die Protokolle geben an, dass unsere Version der Pixel-Shader "0.0" ist, was ein Gleitkommawert ist. Was befindet sich dann unter der Speicheradresse 0x89034c? Hexadezimaldaten sehen aus 00 00 00 40und können verwirrend sein. Aber wir wissen, dass wir Gleitkommazahlen erwarten sollten, also lassen Sie uns die Daten so interpretieren: das heißt 2.0. Dies ist ein logischer Gleitkommawert für die Version von Pixel-Shadern, die Microsoft tatsächlich in seiner High-Level-Shading-Sprache verwendet hatauf denen DirectX-Shader geschrieben sind. Und Age 3 ist ein DirectX-Spiel, obwohl der Port auf dem Mac OpenGL verwendet. Es ist auch logisch, dass 2005 Pixel-Shader der Version 2.0 erforderlich sind, um den High-Parameter zu aktivieren, sodass wir uns in die richtige Richtung bewegen.

Wie beheben wir das? Diese Vergleichsanweisung führt einen Vergleich mit dem Wert durch, xmm0der in der obigen Zeile angegeben ist : movss xmm0, dword [eax+0xA2A8]. MOVSS ist ein spezieller "Verschieben" -Befehl für Gleitkommaregister, und wir schreiben 32 Bit aus der Adresse, was das Ergebnis der Auswertung von eax + 0xA2A8 ist.

Vermutlich ist dieser Wert nicht 2,0, sondern 0,0. Lass es uns reparieren.

Echtes Hacken


Endlich sind wir bereit, zu den Parametern des High-Spiels zu gelangen. Wir wollen den „roten“ Pfad gehen und die gesamte Logik mit der „falschen Version von Pixel-Shadern“ überspringen. Dies kann erreicht werden, indem der Vergleich erfolgreich bestanden wird, dh indem die Übergangsbedingung umgekehrt wird. Wir haben jedoch noch einen Trick: Der Code ist so zusammengesetzt, dass wir beim Löschen des Übergangs den „roten“ Weg gehen. Ersetzen wir einfach den Übergang durch nopeine Operation, die nichts bewirkt (es ist unmöglich, Daten einfach zu löschen oder zur ausführbaren Datei hinzuzufügen, da ein Offset auftritt und alles kaputt geht).

Ich empfehle, aus CFG auszusteigen, da sonst Hopper verrückt wird. Wenn alles richtig gemacht ist, sehen wir im Hex-Fenster rote Linien.



Rot zeigt die Änderungen an, die wir vorgenommen haben. Wenn Sie zu diesem Zeitpunkt für Hopper bezahlt haben, können Sie die Datei einfach speichern. Da ich jedoch nicht bezahlt habe, öffne ich die ausführbare Datei in Hex Fiend, suche den gewünschten Bereich (indem ich den Bereich von Hopper auf die Registerkarte Suchen in Hex Fiend kopiere) und ändere ihn manuell. Seien Sie hier vorsichtig, Sie können alles kaputt machen, daher empfehle ich, die ausführbare Quelldatei irgendwo zu kopieren.

Nachdem Sie dies getan haben, starten Sie das Spiel, gehen Sie zu den Optionen ... Und Prost - wir können "Hoch" auswählen. Aber wir können nicht "mittel" wählen. Und wir können die hohen Parameter der Schatten nicht verwenden. Trotzdem machen wir Fortschritte!


Schauen Sie sich diese großartigen Reflexionen im Wasser an!

Verbesserungen


In der Tat können Sie dies am besten beheben. Unsere Lösung hat funktioniert, aber wir können es so machen, dass die Bedingung korrekt erfüllt ist: Das Spiel denkt, dass unsere Grafikkarte tatsächlich Shader der Version 2.0 hat.

Wie wir uns erinnern, lädt das Spiel den Wert an die Adresse eax+0xA2A8. Mit Hopper können Sie herausfinden, was ursprünglich dort aufgenommen wurde. Wir müssen einen Befehl finden, in dem 0xA2A8 als Empfängeroperand verwendet wird (ich habe 0xA2A8 gewählt, da es sich um einen ziemlich spezifischen Wert handelt und wir nicht sicher sein können, dass dasselbe Register nicht anderweitig verwendet wird). Hier ist die Hopper-Hintergrundbeleuchtung für uns sehr nützlich, da wir einfach auf 0xA2A8 klicken und nach den gelben Teilen in CFG suchen können.


Unser Sieg ist etwas höher: Wie erwartet schreiben wir den Wert einer Art Gleitkommaregister. Ich gebe zu, dass ich nicht wirklich verstehe, was vorher passiert (meine beste Vermutung: Das Spiel prüft die Möglichkeit der Texturkomprimierung), aber das ist nicht besonders wichtig. Schreiben wir „2.0“ und setzen die Arbeit fort.

Dazu verwenden wir die "Assemble-Anweisung" der Hopper-Anwendung und führen dann die gleichen Manipulationen in Hex Fiend durch.




Wir verwenden MOV anstelle von MOVSS, weil wir den üblichen Wert und nicht den speziellen Wert des Gleitkommaregisters schreiben. Außerdem ist es in direkter Bytereihenfolge, dh invertiert.

Sie werden feststellen, dass etwas Seltsames passiert: Wir haben das Team „gegessen“ jb. Dies geschah, weil der neue Befehl mehr Bytes benötigt als der vorherige, und wie gesagt, wir können keine Daten zur ausführbaren Datei hinzufügen oder daraus entfernen, um Offsets zu speichern. Daher hat Hopper keine andere Wahl, als das Team zu zerstören jb. Dies kann zu einem Problem werden, und wir können es beseitigen, indem wir den Befehl nach oben verschieben (schließlich müssen wir den xmm3-Wert nicht mehr schreiben). Dies wird hier funktionieren, da wir auf jeden Fall die Verzweigung auf dem richtigen Weg durchführen. Glücklich.

Wenn Sie das Spiel starten ... wird sich nicht viel ändern. Ich schlug vor, dass die Intel 9-Serie nicht schnell genug ist, also beschwert sich das Spiel. Dies ist kein Problem, denn in Wirklichkeit streben wir nach „Sehr hoch“.

Was war die mächtigste Karte um 2004? NVIDIA GeforceFX 6800. Lassen Sie uns das Spiel davon überzeugen, dass wir es installiert haben.

Den Hasenbau hinunter


Ja, dies ist ein völlig neuer Abschnitt.

Wir wissen, dass das Spiel Hex-Codes verwendet, um auf Grafikkarten zu verweisen, und dass es Informationen aus Datendateien empfängt. Wir wissen, dass dies in der Funktion geschehen sollte, die wir studieren (zumindest wäre es seltsam, wenn es nicht so wäre). So können wir den Code finden, der dies tut. Aber es kann immer noch schwierig sein, weil wir nicht genau wissen, wonach wir suchen sollen.

Wir haben einen Hinweis: Optionen verhalten sich seltsam, es gibt Niedrig / Hoch, aber kein Mittel. Vielleicht gibt es einen anderen Code, der all diese "Sehr hoch", "Hoch", "Mittel" und "Niedrig" behandelt. Und er ist tatsächlich in der gleichen Funktion. Er ist viel früher bei CFG und scheint interessant zu sein.


Wir können davon ausgehen, dass die Rohdaten in einer Art XML gespeichert sind, da dies sehr wahrscheinlich ist, da es mehrere andere XML-Datendateien gibt. Und XML besteht im Wesentlichen aus einer Reihe von Zeigern und Attributen. Daher sucht der Code hier höchstwahrscheinlich nach einigen XML-Attributen (und in der Tat wird im Folgenden "eine der IDs, Sehr hoch ..." erwartet, was der Überprüfung der Richtigkeit der Datei sehr ähnlich ist). Wir können uns eine ähnliche XML-Struktur vorstellen, in der Boolesche Werte die Qualität von Shadern bestimmen:


Es ist zu bedenken, dass dies eine sehr willkürliche Vermutung ist. Alles kann ein bisschen anders aussehen.

Ein sehr interessanter Teil wird auf der linken Seite des CFG angezeigt (für Sie kann es rechts sein, aber im Screenshot links): Wir sehen denselben var_CC, der im Code verwendet wird, der die Geräte-ID in das Protokoll schreibt. Das heißt ebp+var_CC, bezieht sich wahrscheinlich auf die ID unseres Geräts, 0x2773. Das Seltsame ist, dass es beim ersten "Check" kein String-Literal gibt, aber dies ist ein Hopper-Fehler: Die Adresse 0x0089da8e enthält hexadezimale Daten 69006400, die in UTF-16 für "id" stehen (tatsächlich konnte Hopper dies wahrscheinlich nicht verstehen UTF16). Und wir suchen nur nach einem Ausweis.

Weißt du was? Lassen Sie uns den Debugger ausführen und alles in der Realität überprüfen.

Spiel-Debugging


Öffnen Sie ein Terminalfenster und führen Sie lldb aus (geben Sie einfach lldb ein und drücken Sie die Eingabetaste). Zuerst müssen wir LLDB anweisen, Alter 3 zu folgen, und dann können wir das Spiel durch einen Anruf starten run. Das Spiel beginnt und wir bekommen nichts Nützliches.


Jetzt müssen wir einen Haltepunkt hinzufügen: Um unsere Annahmen zu bestätigen, möchten wir die Ausführung stoppen, wenn wir zu dem oben gezeigten Code gelangen. Wir haben keinen Quellcode, aber das spielt keine Rolle: Wir können einen Haltepunkt an der Speicheradresse festlegen. Und wir haben die Adresse im Code. Fügen wir einem MOV-Aufruf, der eine "ID" empfängt, einen Stopp hinzu. Die Adresse lautet 0x001d5e68. Um einen Haltepunkt hinzuzufügen, geben Sie einfach ein br set -a 001D5E68. Nach dem Start des Spiels wird es gestoppt und LLDB zeigt den zerlegten Code an (in der AT & T-Syntax nicht Intel, daher werden alle Operanden invertiert, aber wir sehen, dass dies der gleiche Code ist). Möglicherweise stellen Sie fest, dass LLDB in diesem Fall tatsächlich intelligenter als Hopper ist. Er sagt uns, dass wir Operationen ausführen, die sich auf die XML- und Printf-Ausgabe beziehen.



Lust auf einen Hacker?

Um fortzufahren, nehmen wir eine „Schrittanweisung“. Ein kurzer Befehl dafür
ist ni. Wenn wir uns mehrmals wiederholen, bemerken wir, dass wir wieder da sind, wo wir angefangen haben. Dies ist ein Zyklus! Und das ist völlig logisch: Wir iterieren wahrscheinlich um eine Art XML. Tatsächlich hat uns Hopper dies gezeigt - wenn wir der CFG folgen, werden wir einen (möglichen) Zyklus sehen.


Dies bedeutet:if (*(ebp+var_CC) == *(ebp+var_28)) { *(ebp+var_1D84)=edi }

Die Ausgangsbedingung ist, dass der Wert ebp+var_1D84ungleich Null ist. Und wir sehen einen interessanten Code-Parameter in dem von mir hervorgehobenen var_CC.

Fügen cmpwir diesem einen Haltepunkt hinzu und wir werden es herausfinden.



Links: Setzen Sie einen Haltepunkt auf dem CMP und setzen Sie die Ausführung fort. Rechts überwachen wir die Register und den Speicher.

Wir betrachten Register mit reg rund den Wert des Gedächtnisses mit mem read $ebp-0x28. eaxund enthält tatsächlich 0x2773, und der Wert im Speicher ist jetzt 0x00A0. Wenn Sie thread ces mehrmals ausführen , werden wir feststellen, dass bei der Suche nach Übereinstimmungen eine Iteration über verschiedene IDs erfolgt. Irgendwann stimmen die Werte überein, die Schleife wird beendet und das Spiel beginnt. Jetzt wissen wir, wie es geht.

Rufen Sie die Protokolldatei auf: Sie identifizierte sowohl das Gerät als auch den Hersteller. Wahrscheinlich gibt es irgendwo einen ähnlichen Zyklus für die Namen der Hersteller. Tatsächlich ist es sehr ähnlich und befindet sich in der CFG direkt über unserem Zyklus. Diese Schleife ist etwas einfacher und wir suchen nach der Zeichenfolge "Vendor".

Diesmal ist der Vergleich mitvar_D0. Diese Variable wird tatsächlich als Hersteller-ID verwendet. Wenn wir hier einen Haltepunkt setzen und alles überprüfen, sehen wir den bekannten 0x8086 bei eaxund gleichzeitig findet ein Vergleich mit 0x10DE statt. Schreiben wir es ein eaxund sehen, was passiert.



Beeindruckend.

Das heißt, 0x10DE ist NVIDIA. Tatsächlich könnte man dies verstehen, wenn man eine Datendatei studiert, aber es ist nicht sehr interessant. Die Frage ist nun: Wie lautet die Kennung des GeforceFX 6800? Wir können LLDB verwenden und einfach jede Kennung überprüfen, aber es wird einige Zeit dauern (es gibt viele davon, ich habe versucht, die richtige zu finden). Schauen wir uns diesmal also render.bar an.


Angenommen, dies ist "XMB", die in Age 3 verwendete binäre Darstellung von XML.

Diese Datei ist ziemlich chaotisch. Nach den Geforce FX 6800-Tags werden jedoch mehrere Bezeichner angezeigt. Höchstwahrscheinlich brauchen wir einen von ihnen. Versuchen wir es mit 00F1.

Der einfachste Weg, dies zu testen, besteht darin, LLDB zu verwenden und die gewünschten Haltepunkte festzulegen. Ich werde diesen Schritt überspringen, aber ich werde sagen, dass 00F1 (zum Glück) aufgetaucht ist.

In dieser Phase müssen wir die Frage beantworten: "Wie kann diese Änderung dauerhaft gemacht werden?" Es scheint, dass es einfacher sein wird, die Werte var_CCund var_D0auf 0X00F1 und 0X10DE zu ändern . Dazu müssen wir nur Platz für den Code schaffen.

Eine einfache Lösung wäre, einen der Protokolldatensatzaufrufe durch einen NOP zu ersetzen, beispielsweise einen Subsystemaufruf. Dies gibt uns mehrere zehn Bytes frei, und dies ist sogar mehr als nötig. Lassen Sie uns all dies auswählen und durch NOP ersetzen. Dann müssen wir nur noch Informationen mit den MOV-Variablen Assembler mov dword [ebp+var_CC], 0xF1und schreiben mov dword [ebp+var_D0], 0x10DE.



Vorher und nachher

Lassen Sie uns das Spiel starten. Schließlich haben wir etwas erreicht: Sie können zwischen Niedrig, Mittel oder Hoch wählen und Hohe Parameter für Schatten aktivieren.


Aber wir können Very High immer noch nicht aktivieren, und das ist traurig. Was ist los?


Ah, verstanden.

Wie sich herausstellte, sollte der Geforce FX 6800 Pixel-Shader der Version 3.0 unterstützen, und wir stellen nur die Version 2.0 ein. Jetzt ist es für uns einfach, das Problem zu beheben: Schreiben Sie einfach 3.0-Gleitkommazahlen ein ebp+0xA2A8. Dieser Wert ist 0x40400000.

Schließlich ist das Spiel nicht mehr launisch und wir können sehr hohe Einstellungen genießen.


Seltsam, aber es sieht ganz anders aus. Die Farben sind viel weniger lebendig und es ist ein bereichsabhängiger Nebel aufgetreten. Es gibt auch ein kleines Problem mit Schattenkonflikten (es ist auch in den obigen Screenshots zu sehen, dies liegt an den Parametern für hohe Schatten, nicht an der Qualität der Shader), die ich noch nicht lösen konnte.

Und dies ist das Ende unserer Reise, danke fürs Lesen.

Außerdem werde ich Beispiele geben, wie jeder der Parameter im Spiel aussieht, da alle online gefundenen Screenshots schrecklich oder unvollständig sind.

Die Grafiken auf Medium sind durchaus akzeptabel, leiden jedoch unter einem Mangel an Schatten. Soweit ich das beurteilen kann, fügt Very High größtenteils eine völlig andere LUT hinzu (eine gute Erklärung für diesen Begriff finden Sie im Abschnitt Farbkorrektur dieses Artikels.), Nebel in der Ferne und vielleicht sogar ein ganz anderes Beleuchtungsmodell? Das Bild ist sehr unterschiedlich und ziemlich seltsam.





Von oben nach unten: Sehr hoch, Hoch (mit Optionen für hohen Schatten), Mittel (nur mit aktiviertem Schatten), Niedrig (mit deaktiviertem Schatten).

Andere nützliche Links


Der Blog, auf den ich oben verlinkt habe, enthält eine wunderbare Analyse moderner Rendering-Pipelines: http://www.adriancourreges.com/blog/

Rate 0 AD ist ein Open-Source-RTS-Spiel des gleichen Genres: https://play0ad.com . Sie braucht Entwickler.

Wenn Sie mehr über das XMB-Format erfahren möchten, können Sie dessen Code in 0 AD lesen . Ich habe es nicht überprüft, bin mir aber ziemlich sicher, dass dies das gleiche Format ist, da die Person, die den XMB-Decoder für Age 3 in Age of Empires Heaven geschrieben hat , diese Dateien 2004 auch im 0 AD-Quellcode implementiert hat. Dies wird also eine unterhaltsame Lektion in Code-Paläontologie sein. Vielen Dank für die ganze Arbeit, Ykkrosh.

Ich erinnere mich genau, dass ich ein ausgezeichnetes Tutorial über die Verwendung von Hopper gelesen habe, aber jetzt kann ich den Link nicht finden. Wenn jemand weiß, wovon ich spreche, dann lass den Link fallen.

Am Ende möchte ich das Cosmic Frontier-Projekt erwähnen: ein Remake der klassischen Escape Velocity-Serie (mehr von Override, aber es ist auch mit Nova kompatibel). Dies ist ein erstaunliches Spiel, und es ist sehr traurig, dass nur wenige Leute es wissen. Jetzt haben die Entwickler eine Kampagne auf Kickstarter gestartet, und der Hauptentwickler hat einen sehr interessanten Entwickler- Blog , in dem über die Verwendung von Hex Fiend zum Reverse Engineering von Datenformaten des Originalspiels gesprochen wird. Dies ist eine großartige Lektüre. Wenn Sie meinen, mein Artikel war verrückt. dann in einem der Beiträge sie:

  • Wir haben den klassischen Mac-Emulator gestartet, um mithilfe der längst veralteten APIs eine verschlüsselte Datendatei zu lesen.
  • Reverse Engineering-Verschlüsselung aus dem Spielcode
  • Sie verwendeten einen Dateisystem-Hack, um auf dieselben Daten auf einem modernen Mac zuzugreifen.

Ja, das sind wirklich leidenschaftliche Menschen, und ich hoffe, dass ihr Projekt erfolgreich abgeschlossen wird, denn EV Nova ist mein Lieblingsspiel.

All Articles