PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [Script] Engine-Hooks



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.

Bonne6
24.04.2011, 20:50
Hab's getestet und funktioniert so, wie ich mir das vorstelle. Ist sehr praktisch, vor allem weil ich erst gestern drüber nachgedacht hab zu fragen, ob's eine Möglichkeit gibt, Funktionen abzufangen, z.b. das Beenden des Spiels :D

Sektenspinner
24.04.2011, 21:39
Das ist eine wichtige Vorbereitung für einige Features, die noch kommen können.

Wahrscheinlich ist es auch sinnvoll diese Funktion in Ikarus aufzunehmen, vorausgesetzt natürlich, du bist einverstanden.

Eine kleine Erweiterung, die ich noch für sinnvoll halte: Änderungen an ecx und eax sollten an die Funktion weitergereicht werden.

Was auch nützlich sein könnte, wäre, wenn die Event Funktion sich entscheiden könnte, die gehookte Funktion zu blockieren, das heißt statt in sie zurückzuspringen würde ein retn X ausgegeführt, wobei X eine vom Nutzer in Erfahrung zu bringende Zahl ist, die sagt, wieviele Bytes zuvor vom Stack gepopt werden müssen (ich glaub darüber haben wir auch schonmal gesprochen, weiß nicht mehr, zu welchem Ergebnis wir gekommen sind).

Lehona
24.04.2011, 22:10
Das ist eine wichtige Vorbereitung für einige Features, die noch kommen können.

Wahrscheinlich ist es auch sinnvoll diese Funktion in Ikarus aufzunehmen, vorausgesetzt natürlich, du bist einverstanden.

Eine kleine Erweiterung, die ich noch für sinnvoll halte: Änderungen an ecx und eax sollten an die Funktion weitergereicht werden.

Was auch nützlich sein könnte, wäre, wenn die Event Funktion sich entscheiden könnte, die gehookte Funktion zu blockieren, das heißt statt in sie zurückzuspringen würde ein retn X ausgegeführt, wobei X eine vom Nutzer in Erfahrung zu bringende Zahl ist, die sagt, wieviele Bytes zuvor vom Stack gepopt werden müssen (ich glaub darüber haben wir auch schonmal gesprochen, weiß nicht mehr, zu welchem Ergebnis wir gekommen sind).

Du kannst die Funktion natürlich gerne in Ikarus aufnehmen :)

Ja, das könnte man machen, sind ja bloß ein paar opcodes. Wird beizeiten eingebaut :)

Darüber hatten wir gesprochen und es sollte auch irgendwie passieren, aber das mit dem in Erfahrung bringen ist halt schon doof... Wobei zumindest am Anfang von Funktionen ja bloß die Argumente gepopt werden müssen. Kommt dann wohl auch auf die ToDo-Liste. Vielleicht schaff ich's heute abend ja noch :)

Simon
26.04.2011, 16:10
Hey cool! :)
Gute Arbeit.

Auch wenn ich das als Nicht-Scripter wohl eher schlecht beurteilen kann. ^^

Hab' ich das richtig verstanden und es ist erst jetzt durch dieses Script möglich z.B. Itemnamen in verschiedenen Farben darzustellen?
Oder war das schon zuvor möglich und euer Beispiel bezog sich nur auf Fokusnamen?

Gottfried
26.04.2011, 16:35
Hab' ich das richtig verstanden und es ist erst jetzt durch dieses Script möglich z.B. Itemnamen in verschiedenen Farben darzustellen?
Oder war das schon zuvor möglich und euer Beispiel bezog sich nur auf Fokusnamen?Klar, farbige Itemnamen sind genau so denkbar.. In dem Beispiel ist jetzt nur eine Behandlung für Npcs enthalten (if(Hlp_Is_oCNpc(her.focus_vob))) aber man könnte direkt darunter noch eine andere Farbwahl für oCItems anfügen..

Farbige Fokusnamen sind aber auch nur ein Bruchteil von dem was damit möglich sein könnte :p

MfG Gottfried

Simon
26.04.2011, 18:04
Jaja, das glaub ich eh, aber mir würden ja farbige Itemnamen erstmal reichen. :D

