问题由来 我比较喜欢玩《侠盗猎车:自由城之章》(简称《GTA:EFLC》)。不过本人玩游戏的水平 比较菜,经常被游戏里的贼和警察狂虐,心里十分不爽,而网上下载的外挂不是有病毒就是 不能用,游戏内置的作弊器操作很麻烦,于是萌生了自己做一个外挂的想法。在游戏里,只 要输入一个十位数的作弊码就能进行一系列的作弊,比如补全生命,获得武器,修复汽车等。 但由于要输入10个毫无规律的数字,让人很是抓狂。而且在进行这一系列操作时,往往还 必须进行其它操作,比如驾车、枪战。往往一个不小心,就GameOver了。利用模拟按键的 方式输入,能节省宝贵的时间。 解决方案 因为《GTA:EFLC》是 DirectX游戏,所以一切user32.dll提供的模拟按键函数(如 SendInput之流)都失效了,因为DirectX游戏是利用DirectInput绕过Windows的消息机 制直接和硬件打交道来接收按键信息的。因此,我们必须使用驱动级别的模拟按键。但是在 驱动里如何进行模拟按键,我全然不知,一时我的研究就陷入的僵局。后来偶然得知了WinIO 这个神器,于是开始了我的研究历程。 WinIO是一款免费、开源的系统组件,你可以在www.internals.com上面免费下载它的 源码。在最新版本3.0中,增加了对64位 Windows操作系统的支持。我就是利用它的功能, 实现了驱动级模拟按键。在我使用的WinIO 3.0中,里面有四个bin 文件,分别是 WinIO32.dll、WinIO64.dll、WinIO32.sys、WinIO64.sys。sys文件是实现核心功能的驱动, dll文件是封装驱动功能的接口。由于我的系统是64 位系统,使用了VB做界面编程,所以 仅需要 WinIO32.dll和 WinIO64.sys。dll 文件有 10 个导出函数。在我的模拟按键程序里, 仅需要用到四个:InitializeWinIo、ShutdownWinIo、GetPortVal、SetPortVal。 说到驱动模拟按键,其实就是读写端口。说到读写端口,其实学过16位汇编的人都知 道,用 IN、OUT指令即可。不过可别忘记了,IN、OUT 指令属于特权指令,所以在 Ring 3 下不能调用,必须在驱动里调用。不过WinIO的作者并没有用内联汇编的方法实现读写IO 端口,而是使用了文档化的READ_PORT_UCHAR、READ_PORT_USHORT、READ_PORT_ULONG、 WRITE_PORT_UCHAR、WRITE_PORT_USHORT、WRITE_PORT_ULONG函数来实现。个人认为内联汇 编是一个不好的习惯,因为这样使代码有了很强的平台限制性。网上一些所谓的高手特别喜 欢在代码里内联汇编,以显示所谓的“高手风范”。其实恰恰相反,我在学驱动初期就特别 喜欢在驱动代码里内联汇编模仿“高手”,现在到是能不内联汇编就不内联汇编,以提高代 码的通用程度。 言归正传,我们要用到的四个函数原型和功能如下: bool _stdcall InitializeWinIo(); This function initializes the WinIo library. void _stdcall ShutdownWinIo(); This function performs cleanup of the WinIo library. bool _stdcall GetPortVal( WORD wPortAddr, PDWORD pdwPortVal, BYTE bSize ); This function reads a BYTE/WORD/DWORD value from an I/O port. bool _stdcall SetPortVal( WORD wPortAddr, DWORD dwPortVal, BYTE bSize ); This function writes a BYTE/WORD/DWORDvalue to an I/O port. 根据 VB 和C代码的互换规则,得到以下声明: Public Declare Function InitializeWinIo Lib "WinIo.dll" () As Boolean Public Declare Function ShutdownWinIo Lib "WinIo.dll" () As Boolean Public Declare Function GetPortVal Lib "WinIo.dll" (ByVal PortAddr As Integer, ByRef PortVal As Long, ByVal bSize As Byte) As Boolean Public Declare Function SetPortVal Lib "WinIo.dll" (ByVal PortAddr As Integer, |