Im Grunde geht es hier vermutlich darum bereits gerenderten Text auf den Bildschirm zu bringen. Da das ganze in zCView:: DrawItems() passiert, wird wohl irgendein Text von einem Item gerendert (z.B. Fokusname oder die Statistiken im Inventar/Handelsmenü).
Meine Vermutung ist, dass die Absturzberichte nicht weiterhelfen, da die Abstürze an so grundlegenden Stellen passieren, die eigtl. funktionieren müssten. Ich denke, dass die Ursache ganz wo anders ist. Die Frage ist nur wo
Zumindest verweist die Adresse 0x0000C84D von CallFunc auf diese Funktion:
[...]
Kann ich aus DoStack irgendwie ablesen, wobei es hier genau gescheitert ist? Die Funktionen sind eigentlich alle sehr simpel, verteilen Gold, Erfahrung, loggen TB-Einträge...
Zu dieser Access Violation kann ich dir etwas sagen.
Um das mal etwas transparenter zu gestalten, hier wie ich vorgegangen bin.
Access Violations in zCParser::DoStack sagen uns auf den ersten Blick erst einmal nicht viel, außer dass etwas bei der Ausführung von Daedaluscode schief gelaufen ist. Du hast ja schon herausgefunden in welcher Funktion der Absturz passiert ist. Von der anderen Seite kann man bei Access Violations in IDA zu den aufgelisteten Adressen springen (IDA öffnen und im Dissassembly View mit G zu einer Adresse springen). Das habe ich gemacht, und zwar direkt mit der Adresse bei der es zum Crash kam.
Die Umgebung von Adresse 0x7918D4 sieht so aus:
Code:
...
0x7918CD mov ecx, esi
0x7918CF call zCPar_DataStack::Pop(void)
0x7918D4 mov eax, [eax]
0x7918D6 pop esi
0x7918D7 retn
Man kann erahnen, dass dieses Auslesen von EAX gescheitert ist. EAX, welches den Rückgabewert der zuvor aufgerufenen Funktion zCPar_DataStack::Pop(void) enthält, zeigte also auf keine gültige Adresse.
Um heraus zu finden wie das bitte sein kann, schauen wir in die davor aufgerufene Funktion zCPar_DataStack::Pop(void). Dort wird mit
Code:
0x7A5070 mov eax, [ecx+2000h] ; Lese von ECX+2000h und schiebe es in EAX
0x7A5076 test eax, eax ; Teste EAX gegen sich selbst
0x7A5078 jle short to_0x7A5085 ; Springe, wenn Ergebnis <= 0
überprüft, ob [ECX+2000h] kleiner gleich null ist. Wenn ja, gibt die Funktion hier 0 zurück (super Fehlerbehandlung!):
ECX ist an der Stelle ein Zeiger auf das zCPar_DataStack-Objekt. Der Offset 0x2000 (Dezimal: 8192 = 4 * 2048) ist zCPar_DataStack.sptr, siehe Ikarus Klassendokumentation in zCParser.d:
Code:
//zCPar_DataStack datastack; //Daten-Stack
var int datastack_stack[/*zMAX_SYM_DATASTACK*/ 2048]; //0x0058 //sehr klein. Bei korrekter Nutzung aber egal.
var int datastack_sptr; //0x085C int
Der Datenstackpointer ist also ungültig! Das passiert, wenn in Daedalus zu viel vom Stack gepoppt wird.
Wenn ich mich also jetzt nicht verschaut oder einen Denkfehler gemacht habe, heißt das jetzt, dass ihr die Skripte nach Funktionsaufrufen durchsuchen müsst, wo ein Rückgabenwert verarbeitet wird von einer Funktion, die gar keinen gibt; und zwar womöglich alle Skripte, denn es muss ja nicht zwingend in dem Dialog passiert sein, sondern evtl. schon vorher.
Was mich allerdings wundert ist, dass es nicht vorher zu Problemen kommt, sobald der Datenstack einmal korrumpiert ist, sondern erst, sobald der Stack leer ist.
EDIT: Ich habe gerade mal über die anderen Access Violations geschaut, die du im LeGo-Thread gepostet hast; aus dem verlinkten Beitrag werden alle durch das gleiche Problem ausgelöst. Es sollte also zumindest eine Erleichterung sein, dass alle die gleiche Ursache haben.
Gibt die VM nicht einfach 0 zurück, wenn von einem (leeren) Datenstack gepoppt wird? Es gibt auf jedenfall auch in den Originalskripten einige Stellen, wo int-Funktionen nichts auf den Datenstack schieben.
Edit: Ganz so einfach ist es wohl nicht, ich schau mir das jetzt auch nochmal genauer an.
Edit2: Ganz so simpel ist der Fehler (leider) nicht. Ein leerer Datenstack führt genau wie beschrieben einfach zu einer 0 als Wert. In diesem Fall lag vorher aber ein Wert auf dem Datenstack, nämlich 0x41 oder in anderen Worten zPAR_TOK_PUSHVAR. Das heißt, Gothic denkt, dass als nächstes die Adresse einer Variablen kommt (bzw. ein Zeiger auf zCPar_Symbol.content). Angenommen der Datenstack ist nicht durch evil data parser stack hacking korrumpiert worden, würde ich sagen, dass der Datenstack eigentlich hätte leer sein "sollen" und da zufälligerweise die Instanz 0x41 lag. Das ist allerdings printdebugch.par0, macht also nicht so viel Sinn. Also vielleicht doch ein Fehler à la Ikarus und Konsorten?
Edit3: Ich nehme an, die AV tritt bei dem Dialog nur sporadisch auf? Das macht es natürlich schwierig zu debuggen. Das wundert mich allerdings auch, denn ich dachte eigentlich, dass Gothic den Datenstack zwischen Aufrufen in die Scripts aufräumt, d.h. das nur der Dialog und vielleicht noch die Condition darauf Einfluss gehabt haben sollte.
Der Datenstackpointer ist also ungültig! Das passiert, wenn in Daedalus zu viel vom Stack gepoppt wird.
Wenn ich mich also jetzt nicht verschaut oder einen Denkfehler gemacht habe, heißt das jetzt, dass ihr die Skripte nach Funktionsaufrufen durchsuchen müsst, wo ein Rückgabenwert verarbeitet wird von einer Funktion, die gar keinen gibt; und zwar womöglich alle Skripte, denn es muss ja nicht zwingend in dem Dialog passiert sein, sondern evtl. schon vorher.
Meinst du so etwas?
Code:
Func int foo() {
}
// Irgendwo im Code
var int corrupted; corrupted = foo();
Zu dieser Access Violation kann ich dir etwas sagen.
Um das mal etwas transparenter zu gestalten, hier wie ich vorgegangen bin.
Access Violations in zCParser:oStack sagen uns auf den ersten Blick erst einmal nicht viel, außer dass etwas bei der Ausführung von Daedaluscode schief gelaufen ist. Du hast ja schon herausgefunden in welcher Funktion der Absturz passiert ist. Von der anderen Seite kann man bei Access Violations in IDA zu den aufgelisteten Adressen springen (IDA öffnen und im Dissassembly View mit G zu einer Adresse springen). Das habe ich gemacht, und zwar direkt mit der Adresse bei der es zum Crash kam.
Die Umgebung von Adresse 0x7918D4 sieht so aus:
Code:
...
0x7918CD mov ecx, esi
0x7918CF call zCPar_DataStack::Pop(void)
0x7918D4 mov eax, [eax]
0x7918D6 pop esi
0x7918D7 retn
Man kann erahnen, dass dieses Auslesen von EAX gescheitert ist. EAX, welches den Rückgabewert der zuvor aufgerufenen Funktion zCPar_DataStack::Pop(void) enthält, zeigte also auf keine gültige Adresse.
Um heraus zu finden wie das bitte sein kann, schauen wir in die davor aufgerufene Funktion zCPar_DataStack::Pop(void). Dort wird mit
Code:
0x7A5070 mov eax, [ecx+2000h] ; Lese von ECX+2000h und schiebe es in EAX
0x7A5076 test eax, eax ; Teste EAX gegen sich selbst
0x7A5078 jle short to_0x7A5085 ; Springe, wenn Ergebnis <= 0
überprüft, ob [ECX+2000h] kleiner gleich null ist. Wenn ja, gibt die Funktion hier 0 zurück (super Fehlerbehandlung!):
ECX ist an der Stelle ein Zeiger auf das zCPar_DataStack-Objekt. Der Offset 0x2000 (Dezimal: 8192 = 4 * 2048) ist zCPar_DataStack.sptr, siehe Ikarus Klassendokumentation in zCParser.d:
Code:
//zCPar_DataStack datastack; //Daten-Stack
var int datastack_stack[/*zMAX_SYM_DATASTACK*/ 2048]; //0x0058 //sehr klein. Bei korrekter Nutzung aber egal.
var int datastack_sptr; //0x085C int
Der Datenstackpointer ist also ungültig! Das passiert, wenn in Daedalus zu viel vom Stack gepoppt wird.
Wenn ich mich also jetzt nicht verschaut oder einen Denkfehler gemacht habe, heißt das jetzt, dass ihr die Skripte nach Funktionsaufrufen durchsuchen müsst, wo ein Rückgabenwert verarbeitet wird von einer Funktion, die gar keinen gibt; und zwar womöglich alle Skripte, denn es muss ja nicht zwingend in dem Dialog passiert sein, sondern evtl. schon vorher.
Was mich allerdings wundert ist, dass es nicht vorher zu Problemen kommt, sobald der Datenstack einmal korrumpiert ist, sondern erst, sobald der Stack leer ist.
EDIT: Ich habe gerade mal über die anderen Access Violations geschaut, die du im LeGo-Thread gepostet hast; aus dem verlinkten Beitrag werden alle durch das gleiche Problem ausgelöst. Es sollte also zumindest eine Erleichterung sein, dass alle die gleiche Ursache haben.
Vielen Dank für die Nachforschung und auch für die Erklärung, wie du vorgegangen bist! So kann ich mir langsam aber sicher ein paar eigene Schritte zusammenbasteln, um den Fehlern näher zu kommen. (Wie zuvor nach Anleitung Lehonas schon mal CallFunc herauszusuchen)
Wenn ich mich also jetzt nicht verschaut oder einen Denkfehler gemacht habe, heißt das jetzt, dass ihr die Skripte nach Funktionsaufrufen durchsuchen müsst, wo ein Rückgabenwert verarbeitet wird von einer Funktion, die gar keinen gibt; und zwar womöglich alle Skripte, denn es muss ja nicht zwingend in dem Dialog passiert sein, sondern evtl. schon vorher.
Das scheint mir ja schon mal ein sehr guter Ansatz zu sein! Mir fallen da 3 Möglichkeiten ein:
1) Eine func int (oder string, ...) gibt manchmal keinen Wert zurück - diese Fälle kann ich mir alle vom Parser als Warnung ausgeben lassen (oder ging das nur mit GothicSourcer, der kein Ikarus verträgt?)
2) eine func void abc(){...}; und dann wird eine Variable gesetzt x = abs(); -- das würde aber der Parser schon direkt ablehnen
3) Hook einer Engine-Funktion hat ein return drin, wodurch die Engine-Funktion keinen Wert zurückgibt. Oder lässt sich aus den Fehlermeldungen ablesen, dass es nicht in einer engine Funktion passieren kann?
Ganz so simpel ist der Fehler (leider) nicht. Ein leerer Datenstack führt genau wie beschrieben einfach zu einer 0 als Wert. In diesem Fall lag vorher aber ein Wert auf dem Datenstack, nämlich 0x41 oder in anderen Worten zPAR_TOK_PUSHVAR. Das heißt, Gothic denkt, dass als nächstes die Adresse einer Variablen kommt (bzw. ein Zeiger auf zCPar_Symbol.content). Angenommen der Datenstack ist nicht durch evil data parser stack hacking korrumpiert worden, würde ich sagen, dass der Datenstack eigentlich hätte leer sein "sollen" und da zufälligerweise die Instanz 0x41 lag. Das ist allerdings printdebugch.par0, macht also nicht so viel Sinn. Also vielleicht doch ein Fehler à la Ikarus und Konsorten?
Ah, dann könnte ich mir vorstellen, dass es vielleicht mit der neuen Implementierung der Hooks zusammenhängt. Dort habe ich vor Aufruf der Daedalusfunktionen einen Puffer von 10 Integern auf den Datenstack gelegt, mit der Absicht, dass er, falls in einer Hookfunktionen irgendwo etwas fälschlicherweise vom Stack gepoppt wird, anschließend noch intakt ist und setze anschließend den Datenstackpointer wieder an die selbe Position wie zuvor:
Zitat von HookEngine.d
Code:
// ...// Remember data stack pointer
var int sPtr; sPtr = MEM_Parser.datastack_sptr;
// Add a stack buffer for naughty functions that pop off of the data stack illegally
repeat(j, 10); var int j;
MEM_PushIntParam(0);
end;
// Call the function
MEM_CallByID(MEM_ReadIntArray(a.array, i));
// Reset the data stack pointer (remove buffer and anything left on the stack)
MEM_Parser.datastack_sptr = sPtr;
// ...
Edit3: Ich nehme an, die AV tritt bei dem Dialog nur sporadisch auf? Das macht es natürlich schwierig zu debuggen. Das wundert mich allerdings auch, denn ich dachte eigentlich, dass Gothic den Datenstack zwischen Aufrufen in die Scripts aufräumt, d.h. das nur der Dialog und vielleicht noch die Condition darauf Einfluss gehabt haben sollte.
Korrekt, die einzigen reproduzierbaren Abstürze konnte ich darauf zurückführen, dass teils die Talente nicht mitgespeichert wurden. Die neuen Abstürze hier lassen sich nicht verlässlich reproduzieren.
Func int foo() {
}
// Irgendwo im Code
var int corrupted; corrupted = foo();
So ungefähr. Grundsätzlich ist das kein Problem und wird auch im Original so gemacht (auch wenn es nicht schön ist). Solange der Wert aber nicht 0x40 oder 0x41 (PUSHINT und PUSHVAR) ist, d.h. wenn zufälligerweise irgendeine Instanz auf dem Datenstack liegt (die haben nämlich kein Markierungstoken), wird die Instanz einfach "gefressen" und 0 zurückgegeben. Der Datenstack repariert sich also selber (zumindest in etwa).
Alternative Idee: Vielleicht ist der Datenstack gar nicht leer, sondern da steht wirklich eine 0 auf dem Datenstack. Mit normalen Daedalus sollte man das nicht erreichen können, aber MEM_ReadInt(0) würde zum Beispiel genau dazu führen. Aus Performancegründen haben die entsprechenden Ikarus-Funktionen keine Sanity-Checks mehr, aber zum Debuggen könnte man in MEMINT_ReplaceSlowFunctions() den Aufruf an MEMINT_InitFasterReadWrite() auskommentieren, dann sollten MEM_WriteInt/MEM_ReadInt genau so funktionieren, wie es im Code steht. Solltet ihr die Konstante zERR_StackTraceOnlyForFirst nicht zufällig auf 1 gesetzt haben (0 ist der Default-Wert), wird es dann bei ungültigen Aufrufen, d.h. mit ptr <= 0, einen Stacktrace geben.
Zitat von Milky-Way
Vielen Dank für die Nachforschung und auch für die Erklärung, wie du vorgegangen bist! So kann ich mir langsam aber sicher ein paar eigene Schritte zusammenbasteln, um den Fehlern näher zu kommen. (Wie zuvor nach Anleitung Lehonas schon mal CallFunc herauszusuchen)
Das scheint mir ja schon mal ein sehr guter Ansatz zu sein! Mir fallen da 3 Möglichkeiten ein:
1) Eine func int (oder string, ...) gibt manchmal keinen Wert zurück - diese Fälle kann ich mir alle vom Parser als Warnung ausgeben lassen (oder ging das nur mit GothicSourcer, der kein Ikarus verträgt?)
2) eine func void abc(){...}; und dann wird eine Variable gesetzt x = abs(); -- das würde aber der Parser schon direkt ablehnen
3) Hook einer Engine-Funktion hat ein return drin, wodurch die Engine-Funktion keinen Wert zurückgibt. Oder lässt sich aus den Fehlermeldungen ablesen, dass es nicht in einer engine Funktion passieren kann?
1) Würde mich wundern, wenn der Gothic-Parser da irgendwas meckert. Mit Parsiphae sollte man das recht einfach herausfinden können (wenn wir annehmen, dass einfach nur return; in solchen Funktionen vom Gothic-Parser verboten wird). Wirklich Zeit um sowas zu schreiben habe ich aber nicht. Falls wir uns sicher sind, dass es daran liegt, denk ich nochmal drüber nach
2) Da würde ich dir zustimmen - meines Wissens erlaubt der Parser nicht die Verwendung von void-Funktionen als Wert (d.h. dein Beispiel würde beim Parsen einen Fehler geben).
3) Das halte ich, gerade in Verbindung mit mud-freaks Post, nicht für unmöglich. Die Hooks sollte man halbwegs sinnvoll überprüfen können, ob da der Stack korrumpiert wird. Könnte aber natürlich sein, dass das irgendwo in den LeGo-Funktionen passiert und vielleicht nur mit den neuen Hooks auftritt?
Ah, dann könnte ich mir vorstellen, dass es vielleicht mit der neuen Implementierung der Hooks zusammenhängt. Dort habe ich vor Aufruf der Daedalusfunktionen einen Puffer von 10 Integern auf den Datenstack gelegt, mit der Absicht, dass er, falls in einer Hookfunktionen irgendwo etwas fälschlicherweise vom Stack gepoppt wird, anschließend noch intakt ist und setze anschließend den Datenstackpointer wieder an die selbe Position wie zuvor:
Ich weiß nicht mehr genau, wo dieser Gedankengang herkam... Denn zCParser::CallFunc räumt vor Funktionsaufruf einfach den Datenstack auf (siehe 0x792A76) - wieso habe ich das dann nicht genau so gemacht? Oder nicht einfach ganz sein gelassen, denn _Hook wird ja schon mit leerem Datenstack von zCParser::CallFunc aufgerufen?! Ich gehe mal meine alten Forenbeiträge lesen...
Ob es wirklich damit zutun hat weiß ich nicht.
EDIT: Ah, es ging darum, alles zwischen den Funktionen zu sichern. Ein zCPar_DataStack::Clear wäre aber trotzdem sinnvoller.
Ich weiß nicht mehr genau, wo dieser Gedankengang herkam... Denn zCParser::CallFunc räumt vor Funktionsaufruf einfach den Datenstack auf (siehe 0x792A76) - wieso habe ich das dann nicht genau so gemacht? Oder nicht einfach ganz sein gelassen, denn _Hook wird ja schon mit leerem Datenstack von zCParser::CallFunc aufgerufen?! Ich gehe mal meine alten Forenbeiträge lesen...
Ob es wirklich damit zutun hat weiß ich nicht.
EDIT: Ah, es ging darum, alles zwischen den Funktionen zu sichern. Ein zCPar_DataStack::Clear wäre aber trotzdem sinnvoller.
Wenn ich mich nicht täusche, dann dürfte es zumindest ok sein, wenn man das erst einmal auskommentiert und schaut, ob es dann noch immer zu Abstürzen kommt?
Code:
// Add a stack buffer for naughty functions that pop off of the data stack illegally
repeat(j, 10); var int j;
MEM_PushIntParam(0);
end;
Wenn ich mich nicht täusche, dann dürfte es zumindest ok sein, wenn man das erst einmal auskommentiert und schaut, ob es dann noch immer zu Abstürzen kommt?
Da es sowie Unsinn ist, habe ich es heraus genommen und durch ein Leeren des Datenstacks ersetzt (wie zCParser::CallFunc es macht). Ich habe es in den Developmentbranch von LeGo gepusht:
EDIT: Ah, es ging darum, alles zwischen den Funktionen zu sichern. Ein zCPar_DataStack::Clear wäre aber trotzdem sinnvoller.
Ich verstehe nicht genau, was du damit meinst. In jedem Fall könnte man aber testen, was passiert, wenn eine Hook durch Daedalus-Code ausgelöst wird (via External). Oder wird das so oder so nicht richtig funktionieren, weil CallFunc den Stack cleart und sowieso alles kaputt macht?
Alternative Idee: Vielleicht ist der Datenstack gar nicht leer, sondern da steht wirklich eine 0 auf dem Datenstack. Mit normalen Daedalus sollte man das nicht erreichen können, aber MEM_ReadInt(0) würde zum Beispiel genau dazu führen. Aus Performancegründen haben die entsprechenden Ikarus-Funktionen keine Sanity-Checks mehr, aber zum Debuggen könnte man in MEMINT_ReplaceSlowFunctions() den Aufruf an MEMINT_InitFasterReadWrite() auskommentieren, dann sollten MEM_WriteInt/MEM_ReadInt genau so funktionieren, wie es im Code steht. Solltet ihr die Konstante zERR_StackTraceOnlyForFirst nicht zufällig auf 1 gesetzt haben (0 ist der Default-Wert), wird es dann bei ungültigen Aufrufen, d.h. mit ptr <= 0, einen Stacktrace geben.
Kommt der Stacktrace dann auch irgendwo an für Spieler erreichbarer Stelle, oder muss dafür zSpy laufen?
Zitat von Lehona
3) Das halte ich, gerade in Verbindung mit mud-freaks Post, nicht für unmöglich. Die Hooks sollte man halbwegs sinnvoll überprüfen können, ob da der Stack korrumpiert wird. Könnte aber natürlich sein, dass das irgendwo in den LeGo-Funktionen passiert und vielleicht nur mit den neuen Hooks auftritt?
func void HOOK_AI_Drop(){
if(ECX == 0) {return;};
// ----- an den NPC und das item kommen -----
var c_npc slf; slf = MEM_PtrToInst(ECX); // der NPC, der das Item einsammelt
if (!Hlp_isValidNpc(slf)) {return;};
var C_ITEM itm; itm = MEM_PtrToInst(MEM_ReadInt(ESP + 4)); // Pointer auf das Item
//PrintDebugg(ConcatStrings(slf.name, itm.name));
if (!Hlp_IsValidItem (itm)) {return;};
/*
if (DII_IsDynamic(itm)) {
var DII_USER_DATA data; data = DII_GetUserData(Hlp_GetInstanceID(itm));
if (data.data[MAGICWEAPON_ENCHANTEDWEAPON]) {
MagicWeaponSetEffect(itm, data.magicWeaponNewEffect);
};
};
*/
// only accept hero as npc
if (Hlp_GetInstanceID(slf) != Hlp_GetInstanceID(hero)) {return;};
if (itm.flags & ITEM_MISSION){
PrintDebugg("Missions Item fallen gelassen. Solltest du lieber nicht machen!");
return;
};
if Hlp_IsItem (itm, ItMi_Gold){
PrintDebugg("it's the Gold item");
AMBVAR_DROPWP = Npc_GetNearestWP(hero);
AMBVAR_Hero_dropped_Gold = TRUE;
TIMEVAR_Drop_pickup = Wld_GetTimePlus(0,0,6);
};
};
Sind die return hier ein Problem? Sollte eigentlich immer ein Item in der Welt erstellt werden, aber manchmal verhindern wir, dass das passiert?
Ich verstehe nicht genau, was du damit meinst. In jedem Fall könnte man aber testen, was passiert, wenn eine Hook durch Daedalus-Code ausgelöst wird (via External). Oder wird das so oder so nicht richtig funktionieren, weil CallFunc den Stack cleart und sowieso alles kaputt macht?
Die ursprüngliche Absicht von dem Stackpuffer war es zu vermeiden, dass Funktionen, die die gleiche Adresse hooken sich untereinander den Datenstack kaputt machen (Sachen liegen lassen, zu viel poppen usw.).
Du hast recht, das sollte schief laufen. Der einzige Weg Daedalus auszuführen geht durch CallFunc, welches den Datenstack leert. Hier ein kleiner Test mit gehookten External, der das bestätigt.
Code:
/*
* Printing function
*/
func void hooktest1(var int p1, var int p2, var int p3){
MEM_Info(IntToString(p1));
MEM_Info(IntToString(p2));
MEM_Info(IntToString(p3));
/* Expected output:
Starting hook test
You just got hijacked
1
19333
3
*//* Actual output:
Starting hook test
You just got hijacked
0
19333
3
*/};
/*
* Function that puts something on the stack, then calls a hooked external
*/
func void hooktest2(){
MEM_Info("Starting hook test");
var int arg1; arg1 = 1;
var int arg3; arg3 = 3;
hooktest1(arg1, Hlp_GetInstanceID(hero), arg3);
};
/*
* Hook external function
*/
func void hooktest3(){
MEM_Info("You just got hijacked");
};
/*
* Initialization
*/
func void hooktestinit(){
const int Hlp_GetInstanceID_0x14 = 7305172; //0x6F77D4
HookEngineF(Hlp_GetInstanceID_0x14, 8, hooktest3);
FF_ApplyOnceExt(hooktest2, 5000, 1);
};
Sind die return hier ein Problem? Sollte eigentlich immer ein Item in der Welt erstellt werden, aber manchmal verhindern wir, dass das passiert?
Das Skript an sich sollte keine Probleme machen, allerdings wird hier tatsächlich eine externe Funktion gehookt, ebenso hier:
Code:
HookEngineF (7654416, 6, Hook_EV_DrawWeapon); //Hook EV_DrawWeapon located at 0x74CC10
HookEngineF (7656160, 5, Hook_EV_DrawWeapon); //Hook EV_DrawWeapon1 located at 0x74D2E0
HookEngineF (7656832, 6, Hook_EV_DrawWeapon); //Hook EV_DrawWeapon2 located at 0x74D580
HookEngineF (7621056, 6, Hook_EV_DoTakeVob); //Hook EV_DrawWeapon2 located at 0x74D580
HookEngineF ( 7622096, 6, HOOK_AI_Drop); // Hook AIDrop (/*0x00744DD0*/)
Lehonas Gedanke scheint da ganz richtig zu sein. Sobald in den Skripten irgendwo AI_DrawWeapon, AI_DropItem oder AI_TakeItem aufgerufen wird, ist der Daedalus-Datenstack anschließend leer.
@mud_freak: Ich habe das Beispiel mal versucht zu starten, aber das Spiel stürzt ab nachdem hooktest3() ausgeführt worden ist.
Das würde also bedeuten, dass man Externals nicht hooken darf.
Das Skript an sich sollte keine Probleme machen, allerdings wird hier tatsächlich eine externe Funktion gehookt, ebenso hier:
...
Lehonas Gedanke scheint da ganz richtig zu sein. Sobald in den Skripten irgendwo AI_DrawWeapon, AI_DropItem oder AI_TakeItem aufgerufen wird, ist der Daedalus-Datenstack anschließend leer.
Nein, alle AI-Funktionen erzeugen bloß eine Nachricht, die in die EventManager-Queue ("AI-Queue") eingereiht wird (und das EV_*-Prefix deutet darauf hin, dass diese Funktionen bei der Verarbeitung dieser Mesages aufgerufen werden). DoDropVob() schon eher (ist 'ne virtuelle Funktion und IDA hat keine xrefs), bezweifle ich aber auch. Zur Sicherheit könnte man den kompletten Datenstack sichern? Müsste man aber wohl in Assembly machen. Solange das nicht der Auslöser ist, also den Aufwand erstmal nicht wert (auch performancetechnisch). Könnte maner aber als zusätzliche Funktion in HookEngine anbieten.
Zitat von Milky-Way
Kommt der Stacktrace dann auch irgendwo an für Spieler erreichbarer Stelle, oder muss dafür zSpy laufen?
Das wird nur in den zSpy geprintet, das hatte ich ganz vergessen. Mit den BinaryMachines aus LeGo kann man das aber "einfach" in eine Datei schreiben. Ist aber natürlich mal wieder zusätzlicher Entwicklungsaufwand.
virtual int __thiscall oCNpc::DoDropVob(zCVob *) 0x00744DD0 0 6 public: virtual int __thiscall oCNpc::DoDropVob(zCVob *)
wird gehookt:
...
Sind die return hier ein Problem? Sollte eigentlich immer ein Item in der Welt erstellt werden, aber manchmal verhindern wir, dass das passiert?
Nein, das "return" bedeutet doch nur, dass die Funktion verlassen wird - effektiv das selbe, wie ein vollständiges Durchlaufen der Funktion (vgl. zum Beispiel eine leere Funktion anstatt HOOK_AI_Drop, die führt effektiv auch nur ein "return" aus).
@mud_freak: Ich habe das Beispiel mal versucht zu starten, aber das Spiel stürzt ab nachdem hooktest3() ausgeführt worden ist.
Das würde also bedeuten, dass man Externals nicht hooken darf.
Wirklich? Bei mir funktioniert das. Hast du eine AV und/oder einen Stacktrace im zSpy?
Zitat von Lehona
Nein, alle AI-Funktionen erzeugen bloß eine Nachricht, die in die EventManager-Queue ("AI-Queue") eingereiht wird (und das EV_*-Prefix deutet darauf hin, dass diese Funktionen bei der Verarbeitung dieser Mesages aufgerufen werden).
Da habe ich nicht nachgedacht. Sorry, wollte hier nicht noch mehr Verwirrung stiften.
Nein, das "return" bedeutet doch nur, dass die Funktion verlassen wird - effektiv das selbe, wie ein vollständiges Durchlaufen der Funktion (vgl. zum Beispiel eine leere Funktion anstatt HOOK_AI_Drop, die führt effektiv auch nur ein "return" aus).
Ach, natürlich. Da hatte ich kurz fälschlicherweise gedacht, dass return aus der gehookten Funktion springt. Aber es wird ja nicht der Funktionsinhalt eingefügt, sondern die Funktion aufgerufen. Danke für die Erinnerung
Erscheint es dann aktuell noch wahrscheinlich, dass die Abstürze durch die hook-Version von heute gelöst werden, oder ist weiterhin unbekannt, wie es dadurch zu einem leeren Stack kommen kann? Was ist die empfohlene weitere Vorgehensweise? Ich kann unseren Tester bitten, einige Stunden mit der neuen Version zu spielen, aber man weiß natürlich auch nicht, ab wann genug gespielt wurde, um zu sagen, dass die Abstürze nicht mehr auftreten.
Ist hier irgendein Zusammenhang zu den nicht gespeicherten Talenten von PermMem denkbar?
Wirklich? Bei mir funktioniert das. Hast du eine AV und/oder einen Stacktrace im zSpy?
Es stürzt nicht bei hooktest2() ab sondern an anderer Stelle. Weiß nicht genau wo, ich habe nur alles auskommentiert, was mit Hooks zu tun hatte.
Zudem habe ich hooktest3() in der INIT_Global() aufgerufen.
An sich ist der Absturz aber nicht ganz unlogisch, da ja der Stack geleert wird.