Was könnte man den damit noch so alles anstellen?

Lehona
26.04.2011, 18:42
Jaja, das glaub ich eh, aber mir würden ja farbige Itemnamen erstmal reichen. :D

Was könnte man den damit noch so alles anstellen?

Alles :p
Ein Beispiel wäre das nachträgliche Plündern von Tieren (Das Inventar wird beim Töten aufgebaut), da man das Inventar-Öffnen abfängt. Wenn man also Felle abziehen später erst lernt...

Eine Sache übrigens: Die Hooks müssen bei jedem Neustart von Gothic neu initialisiert werden - aber eben wirklich nur da! Wenn man es permanent in der INIT_Global() tut, stürzt beim Laden das Spiel ab (Zumindest, wenn die Funktion aufgerufen wird). Daher:


var String Gothic_Restarted;

func void Init_Global() {
if (!Hlp_StrCmp(Gothic_Restarted, "Yes") {
Gothic_Restarted = "Yes";
// Hook
};
};

Sektenspinner
26.04.2011, 21:07
Was könnte man den damit noch so alles anstellen?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).

Lehona
26.04.2011, 21:20
Um die Idee erstmal zu erklären (ich glaube das sollte man im Einleitungspost auch etwas klarer darstellen):
[...]


/done

Ich hoffe du hast nichts gegen ein Zitat.

Neconspictor
26.04.2011, 21:31
Könnte man mit diesem System rein theoretisch die Gravitation ausschalten (für Levitation)?

Simon
26.04.2011, 21:55
@Sektenspinner: Danke für die asuführliche Erklärung. :)
Angenommen man wollte, dass sich die Reihenfolge nicht ändert aber trotzdem zwischen MacheA und MacheB Mache A2 eingeschoben wird, was müsste man tun? xD

Eine Blutfliege hat ja einen fest definierten Abstand zum Boden... Könnte man da vielleicht auch etwas in der Hinsicht ändern, dass der Abstand im Spiel für den Spieler veränderbar ist?

Sektenspinner
26.04.2011, 22:09
@Sektenspinner: Danke für die asuführliche Erklärung. :)Hab sie grade noch etwas angepasst.


Angenommen man wollte, dass sich die Reihenfolge nicht ändert aber trotzdem zwischen MacheA und MacheB Mache A2 eingeschoben wird, was müsste man tun? xDNun, in der Ausführungsreihenfolge ist ja MacheA2 an der richtigen Position, es geschieht ja, wenn man verfolgt, was die Engine tun wird (also von oben nach unten liest, aber dabei die Springen Befehle nachvollzieht) nacheinander:

MacheA
MacheA2
MacheB
MacheC
MacheDMacheA2 direkt einfügen (und alles, was danach kommt nach unten verschieben) kann man deshalb nicht, weil man dann auch alle Funktionen die danach kommen verschiebt und viele Sprungadressen (und viele relative Adressen) einfach nicht mehr passen.

Ich verstehe nicht ganz, was du erreichen willst. Die Instruktionen im Speicher zu ordnen kriegt man nicht hin und es hat auch keinen Vorteil. Wenn sie in der richtigen Reihenfolge ausgeführt werden ist das alles, was man wirklich will.


Eine Blutfliege hat ja einen fest definierten Abstand zum Boden... Könnte man da vielleicht auch etwas in der Hinsicht ändern, dass der Abstand im Spiel für den Spieler veränderbar ist?Das ist eine sehr spezielle Frage, ich wüsste nicht wo genau man da eingreifen sollte.
Dazu müsste man erstmal genauer wissen, was sie in der Luft hält. Entweder ist dort ein Vertex oder ein Bone oder sonst irgendwas.


Könnte man mit diesem System rein theoretisch die Gravitation ausschalten (für Levitation)?Vermutlich. Du müsstest die Funktion finden, die die Physik berechnet und dort sinngemäß einfügen "wenn gerade auf dem Spieler gearbeitet wird, dann mache nichts". Die Steuerung in der Luft müsstest du allerdings auch implementieren. Ob die einfachste Methode über Hooks führt (oder ob es leichter ist die Physik für den Spieler anderweitig auszuschalten) kann ich auf die Schnelle nicht sagen.

