Since many people – including me – suspect windows of external overlays to be detected by anti-cheats, having an overlay system without a window entirely has excited many people in the game hacking community.
Traditional overlay windows have very specific and detectable characteristics in general, like being at least as big as the game’s client area, being WS_EX_TRANSPARENT and WS_EX_LAYERED, and sometimes WS_EX_TOPMOST, even if you can remove this one by sending back your window at the top at each cycle.
Many possibilities were considered, including leveraging “whitelisted” programs, who are authorised to draw on top of game’s frames such as FRAPS, Overwolf, Discord, NVIDIA, and many other gaming related software.
I have personally developed an Overwolf plugin that allowed me to display on top of my game’s frames, but I never finished my system, I wasn’t satisfied with it, but other people have implemented full systems, such as the plugin “Ultimate crosshair” that you can see in action here:
Anyway, I recently found a solution that lets you draw your overlay without a window entirely (technically we are still using a window, but we don’t create it and it’s not linked in any way to the program).
The solution is actually so simple that I felt like facepalming myself when I found it.
If you feel like a small challenge here is how I found it, perhaps can you find it too without reading the solution? 😉
I was using Process Hacker checking out the windows in my system (View > Windows) looking for windows that I could hijack.
I thought that if I found a window that was already taking the fullscreen, visible, layered and transparent I could just render on it.
But then an idea came to my mind, is there no other windows that are always on top and take all the screen?
Hint: There are at least 2…
First solution: Drawing directly on the desktop window
Yep, that’s really that simple…
Here some code that you can compile and run to admire that for yourself:
HWND hwnd = GetDesktopWindow();
HDC hdc = GetDC(hwnd);
RECT textPos;
textPos.left = 200;
textPos.right = 200;
textPos.top = 200;
textPos.bottom = 200;
wstring text = L”This is a test”;
while (true)
DrawTextExW(hdc, (wchar_t*)text.c_str(), text.size(), &textPos, DT_CENTER | DT_NOCLIP | DT_NOPREFIX, NULL);
Here is a quick PoC:
Second solution: Use the master window
There is a “master window” that is sort of parent of all windows, and oppositely to the desktop window, this one takes all the screens if you have multiples monitors.
This window is created by CSRSS (the second instance, created at user logon) and seems to have always the same HWND (and that’s a good thing because I fail to find it automatically with EnumWindows, FindWindow, and the likes).
Its ID (on Windows 10 x64) is (HWND)0x10010, it’s the only window CSRSS has and you can see it on top of all others in Process Hacker’s windows explorer:
You can also just draw right on that.
Third (bad) solution: Just draw on the game’s HWND/HDC
Yeah, you can just do that as well, FindWindow on your game, GetDC, and draw there, it does work:
However, it make you modify the game’s window, you will appear in frame captures and are therefore detectable through these methods.
If you use GDI it’s going to be really noisy because to change the font, the color, the background, and all, you have to make calls to functions such as SelectObject, SetTextColor, SetBkMode, etc… that make changes to the window and all this is not supposed to happen and can be monitored (in addition to this it will happen many times per seconds).
And you would be doing all this on a window of a process that is being closely monitored by an anti-cheat…
I am the careful type of guy, let’s leave that process and its window alone entirely, but since all this is not common practice, I wouldn’t be surprised if anti-cheats do not look for such things.
Slight problem to overcome: draw fighting
The only problem that I am about to solve is that your game will refresh the whole area of it’s frame, deleting your previous draws, so there will be some “Draw fighting” and your overlay might flicker if not done correctly.
I intent to make a first solution using pure force: a dedicated thread that will only overwrite over and over again as fast as possible.
Then I will find something cleaner.
I’ll keep you updated on that.
Full compatibility (with DirectX, OpenGL, and other renderers)
Yes, since you can blit (with BitBlt, TransparentBlt, StretchBlt, etc…) on the desktop window (that is a bitmap 32 bit, ARGB, so WITH transparency) you can just send your frames there.
I’ll also demonstrate that.