Results 1 to 1 of 1
  1. #1
    Dwar
    Dwar is offline
    Veteran Dwar's Avatar
    Join Date
    2010 Mar
    Posts
    2,222
    Thanks Thanks Given 
    211
    Thanks Thanks Received 
    2,230
    Thanked in
    292 Posts
    Rep Power
    10

    [C++] Hook console (non-engine)

    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
    Please, post your questions on forum, not by PM or mail

    I spend my time, so please pay a little bit of your time to keep world in equilibrium

  2. The Following 2 Users Say Thank You to Dwar For This Useful Post:


Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •