Greetings. As we're all beginning work on a new game I really would like to get something clear from the beginning: There should be no need to detour functions JUST to execute code from the main thread. It's much nicer to gain access by executing WildStar's function "CEventQueue::Register". You provide it with a function and it will call it from the main thread after a given time interval. It is the perfect function for entering, and remaining in the main thread.
Their polling function is called more often than (for example) DX9's EndScene or DX11's Present, so in most cases you will find yourself entering the main thread faster than using any kind of detour. And personally, I hate seeing DirectX hooks when they aren't needed.
The function:
CEventQueue::Register takes 4 parameters, including the instance of CEventQueue at ecx.
1. this (ecx): Our best option is to create our own instance because of how easily it can be constructed. See below.
2. interval: How long until the callback is called, in milliseconds. 0 is also acceptable if you want to enter instantly - it will be called on the next queue polling.
3. event: A pointer to the CTimedEvent struct. See below.
4. priority: Simply put, it does exactly what you'd expect. It's the priority of the event. See below.
Code:
typedef void(__thiscall *CEventQueue__Register_t)(CEventQueue *_this, uint32 interval, CTimedEvent *callback, EventPriority priority);
CEventQueue__Register_t CEventQueue__Register;
CEventQueue construction:
The construction of a CEventQueue class is very simple (at least, for our needs!) Only the first field must match.
Code:
class CEventQueue {
public:
CEventQueue() {
m_magic = (uint32) 'TNVE';
ZeroMemory(&m_unk[0], sizeof(m_unk));
}
private:
uint32 m_magic;
uint8 m_unk[60]; // Total class size must be 64 bytes.
};
CTimedEvent construction:
The construction of an event is also very easy. Thankfully, we don't need to go any deeper for these fields. The definition of the callback function should change, depending on whether the parameter value will be passed. See example below.
Code:
class CTimedEvent {
public:
BOOL UseParameter;
void *Instance;
void *Callback;
void *Parameter;
};
EventPriority:
This is the call order priority for events. Critical events are fired first, and lowest priority events are fired last.
Code:
enum class EventPriority {
Lowest,
BelowNormal,
Normal,
Highest,
Critical
};
Unregistering events:
We can quite easily remove events using CEventQueue::Remove. It takes no arguments, excluding the instance parameter in ecx which is present as with all __thiscall functions. This function is also thread-safe, so it does not necessarily need to be called from the main thread. If you support unloading of your injected library than it is absolutely necessary to remove events so there are no active events that will call back to invalid memory.
Code:
typedef void(__thiscall *CEventQueue__Remove_t)(CEventQueue *_this);
Example:
Code:
typedef void(__thiscall *CEventQueue__Register_t)(CEventQueue *_this, uint32 interval, CTimedEvent *callback, EventPriority priority);
CEventQueue__Register_t CEventQueue__Register;
typedef void(__thiscall *CEventQueue__Remove_t)(CEventQueue *_this);
CEventQueue__Remove_t CEventQueue__Remove;
// Repeats forever @ 7500ms repeating intervals.
CEventQueue *s_eventQueue1;
CTimedEvent *s_event1;
// Doesn't use parameter @ 2500ms.
CEventQueue *s_eventQueue2;
CTimedEvent *s_event2;
// Uses parameter @ 5000ms.
CEventQueue *s_eventQueue3;
CTimedEvent *s_event3;
// (Hopefully) never gets called.
CEventQueue *s_eventQueue4;
CTimedEvent *s_event4;
void __fastcall RepeatingCallback(void *instance, void *_edx) {
MessageBoxA(nullptr, "RepeatingCallback was called!", "Example callbacks", MB_OK | MB_ICONINFORMATION);
// Requeue the event. Yay for main thread loops!
CEventQueue__Register(s_eventQueue1, 7500, s_event1, EventPriority::Normal);
}
void __fastcall CallbackWithoutParameter(void *instance, void *_edx) {
MessageBoxA(nullptr, "CallbackWithoutParameter was called!", "Example callbacks", MB_OK | MB_ICONINFORMATION);
}
void __fastcall CallbackWithParameter(void *instance, void *_edx, uint32 parameter) {
std::stringstream message;
message << "CallbackWithParameter was called!" << '\n';
message << "Parameter: " << parameter;
MessageBoxA(nullptr, message.str().c_str(), "Example callbacks", MB_OK | MB_ICONINFORMATION);
}
void Initialize() {
CEventQueue__Register = reinterpret_cast<CEventQueue__Register_t>(Offsets::CEventQueue::Register);
CEventQueue__Remove = reinterpret_cast<CEventQueue__Remove_t>(Offsets::CEventQueue::Remove);
s_eventQueue1 = new CEventQueue();
s_event1 = new CTimedEvent();
s_event1->Instance = nullptr;
s_event1->Callback = RepeatingCallback;
s_event1->UseParameter = FALSE;
s_eventQueue2 = new CEventQueue();
s_event2 = new CTimedEvent();
s_event2->Instance = nullptr;
s_event2->Callback = CallbackWithoutParameter;
s_event2->UseParameter = FALSE;
s_eventQueue3 = new CEventQueue();
s_event3 = new CTimedEvent();
s_event3->Instance = nullptr;
s_event3->Callback = CallbackWithParameter;
s_event3->UseParameter = TRUE;
s_event3->Parameter = (void*) 1234;
s_eventQueue4 = new CEventQueue();
s_event4 = new CTimedEvent();
s_event4->Instance = nullptr;
s_event4->Callback = nullptr; // Crashes our game.
s_event4->UseParameter = FALSE;
CEventQueue__Register(s_eventQueue1, 7500, s_event1, EventPriority::Normal);
CEventQueue__Register(s_eventQueue2, 2500, s_event2, EventPriority::Normal);
CEventQueue__Register(s_eventQueue3, 5000, s_event3, EventPriority::Normal);
CEventQueue__Register(s_eventQueue4, 1000, s_event4, EventPriority::Normal);
// The fourth event points to the callback function at 0x00000000, which doesn't exist.
// This will crash our game, so we want to unregister it!
CEventQueue__Remove(s_eventQueue4);
}
Congrats! You're in the main thread.