这是我追求的第二个(和最后)帖子,以便在股票词典中扮演一个实时游戏。在上一篇文章中,我通过使用一个快速和肮脏的内存扫描仪进行谈话,以便在屏幕文本缓冲区上访问记事本(并构建渲染到其中的射线示踪器)。在这篇文章中,我将讨论我如何处理获取用户输入,最后最终在股票记事本中完全可玩的蛇游戏。
这篇文章的标题给出了我结束的事实,即使用钩子捕获用户输入,但我最初认为我可以用dll注入来做到这一点。我几乎不知道DLL注射是什么,但我知道它可能导致事情发生在已经运行的过程中。这似乎是一个重要的地方。事实证明,您需要了解DLL注入以与挂钩一起使用,因此它也不是开始这个博客帖子的糟糕点。
我开始谷歌唱了“DLL注入”,发现了这个优秀的文章,分解了DLL注入的东西,并具有一个伟大的Github回购,其中包括不同的方法的示例。我没有关于我如何使用这些捕获键盘输入的线索,但我认为我会尝试将简单的东西注入运行的记事本进程。
基于注射物品我刚刚链接,注入DLL最简单的方法似乎是:
如果你没有用它做得很多,写作DLLMain()中的DLL就会很容易。我稍后发现,在DLLMain(这里有更多信息),还有很多东西,但对于我的第一个测试项目,我刚刚弹出一个消息框。 DLL有效载荷的整个代码只是几行。
//在任何进程加载DLL #define win32_lean_and_mean #include< windows.h>播放一个消息框的小DLL有效载荷。 BOOL WINAPI DLLMAIN(hinstance hinstdll,dword ul_reason_for_call,lpvoid lpvrevererved){switch(ul_reason_for_call){case dll_process_attach:messagebox(null,"过程附加!"," woohoo" 0);休息 ; }}
正如您可能想象的那样,棘手的部分是在第一个地获得记事本。就像上面的有效载荷一样,我的注射代码几乎完全从上面链接的replalthethings repo复制。与有效载荷不同,它的时间更长。我在这里包括这里,因为如果你以前从未见过怎么做,我假设这比不得不点击github的链接更方便,但我不会潜入它的工作原理,因为这篇文章/ repo我上面链接可以教你比我能更好地给你一个。
Warning: Can only detect less than 5000 characters
如果钩子函数没有调用挂钩链中的下一个函数,则在窗口有可能响应它之前可以丢失消息。
鉴于此信息,尝试拦截发送给记事本发送到记事本发送到记事本的键盘事件似乎合理的似乎是合理的,该钩子功能故意在挂钩链中调用下一个函数。在追求MSDN文档页面上使用挂钩后,我认为我需要将WH_KEYBOACK挂钩安装到记事本的编辑控件中。
Docs还指出,如果要在您自己以外的进程中安装钩子,您真正做的是一种DLL注入的形式。您需要将钩子函数放在DLL中,并使用setwindowshookex()将DLL的代码加载到目标应用程序中。
所以一切都在想,我穿上了我的长袍和巫师帽子并开始工作了。
我开始刚刚尝试防止记事本完全接收键盘输入。我所需要的只是为了钩住wh_keyboard,然后不要拨打挂钩链中的下一个钩子,这似乎是一个简单的地方。要为wh_keyboard编写一个钩子功能,您需要做的就是务必匹配keyboardproc()的函数签名。鉴于我需要这个功能基本上没有什么,这很容易:
#define win32_lean_and_mean #include< windows.h> #include" inject_payload_disablekeyinput.h" LRESULT回调键盘PROC(INT代码,WPARAM WPARAM,LPARAM LPARAM){返回1; BOOL WINAPI DLLMAIN(HINSTANCE HINSTDLL,DWORD UL_REASS_FOR_CALL,LPVOID LPVRESERATED){返回true; }
Bool InstallRemotehook(DWORD THREDID,CONST CHAR * HOOKDLL){HModule hopllib = loadlibrary(hookdll); if(hooklib == null)返回false; hookproc hookfunc =(hookproc)getProcAddress(hooklib," keyboardproc"); if(hookfunc == null)返回false; setwindowshookex(wh_keyboard,hookfunc,hooklib,threadid);返回真; }
ThreotID函数参数用于仅为Notepad的编辑控件安装挂钩(否则它成为全局挂钩)。获取线程ID是Juat在HWND上调用GetWindowThreadProcessID()以进行编辑控件。您可以从我的上一篇文章中使用GetWindowForProcessandClassName()函数获取HWND。这是函数再次:
hwnd getwindowforprocessandclassname(dword pid,const char * classname){hwnd curwnd = gettopwindow(0); // 0 arg表示将窗口放在z zord char classnamebuf [256]的顶部;虽然(Curwnd!= null){dword curpid; dword dwthreadid = getWindowThreadProcessID(Curwnd,& Curpid); if(curpid == pid){getClassName(Curwnd,classnamebuf,256); if(strcmp(classname,classnamebuf)== 0)返回curwnd; hwnd childwindow = findwindowex(curwnd,null,classname,null); if(childwindow!= null)返回childwindow; curwnd = getNextWindow(Curwnd,gw_hwndnext);返回null; }
有一件事要注意的是installEremotehook()函数是因为它获得了带有getProcAddress()的函数指针,所以挂钩回调的编译名是重要的。这意味着我需要确保使用“extern c”导出该函数以防止编译器卸载函数名称。
如果您希望在PACTICE中看到所有这些看起来,此博客文章的Github Repo都有一个概念挂钩应用程序使用上面描述的钩子有效载荷禁用记事本的实例禁用键输入。
只需阻止记事本获取键盘输入很酷,并且能够将输出到游戏重定向到遥远的哭泣。我想要做的是防止记事本获取键盘输入(使用户无法键入字符并弄乱我的渲染),并将该键输入重定向到我使用的过程中的进程来控制我的游戏逻辑。
将键输入重定向到不同的过程的输入并不比防止键输入更困难。我只需复制/粘贴代码以禁用密钥输入并进行以下更改:
挂钩应用程序打开套接字,然后在安装挂钩之前开始侦听消息
在有效载荷中,当截获第一键盘消息时,有效载荷创建客户端套接字并连接到注射器应用程序
然后,每当挂钩回调看到键盘消息时,它会通过此客户端套接字发送到注射器应用程序的CHAR代码
我不会走过如何设置Windows套接字(但是这样的所有代码都在这个项目的GitHub页面上)。相反,我只是想共享我曾经发生过这种情况的钩有效载荷。
套接字SOCK = INVALID_SOCKET; LRESULT回调键盘PROC(INT代码,WPARAM WPARAM,LPARAM LPARAM){const int buflen = 512; char sendbuf [buflen]; Memset(SendBuf,' \ 0',Buflen); if(sock == invalid_socket){sock = createClientsocckt(" localhost"," 1337"); int isekeydown =(int)lparam>> 30; if(iskeydown){_itoa_s< 512> ((int)wparam,sendbuf,10);发送(袜子,sendbuf,(int)strlen(sendbuf),0); }返回1; }
从LPARAM提取关键状态是有点奇怪的,但它似乎是获得该信息的最佳方式。如果您想编写更强大的输入处理钩,您可能会关心该参数中的更多数据,但这足以让WASD。
一旦这是工作的,就是从那里到达工作实时游戏的非常小的跳跃。
所以是的,所有这些劳动的果实都不令人兴奋。我做了蛇。它对ASCII图形提供了超级效果(即使角色高于它们的事实是有点讨厌),我已经有了从夫妇帖子前写的游戏逻辑。
关于实施蛇并没有真正有趣,我已经通过了其他一切谈了,所以我将在劫持的笔记薄窗口中玩蛇的另一个GIF结束。 我希望你尽可能多地享受这里获得这里的过程,因为最终产品是(如承诺)超级哑巴。 这个网站上的帖子是我自己的,不要代表我的雇主的职位,战略或意见。