• Level: Experten
  • Tools: Texteditor
  • Zeitaufwand: ca. 15 min

Funktionen hooken

Dieses Thema ist ein extrem heikles Thema, denn das hooken ist in nur sehr wenigen Fällen zu verwenden und wenn, dann nur unter äusserster Vorsicht.
Das Hooken von Funktionen dient einzig dem Zweck, bestimmte, meist eigene Abläufe, zusätzlich in die bereits vorhanden Funktion einzubauen. Dabei handelt es sich bei der Standardfunktion meist um eine vorgegebene Funktion von Blizzard, welche man nicht direkt verändern kann/will/sollte. Das Hauptproblem beim hooken ist allerdings das man es sauber machen muss, da sonst gravierende Fehler im Spiel auftreten können.
Um dies zu verhindern, kann man entweder eine Bibliothek wie Sea verwenden oder es unter strengsten Voraussetzungen selber machen. Wir wollen in unserem HowTo die eigene Handarbeit durchgehen und aufzeigen auf was man insbesondere zu achten hat.
Natürlich ist es sicherer und einfacher Sea zu verwenden, wir wollen allerdings eine Lösung ohne Abhängigkeit zu einem anderen Mod aufzeigen. Wer sich trotzdem mit Sea der Problematik annähern will, sollte sich deren Dokumentation ansehen. siehe Sea

Grundlagen

Wir benötigen in jedem Fall einen XML-Abschnitt, welche einen OnLoad-Handler mit einer OnLoad-Funktion und einen OnEvent-Handler mit einer OnEvent-Funktion besitzt.

Wir brauchen für die Unternehmung drei Variablen, welche uns den Status des AddOns wiedergeben und eine Sicherung darstellen. Das wäre zum einen VariablesLoaded, zum anderen Initialized und am Ende eine Variable, welche im Namen die zu sichernde Standardfunktion wiederspiegelt. Der Name sollte dabei so gewählt werden, daß er einmalig ist. Die Variable wird als Funktion definiert. Alle Variablen werden als lokale Variablen am Kopf der lua-Datei definiert. VariablesLoaded und Initialized werden mit dem Wert nil belegt.

  1. local VariablesLoaded = nil;
  2. local Initialized = nil;
  3. local MyAddOn_FunctionToHook = function() end;

Innerhalb der OnLoad-Funktion registrieren wir uns zwei Events. Das Event VARIABLES_LOADED, um zu wissen wann alle notwendigen Daten geladen wurden, und das Event PLAYER_ENTERING_WORLD, um das Betreten des Charakters in der Spielwelt zu erkennen.

  1. function MyAddOn_OnLoad()
  2. - Code der sonst noch ausgeführt werden soll -
  3. this:RegisterEvent("VARIABLES_LOADED");
  4. this:RegisterEvent("PLAYER_ENTERING_WORLD");
  5. - Code der sonst noch ausgeführt werden soll -
  6. end

Desweiteren müssen die beiden Ereignisse in der OnEvent-Funktion behandelt werden. Tritt das Ereignis VARIABLES_LOADED ein, soll die Variable VariablesLoaded mit dem Wert 1 belegt werden. Damit geben wir bekannt, dass alle wichtigen Daten geladen wurden.
Wird das Ereignis PLAYER_ENTERING_WORLD ausgelöst, wird untersucht, ob die Daten bereits geladen wurden und ob die Initialisierung schon einmal durchgeführt wurde. Wenn die Daten geladen wurden und die Initialisierung noch nicht aufgerufen wurde, wird die Initialisierung gestartet (eine eigene Funktion) und die Variable Initalized mit dem Wert 1 belegt.

  1. function MyAddOn_OnEvent()
  2. - Code der sonst noch ausgeführt werden soll -
  3. if event == "VARIABLES_LOADED" then
  4. VariablesLoaded = 1;
  5. elseif event == "PLAYER_ENTERING_WORLD" then
  6. if (not Initialized) and VariablesLoaded then
  7. MyAddOn_Initialize();
  8. Initialized = 1;
  9. end
  10. end
  11. - Code der sonst noch ausgeführt werden soll -
  12. end


