General Question Hooking a vessel's methods

meson800

Addon Developer
Addon Developer
Donator
Joined
Aug 6, 2011
Messages
405
Reaction score
2
Points
18
Is it possible to "hook" and see when a certain vessel's methods are called, and what arguments are passed in?

If you have the know the name of a vessel, can you access when its clbkConsumeDirectKey or clbkNavMode etc. are called?
 

Face

Well-known member
Orbiter Contributor
Addon Developer
Beta Tester
Joined
Mar 18, 2008
Messages
4,403
Reaction score
581
Points
153
Location
Vienna
This and this could be starting points for you. In essence it is called virtual table hooking, and it is certainly a hack.

regards,
Face
 

meson800

Addon Developer
Addon Developer
Donator
Joined
Aug 6, 2011
Messages
405
Reaction score
2
Points
18
Thank you. Over the weekend I will test this out, but from the looks of the comments in vhl.h, it is exactly what I was looking for. :)
 

meson800

Addon Developer
Addon Developer
Donator
Joined
Aug 6, 2011
Messages
405
Reaction score
2
Points
18
I have tried hooking using vhl, but I have been unsuccessful. No compile errors for the following code happen, and when Orbiter runs, vhl_plug and my module is enabled. Breakpoints set at InitModule and opcFocusChange show that my module is successfully hooking the vessel named DGIV_SIM. When I load a panel on the vessel(F1 and control+up) and click a button(PanelMouseEvent), the code inside DGIV_HOOK does not run.

Here is some partial code for my module.
Code:
VHL_UCTX moduleId;
VHL_HSUB subscription;

//Event sink needed in vhl.h
class DGIV_HOOK : public VHL_interface
{
public:
	void on_clbkPanelMouseEvent( VESSEL2* vess, int id, int event, int mx, int my)
	{
		//Breakpoint
		sprintf(oapiDebugString(), "Button Pressed!");
		
	}
	 void on_clbkLoadPanel ( VESSEL2* vess, int id) 
	 {
		 //Breakpoint
	 }

	 void on_clbkPreStep ( VESSEL2* vess, double simt, double simdt, double mjd )
	 {
		 //Breakpoint
	 }
};

DGIV_HOOK hook;


DLLCLBK void InitModule(HINSTANCE hModule)
{
	moduleId = VHL_register();

}

DLLCLBK void ExitModule(HINSTANCE hModule)
{
	VHL_unregister(moduleId);
}	

DLLCLBK void opcFocusChanged (OBJHANDLE hGainsFocus, OBJHANDLE hLosesFocus)
{
	OBJHANDLE hVessel = oapiGetVesselByName("DGIV_SIM");
	if (hGainsFocus == hVessel && oapiIsVessel(hVessel))
	{
		subscription = VHL_subscribe(moduleId, &hook, &VESSEL2(hGainsFocus,1) );
	}
	if (hLosesFocus == hVessel && oapiIsVessel(hVessel))
	{
		VHL_unsubscribe(subscription);
	}
}

I believe there is a problem with the way I call VHL_subscribe, but no runtime errors occur.

Is this the correct way to run VHL_subscribe
Code:
subscription = VHL_subscribe(moduleId, &hook, &VESSEL2(hGainsFocus,1) );
and if it is not, what is the correct way?
 

Face

Well-known member
Orbiter Contributor
Addon Developer
Beta Tester
Joined
Mar 18, 2008
Messages
4,403
Reaction score
581
Points
153
Location
Vienna
I believe there is a problem with the way I call VHL_subscribe, but no runtime errors occur.

Is this the correct way to run VHL_subscribe
Code:
subscription = VHL_subscribe(moduleId, &hook, &VESSEL2(hGainsFocus,1) );
and if it is not, what is the correct way?

Despite I never tried Wraith's code that much, I think the constructor trick to obtain a VESSEL2 interface to a certain OBJHANDLE is not working that well with it. Try this instead:

Code:
subscription = VHL_subscribe(moduleId, &hook, (VESSEL2 *)oapiGetVesselInterface(hGainsFocus));
regards,
Face
 

