Reverse Engineering und Verzögerung von "Kosaken"



In dem berühmten Spiel "Cossacks: War Again" gibt es einen Fehler, der das Vergnügen eines Netzwerkspiels auf Null reduziert: Die unmenschliche Geschwindigkeit des Spielprozesses auf modernen Computern. Gleichzeitig hat die Änderung der Spielgeschwindigkeit in den Einstellungen, die im Einzelspielermodus einwandfrei funktioniert, keinen Einfluss darauf, was im Spiel über das Netzwerk geschieht. Dieses Thema wird in vielen Foren diskutiert, aber die beliebtesten Tipps sind:

  1. Laden Sie künstlich den Kern des Prozessors, auf dem das Spiel läuft
  2. Führen Sie ein Spiel in einer virtuellen Maschine mit begrenzten Ressourcen aus
  3. Spielen Sie nicht im lokalen Netzwerk, sondern im Internet - es gibt mehr Verzögerungen

Die ersten beiden Optionen führen dazu, dass das Spiel langsam ist, aber mit Ruckeln. Die Klangqualität sinkt ebenfalls. Die dritte Option ist überhaupt kein Kommentar.

Runterkommen


Suchen Sie zuerst mit der Cheat Engine nach der Geschwindigkeitseinstellung . Dies sollte entweder ein bestimmter Multiplikator sein, der direkt proportional zur Position des Schiebereglers im Einstellungsmenü ist, oder umgekehrt proportional zum Intervall. Diese Speicherzelle befindet sich recht schnell:



Dieses Intervall ist bei maximaler Geschwindigkeit 0, und die für mich angenehme Spielgeschwindigkeit entspricht Intervall 20. Das Ändern des Werts während des Spiels wirkt sich unmittelbar auf die Geschwindigkeit des Spielvorgangs aus. Mal sehen, was wir im Netzwerkspiel haben. Wir booten, ändern den Wert in der Cheat Engine und ... nichts. Nur die Position des Schiebereglers in den Einstellungen ändert sich. Okay, mal sehen, wo dieses Intervall abgearbeitet wird. Cheat Engine zeigt nur zwei benachbarte Adressen an, auf denen die Zelle gelesen wird:



Schauen wir uns die Auflistung des eingebauten Disassemblers an: Auf



Anhieb können wir sagen, dass das Intervall verdoppelt und mit etwas verglichen wird, und wenn es etwas weniger als das verdoppelte Intervall ist, wird irgendwo zurück gewechselt. Nun, wir starten das tatarische Programm, das wir für solche Dinge lieben, und sehen dieses Bild ganz am Ende einer langen Funktion:

Nach einigem Graben finden wir Folgendes heraus:

  • dword_718410 - Intervallwert gemäß Geschwindigkeitseinstellung
  • sub_54C1BE - Prozedur, die GetTickCount ausführt
  • dword_83B1A4 - Ergebnis des vorherigen GetTickCount
  • loc_4D1ABF - eine Unterprozedur , die die Differenz zwischen zwei GetTickCount-Ergebnissen vergleicht und zurückspringt, wenn die Differenz kleiner als das Intervall ist

Auf der Suche nach einem Fehler


Wir setzen irgendwo im Unterprozess des Vergleichs mit dem Intervall einen Haltepunkt. Wir starten ein einzelnes Spiel und fliegen sofort in den Debugger. Wir starten das Spiel im Netzwerk - der Haltepunkt funktioniert nicht. Außerdem funktioniert es immer, wenn Sie einen Haltepunkt ganz am Anfang der Funktion setzen. Es stellt sich heraus, dass im Mehrspielermodus die Intervallprüfung nicht bewusst durchgeführt wird. Das einzige, was bleibt, ist klein: Finden Sie die für diese Funktion verantwortliche Verzweigung und ändern Sie sie so, dass die immer benötigte Verzweigung mit der Unterprozedur loc_4D1ABF immer ausgeführt wird.

Wir werden von unten nach oben gehen. Setzen Sie zunächst einen Haltepunkt im Unterprogramm loc_4D1A9C. Bingo! In einem Einzelspielerspiel ist die Variable word_611B60 immer gleich 1, sodass die Sprungbedingung nicht erfüllt ist und die Steuerung zuerst an loc_4D1AAA und von dort an unser Unterverfahren übertragen wird. Beim Spielen im Netzwerk ist die Variable word_611B60 immer gleich 2, was zu einem sofortigen Vorwärtssprung zu loc_4D1AE6 und zum Ende der Funktion führt. Damit das Spiel die Kontrolle immer mit der Unterprozedur loc_4D1ABF an den Zweig überträgt, ist es ausreichend, die Vergleichsanweisung cmp edx, 2 durch cmp edx, 3 zu ersetzen . Wie zwei Bytes auf Asphalt!


Nicht so einfach


Jetzt funktioniert die Geschwindigkeitseinstellung in einem Netzwerkspiel. Leider ist eine Fliege in der Salbe nicht verschwunden: Im Laufe der Zeit beginnt einer der Spieler, die Scrollgeschwindigkeit und die Geschwindigkeit der Wasseranimation erheblich zu erhöhen. Nach einiger Zeit verschwindet der Effekt und erscheint im anderen Player. Gleichzeitig ist die Geschwindigkeit der verbleibenden Spielvorgänge für beide gleich und entspricht der in den Einstellungen festgelegten Geschwindigkeit.

