-
Nun, diese Namen wurden ja auch nicht ganz zufällig ausgewählt
-
Bei Ikarus war mir das bewusst, aber dass auch noch Lego dabei ist.
-
Ist es eigentlich möglich, eine neu erstellte Klasse/Instanz wieder aus einem Savegame rauszukriegen? Bei Funktionen und Variablen hat Gothic ja kein Problem, wenn sie z.B. in Modversion 1.0 vorkommen und im 1.0-Speicherstand gespeichert sind, aber nicht mehr in der 1.1-Mod vorhanden sind, mit der man den alten Speicherstand lädt. Wenn ich aber eine Klasse erstelle (die nur ints enthält) dann stürzt die Version 1.1 beim Laden des alten Speicherstandes ab.
Konkretes Beispiel:
Code:
CLASS AttackObj
{
var int att; // Pointer
var int vic; // ditto
var int mag; // Spell-ID
var int dmg; // Schaden
var int chargeup; // Aufladungs-Level
var int damtype; // Schadenstyp
var int time; // Zeitpunkt des Castens
};
INSTANCE AttackObj@ (AttackObj) {};
Um sicherzustellen, dass kein gespeichertes Objekt mehr diese Instanz nutzt, habe ich folgendes Skript einmal laufen lassen und dann abgespeichert:
Code:
func int _DelAttObj (var int hndl) {
var AttackObj myAttackObj; myAttackObj = get(hndl);
delete(myAttackObj);
return continue;
};
func void DelAttObjs () {
ForEachHndl(AttackObj@,_DelAttObj);
};
Ist es nicht möglich, das Savegame in einen Zustand zu bringen, sodass es ohne den ersten Codeblock in der Mod lädt? Oder hab ich das Löschen vergeigt?
Mit und ohne die zwei Codeblöcke funktioniert das Spiel ansonsten einwandfrei, es ist einfach nur Code-Ballast den ich in meinem in 1.0 gestarteten Spiel behalten muss und dann entfernen muss, bevor ich 1.1 release.
EDIT: Das Problem scheint die Instanz zu sein, nicht die Klasse. Wenn ich die unterste Zeile im ersten Codeblock auskommentiere, kommt es auch zum Crash zu Startup.
EDIT2: Mir ist gerade erst aufgefallen, dass das Grundproblem gar nichts mit LeGo zu tun hat. Soll ich einen anderen Thread aufmachen?
“Da ist auch noch ein anderer Geruch in der Luft, der Geruch von Feuern, die in der Ferne brennen, mit einem Hauch Zimt darin - so riecht das Abenteuer!”
― aus Walter Moers' "Die 13 1/2 Leben des Käpt'n Blaubär"
Geändert von GiftGrün (18.05.2017 um 14:35 Uhr)
-
Wenn das Problem nicht an LeGo direkt liegt (du kannst in der SCRPTSAVE.SAV einfach alle Einträge der genannten Klasse/Instanz von Hand löschen zum Testen), kann ich dir wohl nicht weiterhelfen. Generell ist es problematisch, alte Spielstände weiterhin zu benutzen, mehr weiß ich leider auch nicht.
Geändert von Lehona (18.05.2017 um 15:15 Uhr)
-
Mir ist nicht ganz klar, warum die Instanz eine Funktion zugewiesen hat und keine Instanzenreferenz ist (wie Self, Other, ... ohne geschweifte Klammern).
"Unter diesen schwierigen Umständen bin ich mir sicher, daß diese guten Menschen meinen augenblicklichen Bedarf an deren Gold verstehen werden." -- Connor
-
Zitat von NicoDE
Mir ist nicht ganz klar, warum die Instanz eine Funktion zugewiesen hat und keine Instanzenreferenz ist (wie Self, Other, ... ohne geschweifte Klammern).
Weil entweder Gottfried oder ich mal geschrieben habe, dass beides okay ist.
Macht das denn einen Unterschied, außer dass der "Konstruktor" bei der einen Variante null ist und bei der anderen "nur" leer?
-
Macht nur intern einen kleinen Unterschied, der meist keine Rolle spielen dürfte. Ist mir nur aufgefallen...
Sollte man vielleicht überprüfen ob get etwas gültiges zurückgibt, bevor man delete aufruft?
Hab hier kein Gothic zur Hand, also nur ins Blaue geraten :-)
"Unter diesen schwierigen Umständen bin ich mir sicher, daß diese guten Menschen meinen augenblicklichen Bedarf an deren Gold verstehen werden." -- Connor
-
Zitat von NicoDE
Sollte man vielleicht überprüfen ob get etwas gültiges zurückgibt, bevor man delete aufruft?
Sollte man, aber delete() kommt auch mit ungültigen Werten zurecht.
Du hast mich aber auf eine Idee gebracht: Man sollte definitv die Dokumentation lesen
Als Parameter wird natürlich ein Handle erwartet, kein Objekt. Daher lässt sich der oben gezeigte Code auch viel einfacher schreiben:
Code:
ForEachHndl(AttackObj@, delete);
-
Danke, damit hat's geklappt!
“Da ist auch noch ein anderer Geruch in der Luft, der Geruch von Feuern, die in der Ferne brennen, mit einem Hauch Zimt darin - so riecht das Abenteuer!”
― aus Walter Moers' "Die 13 1/2 Leben des Käpt'n Blaubär"
-
I might have found a bug (not like the one in my previous thread )
Do you remember when I told you that FFs did not automatically run on loading? Well you fixed that with one of the previous realeses. Nevertheless it seems that the fix does not apply with FFs with data.
Code:
FF_ApplyOnceExt(PoisonDamage, 1000, -1); //automatically starts
FF_ApplyOnceExtData(DMG_HeavyInjury, 1000, -1, MEM_InstToPtr(hero)); //does not, have to call it explicitly in the init
-
Also, I found a problem with Wld_SetTime used with FFs. Trivial example:
Code:
instance TestPC3(C_Item)
{
name = "time";
mainflag = ITEM_KAT_DOCS;
flags = ITEM_MISSION;
value = 0;
visual = "ItWr_Scroll_01.3DS";
material = MAT_LEATHER;
on_state[0] = UseTestPC3;
scemeName = "MAP";
description = name;
};
var int a;
func void aaa()
{
Wld_SetTime(a+9,10+a);
a += 1;
};
func void UseTestPC3()
{
FF_ApplyOnceExt(aaa,3000,5);
};
It just crashes, zspy logs nothing, but I get this window:
[Bild: Immagine.png]
The problem seems not to be in the time change itself, but rather in the routines changes. In fact if I did the same thing this way:
Code:
func void aaa()
{
MEM_WorldTimer.worldTime = addf(MEM_WorldTimer.worldTime,mkf(250000));
};
func void UseTestPC3()
{
FF_ApplyOnceExt(aaa,3000,5);
};
Hours increase by 1 each time, but I have no change in npcs routine.
Geändert von Frank-95 (04.06.2017 um 09:29 Uhr)
-
Interesting find. I'll try to find out what' s causing the crash.
-
If you need more info, this morning I made some reverse engineering to check where the problem could be.
Daedalus Wld_SetTime changes npcs routines and, if needed, spawn npcs or monsters too. No functions of oCWorldTimer do this, so I supposed that the function that Wld_SeTime calls is:
.text:006C4DE0 ; void __thiscall oCGame::SetTime(oCGame *this, int, int, int)
This function, in addition to change the time, calls:
.text:007764D0 ; void __thiscall oCRtnManager::SetDailyRoutinePos(oCRtnManager *this, int)
.text:006CACB0 ; void __thiscall oCGame::SetObjectRoutineTimeChange(oCGame *this, int, int, int, int)
.text:00777A40 ; void __thiscall oCSpawnManager::SpawnImmediately(oCSpawnManager *this, int)
So I've reimplement that function manually:
Code:
func void RtnManager_SetDailyRoutinePos(var int state)
{
const int RtnManager_ptr = 11219400;
CALL_IntParam(state);
CALL__thiscall(RtnManager_ptr,7824592);
};
func void Game_SetObjectRoutineTimeChange(var int hour1, var int min1, var int hour2, var int min2)
{
CALL_IntParam(min2);
CALL_IntParam(hour2);
CALL_IntParam(min1);
CALL_IntParam(hour1);
CALL__thiscall(MEM_ReadInt(MEMINT_oGame_Pointer_Address),7122096);
};
func void SpawnManager_SpawnImmediately(var int state)
{
CALL_IntParam(state);
CALL__thiscall(MEM_Game.spawnman,7830080);
};
func void Game_ManualSetTime(var int day, var int hour, var int minute)
{
var int currentHour; currentHour = Wld_GetHour();
var int currentMinute; currentMinute = Wld_GetMinute();
MEM_WorldTimer.day = day;
MEM_WorldTimer.worldTime = addf(MEM_WorldTimer.worldTime,mulf(mkf(oCWorldTimer_TicksPerHour),mkf(hour)));
MEM_WorldTimer.worldTime = addf(MEM_WorldTimer.worldTime,mulf(mkf(oCWorldTimer_TicksPerMin_approx),mkf(minute)));
RtnManager_SetDailyRoutinePos(1);
Game_SetObjectRoutineTimeChange(currentHour,currentMinute,Wld_GetHour(),Wld_GetMinute());
SpawnManager_SpawnImmediately(1);
};
(okay, I've mistakened since my function just adds time and not sets it, but the point is not in there, which is the easiest part)
My function works, except for what I've just said. Anyway it crashes the same way when I call it via frame function:
Code:
func void aaa()
{
Game_ManualSetTime(1,12,32);
};
func void UseTestPC3()
{
FF_ApplyOnceExt(aaa,3000,1);
};
Exactly like the original function
Code:
func void Game_SetTime(var int day, var int hour, var int minute)
{
CALL_IntParam(minute);
CALL_IntParam(hour);
CALL_IntParam(day);
CALL__thiscall(MEM_ReadInt(MEMINT_oGame_Pointer_Address),7097824);
};
Then I commented the last three function and checked which made the game crashes. It resulted to be RtnManager_SetDailyRoutinePos.
-
Okay I've found out that the problem is not just in that function. If I call SetDailyRoutinePos via FF, it works. If I call SetObjectRoutinePos and/or SpawnImmediately, it works. If I call the three together, it doesn't.
A hack to make oCGame_SetTime work via FF, is to do something like this:
Code:
var int currentHour; currentHour = Wld_GetHour();
var int currentMinute; currentMinute = Wld_GetMinute();
FF_ApplyOnceExt(func1, time, 1);
func void func()
{
MEM_WorldTimer.worldTime = ...; //change or add time as needed
RtnManager_SetDailyRoutinePos(1);
FF_ApplyOnceExt(func2,1000,1);
};
func void func2()
{
Game_SetObjectRoutineTimeChange(currentHour,currentMinute,Wld_GetHour(),Wld_GetMinute());
SpawnManager_SpawnImmediately(1);
};
It's not clean, but it's the best I've come up so far
-
Gibt es eine einfache Möglichkeit zu testen, ob eine Adresse gültig ist?
Der Hintergrund ist eine Data-FrameFunction, dessen Data ein Pointer ist, der nach Spielladen sicherlich ungültig ist.
Code:
func void exampleFF(var int ptr) {
if (valid(ptr)) {
MEM_ReadInt(ptr+someoffset); // This would crash after loading a saved game
};
};
Das von PermMem bereitgestellte Handle-System ist ein guter Ansatz. Allerdings zeigt der Pointer auf eine nicht-dokumentierte Klasse, daher komme ich mit wrap so nicht weiter. Gibt es da eine einfache Alternative?
Der Pointer muss das Laden eines Spiels nicht überleben, ich will nur einen Crash abfangen.
EDIT: Die Problemstellung ist ein bisschen falsch hier (der Beispiel-Code führt nicht zwingend zum Crash), da die Adresse an sich immer gültig ist. Was ich einfach überprüft habe ist, ob der Wert an bestimmtem Offset vom Pointer den Erwartungen enspricht.
Geändert von mud-freak (14.06.2017 um 11:38 Uhr)
-
Dein Beispiel ist etwas sehr minimal, aber deine Frage geht auf jedenfall in die Richtung "geht nicht".
Ohne deinen Fall besser zu kennen würde ich es so machen:
Code:
var int currGen;
class PtrGeneration {
var int ptr;
var int generation;
};
instance PtrGeneration@(PtrGeneration);
func int createPtr(var int ptr) {
var int h; h = new(PtrGeneration@);
var PtrGeneration PtrG; PtrG = get(h);
PtrG.ptr = ptr;
PtrG.generation = currGen;
};
func int validPtr(var int h) {
var PtrGeneration PtrG; PtrG = get(h);
return (PtrG.generation == currGen);
};
Dann musst du currGen nur noch an geeigneter Stelle inkrementieren - beim Spielstand laden oder beim Neustart von Gothic.
-
Gibt es ein definiertes Verhalten, wenn man zwei verschiedene Deadalusfunktionen an die gleiche Adresse hooked?
Beispiel:
Code:
HookEngineF(addr1, 5, func1);
HookEngineF(addr1, 5, func2);
Ich hätte erwartet, dass beim erreichen der Adresse zuerst func1 und dann func2 aufgerufen wird, aber ich bin mir nicht so sicher. Kann man überhaupt für eine Reihenfolge garantieren, oder ist so etwas so wie etwas gefährlich?
EDIT: Hat sich erledigt. Hat eine Weile gedauert bis ich nachvollziehen konnte, dass der ursprüngliche Opcode an der Adresse ans Ende kopiert wird und dann durch den rekursiven Aufrufen (wenn man erneut an die Adresse hooken will) erneut überschrieben/verschoben wird. Somit werden also die Hooks natürlich der Reihenfolge entsprechend der Initialisierung nach aufgerufen.
Geändert von mud-freak (09.08.2017 um 14:45 Uhr)
-
Zitat von mud-freak
Gibt es ein definiertes Verhalten, wenn man zwei verschiedene Deadalusfunktionen an die gleiche Adresse hooked?
Beispiel:
Code:
HookEngineF(addr1, 5, func1);
HookEngineF(addr1, 5, func2);
Ich hätte erwartet, dass beim erreichen der Adresse zuerst func1 und dann func2 aufgerufen wird, aber ich bin mir nicht so sicher. Kann man überhaupt für eine Reihenfolge garantieren, oder ist so etwas so wie etwas gefährlich?
EDIT: Hat sich erledigt. Hat eine Weile gedauert bis ich nachvollziehen konnte, dass der ursprüngliche Opcode an der Adresse ans Ende kopiert wird und dann durch den rekursiven Aufrufen (wenn man erneut an die Adresse hooken will) erneut überschrieben/verschoben wird. Somit werden also die Hooks natürlich der Reihenfolge entsprechend der Initialisierung nach aufgerufen.
Ich hätte erwartet, dass zuerst func2, dann func1 und dann der ursprüngliche Code ausgeführt wird? (oder ist es das, was du meinst)
Es könnte aber vielleicht zu Problemen führen, wenn die 5 Bytes nicht passen für den ersten Befehl von func1?
-
So wie ich es aus dem Code entnehmen konnte, wird die ursprüngliche Instruktion durch einen Jump zu einer neuen Adresse ersetzt. An dieser neuen Adresse wird (u.a.) die Deadalusfunktion aufgerufen und anschliessen wird die ursprüngliche Instruktion drangehängt, bevor es zurück "nach" die usprüngliche Adresse geht.
Wenn man nun erneut an der selben Adresse hookt, wird dem Jump gefolgt und dann wieder die ursprüngliche Instruktion, die einst an der ursprünglichen Adresse war, durch einen Jump ersetzt und anschliessend wieder angehängt. So geht das immer weiter und die Reihenfolge der Hooks bleibt erhalten.
Ich bitte um Korrektur wenn ich das falsch verstanden habe.
-
Ist es nicht so, dass der Jump ersetzt wird? Wie ich das verstehe:
Sagen wir, addr1 wäre 00F43960 und die Funktion an dieser Stelle foo (10 bytes). Dann sähe das nach dem ersten Hook doch so aus:
Code:
00F43960 - JUMP TO
00F43961 - 13
00F43962 - 5A
00F43963 - 92
00F43964 - 34
Und beginnend ab Adresse 135A9234 steht [was auch immer func1 macht] [erste fünf Bytes von foo] RETURN.
Der zweite Hook macht dann daraus:
Code:
00F43960 - JUMP TO
00F43961 - 24
00F43962 - AF
00F43963 - 40
00F43964 - 19
Und an Adresse 24AF4019 steht [was auch immer func2 macht] JUMP TO 135A9234 RETURN. Was dann dazu führt, dass func2 vor func1 aufgerufen wird.
“Da ist auch noch ein anderer Geruch in der Luft, der Geruch von Feuern, die in der Ferne brennen, mit einem Hauch Zimt darin - so riecht das Abenteuer!”
― aus Walter Moers' "Die 13 1/2 Leben des Käpt'n Blaubär"
Berechtigungen
- Neue Themen erstellen: Nein
- Themen beantworten: Nein
- Anhänge hochladen: Nein
- Beiträge bearbeiten: Nein
|