Lehona
26.04.2011, 22:16
Hab's geändert. Danke, ein sehr guter Post :gratz

Gottfried
26.04.2011, 22:22
Vermutlich. Du müsstest die Funktion finden, die die Physik berechnet und dort sinngemäß einfügen "wenn gerade auf dem Spieler gearbeitet wird, dann mache nichts".Ein zCVob hat in seinem Bitfield ein Flag namens physicsEnabled.. Dieses zu entfernen könnte sogar schon reichen um die Gravitation abzuschalten (sofern die Engine darauf überhaupt Rücksicht nimmt..)

MfG Gottfried

orcwarriorPL
28.04.2011, 12:46
Hi, I hope writing here in english is not prohibited :P

First, thanks for another powerful script, I dunno how you get ideas like this, but thats cool :D.

Secondly, like everything showed up there, I tried to make that script working with G1. But unfortunately it causes problem. Plus I'm don't know much about assembler.

I tried to create hook of oCNpc::DropUnconscious function (in GothicMod.exe it begins at 0x692C10) I tried many parrameters, all brings same or almost the same crash. Here's how last one hook was looking:

HookEngine(oCNpc__DropUnconscious_offset+2,5,"TEST_HOOK"); TEST_HOOK a is just one Print so it's rather not an problem.
http://i56.tinypic.com/w01f2b.jpg I don't close it, but I runned Ida debbuger, and copied some bytes, so I hope it will be helpful:

ORGINAL GothicMod:
.text:00692C00 C2 04 00 90 90 90 90 90 90 90 90 90 90 90 90 90 T.ÉÉÉÉÉÉÉÉÉÉÉÉÉ
.text:00692C10 6A FF 68 4E E8 7B 00 64 A1 00 00 00 00 50 64 89 j•hNŔ{.dí....Pdë

after runing of hooks:
GothicMod.exe:00692C00 C2 04 00 90 90 90 90 90 90 90 90 90 90 90 90 90 T.ÉÉÉÉÉÉÉÉÉÉÉÉÉ
GothicMod.exe:00692C10 6A FF E9 76 2C D1 0A 64 A1 00 00 00 00 50 64 89 j•Úv,Đ
dí....Pdë

where old instr was coppied to(+Some of code before it):
debug276:0B3A5880 00 6F 64 6E 79 20 74 75 00 00 00 00 00 60 89 05 .odny tu.....`ë
debug276:0B3A5890 F8 66 02 09 8B C1 89 05 AC 67 02 09 8B C4 89 05 °f ő+ëČg ő¦ë
debug276:0B3A58A0 60 68 02 09 68 30 5A 00 00 68 08 CE 8D 00 E8 DD `h h0Z..h+Ź.ŔŢ
debug276:0B3A58B0 3D 34 F5 83 C4 08 61 A1 AC 67 02 09 89 A1 F8 66 =4§â¦aíČg ëí°f
debug276:0B3A58C0 02 09 68 4E E8 7B 00 68 17 2C 69 00 C3 C3 00 00 hNŔ{.h,i.++..

Old Instr is at: 0B3A58C2 / Game crashed at: B3A58BC
If I don't mess up something, parser was trying to do this:
ASM_1(ASMINT_OP_movMemToEAX);

And here's constants that I used for HookEngine:

const int parser = ContentParserAddress; //0xAB40C0 zCParser
const int zParser__CallFunc = 7247504; //0x6E9690 CallFunc(int,...)
I don't tested if CallFunc is really adress of CallFunc but it should be ok.

Greetings, orcwarrior.

EDIT:
LOL! I dunno how important piece of code I removed, but it looks like now it works without it :D
I removed that part (orginally this code starts at 133).


//ASM_1(ASMINT_OP_movMemToEAX);
//ASM_4(ECXAdr());
//ASM_1(ASMINT_OP_movEAXtoECX);//-6
//
//ASM_1(ASMINT_OP_movMemToEAX);
//ASM_4(EAXAdr());

Now both npc falling on floor without problem and message from TEST_HOOK func is showed.
How Important that code was?

Lehona
29.04.2011, 11:00
It's just for the possibility to adjust EAX and ECX in the scripts. So just leave it out if you don't want to manipulate ECX or EAX (which are two common registers (Data storage), if you don't know much about IDA etc., forget about them) :)

(Though this crash is bugging me, can't say why it doesn't work for Gothic 1 since it does the job in G2)

orcwarriorPL
29.04.2011, 14:42
(Though this crash is bugging me, can't say why it doesn't work for Gothic 1 since it does the job in G2)

Hmm... I think I might found it. This part of code I had disabled.

ASM_1(ASMINT_OP_movMemToEAX);
ASM_4(ECXAdr());
ASM_1(ASMINT_OP_movEAXtoECX);//-6

ASM_1(ASMINT_OP_movMemToEAX);
ASM_4(EAXAdr());

But all was working only without this:

ASM_1(ASMINT_OP_movEAXtoECX);
So I looked for that ASMINT_OP_movEAXtoECX instruction, and it is 2bytes lenght, but it was tried to be written just with one byte by ASM_1, so I Simply changed that line to:

ASM_2(ASMINT_OP_movEAXtoECX);
And it looks like everything is working now fine, Plus it's make a sense if part of code was overwritten that assembler "parser" could be trying to write some data at invaild adress (see my crash). There is a way to check if everything is REALLY working good?
Hmm... And You're sure that this code works in G2? :p


It's just for the possibility to adjust EAX and ECX in the scripts. So just leave it out if you don't want to manipulate ECX or EAX (which are two common registers (Data storage), if you don't know much about IDA etc., forget about them :) )
Reading of them looks to be enough for me anyway.

And I have one question, there is a ability to get adress of "Hooked" function local varible stack? I find out pointer to this stack should be stored in EBP register, but there is no Another "magical" like GetECX/EAX/ESP for EBP. If I found proper Opcode for movEBPtoEAX it could work?

Gottfried
29.04.2011, 14:59
I have updated the script in the startpost.
EBP was already implemented by Lehona, but not released yet ;)

WfG Gottfried

Bonne6
30.04.2011, 23:01
Weiß jemand, wie die Funktion lautet, die den Held zurückverwandelt? Würde das gerne während Dialogen unterbinden, ist nämlich doof, wenn man Enter nicht benutzen darf, wenn man das sonst immer nimmt und als Monster keine Choices-Dialoge benutzen ist auch nicht das Wahre.

Sektenspinner
01.05.2011, 13:21
Weiß jemand, wie die Funktion lautet, die den Held zurückverwandelt? Würde das gerne während Dialogen unterbinden, ist nämlich doof, wenn man Enter nicht benutzen darf, wenn man das sonst immer nimmt und als Monster keine Choices-Dialoge benutzen ist auch nicht das Wahre.Das ist oCNpc::CheckActiveSpells(void).
Wenn du ecx auf einen gültigen Npc ohne aktive Spells änderst sollte das genau das bewirken, was du willst.

Lehona
01.05.2011, 21:00
Hmm... And You're sure that this code works in G2? :p

Well, in my internal scripts (those Gottfried uploaded) this error is already fixed, so I guess I got you the wrong version... I'm sorry :p

You can't check if EVERYTHING is really okay, but Debuggers like IDA (with the Bochs Debugger or the local Win32 Debugger) or OllyDBG are good tools to look up the new code the script made up. They allow you to inspect registers, stack and memory as well, of course.

Bonne6
15.05.2011, 16:53
Das ist oCNpc::CheckActiveSpells(void).
Wenn du ecx auf einen gültigen Npc ohne aktive Spells änderst sollte das genau das bewirken, was du willst.

Weil ich heute so fleißig bin, wird auch hier weitergemacht.

Also oCNPc::CheckActiveSpells hab ich nicht gefunden mit IDA, aber ein Human_AniCtrl::CheckActiveSpells (oder so ähnlich, ist denk ich klar, welches gemeint ist). Das wird aber offenbar ständig aufgerufen und crasht, sobald ich eine Spruchrolle inserte.

Hab mir dann in IDA diese Hierarchie anzeigen lassen, aber hat nichts funktioniert, meistens direkt beim hooken gecrasht.

Jetzt wollte ich noch das Öffnen des Inventars abfangen, hab's mit oCNpc::OpenInventory versucht, also Adresse 7742032 (oder 0X762250) und Länge 433, crasht beim Öffnen den Inventars. Ist es überhaupt richtig, die Größe zu nehmen, also Ende der Funktion - Anfang der Funktion, oder brauch ich irgendeine andere Länge? Hab bei ziemlich jeder Funktion direkt einen Crash, so bringt mir das ja dann auch nichts :(

Lehona
15.05.2011, 21:00
Err, du hast da wohl irgendwas missverstanden :p

Solltest dir die Erklärung dazu nochmal genau ansehen, nimm die Anzahl der Opcode-Bytes in der Zeile, die du hooken möchtest (Meistens genau am Anfang). Wenn dort weniger als 5 Opcode-Bytes stehen, nimm die nächste Zeile noch mit etc., hauptsache, ich habe nur ganze Instruktionen.

Das Problem dabei ist, dass alle calls, jumps etc, also alles, was relative Angaben hat, "kaputt" geht. Solange man am Anfang der Funktion hookt, könnte man etwas basteln, was das verhindert, aber dafür war ich bisher zu faul bzw. hatte zu wenig Zeit.
Versuch's einfach mal mit 'ner vernünftigen Länge :p

Bonne6
16.05.2011, 08:01
Ok, also nur die Größe des ersten Befehls, in dem Fall also von dem mov, also 6 Bytes? Zumindest crasht es damit nicht mehr und printet schön meine Meldung :D

Und gehe ich recht in der Annahme, dass in ECX der this-Pointer ist? Sekti meinte ja, ich soll das ändern, würde ja nur so Sinn machen, oder? Werde das mal anhand den Inventars testen.

Lehona
16.05.2011, 14:13
In __thiscalls enthält ECX this.

Bisasam
26.10.2017, 20:52
Das Ausstellen von physicsEnabled beim vob-bitfield mit &~ stellt leider nicht die Wahrnehmung für Fallhöhen aus. Das muss irgendwo anders liegen

mud-freak
27.10.2017, 11:04
Das Ausstellen von physicsEnabled beim vob-bitfield mit &~ stellt leider nicht die Wahrnehmung für Fallhöhen aus. Das muss irgendwo anders liegen

Die Klasse oCNpc enthält drei relevante Eigenschaften:


oCNpc.overrideFallDownHeight // 0x0908 zBOOL
oCNpc.fallDownHeight // 0x090C zREAL
oCNpc.fallDownDamage // 0x0910 int
Ich habe die mir mal etwas angeschaut.

overrideFallDownHeight wäre zwar genau das was du brauchst, allerdings ist einmaliges setzen auf false nicht genug, weil es scheinbar an irgendeiner Stelle jedes Frame wieder auf true gestellt wird.

fallDownDamage funktioniert sehr gut, allerdings gibt es immer minimalen Fallschaden (schätzungsweise 1 HP pro Meter über fallDownHeight). Daher vielleicht auch nicht wonach du suchst.

fallDownHeight entscheidet nicht nur darüber ab welcher Höhe Schaden vergeben wird, sondern (leider) auch ab welcher Höhe der Held die Fallen-Animation abspielt. Stellt man diesen Wert beispielsweise auf 99999.0 (ist ein Float), fällt der Hero mit der Animation, die er normalerweise abspielt bei kleineren Höhen (bis zu 5m). Das könntest du mal ausprobieren, ob dir das zuspricht.

Bisasam
30.10.2017, 14:05
Danke für die Infos. Ich will keine Levitation einbauen, sondern beim tragen von Npcs das Fallen verhindern. Sonst fallen die durchs Mesh und solche Späße.
Wenn ich nur für den getragenen Npc die Fallhöhe hochsetze, müsste sich das nicht auf den Helden auswirken, oder? Ich probiere das mal aus.