Die Initialisierung

Zwar wissen wir nun wann wir die Initialisierung durchführen aber nicht wie diese auszusehen hat.
Das hooken einer Funktion geht recht simpel. Da Funktionen in lua als Variablen behandelt werden und lua Variablen per CallByValue übergibt, können wir einfach die Funktionsnamen der notwendigen Funktionen wie Variablen behandeln.
Um die ehemalige Standardfunktion zu sichern, setzen wir unsere Variable MyAddOn_FunctionToHook als Speicher ein und übergeben ihr als Wert die zu sichernde Funktion.

  1. MyAddOn_FunctionToHook = FunctionToHook;

Damit wird der Inhalt der FunctionToHook-Funktion in die Sicherungsvariable kopiert.
Nachdem wir dies getan haben, wollen wir natürlich auch den neuen Inhalt überspielen, damit dieser zum Einsatz kommt. Dazu verwenden wir das gleiche Schema und ersetzen den Inhalt der Standard-Funktion durch unsere eigene Funktion.

  1. FunctionToHook = MyAddOn_OwnFunctionToHook();

Diese beiden Zeilen werden in die Initialisierungs-Funktion geschrieben.

  1. function MyAddOn_Initialize()
  2. MyAddOn_FunctionToHook = FunctionToHook;
  3. FunctionToHook = MyAddOn_OwnFunctionToHook();
  4. end

Das war alles, was man zur Initialisierung des hooking benötigt. Solltet ihr weitere Anweisungen haben, die bei der Initialisierung durchgeführt werden sollen, so fügt diese einfach in die Funktion ein.

Kommen wir nun aber zu der wichtigsten Sache, der neuen Funktion.

Die hook-Funktion

Die Funktion sollte immer(!) die alte Funktion aufrufen und am Besten noch deren Rückgabewert am Ende zurückliefern. Dadurch wird verhindert das man eventuelle Seiteneffekte oder Besonderheiten der Standard-Funktion aushebelt und das Spiel beeinträchtigt.
Nachdem ihr die gespeicherte Standard-Funktion aufgerufen habt, könnt ihr euren eigenen Abschnitt hinzufügen.
Am Ende gebt ihr das gespeicherte Ergebnis der Standardfunktion zurück.
Im Groben sollte die Funktion dann folgendermassen aussehen (ohne Parameter in diesem Beispiel):

  1. function MyAddOn_OwnFunctionToHook()
  2. local ret = MyAddOn_FunctionToHook (); - die alte Standardfunktion -
  3. - mach das was du gerne drin hättest -
  4. return ret; - Rückgabewert der Standardfunktion zurückgeben -
  5. end

Das war auch schon alles. Solltet ihr sauber gearbeitet haben, dürftet ihr nicht in Schwierigkeiten geraten beim hooken mit der Funktion.
Trotz alledem solltet ihr euch darüber im klaren sein, dass das hooken nur in wirklich notwendigen Fällen anzuwenden ist. Das hooken kann zu viele Fehler verursachen, weshalb es keine Aufgabe für Jedermann und Alles ist.

Zusammenfassung

Zusammenfassend können folgende Punkte aufgezählt werden die zum hooken wichtig sind:

  • Es sollte erst gehooked werden wenn alle Daten geladen wurden.
  • Es sollte darauf geachtet werden das die Funktion nur einmalig gehooked wird. Mehrmaliges hooken kann zu stack overflow oder sogar freezes führen.
  • Speichert die Standardfunktion in einer lokalen Variablen ab.
  • Ruft in der Hook-Funktion die gespeicherte Standardfunktion am Anfang auf und speichert deren Rückgabewert.
  • Gebt am Ende der Hook-Funktion den gespeicherten Rückgabewert zurück.

zurück zur HowTo-Übersicht
zurück zur Übersicht der Skript-Sektion