Return values are passed in eax, that is correct. Have you actually looked at the assembly or are you just hooking based on function pointers? In the latter case you probably hooked at the beginning of the function, therefore eax does not contain the correct value yet.
I don't know which function is the correct one, but you should consider that even if you find the correct function, it may have been inlined at various places, so you can't reliably update the current item.
In my opinion there are two approaches for developing this:
1. Have a look at oCNpc.d: The oCNpc class contains some sort of Inventory-struct, which also contains members that seem to indicate the currently selected item or at least index into the itemlist. This would include some trial-and-error to find out which of those is actually used/what exactly they mean.
2. Look at e.g. Something::TransferLeft (can't remember the class name), this method is called when the player sells something in a trade dialog. Although trade dialogs are slightly different, this method will have to use the currently selected item, so maybe you can find out which member of the inventory that is. Unless you have your workflow for reverse engineering already set up, this will probably take longer than just toying around with the inventory class.
Once you know how to find the selected item, hook opening/closing of the inventory to start/stop a function that reads the selected item and updates your bars accordingly.
I believe this one works for us and is what you are looking for:
Code:
//=====func vars=======
var oCNpc npc;
var int nmb;
var zCListSort list;
var int itm_ptr;
//=====Inv_GetSelectedItem=====
func int Inv_GetSelectedItem(var int slf)
{
npc = Hlp_GetNpc (slf);
nmb = npc.inventory2_oCItemContainer_selectedItem + 2; //anscheinend sind die ersten beiden items in der List nie belegt.
list = _^(npc.inventory2_oCItemContainer_contents);
if (List_HasLengthS (_@(list), nmb))
{
itm_ptr = List_GetS (_@(list), nmb);
return itm_ptr;
}
else
{
return 0;
};
};
Example usage (dismantling runes: if a player selects an item, this code is called and checks whether it is a rune - if yes, it dismantles the rune):
I believe this one works for us and is what you are looking for:
Code:
//=====Inv_GetSelectedItem=====
func int Inv_GetSelectedItem(var int slf)
{
npc = Hlp_GetNpc (slf);
nmb = npc.inventory2_oCItemContainer_selectedItem + 2; //anscheinend sind die ersten beiden items in der List nie belegt.
list = _^(npc.inventory2_oCItemContainer_contents);
if (List_HasLengthS (_@(list), nmb))
{
itm_ptr = List_GetS (_@(list), nmb);
return itm_ptr;
}
else
{
return 0;
};
};
Example usage (dismantling runes: if a player selects an item, this code is called and checks whether it is a rune - if yes, it dismantles the rune):
Return values are passed in eax, that is correct. Have you actually looked at the assembly or are you just hooking based on function pointers? In the latter case you probably hooked at the beginning of the function, therefore eax does not contain the correct value yet.
I am hooking based on the function pointers of the gothic.xls which is going around. Don't know yet how to use IDA, besides I have issues with windowed Gothic, which does not allow focus loss at all. So I am trail n erroring my way with Zspy
How do I find the correct hook then?
concerning EAX would I use the Call_Ret... or MEM_PtrToInst(EAX) to get the return value if I had the correct function?
Is it in general wise to look for the right trigger / function to hook or just rely on some FrameFunctions called on each frame?
I believe this one works for us and is what you are looking for:
Code:
//=====Inv_GetSelectedItem=====
func int Inv_GetSelectedItem(var int slf)
{
npc = Hlp_GetNpc (slf);
nmb = npc.inventory2_oCItemContainer_selectedItem + 2; //anscheinend sind die ersten beiden items in der List nie belegt.
list = _^(npc.inventory2_oCItemContainer_contents);
if (List_HasLengthS (_@(list), nmb))
{
itm_ptr = List_GetS (_@(list), nmb);
return itm_ptr;
}
else
{
return 0;
};
};
Example usage (dismantling runes: if a player selects an item, this code is called and checks whether it is a rune - if yes, it dismantles the rune):
Do you use a different compiler? Or are the npc, nmb, list variables global instances or something? Why don't you have to define the type before assigning values?
Do you use a different compiler? Or are the npc, nmb, list variables global instances or something? Why don't you have to define the type before assigning values?
Greetz
Sorry, I didn't check the code carefully -- a few variables were defined beforehand. I included them in my initial post above (so people who stumble upon it have an easier time than you). I don't think there's too much of a use case where you want to also have access to those variables after calling the function, so you can probably move them into the function definition.
I am hooking based on the function pointers of the gothic.xls which is going around. Don't know yet how to use IDA, besides I have issues with windowed Gothic, which does not allow focus loss at all. So I am trail n erroring my way with Zspy
There should be a tutorial on how to use IDA in the new editing wiki, but you need to know assembly to *really* make use of it.
Zitat von blood4ng3l
How do I find the correct hook then?
concerning EAX would I use the Call_Ret... or MEM_PtrToInst(EAX) to get the return value if I had the correct function?
Look at the function and hook somewhere close to the return-instruction. Depending on how long the function is this may be impossible (need some place for the hook itself), but in that case you can probably just rewrite it in daedalus yourself to compute the result.
Call_Ret... is for when you call an engine function yourself, so not quite applicable to this situation.
Zitat von blood4ng3l
Is it in general wise to look for the right trigger / function to hook or just rely on some FrameFunctions called on each frame?
Greetz and thx for the help =)
It's cleaner to have a hook (less performance impact, no weird edge cases), but also more difficult
There should be a tutorial on how to use IDA in the new editing wiki, but you need to know assembly to *really* make use of it.
Look at the function and hook somewhere close to the return-instruction. Depending on how long the function is this may be impossible (need some place for the hook itself), but in that case you can probably just rewrite it in daedalus yourself to compute the result.
Call_Ret... is for when you call an engine function yourself, so not quite applicable to this situation.
It's cleaner to have a hook (less performance impact, no weird edge cases), but also more difficult
I read the Tutorial of IDA and its great! But I struggle with the setup,...
Can you elaborate a bit on the manual part of the tutorial of how to setup IDA? I am using 7.0 and some of the options in the cfg seem to be illegal, hope the rest loads as expected.
Using the gothic2.exe from the report version 2.6.
Manuelle Bearbeitung für die Report-Version 2.6:
[G] .text:00784F80
[Y] void __thiscall zCConsole__AddEvalFunc(void *, int (__cdecl *)(const void *, void *));
[G] .text:007A0190
[Y] void zCParser__DefineExternal(void *, const void *, int (__cdecl *)(), int, int, ...);
[G] .text:0054ED60 [P]
[G] .text:0054EEE0 [P]
Options / General... / Analysis / Reanalyze program
I am not sure how and what I should edit here.
What do the [P] mean? [G] seems location [Y] entry on location
At the .text:00784F80 there are several lines of a Subroutine, which has a different signature. I assume ";" means comment. So the actual line similar to
"void __thiscall zCConsole__AddEvalFunc(void *, int (__cdecl *)(const void *, void *));" should not be changed at all but somehow the following.
It's been a long time since I set up IDA and I was using a much older version, so let me just quote from the wiki entry:
NicoDE hat schon eine Anleitung geschrieben wie man die Analyse in IDA vorbereitet und die EXE in IDA hineinlädt. Nicos Post bezieht sich auf Version 5. Wenn einige Schritte in seiner Anleitung nicht möglich sind (aufgrund von anderer Version), kann man sie bedenkenlos überspringen, es sollte trotzdem alles funktionieren.
So I'd just skip that step. What Hotkey P is supposed to be I don't know.
One thing I have to add: Don't discount the graph view yet - I find it much more useful than the text view most of the time.
Hallo alle zusammen!
Ich habe versucht, eine Funktion zu schreiben, die für jedes aktive beschworene Wesen dem Spieler jede Sekunde x Mana abzieht und bei zu vielen Beschwörungen den Kreaturen jede Sekunde Leben abzieht.
Ich habe das über eine FrameFunction implementieren wollen, aber mir stellt sich nun das Problem, dass ich mir nicht sicher bin, wie ich auf die Instanzen der beschworenen NPCs zugreifen soll. Schließlich kann man Beschwörungen ja wiederholen, und dadurch komme ich über den Instanznamen nicht mehr an alle Instanzen (soweit ich weiß?), und wenn ich sowas mache:
Code:
func void Spell_Cast_SummonSkeleton() {
if (NPC_IsPlayer(self)) {
var int hndl;
hndl = new(Summoned_Skeleton);
var c_npc myNPC;
myNPC = get(hndl);
Wld_SpawnNpcRange (self, myNPC, 1, 500);
};
};
var int SummonedCreatureCounter;
var int CurrentCounter;
func void Summon_LifeDrain (var int hndl) {
CurrentCounter += 1;
var c_npc mySummon;
mySummon = get(hndl);
if (SummonedCreatureCounter > MaxStableSummons) {
Npc_ChangeAttribute (mySummon, ATR_HITPOINTS, MaxStableSummons - SummonedCreatureCounter);
};
};
func void Summon_ManaDrain () {
CurrentCounter = 0;
ForEachHndl(Summoned_Skeleton, Summon_LifeDrain);
SummonedCreatureCounter = CurrentCounter;
hero.attribute[ATR_MANA] = Heav(hero.attribute[ATR_MANA] - ManaPerSummon * SummonedCreatureCounter);
};
dann crasht Gothic mit
Spoiler:(zum lesen bitte Text markieren)
01:35 Info: 5 C: SND: Creating Sound Instance SKE_DIE (alternative: 0) .... <zSndMss.cpp,#1297>
01:35 Info: 5 C: SND: Creating Sound Instance SKE_DIE (alternative: 1) .... <zSndMss.cpp,#1297>
01:35 Info: 5 N: MSB: numAnis: 7, numMAN: 3 .... <zModelProto.cpp,#4050>
01:35 Info: 5 N: MSB: fpsRates min, max, avg: 25, 25, 25 .... <zModelProto.cpp,#4052>
01:35 Info: 5 D: MDL: Applying mds-overlay: HUMANS_SKELETON .... <zModel.cpp,#1417>
01:35 Info: 3 D: MDL: Loading Model-Mesh: SKE_BODY.mdm .... <zModelProto.cpp,#5333>
01:35 Fault: 0 Q: [start of stacktrace]
01:35 Fault: 0 Q: B_SETATTITUDE((instance)87303364, 3) + 27 bytes
01:35 Fault: 0 Q: [end of stacktrace]
01:35 Fault: 0 Q: Exception handler was invoked. Ikarus tried to print a Daedalus-Stacktrace to zSpy. Gothic will now crash and probably give you a stacktrace of its own.
[...]
01:35 Warn: 0 X: zCSurfaceCache_D3D :: CacheInSurface: slotindex->width out of range ! .... <zRndD3D_SurfaceCache.cpp,#116>
[...]
02:41 Warn: 0 X: Ref-Counter != 0 after vertexbuffer release! .... <zError.cpp,#474>
als einziger Erklärung. Ich hatte mir schon gedacht, dass Wld_SpawnNpcRange das nicht so einfach macht, aber das war die einzige Möglichkeit, die mir eingefallen ist.
“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"
Broadcasts halte ich auch für am sinnvollsten. Wenn du es mit LeGo lösen möchtest, würde ich es ca. so machen:
Code:
class SummonLifedrainInstance {
var int summonedNpcId;
// Evtl.:
// var int lifedrainAmount;
};
instance SummonLifedrainInstance@(SummonLifedrainInstance);
var int summonedCreatureCounter;
func void Spell_Cast_SummonSkeleton() {
Wld_SpawnNpcRange (self, Summoned_Skeleton, 1, 500);
if (NPC_IsPlayer(self)) {
summonedCreatureCounter += 1;
var int hndl; hndl = new(SummonLifedrainInstance@);
var SummonLifedrainInstance lifedrainInst; lifedrainInst = get(hndl);
lifedrainInst.summonedNpcId = Npc_GetId(Summoned_Skeleton);
};
};
func void Summon_Lifedrain_Sub(var int hndl) {
var SummonLifedrainInstance lifedrainInst; lifedrainInst = get(hndl);
var int npcPtr; npcPtr = Npc_FindById(lifedrainInst.summonedNpcId);
// Falls der zugehörige NPC (=Summon) nicht mehr existiert, löschen wir den Eintrag.
// Das kann zwar auch bei Weltenwechseln passieren, in diesem Fall verschwinden die beschworenen
// NPCs aber sowieso glaube ich.
if (!npcPtr) {
delete(hndl);
summonedCreatureCounter -= 1;
// Nächsten Summon bearbeiten
return rContinue;
};
var c_npc summon; summon = _^(npcPtr);
// Falls der beschworene NPC tot ist, löschen wir ihn ebenfalls aus unserem "Gedächtnis"
if (Npc_IsDead(summon)) {
delete(hndl);
summonedCreatureCounter -= 1;
return rContinue;
};
if (summonedCreatureCounter > MaxStableSummons) {
// Lebenspunkte abziehen wie es dir beliebt
Npc_ChangeAttribute (summon, ATR_HITPOINTS, MaxStableSummons - SummonedCreatureCounter);
};
return rContinue;
};
// Framefunction
func void Summon_Lifedrain() {
ForEachHndl(SummonLifedrainInstance@, Summon_Lifedrain_Sub);
// Mana abziehen wie es dir beliebt
Npc_ChangeAttribute(hero, ATR_MANA, 5*SummonedCreatureCounter);
};
Ist jetzt nur eine grobe Idee, die vermutlich noch ein paar Fehler enthält und auch noch nicht 100%ig schön gescriptet ist, aber der wesentliche Unterschied zu deinem Ansatz ist, dass wir das Beschwören nicht ersetzen, sondern eine parallele "Buchführung" machen.
Ein weiterer Schönheitsfehler: Bisher werden die NPC-IDs, die LeGo mit Npc_GetID vergibt, noch nicht aufgeräumt. Im Zweifelsfall müllt dir das dein Savegame voll. Daher Broadcasts bevorzugen.
Danke euch beiden! Ich habe es jetzt über Broadcasts umgesetzt.
Sehr elegant das alles.
“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"
Just define your function with the same parameters as the function you are hooking. You should in fact always do that, which is why you need to pass the parameters before ContinueCall() in the first place.
I am working on an more complex Interface Project, which makes use of LeGo, and builds upon the basic LeGo Bars.
After some prototyping I wanted to start building my own class compositions, but now I have some issues with handles, which seem to get invalid somehow, or not created correctly in the firstplace.
here my classes:
Spoiler:(zum lesen bitte Text markieren)
Code:
/***********************************\
B4DI extended BARS
\***********************************///========================================
// [intern] Klasse für PermMem
//========================================
class _extendedBar {
var int bar; // _bar(h)
var int barPreview; // _barPreview(h)
var int isFadedOut; // Bool
var int anim8FadeOut; // A8Head(h)
};
instance _extendedBar@(_extendedBar);
func void _extendedBar_Delete(var _extendedBar eBar){
if(Hlp_IsValidHandle(eBar.bar)){
delete(eBar.bar);
};
if(Hlp_IsValidHandle(eBar.bar)){
delete(eBar.barPreview);
};
if(Hlp_IsValidHandle(eBar.anim8FadeOut)){
Anim8_Delete(eBar.anim8FadeOut);
};
};
/***********************************\
BARPREVIW
\***********************************///========================================
// [intern] Klasse für PermMem
//========================================
class _barPreview {
var int vPreView; // zCView(h)
var int vOverShoot; // zCView(h)
var int val;
var int anim8Pulse; // A8Head(h)
var int eBar_parent; // _extentedBar(h)
};
instance _barPreview@(_barPreview);
func void _barPreview_Delete(var _barPreview bp){
if(Hlp_IsValidHandle(bp.vPreView)){
delete(bp.vPreView);
};
if(Hlp_IsValidHandle(bp.vOverShoot)){
delete(bp.vOverShoot);
};
if(Hlp_IsValidHandle(bp.anim8Pulse)){
Anim8_Delete(bp.anim8Pulse);
};
};
These are my constructor functions which seem to work.
Spoiler:(zum lesen bitte Text markieren)
Code:
//========================================
// BarPreview Create
//========================================
func int B4DI_BarPreview_Create( var int eBar_hndl){
if(!Hlp_IsValidHandle(eBar_hndl)){
MEM_Warn("tried to init Preview of a not initialized ebar ");
return 0;
};
var _extendedBar eBar; eBar = get(eBar_hndl);
var _bar bar; bar = get(eBar.bar);
var zCView vBar; vBar = View_Get(bar.v1);
var int new_bp_hndl; new_bp_hndl = new(_barPreview@);
var _barPreview bp; bp = get(new_bp_hndl);
bp.vPreView = View_Create(vBar.vposx, vBar.vposy, vBar.vposx + vBar.vsizex, vBar.vposy + vBar.vsizey );
bp.vOverShoot = View_Create(vBar.vposx, vBar.vposy, vBar.vposx + vBar.vsizex, vBar.vposy + vBar.vsizey );
//TODO maybe different texture for previews?
View_SetTexture(bp.vPreView, ViewPtr_GetTexture( MEM_InstToPtr(vBar)));
View_SetTexture(bp.vOverShoot, ViewPtr_GetTexture( MEM_InstToPtr(vBar)));
bp.val = 0;
bp.eBar_parent = eBar_hndl;
/*eBar.barPreview = new_bp_hndl; *///unsure about this here
return new_bp_hndl;
};
//========================================
// eBar Create
//========================================
func int B4DI_eBar_Create(var int Bar_constructor_instance){
var int new_eBar_hndl; new_eBar_hndl = new(_extendedBar@);
var _extendedBar eBar; eBar = get(new_eBar_hndl);
eBar.bar = Bar_CreateCenterDynamic(Bar_constructor_instance);
B4DI_Bar_dynamicMenuBasedScale(eBar.bar);
eBar.barPreview = B4DI_BarPreview_Create(new_eBar_hndl);
if(!Hlp_IsValidHandle(eBar.barPreview)){
MEM_Warn("B4DI_eBar_Create failed at B4DI_BarPreview_Create");
return 0;
};
B4DI_eBar_SetAlpha(eBar.bar, 0);
eBar.isFadedOut = 1;
return new_eBar_hndl;
};
But for example in this BarPreview function, which gets called by the eBar function I get the warning that the handle is invalid.
Spoiler:(zum lesen bitte Text markieren)
Code:
func void B4DI_BarPreview_hide( var int barPreview_hndl){
if(!Hlp_IsValidHandle(barPreview_hndl)){
MEM_Warn("B4DI_BarPreview_hide failed ");
return;
};
var _barPreview bp; bp = get(barPreview_hndl);
View_Close(bp.vPreView);
View_Close(bp.vOverShoot);
MEM_Info("B4DI_Bars_hideItemPreview");
};
func void B4DI_eBar_HidePreview(var int eBar_hndl){
if(!Hlp_IsValidHandle(eBar_hndl)){ return; };
var _extendedBar eBar; eBar = get(eBar_hndl);
B4DI_BarPreview_hide(eBar.barPreview);
MEM_Info("B4DI_eBar_HidePreview successful");
};
I Don't know how to proceed to find the issue since i reproduced the calls used in original bars for handle creation and the creation seems to work but not the access afterwards?!
Classes are in one script separated from 2 function scripts each for bPreview and eBar which get loaded in that order.
Maybe someone has the experience and time to take a look, to help be fix this blocker^^
None of the scripts you showcased actually call either of the functions that are supposedly failing to work. Make sure to post a minimal working example, so we can actually test the problem properly. In a perfect world I'd just have to copy paste your code into a file (or maybe multiple files if required) and be able to reproduce the big immediately.
But yes those are just "class"-functions, cause i wanted to know If I am using the handles correctly, in general, which seems the case,...
I did not expected that someone actually want to reproduce my issue. But for the future I will know,...
For this instance I found the issue by myself, while shrinking down a minimal version.
The B4DI_eBar_HidePreview function, besides others, got called with an deprecated _bar handle instead of an _extentedBar handle as parameter, therefore the error.
I've noticed that FrameFunctions aren't dependent on MEM_Timer.factorMotion. Should it be like that? Or maybe there should be separate functions? I managed that in this way but it requires refresh per call because player may use slow motion effect or something
Code:
var int delayMultiplier; delayMultiplier = divf(mkf(1), MEM_Timer.factionMotion);
itm.delay = roundf(mulf(mkf(delay), delayMultiplier));
I've noticed that FrameFunctions aren't dependent on MEM_Timer.factorMotion. Should it be like that? Or maybe there should be separate functions? I managed that in this way but it requires refresh per call because player may use slow motion effect or something
Code:
var int delayMultiplier; delayMultiplier = divf(mkf(1), MEM_Timer.factionMotion);
itm.delay = roundf(mulf(mkf(delay), delayMultiplier));
Well they are frame fucntions, and number of rendered frames is independent of the game time....
But there could be variation of "frame functions" which would be rather game time function.