Ich konnte den Grund für dieses Verhalten nicht herausfinden, aber es gab einen starken Verdacht auf die loc_4D1AAA-Unterprozedur, die ProcessMessages verursachte. Anscheinend wurde dieses Spiel nicht zum Spielen über das Netzwerk benötigt. Vielleicht war es gerade wegen des oben beschriebenen seltsamen Verhaltens, dass dieses Teilverfahren umgangen wurde? In jedem Fall werden wir versuchen, es aus der Verzweigung auszuschließen, wobei nur die loc_4D1ABF-Unterprozedur für uns nützlich bleibt.

Wir bekommen den Rechner


Also, was müssen wir tun:

  1. Ersetzen Sprungbedingung in subprotsedure loc_4D1A9C kurzen geraden Sprung in subprotseduru loc_4D1ABF
  2. Ändern Sie den Jumpback-Offset in der Unterprozedur loc_4D1ABF , um ihn für sich selbst zu sperren und nicht in loc_4D1AAA zu gelangen
  3. Entfernen der zweiten Sprungbefehle in der loc_4D1AAA , befindet sich in einem Block unmittelbar nach subprotsedury loc_4D1ABF

Mit dem ersten Absatz ist alles klar: Der Short-Jump-Operationscode ist EB , und der Offset, den wir benötigen, wird durch Subtrahieren der Adresse des Bytes, das der Sprunganweisung folgt, von der Adresse des Starts der Unterprozedur loc_4D1A9C bestimmt.

Der dritte Absatz ist noch einfacher: Ersetzen Sie die Sprunganweisung durch zwei Nopps .

Der zweite Punkt erfordert die Berechnung des Offsets für einen kurzen Rücksprung. Glücklicherweise bin ich auf einen Artikel gestoßen, der den Algorithmus für diese Aktion klar beschreibt, nämlich: Subtrahiere die Zieladresse von der Adresse des nächsten Byte-Sprungbefehls, subtrahiere 1h , übersetze die Zahl in binäre Form (in Byte-Größe), invertiere und übersetze wieder in hexadezimal das System. Die resultierende Zahl ist der Offset, den wir benötigen.

Na dann meine Herren. Patch!





Ergebnis


Öffnen Sie die geänderte Datei in demselben geliebten Programm und sehen Sie das folgende Bild:



Der Block, der den Aufruf von ProcessMessages enthält, wird niemals ausgeführt. Nachdem wir die Prüfung für ein Mehrspielerspiel in der Unterprozedur loc_4D1A9C deaktiviert haben, wird die Steuerung mit einem GetTickCount-Aufruf an unsere Unterprozedur übergeben und mit dem Intervall verglichen. Wenn die Differenz kleiner als das Intervall ist, springt die Unterprozedur an den Anfang von sich selbst zurück, bis das Intervall eingehalten wird.

Jetzt verhält sich das Spiel so, wie es sollte. Die Spielgeschwindigkeit entspricht der niedrigsten Geschwindigkeit unter den Spielern, die Synchronisation ist nicht unterbrochen. Die Bildlaufgeschwindigkeit ist ebenfalls anpassbar.

Nachwort


Da dies meine erste Erfahrung im Reverse Engineering und in der Arbeit mit Assemblern ist, ist dies höchstwahrscheinlich nicht die eleganteste Lösung. Die Wurzel des Problems liegt in der Verwendung der Funktionen QueryPerformanceFrequency und QueryPerformanceCounter, auf denen das Timing des Spiels basiert. Diese Funktionen werden beim Erstellen eines neuen Spiels einmal aufgerufen und geben den Ton für alle nachfolgenden Berechnungen mit GetTickCount an . Leider konnte ich diesen Teil des Programms nicht richtig beeinflussen, da ich zu faul war und beim Aufruf der oben genannten Funktionen eine seltsame Arithmetik auftrat .

UPD: Bei sorgfältiger Analyse wird schnell klar, dass QPF und QPC funktionierenmissbraucht. Ihre Ergebnisse werden zusammengefasst und QPC wird anschließend nirgendwo anders aufgerufen. Eine Variable, die das Ergebnis speichert, wird anschließend verwendet, bevor Funktionen aufgerufen werden, die mit Zeichenfolgen arbeiten. Daher werden QPF und QPC hier höchstwahrscheinlich nur als PRNs beim Erstellen einer zufälligen Karte und / oder eines Kartendateinamens verwendet.

Referenzen
  • Ein Artikel über Habre über das Reverse Engineering des Batman-Spiels, der mich zum Handeln veranlasste und ein paar Ideen gab.
    Danke, ID_Daemon !
  • Ein Artikel über die Berechnung von Offsets für kurze Sprünge in Assembler



UPD: Auf Wunsch von VRV wurde auch die über Steam erhältliche Version von "Cossacks" gepatcht. Die Diskussion, die er erstellt hat, ist im LCN- Forum .

Patcher für verschiedene Versionen der ausführbaren Cossacks-Datei finden Sie hier .

Jetzt auch beliebt: