Lehona
24.04.2011, 17:48
Vorweg: Das Script ist nicht in Alleinarbeit entstanden sondern neben der quasi schon obligatorischen Hilfe von Sektenspinner, dem daher ein großer Teil des Danks gebührt, hat Gottfried auch daran mitgearbeitet - wie bei eigentlich allen Sachen, die ich irgendwie verzapfe :p
Naja, das Script lag jetzt schon ein paar Wochen bei uns auf der Platte und wir hatten noch ein paar Hemmungen es zu veröffentlichen, weil man solche Sachen nie ausgiebig genug testen kann. Aber letztendlich gerade deswegen wollen wir es hier jetzt doch veröffentlichen, denn es funktioniert alles - aber wer weiß, ob sich nicht hier und da doch noch ein kleiner Fehler eingeschlichen hat.
Vielleicht sollte ich aber auch erstmal erklären, was man mit diesem nützlichen Script überhaupt machen kann: Die Engine hooken. Da Sektenspinner das so wunderschön erklärt hat - an schriftlichen Erklärungen scheitere ich doch oft genug - erlaube ich mir einfach mal, meine Erklärung durch seinen Post zu ersetzen:
Um die Idee erstmal zu erklären (ich glaube das sollte man im Einleitungspost auch etwas klarer darstellen):
Mit diesen Skripten kann man sich an beliebigen Stellen in der Engine dazwischenschummeln und eigene Anweisungen einschieben. Jedesmal wenn die Engine dann an dieser Stelle vorbeikommt, wird das unterbrochen, was sie gerade tut und zunächst der eingeschobene Code ausgeführt. Der Prozess sich dort einzuklinken heißt "hooken". In der Implementierung von Lehona ist es so, dass das, was man einschieben kann eine Daedalus Funktion ist (und nicht etwa Assemblercode oder sonst etwas).
Im Beispiel ist es so, dass Lehona sich in die Funktion einklinkt, die den Fokusnamen auf den Bildschirm zeichnet (diese Funktion heißt oCGame::Update_Status). Lehona möchte, dass zu Beginn dieser Funktion noch etwas zusätzliches geschieht, nämlich, dass die Stiftfarbe, mit der der Text gezeichnet wird, verändert wird. Dazu benutzt er sein Hook System und sagt, dass direkt am Anfang von oCGame::Update_Status (und die Adresse dieser Stelle hat er zuvor herausgesucht) noch schnell die Funktion EVT_UpdateStatus_FocusName, eine Daedalus Funktion also, ausgeführt werden soll. Und diese Daedalus Funktion kümmert sich dann darum, dass die Stiftfarbe geändert wird.
Durch Einschieben von Instruktionen kann man sehr, sehr vieles erreichen, was bislang nicht ging. Zum Beispiel kann man sich einklinken, wo die Tastatureingabe gelesen wird (und dort etwas verändern), oder man kann sich einklinken an Stellen, wo die Engine Schaden berechnet (und den Schaden verändern) oder dort, wo ausgewürfelt wird, ob es im Himmel blitzt oder nicht (und man könnte z.B. Regionen machen, in denen es sehr oft blitzt), oder dort, wo der Fallschaden ausgerechnet wird (man könnte ihn z.B. für Npcs reduzieren), oder dort wo die nächste Kampfbewegung für den Npc bestimmt wird (und man käme von dem doofen FAI System los).
Kurz: Überall dort, wo man etwas Tolles erreichen kann, indem man die Engine kurz unterbricht und schnell eine Kleinigkeit verändert, ist das Hook System nützlich.
Intern Funktioniert das Hooken so:
Sagen wir eine Funktion sieht so aus:
0x01: MacheA
0x07: MacheB
0x0D: MacheC
0x0E: MacheDHierbei sollen die Zahlen am Anfang irgendwelche Adressen sein (die Instruktionen können durchaus verschieden groß sein, hier ist MacheA zum Beispiel 0x07 - 0x01 = 6 Bytes groß, MacheC ist dagegen nur 0xE - 0xD = 1 Byte groß) und MacheX sind irgendwelche Instruktionen. Wenn man nun möchte, dass nach MacheA noch etwas anderes geschieht, sagen wir MacheA2, dann wird man den Code folgendermaßen verändern (bzw. Lehonas Hook-System wird das tun):
0x01: MacheA
0x07: SpringeZu 0x42
0x0C: ????
0x0D: MacheC
0x0E: MacheD
....
0x42: MacheA2
0x46: MacheB
0x4C: SpringeZu 0x0DHierbei wurde zusätzlicher Code an einem freien Stück Speicher geschrieben, nehmen wir mal an, wir haben freien Speicher ab Position 0x42. Wenn nun die Engine die selbe Funktion ausführt (also wieder bei 0x01 beginnt), wird sie nach MacheA zur Stelle 0x42 springen, dort MacheA2 und MacheB ausführen, zurück zu 0x0D springen und mit MacheC und dann MacheD fortfahren (also genau in der Reihenfolge in der wir es wollen, alles wie vorher, nur zwischen MacheA und MacheB kommt MacheA2).
Auffällig ist, dass MacheB seinen Ort gewechselt hat, also nun an die Stelle 0x46 gewandert ist. Das liegt einfach daran, dass das herausspringen aus der Funktion, also die SpringeZu Instruktion auch Platz braucht und daher kein Platz mehr für MacheB an der ursprünglichen Stelle ist.
Auffällig ist auch, dass Stelle 0x0C gar nicht mehr erreicht wird (und daher egal ist, was dort steht), was hier einfach daran liegt, dass die Springen Instruktion 5 Bytes groß ist, und damit kleiner als MacheB (6 Bytes groß), was herausgenommen wurde (das soll uns aber nicht weiter irritieren).
Das sieht auf den ersten Blick vielleicht etwas kompliziert aus, ist aber eigentlich ein recht natürliches Vorgehen (wie sollte man es sonst tun, wenn man etwas einfügen will, kann man ja nicht alles was danach kommt verschieben!).
Vielleicht versteht man jetzt auch weshalb Lehona wissen muss, wie groß das MacheB ist, was herausgeschoben werden muss (sonst würde Lehona versehentlich eine Instruktion in der Mitte auseinanderschneiden, das darf nicht passieren). Außerdem muss MacheB mindestens 5 Bytes lang sein, weil die Springen Instruktion die eingefügt werden muss gerade 5 Bytes groß ist (zur Not kann man mehrere kleine Instruktionen als eine große Instruktion MacheB auffassen).
Wenn wir Lehonas HookEngine Funktion auf unser Beispiel mit den MacheX Instruktionen übertragen, also das, was wir uns von Hand überlegt haben nun in die Hände von HookEngine legen wollen, dann sähe das folgendermaßen aus.
func void HookEngine(var int address, var int oldInstr, var string function)Wir würden als address die Zahl 7 übergeben, an Stelle 0x07 wollen wir schließlich etwas einschieben, oldInstr wäre die Größe der Instruktion MacheB die umkopiert wird (also hier 6, also größer als 5, wie gefordert) und MacheA2 nimmt die Rolle von function ein (wobei function allerdings der Name einer Daedalusfunktion ist und das System automatisch eine Instruktion baut, die diese Funktion aufruft).
/****************************************************\
* ENGINEHOOKS *
* Dieses kleine Paket erlaubt Daedalusfunktionen *
* an Enginefunktionen zu hängen und somit Zugriff *
* auf bestimmte Vorgänge außerhalb der Scripte zu *
* bekommen. *
* Beispiel: Man könnte einen Hook an die Speicher- *
* funktion der Engine setzen und damit strings die *
* regulär nicht gespeichert werden noch schnell in *
* bspw. den Namen eines zCVob legen um sie zu be- *
* halten. *
\****************************************************/
//-------------------
// OPCODES
//-------------------
/* 1 Byte */
const int ASMINT_OP_pusha = 96; //0x60
const int ASMINT_OP_popa = 97; //0x61
const int ASMINT_OP_movMemToEAX = 161; //0xA1
/* 2 Byte */
const int ASMINT_OP_movECXtoEAX = 49547; //0xC18B
const int ASMINT_OP_movESPtoEAX = 50315; //0xC48B
const int ASMINT_OP_movEAXtoECX = 49545; //0xC189
const int ASMINT_OP_movEBXtoEAX = 55433; //0xD889
const int ASMINT_OP_movEBPtoEAX = 50571; //0xC58B
//-------------------
// KONSTANTEN
//-------------------
const int parser = 11223232; //0xAB40C0 zCParser
const int zParser__CallFunc = 7940592; //0x7929F0 CallFunc(int,int)
//-------------------
// RÜCKGABEVARIABLEN
//-------------------
var int EAX;
func int GetEAX() { return EAX; };
func int EAXAdr() {
GetEAX();
MEMINT_StackPopInst();
MEMINT_StackPushInst(zPAR_TOK_PUSHINT);
};
var int ECX;
func int GetECX() { return ECX; };
func int ECXAdr() {
GetECX();
MEMINT_StackPopInst();
MEMINT_StackPushInst(zPAR_TOK_PUSHINT);
};
var int ESP;
func int GetESP() { return ESP; };
func int ESPAdr() {
GetESP();
MEMINT_StackPopInst();
MEMINT_StackPushInst(zPAR_TOK_PUSHINT);
};
var int EBX;
func int GetEBX() { return EBX; };
func int EBXAdr() {
GetEBX();
MEMINT_StackPopInst();
MEMINT_StackPushInst(zPAR_TOK_PUSHINT);
};
var int EBP;
func int GetEBP() { return EBP; };
func int EBPAdr() {
GetEBP();
MEMINT_StackPopInst();
MEMINT_StackPushInst(zPAR_TOK_PUSHINT);
};
/*============================*
/* HOOKENGINE *
/*============================*
- address: Addresse einer Enginefunktion an die die Funktion angehängt werden soll.
- oldInstr: Die Länge in Bytes der Anweisung die an 'address' zu finden ist, mindestens 5 Bytes (Notfalls nächste Zeile noch mitnehmen).
Kann zB. in IDA nachgesehen werden.
- function: Die Daedalusfunktion die aufgerufen werden soll.
*/
func void HookEngine(var int address, var int oldInstr, var string function) {
var int SymbID; // Symbolindex von 'function'
var int ptr; // Pointer auf den Zwischenspeicher der alten Anweisung
var int relAdr; // Relative Addresse zum neuen Assemblercode, ausgehend von 'address'
// ----- Sicherheitsabfragen -----
if(oldInstr < 5) {
PrintDebug("HOOKENGINE: oldInstr ist zu kurz. Es werden mindestens 5 Bytes erwartet.");
return;
};
SymbID = MEM_FindParserSymbol(function);
if(!SymbID) {
PrintDebug("HOOKENGINE: Die gegebene Daedalusfunktion kann nicht gefunden werden.");
return;
};
MemoryProtectionOverride (address, oldInstr+3);
// ----- Eventuell geschützen Speicher behandeln -----
// ----- Die alte Anweisung sichern -----
ptr = MEM_Alloc(oldInstr);
MEM_CopyBytes(address, ptr, oldInstr);
// ----- Einen neuen Stream für den Assemblercode anlegen -----
ASM_Open(100 + oldInstr);
// ----- Jump aus der Enginefunktion in den neuen Code einfügen -----
relAdr = ASMINT_CurrRun-address-5;
MEM_WriteInt(address + 0, 233);
MEM_WriteInt(address + 1, relAdr);
// ----- Neuen Assemblercode verfassen -----
// Alle Register sichern
// EAX in Daedalus Variable sichern
ASM_2(ASMINT_OP_movEAXToMem);
ASM_4(EAXAdr());
ASM_1(ASMINT_OP_pusha);
// ECX in Daedalus Variable sichern
ASM_2(ASMINT_OP_movECXtoEAX);
ASM_2(ASMINT_OP_movEAXToMem);
ASM_4(ECXAdr());
// ESP in Daedalus Variable sichern
ASM_2(ASMINT_OP_movESPtoEAX);
ASM_2(ASMINT_OP_movEAXToMem);
ASM_4(ESPAdr());
// EBX in Daedalus Variable sichern
ASM_2(ASMINT_OP_movEBXtoEAX);
ASM_2(ASMINT_OP_movEAXtoMem);
ASM_4(EBXAdr());
// EBP in Daedalus Variable sichern
ASM_2(ASMINT_OP_movEBPtoEAX);
ASM_2(ASMINT_OP_movEAXtoMem);
ASM_4(EBPAdr());
// --- Daedalusfunktion aufrufen ---
ASM_1(ASMINT_OP_pushIm);
ASM_4(SymbID);
ASM_1(ASMINT_OP_pushIm);
ASM_4(parser);
ASM_1(ASMINT_OP_call);
ASM_4(zParser__CallFunc-ASM_Here()-4);
ASM_2(ASMINT_OP_addImToESP);
ASM_1(8);
ASM_1(ASMINT_OP_popa);
ASM_1(ASMINT_OP_movMemToEAX);
ASM_4(ECXAdr());
ASM_2(ASMINT_OP_movEAXtoECX);
ASM_1(ASMINT_OP_movMemToEAX);
ASM_4(EAXAdr());
// Alte Anweisung wieder einfügen
MEM_CopyBytes(ptr, ASMINT_Cursor, oldInstr);
MEM_Free(ptr);
ASMINT_Cursor += oldInstr;
// Zur Enginefunktion zurückkehren
ASM_1(ASMINT_OP_pushIm);
ASM_4(address + oldInstr);
ASM_1(ASMINT_OP_retn);
ASM_Close();
};
So, nun das Beispiel nicht vergessen:
Die Idee entstammt praktisch NicoDE, der u.a. auch für die Idee dieser Hooks verantwortlich ist, er hatte damals meines Wissens versucht ähnliches für G2 zu basteln (Allerdings mit Sourcecode *grml*). Wer G3 gespielt hat, wird es kennen :)
func void EVT_UpdateStatus_FocusName() {
const int zCView__SetFontColor = 8034576; //0x7A9910
var int col;
var oCNpc her; her = Hlp_GetNpc(hero);
if(Hlp_Is_oCNpc(her.focus_vob)) {
var c_npc oth; oth = MEM_PtrToInst(her.focus_vob);
var int att; att = Npc_GetPermAttitude(hero, oth);
if(att == ATT_FRIENDLY) {
col = RGBA(0,255,0,255); //Grün
}
else if(att == ATT_ANGRY) {
col = RGBA(255,180,0,255); //Orange
}
else if(att == ATT_HOSTILE) {
col = RGBA(255,0,0,255); //Rot
}
else if(att == ATT_NEUTRAL) {
col = RGBA(255,255,255,255); //Weiß
};
}
else {
col = RGBA(255,255,255,255); //Weiß
};
var int ptr; ptr = MEM_Alloc(4);
MEM_WriteInt(ptr, col);
CALL_IntParam(ptr);
CALL__thiscall(MEM_ReadInt(screen_offset), zCView__SetFontColor);
MEM_Free(ptr);
};
Das initialisieren der Hook geschieht denkbar einfach durch folgenden Schnipsel:
const int oCGame__UpdateStatus_X = 7093113; //0x6C3B79
HookEngine(oCGame__UpdateStatus_X, 8, "EVT_UPDATESTATUS_FOCUSNAME");
Was macht es? Farbige Fokusnamen je nach Gesinnung :) (http://upload.worldofplayers.de/files6/ColoredNames.jpg)
Um das Script verwenden zu können wird noch eine kleine Zusatzfunktion benötigt:
//========================================
// Farbhandling
//========================================
/* r : Roter Farbanteil (0..255)
* g : Grüner Farbanteil (0..255)
* b : Blauer Farbanteil (0..255)
* a : Alpha (0 = unsichtbar, 0..255)
* return : Fertiger zColor */
func int RGBA(var int r, var int g, var int b, var int a) {
return ((r&zCOLOR_CHANNEL)<<zCOLOR_SHIFT_RED)
|((g&zCOLOR_CHANNEL)<<zCOLOR_SHIFT_GREEN)
|((b&zCOLOR_CHANNEL)<<zCOLOR_SHIFT_BLUE)
|((a&zCOLOR_CHANNEL)<<zCOLOR_SHIFT_ALPHA);
};
Ich hoffe ihr könnt mit dem Script was anfangen :gratz
MfG Gottfried und Lehona :)
Edit1: Änderungen an EAX und ECX werden in die jeweiligen Register geschrieben.
Edit2: EBX und EBP werden zusätzlich als Daedalusvariablen bereitgestellt.
Naja, das Script lag jetzt schon ein paar Wochen bei uns auf der Platte und wir hatten noch ein paar Hemmungen es zu veröffentlichen, weil man solche Sachen nie ausgiebig genug testen kann. Aber letztendlich gerade deswegen wollen wir es hier jetzt doch veröffentlichen, denn es funktioniert alles - aber wer weiß, ob sich nicht hier und da doch noch ein kleiner Fehler eingeschlichen hat.
Vielleicht sollte ich aber auch erstmal erklären, was man mit diesem nützlichen Script überhaupt machen kann: Die Engine hooken. Da Sektenspinner das so wunderschön erklärt hat - an schriftlichen Erklärungen scheitere ich doch oft genug - erlaube ich mir einfach mal, meine Erklärung durch seinen Post zu ersetzen:
Um die Idee erstmal zu erklären (ich glaube das sollte man im Einleitungspost auch etwas klarer darstellen):
Mit diesen Skripten kann man sich an beliebigen Stellen in der Engine dazwischenschummeln und eigene Anweisungen einschieben. Jedesmal wenn die Engine dann an dieser Stelle vorbeikommt, wird das unterbrochen, was sie gerade tut und zunächst der eingeschobene Code ausgeführt. Der Prozess sich dort einzuklinken heißt "hooken". In der Implementierung von Lehona ist es so, dass das, was man einschieben kann eine Daedalus Funktion ist (und nicht etwa Assemblercode oder sonst etwas).
Im Beispiel ist es so, dass Lehona sich in die Funktion einklinkt, die den Fokusnamen auf den Bildschirm zeichnet (diese Funktion heißt oCGame::Update_Status). Lehona möchte, dass zu Beginn dieser Funktion noch etwas zusätzliches geschieht, nämlich, dass die Stiftfarbe, mit der der Text gezeichnet wird, verändert wird. Dazu benutzt er sein Hook System und sagt, dass direkt am Anfang von oCGame::Update_Status (und die Adresse dieser Stelle hat er zuvor herausgesucht) noch schnell die Funktion EVT_UpdateStatus_FocusName, eine Daedalus Funktion also, ausgeführt werden soll. Und diese Daedalus Funktion kümmert sich dann darum, dass die Stiftfarbe geändert wird.
Durch Einschieben von Instruktionen kann man sehr, sehr vieles erreichen, was bislang nicht ging. Zum Beispiel kann man sich einklinken, wo die Tastatureingabe gelesen wird (und dort etwas verändern), oder man kann sich einklinken an Stellen, wo die Engine Schaden berechnet (und den Schaden verändern) oder dort, wo ausgewürfelt wird, ob es im Himmel blitzt oder nicht (und man könnte z.B. Regionen machen, in denen es sehr oft blitzt), oder dort, wo der Fallschaden ausgerechnet wird (man könnte ihn z.B. für Npcs reduzieren), oder dort wo die nächste Kampfbewegung für den Npc bestimmt wird (und man käme von dem doofen FAI System los).
Kurz: Überall dort, wo man etwas Tolles erreichen kann, indem man die Engine kurz unterbricht und schnell eine Kleinigkeit verändert, ist das Hook System nützlich.
Intern Funktioniert das Hooken so:
Sagen wir eine Funktion sieht so aus:
0x01: MacheA
0x07: MacheB
0x0D: MacheC
0x0E: MacheDHierbei sollen die Zahlen am Anfang irgendwelche Adressen sein (die Instruktionen können durchaus verschieden groß sein, hier ist MacheA zum Beispiel 0x07 - 0x01 = 6 Bytes groß, MacheC ist dagegen nur 0xE - 0xD = 1 Byte groß) und MacheX sind irgendwelche Instruktionen. Wenn man nun möchte, dass nach MacheA noch etwas anderes geschieht, sagen wir MacheA2, dann wird man den Code folgendermaßen verändern (bzw. Lehonas Hook-System wird das tun):
0x01: MacheA
0x07: SpringeZu 0x42
0x0C: ????
0x0D: MacheC
0x0E: MacheD
....
0x42: MacheA2
0x46: MacheB
0x4C: SpringeZu 0x0DHierbei wurde zusätzlicher Code an einem freien Stück Speicher geschrieben, nehmen wir mal an, wir haben freien Speicher ab Position 0x42. Wenn nun die Engine die selbe Funktion ausführt (also wieder bei 0x01 beginnt), wird sie nach MacheA zur Stelle 0x42 springen, dort MacheA2 und MacheB ausführen, zurück zu 0x0D springen und mit MacheC und dann MacheD fortfahren (also genau in der Reihenfolge in der wir es wollen, alles wie vorher, nur zwischen MacheA und MacheB kommt MacheA2).
Auffällig ist, dass MacheB seinen Ort gewechselt hat, also nun an die Stelle 0x46 gewandert ist. Das liegt einfach daran, dass das herausspringen aus der Funktion, also die SpringeZu Instruktion auch Platz braucht und daher kein Platz mehr für MacheB an der ursprünglichen Stelle ist.
Auffällig ist auch, dass Stelle 0x0C gar nicht mehr erreicht wird (und daher egal ist, was dort steht), was hier einfach daran liegt, dass die Springen Instruktion 5 Bytes groß ist, und damit kleiner als MacheB (6 Bytes groß), was herausgenommen wurde (das soll uns aber nicht weiter irritieren).
Das sieht auf den ersten Blick vielleicht etwas kompliziert aus, ist aber eigentlich ein recht natürliches Vorgehen (wie sollte man es sonst tun, wenn man etwas einfügen will, kann man ja nicht alles was danach kommt verschieben!).
Vielleicht versteht man jetzt auch weshalb Lehona wissen muss, wie groß das MacheB ist, was herausgeschoben werden muss (sonst würde Lehona versehentlich eine Instruktion in der Mitte auseinanderschneiden, das darf nicht passieren). Außerdem muss MacheB mindestens 5 Bytes lang sein, weil die Springen Instruktion die eingefügt werden muss gerade 5 Bytes groß ist (zur Not kann man mehrere kleine Instruktionen als eine große Instruktion MacheB auffassen).
Wenn wir Lehonas HookEngine Funktion auf unser Beispiel mit den MacheX Instruktionen übertragen, also das, was wir uns von Hand überlegt haben nun in die Hände von HookEngine legen wollen, dann sähe das folgendermaßen aus.
func void HookEngine(var int address, var int oldInstr, var string function)Wir würden als address die Zahl 7 übergeben, an Stelle 0x07 wollen wir schließlich etwas einschieben, oldInstr wäre die Größe der Instruktion MacheB die umkopiert wird (also hier 6, also größer als 5, wie gefordert) und MacheA2 nimmt die Rolle von function ein (wobei function allerdings der Name einer Daedalusfunktion ist und das System automatisch eine Instruktion baut, die diese Funktion aufruft).
/****************************************************\
* ENGINEHOOKS *
* Dieses kleine Paket erlaubt Daedalusfunktionen *
* an Enginefunktionen zu hängen und somit Zugriff *
* auf bestimmte Vorgänge außerhalb der Scripte zu *
* bekommen. *
* Beispiel: Man könnte einen Hook an die Speicher- *
* funktion der Engine setzen und damit strings die *
* regulär nicht gespeichert werden noch schnell in *
* bspw. den Namen eines zCVob legen um sie zu be- *
* halten. *
\****************************************************/
//-------------------
// OPCODES
//-------------------
/* 1 Byte */
const int ASMINT_OP_pusha = 96; //0x60
const int ASMINT_OP_popa = 97; //0x61
const int ASMINT_OP_movMemToEAX = 161; //0xA1
/* 2 Byte */
const int ASMINT_OP_movECXtoEAX = 49547; //0xC18B
const int ASMINT_OP_movESPtoEAX = 50315; //0xC48B
const int ASMINT_OP_movEAXtoECX = 49545; //0xC189
const int ASMINT_OP_movEBXtoEAX = 55433; //0xD889
const int ASMINT_OP_movEBPtoEAX = 50571; //0xC58B
//-------------------
// KONSTANTEN
//-------------------
const int parser = 11223232; //0xAB40C0 zCParser
const int zParser__CallFunc = 7940592; //0x7929F0 CallFunc(int,int)
//-------------------
// RÜCKGABEVARIABLEN
//-------------------
var int EAX;
func int GetEAX() { return EAX; };
func int EAXAdr() {
GetEAX();
MEMINT_StackPopInst();
MEMINT_StackPushInst(zPAR_TOK_PUSHINT);
};
var int ECX;
func int GetECX() { return ECX; };
func int ECXAdr() {
GetECX();
MEMINT_StackPopInst();
MEMINT_StackPushInst(zPAR_TOK_PUSHINT);
};
var int ESP;
func int GetESP() { return ESP; };
func int ESPAdr() {
GetESP();
MEMINT_StackPopInst();
MEMINT_StackPushInst(zPAR_TOK_PUSHINT);
};
var int EBX;
func int GetEBX() { return EBX; };
func int EBXAdr() {
GetEBX();
MEMINT_StackPopInst();
MEMINT_StackPushInst(zPAR_TOK_PUSHINT);
};
var int EBP;
func int GetEBP() { return EBP; };
func int EBPAdr() {
GetEBP();
MEMINT_StackPopInst();
MEMINT_StackPushInst(zPAR_TOK_PUSHINT);
};
/*============================*
/* HOOKENGINE *
/*============================*
- address: Addresse einer Enginefunktion an die die Funktion angehängt werden soll.
- oldInstr: Die Länge in Bytes der Anweisung die an 'address' zu finden ist, mindestens 5 Bytes (Notfalls nächste Zeile noch mitnehmen).
Kann zB. in IDA nachgesehen werden.
- function: Die Daedalusfunktion die aufgerufen werden soll.
*/
func void HookEngine(var int address, var int oldInstr, var string function) {
var int SymbID; // Symbolindex von 'function'
var int ptr; // Pointer auf den Zwischenspeicher der alten Anweisung
var int relAdr; // Relative Addresse zum neuen Assemblercode, ausgehend von 'address'
// ----- Sicherheitsabfragen -----
if(oldInstr < 5) {
PrintDebug("HOOKENGINE: oldInstr ist zu kurz. Es werden mindestens 5 Bytes erwartet.");
return;
};
SymbID = MEM_FindParserSymbol(function);
if(!SymbID) {
PrintDebug("HOOKENGINE: Die gegebene Daedalusfunktion kann nicht gefunden werden.");
return;
};
MemoryProtectionOverride (address, oldInstr+3);
// ----- Eventuell geschützen Speicher behandeln -----
// ----- Die alte Anweisung sichern -----
ptr = MEM_Alloc(oldInstr);
MEM_CopyBytes(address, ptr, oldInstr);
// ----- Einen neuen Stream für den Assemblercode anlegen -----
ASM_Open(100 + oldInstr);
// ----- Jump aus der Enginefunktion in den neuen Code einfügen -----
relAdr = ASMINT_CurrRun-address-5;
MEM_WriteInt(address + 0, 233);
MEM_WriteInt(address + 1, relAdr);
// ----- Neuen Assemblercode verfassen -----
// Alle Register sichern
// EAX in Daedalus Variable sichern
ASM_2(ASMINT_OP_movEAXToMem);
ASM_4(EAXAdr());
ASM_1(ASMINT_OP_pusha);
// ECX in Daedalus Variable sichern
ASM_2(ASMINT_OP_movECXtoEAX);
ASM_2(ASMINT_OP_movEAXToMem);
ASM_4(ECXAdr());
// ESP in Daedalus Variable sichern
ASM_2(ASMINT_OP_movESPtoEAX);
ASM_2(ASMINT_OP_movEAXToMem);
ASM_4(ESPAdr());
// EBX in Daedalus Variable sichern
ASM_2(ASMINT_OP_movEBXtoEAX);
ASM_2(ASMINT_OP_movEAXtoMem);
ASM_4(EBXAdr());
// EBP in Daedalus Variable sichern
ASM_2(ASMINT_OP_movEBPtoEAX);
ASM_2(ASMINT_OP_movEAXtoMem);
ASM_4(EBPAdr());
// --- Daedalusfunktion aufrufen ---
ASM_1(ASMINT_OP_pushIm);
ASM_4(SymbID);
ASM_1(ASMINT_OP_pushIm);
ASM_4(parser);
ASM_1(ASMINT_OP_call);
ASM_4(zParser__CallFunc-ASM_Here()-4);
ASM_2(ASMINT_OP_addImToESP);
ASM_1(8);
ASM_1(ASMINT_OP_popa);
ASM_1(ASMINT_OP_movMemToEAX);
ASM_4(ECXAdr());
ASM_2(ASMINT_OP_movEAXtoECX);
ASM_1(ASMINT_OP_movMemToEAX);
ASM_4(EAXAdr());
// Alte Anweisung wieder einfügen
MEM_CopyBytes(ptr, ASMINT_Cursor, oldInstr);
MEM_Free(ptr);
ASMINT_Cursor += oldInstr;
// Zur Enginefunktion zurückkehren
ASM_1(ASMINT_OP_pushIm);
ASM_4(address + oldInstr);
ASM_1(ASMINT_OP_retn);
ASM_Close();
};
So, nun das Beispiel nicht vergessen:
Die Idee entstammt praktisch NicoDE, der u.a. auch für die Idee dieser Hooks verantwortlich ist, er hatte damals meines Wissens versucht ähnliches für G2 zu basteln (Allerdings mit Sourcecode *grml*). Wer G3 gespielt hat, wird es kennen :)
func void EVT_UpdateStatus_FocusName() {
const int zCView__SetFontColor = 8034576; //0x7A9910
var int col;
var oCNpc her; her = Hlp_GetNpc(hero);
if(Hlp_Is_oCNpc(her.focus_vob)) {
var c_npc oth; oth = MEM_PtrToInst(her.focus_vob);
var int att; att = Npc_GetPermAttitude(hero, oth);
if(att == ATT_FRIENDLY) {
col = RGBA(0,255,0,255); //Grün
}
else if(att == ATT_ANGRY) {
col = RGBA(255,180,0,255); //Orange
}
else if(att == ATT_HOSTILE) {
col = RGBA(255,0,0,255); //Rot
}
else if(att == ATT_NEUTRAL) {
col = RGBA(255,255,255,255); //Weiß
};
}
else {
col = RGBA(255,255,255,255); //Weiß
};
var int ptr; ptr = MEM_Alloc(4);
MEM_WriteInt(ptr, col);
CALL_IntParam(ptr);
CALL__thiscall(MEM_ReadInt(screen_offset), zCView__SetFontColor);
MEM_Free(ptr);
};
Das initialisieren der Hook geschieht denkbar einfach durch folgenden Schnipsel:
const int oCGame__UpdateStatus_X = 7093113; //0x6C3B79
HookEngine(oCGame__UpdateStatus_X, 8, "EVT_UPDATESTATUS_FOCUSNAME");
Was macht es? Farbige Fokusnamen je nach Gesinnung :) (http://upload.worldofplayers.de/files6/ColoredNames.jpg)
Um das Script verwenden zu können wird noch eine kleine Zusatzfunktion benötigt:
//========================================
// Farbhandling
//========================================
/* r : Roter Farbanteil (0..255)
* g : Grüner Farbanteil (0..255)
* b : Blauer Farbanteil (0..255)
* a : Alpha (0 = unsichtbar, 0..255)
* return : Fertiger zColor */
func int RGBA(var int r, var int g, var int b, var int a) {
return ((r&zCOLOR_CHANNEL)<<zCOLOR_SHIFT_RED)
|((g&zCOLOR_CHANNEL)<<zCOLOR_SHIFT_GREEN)
|((b&zCOLOR_CHANNEL)<<zCOLOR_SHIFT_BLUE)
|((a&zCOLOR_CHANNEL)<<zCOLOR_SHIFT_ALPHA);
};
Ich hoffe ihr könnt mit dem Script was anfangen :gratz
MfG Gottfried und Lehona :)
Edit1: Änderungen an EAX und ECX werden in die jeweiligen Register geschrieben.
Edit2: EBX und EBP werden zusätzlich als Daedalusvariablen bereitgestellt.