meson800

Addon Developer
Addon Developer
Donator
Joined
Aug 6, 2011
Messages
405
Reaction score
2
Points
18
When using
Code:
subscription = VHL_subscribe(moduleId, &hook, (VESSEL2 *)oapiGetVesselInterface(hGainsFocus));
I constantly recieve the following error
Unhandled exception at 0x777b15de in orbiter.exe: 0xC0000005: Access violation reading location 0x7473616d.

I have a feeling that the (VESSEL2 *) cast stores the result in temp memory, and cannot be accessed later.

I have tried removing the cast, but then the compiler errors, saying that it cannot use VESSEL * instead of VESSEL2 *.

Are there any examples using Wraith's code? I downloaded his Axial Velocity HUD mentioned on the VMT Hijackers League post, put there were no sources.
 

Face

Well-known member
Orbiter Contributor
Addon Developer
Beta Tester
Joined
Mar 18, 2008
Messages
4,403
Reaction score
581
Points
153
Location
Vienna
Well, for further advice I'd have to go over Wraith's code, but that will take a while. In the meantime you could try my hooking classes.
 

escapetomsfate

OBSP Developer
Addon Developer
Joined
Jun 21, 2008
Messages
282
Reaction score
0
Points
0
Location
GB
I've been thinking about using hooking myself, perhaps in OBSP. It definitely seems to be a cheaper way to do things than "simulating" callbacks (checking for events manually in the Module's own global callback functions, then calling a fake callback function in a class associated with the relevant vessel). I have a few questions...

When you hook a vessel's methods, does the vessel still receive the callbacks itself? Or would they have to be manually called inside the function that the hooking library "reroutes" the callback to?

Also, I understand that unless a common hooking library/method is used between two addons, there will be compatibility issues? Is it stable provided there are no such clashes?

Thanks!
 

meson800

Addon Developer
Addon Developer
Donator
Joined
Aug 6, 2011
Messages
405
Reaction score
2
Points
18
Is there any simple documentation for your hooking classes?
I downloaded OMP and have been looking through the source code for how to use your classes, but I have not gotten far.

Here is some code from OMPClient.cpp
Code:
                                   entry=new struct IDload;
					entry->object=oapiGetVesselByIndex(i);
					entry->vessel=oapiGetVesselInterface(entry->object);
					InstallVesselHook((VESSEL2 *)entry->vessel);
					vesselsize=(long)entry->vessel->GetSize();
					entry->size=0;
					entry->MasterID=0;
					entry->GlobalID=0;
					entry->linkage=1;
					entry->refcount=0;
					entry->lastJump=0;
					entry->idle=0;
					entry->NavInfo=0;
					entry->DockInfo=0;
					entry->AnimState=0;
					entry->Events=new Queue();

I can understand that that gets a pointer to a vessel, installs a hook on it, and sets the vessels "events" to a Queue class in OrbiterHooking. Unfortunately, I don't see how HookingMethods relates to the hook at all. Is there any simpler examples that use your hooking classes?
 

Face

Well-known member
Orbiter Contributor
Addon Developer
Beta Tester
Joined
Mar 18, 2008
Messages
4,403
Reaction score
581
Points
153
Location
Vienna
When you hook a vessel's methods, does the vessel still receive the callbacks itself? Or would they have to be manually called inside the function that the hooking library "reroutes" the callback to?

Well, I can only speak of my classes here, but in this case the original callbacks are called. Of course it is exactly as you've outlined in your last sentence. My functions call the original "manually", but aided by the system's original function table. I can't say much about Wraith's solution ATM.

Also, I understand that unless a common hooking library/method is used between two addons, there will be compatibility issues? Is it stable provided there are no such clashes?

It is true that two hooking libraries would collide one way or the other. My classes are as stable as such a hack can be: not really ;) .

