In diesem Teil möchte ich auf das Auslesen von Strukturen aus dem Speicher und meiner Art von Management dieser Klassen eingehen. Am Ende wird noch die Klasse MemoryHelper angeschnitten, welche uns einiges an Abeit ersparen wird
Bisher haben wir eine Klasse, die Clientinfo Klasse, angeschaut. Diese können wir jetzt direkt verwenden und darauf zugreifen. Doch was ist, wenn wir eine zusätzliche Variable für Clientinfo brauchen oder sogar ein Referenz auf eine andere Klasse? Hier ein Beispiel wie es dann aussehen könnte…
public class Gamemanager { ClientInfo[] arClientinfos; // Jetzt fällt auf, wir würden auch noch gerne die Namen in einem einzelnen Array haben String[] arNames; // Und jetzt wollen wir noch jeder ClientInfo-Klasse eine ID zuordnen: Int[] ClientinfoIDs; }
Das Chaos wäre nach min. 2 weiteren dieser Sachen komplett. Daher hab ich mich entschlossen das ganze über OOP und sog. Container-Klassen zu lösen. Ich weiß nicht ob das einem Design-Pattern entspricht, aber ich vermute mal stark
Quelle : http://easysurfer.back2hack.cc
[spoiler]
Die Grundidee dahinter ist folgende: Clientmanager, Entity und alle weiteren Klassen, die aus dem Speicher gelesen werden, erben von der BaseStruct<T>, wobei T eine generetische Klasse ist. Diese BaseStruct enthält eine Inztanz von T (der Klasse des Types T), einen Offset und eine RefreshData Funktion. Und in der Praxis sieht das ganze dann so aus:
public abstract class BaseStruct where T : class // T sollte ein Klassentyp sein { public IntPtr pStructOffset; // Das ist der Offset zu dem jeweiligen Speicherabschnitt der Klasse public int iStructSize; // Die Größe der Struktur (wird nicht wirklich benötigt) public T Instance; // Eine Instanz der Klasse T public BaseStruct(IntPtr pOffset) // Im Konstruktor wird ein Offset für die Klasse übergeben -> aus dem wird immer gelesen { this.iStructSize = Marshal.SizeOf(typeof(T)); // Die Größe wird berechnet this.pStructOffset = pOffset; // Der Offset wird gesetzt this.RefreshData(); // Und die Daten werden zum ersten mal abgerufen } public virtual T RefreshData() // Dieses wird jeden Frame aufgerufen um die Daten der Instanz zu updaten { this.Instance = MemoryHelper.GetClassFromAddress(pStructOffset); // MemoryHelper Klasse ;-) return Instance; } }
Die Klasse hält also eine Instanz des Types T und aktualisiert diese jedes mal bei RefreshData. Der Rest sollte soweit klar sein und die MemoryKlasse wird auch noch besprochen werden
Das Vorteil dieses Prinzips ist, dass wir z.B. eine Container-Klasse ClientInfoContainer erstellen können, diesen Container von BaseClass<Clientinfo> ableiten und weitere Infos mit in den Container packen. Und genau das werden wir jetzt machen:
public class ClientinfoContainer : BaseStruct { public int iClientinfoID; // Eine zusätzliche Variable (ist im echten SRC nicht so, nur zur Verdeutlichung) public ClientinfoContainer(int Index) : base((IntPtr)(0x8EB254 + (Index * 0x52C))) // Offset errechnet sich aus BASE+Index*Size { } } public class RefdefContainer : BaseStruct { public RefdefContainer() : base((IntPtr)0x85EFB8) { } }
Dieses sind zwei Container-Klassen aus meinem Source-Code. Wie ihr sehen könnt ist ClientinfoContainer von BaseStruct<Clientinfo> abgeleitet, hat also die ClientInfo Klasse “in sich”. Nun können wir diesen Container weiter mit Werten füllen, welche nicht zu den Daten zählen, die aus dem Memory gelesen werden. Solch ein Container können wir jetzt bequem als Member der PlayerManager-Klasse hinzufügen und müssen nur jeweils “RefreshData()” aufrufen.
Da wir uns gerade schon die Vorteile von genetischen Klassen angeschaut haben, verwenden wir das doch in der MemoryHelper Klasse. Diese Klasse wird uns Funktionen liefern, Klassen aus dem Speicher zu lesen und parsen. Zudem wird diese Klasse auch Funktionszeiger aus dem Speicher holen können und zu Delegates machen.
public class MemoryHelper { // Not rly needed, but still good to have^^ [DllImport("kernel32.dll")] public static extern IntPtr GetModuleHandle(string lpModuleName); // Diese Funktion brauchen wir nicht, da wir mit absoluten Adressen arbeiten, nicht abhängig von ModulOffsets public static IntPtr GetOffsetForModule(String sModuleName, UInt32 iOffset) { IntPtr pModule = GetModuleHandle(sModuleName); return (IntPtr)(pModule.ToInt32() + iOffset); } // [Gets the class from an absolute address] // Diese Funktion liefert uns die Klasse vom Type T aus dem Offset pAddress zurück. // Dabei wird die Marshal.PtrToStructure Funktion verwendet. public static T GetClassFromAddress(IntPtr pAddress) where T : class { return Marshal.PtrToStructure(pAddress, typeof(T)) as T; // as T = Das Objekt wird als Type T behandelt } // [Gets the function pointer from an absolute address] // Diese Funktion liefert uns eine Delegate des Types T aus dem Offset pAddress zurück public static T GetFunctionFromAddress(IntPtr pAddress) where T : class { return Marshal.GetDelegateForFunctionPointer(pAddress, typeof(T)) as T; } }
Die Klasse ist soweit sonst selbsterklärend, die Marshal-Funktionen nehmen uns ja schon die meiste Arbeit ab
Wie diese MemoryHelper-Klasse für Strukturen genutzt wird, haben wir ja schon oben gesehn. Bei Delegates ist das etwas anders, und dieses wird unser Thema für den nächsten Teil sein!
Greez Easy
[/spoiler]