This tut will show you how to hook a games console very efficiently, for read and write operations (and more stuff).
Outline
Most multi-line edit controls ("consoles") contain a READABLE class (where the text is displayed in the console area) and a WRITEABLE class which allows you to type into an area usually just below the console text.
Our aim is to hook both classes so we can read and write. The write part is easy enough since its something that is called once, but reading text can be very intensive.
Solution
We will hook the console's window procedure (the central messaging function). In our hook we can check for certain messages so we know exactly when a new line is written to the console and we can update our "LastLine" string.
Note: You could do this externally but you would loose the advatage of knowing exactly when a new line is written (without a loop of some sort) .. and a lot of other cool stuff you can do in a wndproc hook :P
The Code
OK firstly you must obtain a HWND handle to the CONSOLE window (not game window).
Personally I hook CreateWindow, but note that as long as the window exists obtaining a handle is as easy as using FindWindow API. If you follow my way CreateWindowEx returns a HWND, so my hook looks like this:
HWND __stdcall h_CreateWindowExA(DWORD dwExStyle, LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam)
{
HWND hRetval = o_CreateWindowExA(dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam);
if(lpWindowName && strstr(lpWindowName, "ET Console"))
SubClass(hRetval);
return hRetval;
}
So now we have sent off the handle to "ET Console" to our function "SubClass". This is the subclass function:
// Put this with the global vars
WNDPROC o_WndProc;
void SubClass(HWND hWnd)
{
// Store the original WndProc
o_WndProc = (WNDPROC)GetWindowLong(hWnd, GWL_WNDPROC);
// Set the game's WndProc to ours
SetWindowLong(hWnd, GWL_WNDPROC, (long)h_WndProc);
}
Ok so now we have redirected the windowsproc to our function "h_WndProc". This is our WndProc func:
LRESULT __stdcall h_WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
return CallWindowProc(o_WndProc, hWnd, uMsg, wParam, lParam);
}
Adding Functionality - Reading
Now all messages processed by the console will be first sent to our hook.
The edit control / edit box / console (whatever you wanna call it) will send a EN_CHANGE message whenever a newline is written to it. So in order to catch this message we need to check the uMsg parameter in our WndProc hook:
if(uMsg == WM_COMMAND)
{
if(HIWORD(wParam) == EN_CHANGE) // Newline in console
{
}
}
Ok so we have our hook set up and ready to catch newlines and respond to them, like the game engine does. Now all that is left is to actually READ the new line whenever it is written. We will do this with the SendMessage API which can take a message of EM_GETLINE which will get the text on a certain line (0 is the first line). We need to always check the very newest line so we will count the lines with a EM_GETLINECOUNT message.
// global vars
#define LINE_LEN 512
HWND hWndEditRead = NULL, hWndEditWrite = NULL;
LRESULT thisline = 0;
LRESULT numlines = 0;
char szLastLine[LINE_LEN];
bool bOnce = false;
// in our hook
if(uMsg == WM_COMMAND)
{
if(HIWORD(wParam) == EN_CHANGE) // Newline in console
{
if(!bOnce)
{
hWndEditRead = (HWND)lParam;
hWndEditWrite = FindWindowExA(FindWindow(0, GAME_WINDOW), NULL, "Edit", 0);
bOnce = true;
}
*LPWORD(szLastLine) = LINE_LEN; // Set first word to sizeof char (for SendMessage to work properly)
numlines = SendMessage(hWndEditRead, EM_GETLINECOUNT, 0, 0); // Get total number of lines
thisline = SendMessage(hWndEditRead, EM_GETLINE, numlines-2, (LPARAM)szLastLine); // Store the last line of the console (the new line)
if(thisline)
{
// Terminate the string so we don't have remains of other lines
*(szLastLine + thisline) = '\0';
// Scan console..
if(strstr(szLastLine, "sinner"))
{
MessageBox(GetForegroundWindow(), "what about meh? :o", "msg", 0);
return 0;
}
}
}
}
Adding Functionality - Writing
Now we have a "scan console" hook going lets say we want to output "sinner is sexeh" in global chat whenever someone types "sinner".
if(strstr(szLastLine, "sinner"))
{
ConsoleWrite(hWndEditWrite, (LPARAM)"\say ^7sinner is sexeh");
return 0;
}
And our ConsoleWrite func will look like this:
void ConsoleWrite(HWND hWnd, LPARAM lParam)
{
SendMessage(hWndEditWrite, WM_SETTEXT, 0, lParam);
SendMessage(hWndEditWrite, WM_CHAR, 13, 0); // Simulate Enter
}
For further coding you could for example filter out the "Unknown Command" message when a user types /set aim 1 so it would act just like a engine cvar hook.
Author Sinner