Is there any simple documentation for your hooking classes?
I downloaded OMP and have been looking through the source code for how to use your classes, but I have not gotten far.
<snip>
I can understand that that gets a pointer to a vessel, installs a hook on it, and sets the vessels "events" to a Queue class in OrbiterHooking. Unfortunately, I don't see how HookingMethods relates to the hook at all. Is there any simpler examples that use your hooking classes?

Sorry, it is certainly no full package, so there is no documentation for it. Here is a small help:

int InstallVesselHook(VESSEL2 *link) ... installs the hooks of HookMethods for the appropriate vessel instance.

int RemoveVesselHook(VESSEL2 *link) ... releases the hooks for that instance again.

void CleanUpHooking() ... removes all open hookings.

Queue *GetEventQueue() ... gives you the event queue that holds all hook events. Using Dequeue() gives you the next event.

void SetEventsEnabled(bool enabled) ... switches the event recording globally on and off. Default is off, so you have to switch it on before you see any events in the queue.
bool GetEventsEnabled() ... gets the status of event recording.

The code additionally creates an event logfile in Modules/Plugin/Events.log .


As you can see, my classes used in OMP are using the HookMethods to build up a event queue that I can asynchronously transmit without blocking the callback itself. So they are specialized for this use-case, but you can of course modify the HookMethods to do whatever you want. The macros there show how the hook method's structure is layed out for callbacks with and without results. The non-trivial part is the assembler one that just calls the original method. Be aware that within the HookMethods, the "this->" pointer gives you the original class instance, not the one of the HookMethods instance.

If you need a specific function called for certain callbacks, I can assemble a small example for you. But I guess it is better to get Wraith's work running, as it seems to be more "designed" than mine.

regards,
Face

---------- Post added at 21:41 ---------- Previous post was at 20:34 ----------

OK, I've gone through Wraith's code a bit, and I must admit that I forgot most of the details, despite my involvement in VHL discussions. The VHL hooking method involves replacing of the complete virtual table pointer back and forth to call the original functions without help of assembler hacks. I could be mistaken, but I think this is the reason for it to crash in this version of Orbiter. There is the new VESSEL3 interface now, and it derives from VESSEL2.

It could very well be that simply replacing the complete virtual table pointer triggers the access violation that meson wrote about. However, the constructor trick worked because of it being a complete new class instance, only having the virtual table for VESSEL2. Still, that later instance did not receive the callbacks, thus never saw the hooked functions.

Nevertheless, Wraith's code is elegant. I'll take a look if I can integrate my assembler method into his library framework. Let's see...
 

meson800

Addon Developer
Addon Developer
Donator
Joined
Aug 6, 2011
Messages
405
Reaction score
2
Points
18
Thanks face. I don't need any examples, your "documentation" on your hook methods provided me with enough info to implement your hooks into my code. Your classes work like a charm even if they are a bit rough! :cool:

Thanks again, Face!
 

escapetomsfate

OBSP Developer
Addon Developer
Joined
Jun 21, 2008
Messages
282
Reaction score
0
Points
0
Location
GB
Face, I'm trying out your hooking library and it's working brilliantly! :)

I'm attempting to modify the HookMethods class so that it works for clbkDrawHUD. I've managed so far to get it to call the function and pass the mode argument, but I can't get it to pass the hDC argument :(

This is because I have no experience with assembler. I copy-pasted one of the other callback functions and semi-guessed at what changes are needed to make it suit clbkDrawHUD. This is what I have so far:

Code:
void HookMethods::clbkDrawHUD (int mode, const HUDPAINTSPEC *hps, HDC hDC)
{
	PRETASK;
	event->Mode.flag=0x0B;
	event->Mode.vessel=this->GetHandle();

	event->HudInfo.hDC = hDC;
	event->HudInfo.mode = mode;

	fprintf(l, "%d[%s]DrawHUD %d\n", GetTickCount(), this->GetName(), mode);
	POSTTASK
	{
		_asm
		{		
			mov         eax,hDC
			push        eax
			mov         edx,hps
			push        eax
			mov         eax,mode
			push        eax
			mov         eax,this
			mov         edx,dword ptr [eax]
			mov         eax,me
			mov         ecx,dword ptr [this]
			call        dword ptr [eax+34h]	
		}
	}
	else sprintf(oapiDebugString(), "HOOKING ERROR");
}

I edited the Event class to make it store the arguments for clbkDrawHUD, and also modified VesselHook::hookVtable so that it hooks clbkDrawHUD.

Can you see what's wrong? Thanks!
 

orb

New member
News Reporter
Joined
Oct 30, 2009
Messages
14,020
Reaction score
4
Points
0
Code:
		_asm
		{		
			mov         eax,hDC
			push        eax
			mov         [highlight]eax[/highlight],hps
			push        eax
			mov         eax,mode
			push        eax
			mov         eax,this
			mov         edx,dword ptr [eax]
			mov         eax,me
			mov         ecx,dword ptr [this]
			call        dword ptr [eax+34h]	
		}
You pushed the hDC on stack twice as a parameter for the called function, and hps wasn't passed at all, because EDX register, which contained hps, wasn't pushed, but EAX, which contained hDC.
 

escapetomsfate

OBSP Developer
Addon Developer
Joined
Jun 21, 2008
Messages
282
Reaction score
0
Points
0
Location
GB
You pushed the hDC on stack twice as a parameter for the called function, and hps wasn't passed at all, because EDX register, which contained hps, wasn't pushed, but EAX, which contained hDC.

Thanks for the help.

I originally had them all set to EAX, and then tried changing them when I realised hDC wasn't being passed.

Either way it seemingly makes no difference - I'm still unable to draw on the hDC. There's no crash to desktop or anything, just nothing shows up on the HUD. It's also not NULL.

What does this line do? It differs between callbacks:
Code:
call        dword ptr [eax+34h]
 

orb

New member
News Reporter
Joined
Oct 30, 2009
Messages
14,020
Reaction score
4
Points
0
What does this line do? It differs between callbacks:
Code:
call        dword ptr [eax+34h]

It calls 14th [0x34 / sizeof(DWORD)] function in vtable, which you referenced as "me", of the class.
 

Face

Well-known member
Orbiter Contributor
Addon Developer
Beta Tester
Joined
Mar 18, 2008
Messages
4,403
Reaction score
581
Points
153
Location
Vienna
:hesaid:

The appropriate number for clbkDrawHud would be 0x2C. This is nothing but the order of the function (starting at 0) in the declaration times 4. That's why I have the unsupported functions commented out seemingly at random. That's also why the for-loops in the actual hooking-function are so segmented:
Code:
            Vtabl=new DWORD[i];
            for(i=12;i<18;i++)
            {
                memcpy((LPVOID)(Vtabl+i), (LPVOID)(pVtabl + i), 4);
                memcpy((LPVOID)(pVtabl + i), (LPVOID)(pVtabl2 + i), 4);
            }
            for(i=20;i<27;i+=3)
            {
                memcpy((LPVOID)(Vtabl+i), (LPVOID)(pVtabl + i), 4);
                memcpy((LPVOID)(pVtabl + i), (LPVOID)(pVtabl2 + i), 4);
            }
You have to change the starting value of the first loop to 11 in order to get it hooked properly.

Keep in mind that the whole queue-thingy is my use-case. I guess you just want to draw something on the HUD, so do not use the events, just call your drawing routines inside the HookMethod itself. The events are there to decouple the callback thread from my multiplayer threads in order to transmit the event itself to the other clients.

regards,
Face

BTW: If that works for you all, I guess it would make sense to redesign the VHL internals (not the interface!) to use the assembler method instead of the full-table swapping method.
 

escapetomsfate

OBSP Developer
Addon Developer
Joined
Jun 21, 2008
Messages
282
Reaction score
0
Points
0
Location
GB
:hesaid:

The appropriate number for clbkDrawHud would be 0x2C. This is nothing but the order of the function (starting at 0) in the declaration times 4.

