HearthstoneCalc – Injecting

Der Werdegang von HearthstoneCalc hat einen eigenen Post verdient, in welchem dann weitere Ansätze (Monte Carlo Tree Search) vorgestellt werden. In diesem Blogeintrag geht es allerdings um das Injecten und Daten abrufen aus Hearthstone. Denn ohne Daten aus dem Spiel müssen wir HearthstoneCalc garnicht mehr weiterentwickeln.

Uns wird es zum Glück leicht gemacht. Hearthstone ist mit der Engine Unity3D geschrieben. Für Blizzard hat das den großen Vorteil, das Spiel einfach auf mobile Geräte (Tables und Smartphones, Android und iOS) portieren zu können. Für uns hat es den großen Vorteil, dass Unity .NET (genauer gesagt: Mono) verwendet. Und da die Binaries noch nicht einmal obfuscated vorliegen, haben wir ein relativ einfaches Spiel.

Der für Unity relevante Code wird in der Assembly-CSharp.dll gespeichert. Die Anzahl der Klassen überschreitet alles, was ich bisher gesehen habe. Dementsprechend fällt es am Anfang schwer, uns zurecht zu finden. Auch muss man sich zunächst an die Unity-Eigenarten gewöhnen: Jedes Objekt ist eine Entity, die wiederrum mehrere Unterentities haben kann. Oftmals haben Klassen Unterklassen, enthalten viel zu viel Renderlogik oder scheinen nie erzeugt zu werden.

GameStateDiagDaher gilt zu klären: Welche Daten müssen wir alle Abrufen? Die Daten aus dem Spiel müssen in die Strukturen von HearthstoneCalc gebracht werden. Die wichtigste und größte Struktur ist hierbei die GameState-Klasse (Links). Sie enthält das Spielfeld mit den Monstern, die Karten des Spielers und die Infos über die jeweiligen Helden. Natürlich haben die Helden weitere Unterklassen, wie z.B. die ausgerüstete Waffe oder die gesetzen Geheimnisse. Auch Monster sind inzwischen komplexer geworden, denn sie können verschiedene Buffs besitzen und auf andere umliegende Minions Einfluss ausüben.

Die Klassen aus Hearthstone können wir nicht einfach übernehmen, denn sie enthalten viel zu viele Informationen. Diese Klassen sind nicht dafür ausgelegt, Millionen möglicher Boardzustände zu speichern. Zu diesem Problem werde ich nächsten Post über HearthstoneCalc noch mehr sagen.

Der große Vorteil von Mono ist natürlich, dass wir realtiv einfach .NET Code ausführen können. .NET Code in einem Programm ausführen? Klingt doch sehr nach CLR-Hosting!? Leider ist das nicht so einfach, denn die Unity-Klassen mögen es nicht, wenn sie außerhalb von mscorlib.dll Thread aufgrufen werden. Versucht man eine Unity-Funktion direkt aufzurufen, so erhält man folgende Fehlermeldung zu gesicht:

System.Security.SecurityException: ECall methods must be packaged into a system module

Anscheinend sind alle Unity-Methoden auf diesem Wege Threadsicher gemacht. Hearthstone ist nicht das erste Unity-Projekt das von Gamehackern unter die Lupe genommen wird. Kluge Köpfe auf Unknown Cheats haben sich viel mit Rust-Hacks beschäftigt und eine Loader-Methode entwickelt. Die Idee dahinter ist einfach: Man injectet eine native DLL, die über die mono.dll-Exports Funktionen wie „mono_runtime_invoke“ abruft. Nun sucht sich die injectete DLL denjenigen Thread, der am ältesten ist (GetThreadTimes) und markiert ihn als MainThread. Nun kommen Windows Debugger Funktionen zum Einsatz, welche den Thread anhalten, den EIP (Instruction Pointer) auf unseren Mono-Load-Code ändern und den Thread wieder fortsetzen. Damit wird der Main-Thread unseren Loader-Code ausführen. Die .NET Assembly wird also im Main-Thread geladen und von da an können wir tun und lassen was wir wollen. Und wenn wir unsere .NET DLL nun mit der Assembly-CSharp.dll referenzieren, haben wir Zugriff auf alle internen Klassen von Hearthstone. Das Codespiel zu der oben beschriebenen Technik gibts hier.

Fangen wir mit Basics an: Das abrufen des Spielers und dessen Karten.

// Wir rufen den aktuellen (staischen \o/) GameState ab
var gameState = GameState.Get();
 
// Wenn dieser gültig ist
if (gameState != null)
{
    // eigenen Spieler abrufen
    var currentPlayerId = gameState.GetFriendlyPlayerId();
    var friendlyPlayer = gameState.GetPlayer(currentPlayerId);
 
    // Wir rufen die "Handzone" und davon die Karten ab
    String cardIds = "";
    // Und formatieren sie ein wenig
    foreach (var t in friendlyPlayer.GetHandZone().GetCards())
        cardIds += t.ToString() + ",";
 
    MessageBox(IntPtr.Zero, String.Format("Name: {0} CountCards: {1}. Cards: {2}", friendlyPlayer.ToString(), friendlyPlayer.GetHandZone().GetCards().Count, cardIds), "Player", 0);
}

Ingame sieht die Messagebox dann so aus:

tempHearthstone

Mit diesen Daten kann man doch Arbeiten! Zuzugegeben, die Karten sind das einfachste was man abrufen kann. Spannend wird es bei Minions und Buffs, aber das kommt nächstes mal 😉

0

5 Comments

    • Antworten Easysurfer |

      Gute Frage. Habe schon weiter gemacht, aber habe noch aktuell ein paar Probleme die den nächsten coolen Blogpost verzögern. Ich muss z.B. die Zugmöglichkeiten optimieren (bei 5 eigenen und 4 gegernischen Minions über 3 Millionen Angriffsmöglichkeiten) und viele Karten implementieren …

      0
      • Antworten Flo |

        Kannst du da nicht ggf. einfach „Well Known“ Decks von einer Website benutzen und nicht „alle“ ? Das würde die Angriffsmöglichkeiten doch deutlich verringern oder?

        0
  • Antworten Enrico Vogt |

    Sehr cooler Artikel, bin heute Mittag über deinen Blog gestolpet. Klasse Beiträge!

    Ich selber hab zwar keine Ahnung von Hacking oder Reverse Engineering aber klingt auf jeden Fall alles sehr spanned.

    Klasse. Weiter so!

    0
  • Antworten Jup |

    Jap wie Enrico habe ich keine Ahnung von was du da machst (Programmieren längst aufgegeben) aber es kling alles sehr spannend!

    0

So, what do you think ?