Hi folks,
I hate to simulate keyboard input, but would love to do so.
There are few references on the internet right now and if you find one, the same questions are asked over and over, so here's my centralized keyboard input progress story. Note that I write this, because there's a lack on information regarding what and how to do and what flag affects what. I don't claim to write interesting nor completely right information.
Everyone likes to bot "safe". Safe ASM injection, safe memory writes, save waypoints...
Rumors are, if you want to be "really safe", then you need to simulate real user input!!
If you want to be safe (kind of), just download the source.
For everyone else, here's what I've been through.
As a botter, we are looking for a way to simulate keyboard actions to preferably non-focused and/or hidden windows.
Edit: As _Mike pointed out, this means that you aren't actually safe anymore, because hidden or minimized windows are easily detected. It's just that Blizzard doesn't care (because of false positives?).
For the situations in which the botted window is already focused, you should plain simply use SendInput, which will work if the botted program doesn't reject 'injected' key messages (not really safe after all duh!).
This ofc for Windows only, since I'm a Linux noob and not interested in OS X at all.
The Win API provides 2 major ways for faking keyboard input:
- Already mentioned SendInput, should always be your first choice
- PostMessage/SendMessage (PM/SM): Can be used to generate the window messages manually, thus simulating key strokes
- No 3rd way, but if you don't know these functions, read them up on MSDN!
I first attempted to solve my problems with SendInput. I actually knew about its limitations regarding focus, but hoped that I could circumvent it somehow with AttachThreadInput (read up!), which, as it turned out when I read the docs more carefully, I could not. There's a next to fully functional implementation with SendInput of IKeyboard, which only fails the test regarding focus windows.
So I needed to mess around with PM/SM like several times before and like several others like you probably did.
Keyboard messages for day-to-day alphanumeric keys are generated in the following order (read them up. all!):
- WM_KEYDOWN: Obvious. You press down a key.
- WM_CHAR: This contains the actual 'key press' and character information. Is generated by TranslateMessage when called with a WM_KEYDOWN or WM_KEYUP message (!), when the keyup flag isn't set. This means, we don't ever post/send this!
- WM_KEYUP: Obvious. You release the key.
Credits to Spy++.
Specifically, if you press a key and hold it, WM_KEYDOWN is posted again and again with the repeat flag set, generating WM_CHAR messages each time, which actually are responsible for textual input. WM_KEYDOWN and WM_KEYUP are only used f.e. in games for starting/stopping movement (think of wasd), not in a textual context. Strangely, although the docs stated the lower word of the lParam to be the repeat count, it always seems to be one.
So.. now our tools. PM posts its message asynchronously to 'the' message queue of the target window. SM does the same synchronously and has (deceivingly) the same protocol. Since they are so similar (and I didn't read the docs carefully), I first implemented the same IMessageEmitter interface with both of them, giving me freedom to choose between an AsynchronousMessageEmitter and a SynchronousMessageEmitter (ok, actually my tests supposed to). No big deal!
What seems a problem too, is that most games use GetAsyncKeystate to query the keystate of modifiers and stuff. Sadly, the keystate is influenced by SendInput, but not by PM and SM.
A first glance at the docs made clear how to provide PM/SM with the first 2 parameters. The wParam is the virtual key code, which is already present in the WinForms assembly as the Keys enum (if, for some reason, you don't want to reference that, change the code to use a selfcoded enum). Easy!
This leaves the lParam. Since I wanted to make it work quickly, I used 1 (for repeat count) as the lParam for both keydown and keyup.
This is where the first real differences between PM and SM came into my mind. SM is treated like you actually call the wndproc of the foreign thread (of course with it's delegated thread). You get a return value (an LRESULT, not just a simple-minded BOOL), which is also displayed in Spy++... and there is never a WM_CHAR message generated. Also since botted programs can easily find out if your message is sent or posted via InSendMessage (keyboard messages are sent according to Spy++), it will never be safer than poking around in your game with a public speed hack.
So SM bails out as well... at least as single solution.
Spy++ for PM:
Ok, sending j worked fine, but it appeared twice. This was fixed when I provided the up and repeat flag as needed (see the chronology of my tests).Code:P WM_KEYDOWN nVirtKey:'J' cRepeat:1 ScanCode:00 fExtended:0 fAltDown:0 fRepeat:0 fUp:0 P WM_KEYUP nVirtKey:'J' cRepeat:1 ScanCode:00 fExtended:0 fAltDown:0 fRepeat:0 fUp:0 P WM_CHAR chCharCode:'106' (106) cRepeat:1 ScanCode:00 fExtended:0 fAltDown:0 fRepeat:0 fUp:0 P WM_CHAR chCharCode:'106' (106) cRepeat:1 ScanCode:00 fExtended:0 fAltDown:0 fRepeat:0 fUp:0
Next I monitored the use of the alt flag, which was surprisingly very obvious. These flags already required me to keep track of my keys is a keystate hashset.
I recognized that the empty scancode of keydown was "copied" into the corresponding keypress message. Anyway, I provided the scancode in the 3rd lowest byte of lParam with some other windows API call (not important, if you are too lame to google, look at my code).
These were the final adjustments to lParam. It was called in the documented way.
Spy++:
BUT the most annoying problem still is, that the keypress is posted after the keyup message is through. I repressed that point and tried to implement modifiers.Code:P WM_KEYDOWN nVirtKey:'A' cRepeat:1 ScanCode:1E fExtended:0 fAltDown:0 fRepeat:0 fUp:0 P WM_KEYUP nVirtKey:'A' cRepeat:1 ScanCode:1E fExtended:0 fAltDown:0 fRepeat:1 fUp:1 P WM_CHAR chCharCode:'97' (97) cRepeat:1 ScanCode:1E fExtended:0 fAltDown:0 fRepeat:0 fUp:0
This is where I'm stuck. It just doesn't work. The end-to-end tests don't pass for a simple reason: The modifiers which are passed through don't affect the key state.
I tried to nail it down on that WM_KEYDOWN being posted too soon.
So I tried to synchronize with SM and WM_NULL. This is where all similarity between PM and SM passes off. This is where you get to know, that you aren't posting to ONE message queue but an opaque, queue-like interface: SM calls are treated with higher priority than PM calls. So just becuase SM with WM_NULL completes after PM'ing a WM_KEYDOWN completes doesn't mean that your WM_KEYDOWN was completely handled. Spy++ reflected just that, WM_NULL was sometimes handled before WM_KEYDOWN. But even in those cases where WM_NULL was handled after WM_KEYDOWN, WM_CHAR was possibly posted after WM_KEYUP. This made me google a lot and I finally found this ( Thread Desynchronization Issues in Windows Message Handling | !pool @eax ) which essentially says, that the message queue are actually 3 queues, one for input, one for posting and one for synchronously sending messages (which have a higher priority).
TLDR: We are posting keydown and keyup to the wrong queue. The input queue, where those messages belong and where WM_CHAR is enqueued, is only accessible through SendInput, which doesn't allow that nifty stuff we need.
For hack's purpose, I let the Thread sleep 15 ms between keydown and keyup, which made the WM_CHAR move between the two messages.
Even that didn't pass my end-to-end tests, though WM_CHAR actually was dispatched in the right modifier frame.
Edit: Ok, from prior experience, casting spells and moving and actual game experience is affected by posted modifiers, so WoW specifically does not use GetAsyncKeyState for determining modifier key state. It's just that TranslateMessage does internally use modifier key state to enqueue WM_CHAR.
So, GetAsyncKeyState and the hidden input queue made my day.
I'm not that into kernelmode dejbugging, that's why I gave up on reversing SendInput.
Thoughts, improvements and constructive slaps are welcome.
I actually wanted to provide the.. (insert sth for final) sufficent solution to keyboard simulation... but all you can do right now is write lowercase chat messages.
So here it goes: http://github.com/Bananenbrot/Keyboard