That's why I have the unsupported functions commented out seemingly at random. That's also why the for-loops in the actual hooking-function are so segmented:
Code:
            Vtabl=new DWORD[i];
            for(i=12;i<18;i++)
            {
                memcpy((LPVOID)(Vtabl+i), (LPVOID)(pVtabl + i), 4);
                memcpy((LPVOID)(pVtabl + i), (LPVOID)(pVtabl2 + i), 4);
            }
            for(i=20;i<27;i+=3)
            {
                memcpy((LPVOID)(Vtabl+i), (LPVOID)(pVtabl + i), 4);
                memcpy((LPVOID)(pVtabl + i), (LPVOID)(pVtabl2 + i), 4);
            }
You have to change the starting value of the first loop to 11 in order to get it hooked properly.

Thanks for the help :)

I've already modified the for loops so that clbkDrawHUD is called (which it is) - the problem is that the arguments that are passed to it seem to be invalid. I can't draw on the hDC, even if I try to do so directly in the HookMethods' clbkDrawHUD.

Do you multiply the number of the function by 4 arbitrarily, so that it's never the same as another function, or is there some other reason for it?

Keep in mind that the whole queue-thingy is my use-case. I guess you just want to draw something on the HUD, so do not use the events, just call your drawing routines inside the HookMethod itself. The events are there to decouple the callback thread from my multiplayer threads in order to transmit the event itself to the other clients.

regards,
Face

I'm only drawing on the HUD just to try out hooking, and to see whether I could add support for more callbacks by myself. I think your event queue is a rather elegant way of doing things, even though it was designed for threading. I'll probably stick with it for now and modify it to my needs.

Thanks again :tiphat:

EDIT: Got it working! I think I must've just not noticed the text I was drawing on the HUD! I'm an idiot :facepalm:

EDIT2: I think I've got it working for VESSEL3. All I had to do was replace every VESSEL2 with VESSEL3 in the code, and then change the range of the for loops to include the extra callback functions that VESSEL3 uses. It seems to be working for VESSEL3's clbkDrawHUD (the one that uses Orbiter's Sketchpad class instead of HDC).
 
Last edited:

orb

New member
News Reporter
Joined
Oct 30, 2009
Messages
14,020
Reaction score
4
Points
0
Do you multiply the number of the function by 4 arbitrarily, so that it's never the same as another function, or is there some other reason for it?
The reason is that pointers are 32-bit and the table is array of those 4-byte long, 32-bit pointers. Each next element in the array has 4 bytes farther address, and in x86 assembly you provide addresses aligned to a byte (8-bit) boundary.
 

Face

Well-known member
Orbiter Contributor
Addon Developer
Beta Tester
Joined
Mar 18, 2008
Messages
4,403
Reaction score
581
Points
153
Location
Vienna
Again, :hesaid:

EDIT2: I think I've got it working for VESSEL3. All I had to do was replace every VESSEL2 with VESSEL3 in the code, and then change the range of the for loops to include the extra callback functions that VESSEL3 uses. It seems to be working for VESSEL3's clbkDrawHUD (the one that uses Orbiter's Sketchpad class instead of HDC).

Yes, that should do fine. It will not work for pure VESSEL2, though, as even with a cast to VESSEL3 in order to make it pass the InstallVesselHook signature, the hook procedure will either attempt to patch non-existing virtual function pointers (if you've left the security if-clause on 26), or refuse to hook it with an error return (if you've also increased the if-clause).
 

escapetomsfate

OBSP Developer
Addon Developer
Joined
Jun 21, 2008
Messages
282
Reaction score
0
Points
0
Location
GB
Yes, that should do fine. It will not work for pure VESSEL2, though, as even with a cast to VESSEL3 in order to make it pass the InstallVesselHook signature, the hook procedure will either attempt to patch non-existing virtual function pointers (if you've left the security if-clause on 26), or refuse to hook it with an error return (if you've also increased the if-clause).

That's true. Can you think of any way to hook VESSEL2 functions for both pure VESSEL2 and pure VESSEL3, but also hook the exclusive VESSEL3 functions for pure VESSEL3?
 
Top