Note: You won't probably ever need this. The example provided here is completely bogus. FrameScript__SignalEvent wraps around a function which takes a pointer as the third argument. If you just hook this, you will have a pointer to the stack at runtime anyway without any weird types like RuntimeArgumentHandle. This was more a "cool!" moment when I realized this worked.
So anyways, some time ago Apoc told me about the __arglist keyword. Eager to see if it worked, I tried it on FrameScript__SignalEvent and it turned out you can't use the keyword in delegates.
To my surprise, I discovered that __arglist translates directly into a structure. This structure is a special structure and is called RuntimeArgumentHandle. This structure can not be boxed into an object, which means you won't be able to call the original function if you use a late bound delegate, like WhiteMagic does. Fortunately I wrote my own library that uses generics and it works with that.
RuntimeArgumentHandle was originally thought to be used together with ArgIterator to iterate through arguments. This, however, throws an exception when you do it in WoW (?!? why?). Fortunately RuntimeArgumentHandle contains a single member which is an IntPtr and points to the first argument on the stack. Unfortunately Microsoft made this member internal, so my first thought was to use Reflection. But oops, RuntimeArgumentHandle can not be boxed to an object, so you can't get the value on the instance! I fixed this by simply casting a pointer to it to an IntPtr* and dereferencing that.
Code:
IntPtr ptr = *(IntPtr*)&args;
Embrace yourself for code:
Code:
public static class Lua
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void FrameScript__SignalEventDelegate(uint id, string fmt, RuntimeArgumentHandle args);
private static readonly RoutedDetour<FrameScript__SignalEventDelegate> _signalEventDetour;
static Lua()
{
_signalEventDetour = Memory.CreateAndApplyRoutedDetour<FrameScript__SignalEventDelegate>((IntPtr)GlobalOffsets.FrameScript__SignalEvent, FrameScript__SignalEventHk, 6);
}
internal static void Init()
{
}
private static unsafe void FrameScript__SignalEventHk(uint id, string fmt, RuntimeArgumentHandle args)
{
_signalEventDetour.Original(id, fmt, args);
if (fmt == null)
return;
IntPtr ptr = *(IntPtr*)&args;
var vals = new List<object>(fmt.Length / 2);
for (int i = 1; i < fmt.Length; i += 2)
{
switch (fmt[i])
{
case 'b':
vals.Add(Memory.Read<uint>(ptr) != 0);
ptr += 4;
break;
case 'd':
vals.Add(Memory.Read<int>(ptr));
ptr += 4;
break;
case 'f':
vals.Add(Memory.Read<double>(ptr));
ptr += 8;
break;
case 'u':
vals.Add(Memory.Read<uint>(ptr));
ptr += 4;
break;
case 's':
vals.Add(Memory.Read<string>((uint)ptr, 0));
ptr += 4;
break;
}
}
LuaEventDispatcher.Dispatch(id, vals);
}
}
So yeah.. Not so useful but still fun that C# (more like .NET) actually supports this after all.