从零开始写一个DNF连发(一):初探

您所在的位置:网站首页 威尼斯度假村酒店地址 从零开始写一个DNF连发(一):初探

从零开始写一个DNF连发(一):初探

2024-05-01 09:02| 来源: 网络整理| 查看: 265

一、历史背景

        众所周知,DNF的连发主要依靠wegame,一旦离开了wegame,游戏自带的连发简直和便秘没什么区别。为了能够在不依靠wegame的情况下畅玩,我开始进行了寻找。

        然而,现在能找到的连发,要么自带广告全家桶;要么过于古老失效;要么虽然能用,但用几分钟以后便会卡死。最终,我放弃了找连发的想法,自己动手丰衣足食,干脆写一个出来。

二,功能分析

        一个连发程序,必须要有以下功能:

        1,输出按键

        2,检测键盘输入状态

        3,根据键盘输入状态,实时启动和停止

        为了实现这些功能,我们需要:

        1,使用keybd_event()函数输出按键

        虽然输出一个按键有很多种方式,但是经过测试,DNF对消息和虚拟键的方式进行了屏蔽。这个函数可以模拟扫描码和系统输入,因此它可以满足需要。

        2,使用全局按键钩子检测键盘输入状态

        钩子是挂载在消息处理序列之前的一段程序,使用钩子可以截获键盘按下时的消息,并首先处理。与检测按键状态的函数不同的是,钩子可以获取关于按键更多的信息,有助于分辨按键是键盘输入还是连发产生;钩子还可以直接截断按键,防止由于过多的输入导致的连发不畅。

        3,使用多线程来实现按下启动,抬起停止

        因为使用了钩子,所以任何操作都要及时释放,否则便会堵塞。为了保持消息的畅通,就需要另外开辟一个线程,让它单独地完成任务,而钩子则负责启动和停止它。

        4,使用互斥锁来实现线程交互

        多个线程使用同一资源时,会产生数据冲突的危险,因此当某一资源需要由多个线程共享时,需要使用互斥锁。

        此外,还以通过空锁等待的方法来控制线程的同步。

三,准备工作

        以下内容使用c++编写,使用头文件:

        由于不包含面向对象方法,所以c也是可以的,但日后若想要改为多键连发,封装起来会更方便。

        1,keybd_event()

        使用keybd_event()函数输出一个按键的方法为:

        其中,MapVirtualKey('X', 0)的作用是获取X的扫描码。

        2,钩子

        使用钩子时,先声明一个钩子函数,当钩子触发时,便会执行该函数。钩子函数的声明格式为:

        钩子函数传入的参数包含了按键的虚拟键码和扫描码,能够用来区分虚拟按键,但无法区分键盘输入和模拟按键,需要另外处理。

        随后,将该钩子挂载。由于是底层钩子,不需要进行额外的操作,直接在程序内就可以使用。挂载钩子的方式为:

        为了让钩子能准确运行,需要加入消息循环,消息循环的格式为:

        如果想使用钩子截断消息,则可以:

        一般来说,钩子使用完需要卸载:

        3,多线程

        使用线程时,先声明一个线程函数,当线程创建时,便会执行该函数。线程函数的声明格式为:

        启动时,创建线程:

        退出线程时,使用标志控制线程内返回的形式,而不建议使用外部强制结束的形式。

        退出线程后,关闭它的HANDLE(句柄),以释放资源。

        4,互斥锁

        创建一个互斥锁:

        其中,第二个参数设为1表示创建时立刻锁定,为0表示创建时不锁定。

        使用互斥锁时,首先等待并锁定一个互斥锁:

        这会等待该互斥锁,直到它处于未锁定状态时,锁定它并继续。

        当处理完后,将它解锁:

四,功能设计

        以下部分环环相扣,所以虽然大体按照一定的顺序介绍,但实际上是大交替执行,当一遍看不懂时,可以看完再回头来一遍。

    1,连发线程

        1)按键识别

        连发线程应该满足按下键盘启动,抬起键盘停止。但由于在模拟按键时加入了扫描码,所以单凭按键消息,无法分辨按键来源是键盘还是程序。所以按键输出的同时,需要一点辅助措施来协助识别。

        辅助措施为,在每个模拟按键周期前额外发送一个虚拟按键(即不含扫描码的按键),当虚拟按键发送时,按键钩子就会捕获到它,并进行相应的操作。

        2)线程退出

        连发线程是被外部启动的,但需要内部退出。并且,线程退出时,可能需要额外的时间,当线程退出产生延时的时候,连发便会暂时失效。

        考虑以上原因,线程退出采用两段同步式。一是使用线程退出标志控制是否退出,二是使用一个退出锁来取消退出等待。

        当线程需要退出时,外部使退出标志置1,并等待退出锁。线程每发送一个按键,就检测一遍退出标志,在发现退出标志时,就准备退出。在线程确定将要退出,但还未执行退出操作之前,首先解锁退出锁,然后在执行退出,避免了线程退出产生延迟的问题。

    2,按下消息钩子

        这是有按键被按下时触发的钩子。分析按键按下的构成,不难发现,按键按下的消息包括以下四类:按下键盘时的消息、长按键盘时反复发送的消息、模拟按键的消息以及虚拟按键的消息。

        通过钩子函数传入的参数,可以区分虚拟按键,但不能区分键盘按键和模拟按键。所以需要额外的标志来区分这些按键,再根据按键种类进行处理。

        1)初始状态

        收到一个按下消息时,如果消息正常接收未被截断,且当前连发未启动,则启动连发线程,进入连发状态。

        2)连发状态

        此状态下模拟按键开始启动,并且每次程序发送模拟按键之前,都会产生一个虚拟按键。也就是说,每当收到一个虚拟按键,便意味着连发程序将要输出一个模拟按键,包括一个按下和一个抬起。

        因此,当收到一个虚拟按键时,便进行一次记录,根据记录对接下来的一个按下消息和一个抬起消息进行特殊处理。随后,截断这个虚拟按键,防止其产生干扰。

        由于二级连发状态的存在,所以每当收到一个虚拟按键时,需要放过接下来的第一个按下消息,截断第二个及以后的所有按下消息。这是为了让连发过程更完整,确保每次连发都只有一个按键过程被发送,减少不必要的操作,防止连发不顺畅。

        3)二级连发状态

        按下键盘一段时间后,键盘会反复发送按下消息,这会带来不稳定性,所以该状态下输入的额外按下消息需要全部截断。

    3,抬起消息钩子

        这是有按键抬起时触发的钩子。抬起消息很简单,只包括程序模拟按键的抬起和键盘的抬起。所以处理起来会简单一些。

        1)确认键盘抬起

        按下钩子接收的一个虚拟按键,记录了一个模拟按键将要被抬起。因此,在记录后收到连续两个抬起时,说明第二次一定是键盘抬起。所以每当收到一个虚拟按键时,放过接下来的第一个消息,而当收到第二个消息时,准备停止连发。

        2)停止连发

        当收到第二个消息时,如果连发正在运行,则进行停止。停止时,通过将停止标志置1来通知连发线程准备停止,随后开始等待连发线程将退出锁解锁。退出锁解锁时,连发线程已经停止连发,准备进行线程退出的任务,因此退出锁解锁时,便不会再对接下来的输入产生干扰,可以认为连发线程已结束,进入停止连发状态。

        退出锁在创建时需要保持锁定状态,而等待退出锁的过程中,退出锁也会自然锁定,完成下一个循环。

五、代码

        注意,需要以管理员权限运行,否则无法进行连发。

        这样,一个简单有趣的连发就做好了~



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3