游戏开发者简介怎么制作游戏 新手怎么制作及制作方法简介

You are here:
独立开发者:新手做2D手游该用哪些工具
【GameLook专稿,转载请注明出处】
GameLook报道/随着全球手游行业规模将突破250亿美元,越来越多的开发者开始进入手游研发领域,而作为一名菜鸟,很多时候,如果没有其他开发者的建议,我们会走很多弯路,最近独立工作室Sheado.net公司的Chad Ata在博客中分享了他们从一开始进入手游研发到如今四年多以来积累的经验,希望可以给新入行者提供一些帮助。以下是GameLook编译的博客内容:
一开始进入游戏研发领域的时候,你很难知道该选择什么工具、什么程序语言以及哪些框架,你会面临许许多多的选择和建议,我和我的团队总能发现其他游戏公司的经验是有用的,所以这里提供一些我们的经验,希望给做手游的新手们一些帮助。
初入手游行业
虽然在其他行业有过10多年的编程和策划经验,但当我开始做手游研发的时候,依然觉得自己是一只菜鸟。我们的第一个游戏Furdiburb(宠物冒险游戏)最初是在2009年开始研发的,当时是专门为Android而做的。对于毫无游戏研发经验的我们来说,Java是唯一可用到编程语言。作为一个2D游戏,我们(非常不明智)没有使用OpenGL就开始非硬件加速图形开发。随着Furdi受到了更多人的欢迎,我们的游戏项目也得到更多的注意,最终我们遇到了性能和移植问题。如果我们此前学习了其他开发者的经验,很多问题都是可以完全避免的。
找到更好的方法
在完成了Furdiburb的研发,并且使用playn缓慢的把游戏移植到了iOS平台之后,我们决定开始第二款游戏(Eras of Alchemy)的研发。我们当时希望摆脱Java语言,找到可以广泛使用的跨平台研发工具,最好是未来还可以支持主机游戏平台。
随后我们开始了搜索,我用了将近一个月的时间对框架、工具以及引擎进行对比,最后,我和我的团队非常满意新的研发方式,直到现在也非常不错。这里我不会说为什么要选择这些,框架是经常变化的,一年前选择它的理由可能到现在就已经不再是考虑的主要因素了。所以,这篇文章的其余部分只会对我们选择某个工具的原因进行简单的描述。以下就是我们当初选择并对比了一个月之后的结果:
我们所列举的都是在研发我们游戏的时候用到的,而且只是我们做游戏的方式而已,很多工具和框架的结合也是非常完美的,需要开发者们自行发现。
我们选择的所有架构,要么是开源的,要么就是有开放的代码。这是非常好的,因为我们在必要的时候可以进行紧急修改和优化。
Cocos2d-x:我们所有的跨平台研发都是使用开源Cocos2d-x框架完成,在Eras Of Alchemy的研发过程中,我们使用了版本2,我们的下一个游戏正在使用的是版本3,进行了大幅度的API以及性能提升。整体来说,Cocos2d-x的2D表现非常好,而且文件管理非常方便。
Spine:我们使用Spine做了动画,所以我们加入了C语言为基础的Cocos2d-x插件。
Box2D:我们还没有发布一款使用该引擎的游戏,但我们的下一款游戏A Quiver Of Crows将会使用,我们目前研发就使用了这个工具。
SQLite:Cocos2d-x本身也提供数据存储方案,但我们更喜欢使用SQLite,因为它的读写速度和表现更好。
C++:Cocos2d-x支持多种语言编程,但我们选择了C++,因为我们觉得目前该语言是最合适做跨平台研发的。
C:你经常会想要加入一些开源的API,其中有一些就是C语言编程的。
Java:我们依然需要用到Java,但也只是在Android平台做游戏内IAP、广告以及特殊系统功能的时候。语言之间的切换可以通过JNI来完成。
Objective-C:选择它的理由和Java一样,我们使用Objective-C是为了使用iOS系统的特定功能。
Scripting:这包括程序化脚本和其他脚本语言,所有的开发者们都要时不时的写脚本,但我们却很少会谈论这个问题。这个问题是非常容易的,但如果你和我一样而且由于不常使用而不记得一些语法的话,这个工作有是非常耗时间的,我们要给维护代码、自动音频转换以及纹理打包写脚本。
Xcode:所有人都告诉我们说Xcode非常好,所以我进行了尝试,而且我不得不说的是,这是目前我最喜欢的开发环境。这里我并不想说太多具体的原因,因为我不想引发集成开发环境(IDE)争论,我们使用Xcode做跨平台研发,也为苹果平台做专门的编程。
Eclipse:对于IDE来说慢的可怕,但我看来却非常好用。我们用Eclipse做了所有的Java和Android研发,包括适配和修复bug。Android目前在推Android Studio而不是Eclipse,但我们没有那么多的时间,也没有什么特别的理由去转换到新的工具。
Visual Studio:也是个非常优秀的IDE,我们用它来做左右和微软相关的编程、Bug修复以及适配。
即便你的团队只有一个人,你也应该使用版本控制。所有人都会犯错,而且任何一次大改都可能导致游戏神秘的死亡。我们的团队只有3个人,因此从第一天做手游开始,版本控制就是非常必要的。目前有非常多的方案可以选择,但我们使用的有以下几个:
SVN:我个人喜欢SVN,因为可以做到所有我需要的功能,比如合并、同步、恢复等等,但学习起来比较困难。
Git:我们使用的很多开源框架都使用Git。我们使用Git就是为了保持与框架同步,当需要的时候可以进行快速修复。
很明显,你做游戏是需要使用电脑的。最初所有的研发都是在Linux机器上完成的。但我们开始了iOS平台的研发之后,用两三台电脑变得效率非常低,所以我们买了一些iMac,而且我们都非常喜欢用它来做游戏研发。幸运的是,OSX的很多指令与Linux相同,所以我们的很多脚本都没有做改变。
Adobe CS:相信这个没有人觉得奇怪,我们的美术师最常使用的是Illustrator和Photoshop。
Spine:非常推荐这个工具制作骨骼动画,和传统的帧到帧动画相比,骨骼动画可以节约硬盘空间,还可以节约大量的研发时间,提供强大的功能,比如动画混合、蒙皮技术以及网格变形。
Texture Packer:你或许会想要把图片进行打包获得更大的图像以获得更好的游戏表现。我们选择Texture Packer来完成这项工作,而且我们还使用它的指令功能进行自动化打包处理。
关卡编辑器
我们使用的关卡编辑工具包括:
没有编辑器:如果可以不用的话,我们绝不会使用编辑器。我们可以用代码解决,这听起来非常疯狂,而且有点浪费时间,但如果你的团队非常小的话,有时候为了节约时间可以不必为了一次性的任务专门用代码写一个编辑器。
定制化编辑器:有时候我们写了一个非常不好用的游戏内编辑器来做图形或者关卡,我的意思是未经优化的,恐怕也只有我们会这么做。
R.U.B.E:对于我们的下一个游戏,我们在使用R.U.B.E(Really Useful Box2D Editor)之前,几乎自己研发了一个游戏内编辑器,这个非常强悍的工具节约了我们大量的时间,但如果我们决定要做关卡编辑器的话,我们必须自己研发。
音乐和视频
Ffmpeg:我个人非常喜欢ffmpeg,这个工具非常好用,我们通常使用脚本用它把我们的视频变成各个平台需要的格式。
Cakewalk Sonar:这是个非常强悍的音乐制作软件,一开始的学习会比较困难。
GArritan Personal Orchestra:如果你想给自己的游戏加入管弦乐,Garritan可以带来非常高质量的音乐,我们通常和Sonar混合使用。
Audacity:一个非常不错的视频编辑和录制工具。
以下2个是我们已经不再使用的工具,但可能对于新手来说依然具有推荐意义:
Anvil Studio:如果你熟悉乐器而且乐意学一些音乐知识,并且想要做MIDI格式的音乐,这是个非常不错的软件。
Linux Multaimedia Studio:这是个非常不错而且简单的软件,可以制作非常不错的音乐,而且不需要你阅读很多的音乐知识。
目前做游戏的工具非常多,以上的这些工具只是我们在做2D游戏的时候选择的工具,目前为止,我们对这些工具非常满意,我们最新的游戏发布到了iOS、Android和Windows Phone平台。我们还打算在下一款游戏发布的时候,用同样的工具把游戏扩展到PC、Mac以及Linux平台。
& 2017 . All rights reserved.【原创】新人入手第一个游戏外挂,附上详细制作过程 - 看雪安全论坛
『软件逆向』 [综合性论坛]本版讨论的主题包括:调试逆向、系统底层、商业保护、虚拟机保护、.NET平台等安全相关的话题。
该主题: "【原创】新人入手第一个游戏外挂,附上详细制作过程" 因在一定的时间里没有任何回复而自动关闭。如果您还对该主题感兴趣或者想参与对此主题的讨论,请您重新发表一篇相关的新主题。
本站声明:看雪论坛文章版权属于作者,受法律保护。没有作者书面许可不得转载。若作者同意转载,必须以超链接形式标明文章原始出处和作者信息及本声明!
注册日期: Jan 2012
现金: 100 Kx
获感谢文章数:2获会员感谢数:19
【原创】新人入手第一个游戏外挂,附上详细制作过程
标 题: 【原创】新人入手第一个游戏外挂,附上详细制作过程 作 者: caigui
时 间: ,00:56:16 链 接: /showthread.php?t=160887
此教程为原创,但本人是个菜鸟,刚入手写程序,因此本帖有部分内容是参照其他一些教程,以及本论坛里有关连连看的帖子,再经过一些修改,再附上制作过程,以及本人的一些经验.
经过这次的练习,才知道很多东西要通过自己动手去做,才能领悟```其中有不对之处,希望有高人能够指点一二,希望此贴能够对像我一样的纯菜鸟有帮助.(发帖子,不懂技巧,附件都被吞了,可以下载二楼的[复件&QQ连连看游戏外挂详解教程.rar]文件.
本教程分为几大部分
一、&&先创建外挂对话框,定义外挂所需要的基本功能
二、&&找出棋盘数组的地址,读出棋盘数据
三、&&编写实现消去棋子的代码框架
四、&&实现消去棋子功能
五、&&修补与完善外挂
一、&&先创建外挂对话框,定义外挂所需要的基本功能
1.&&&创建对话框:&&&创建一个MFC工程对话框
打开VC++软件&&选择??------&(File)文件------&New(新建)
选择---&Project(工程文件)----&MFC&AppWizard[exe],填写好工程名字,选择好文件路径,点击OK&
2.&&选择----&Dialog&based(基本对话框)-------&Finish(完成)------&OK,如下图:
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75815&d=')}}"
3.&&先删除了原来的按钮(选中按delete),再添加我们所按钮.(点一下想要添加的按钮或控件,再到对话框内点击一下,即可添加)如图:
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75816&d=')}}"
4.&&再把按钮的属性先改好(ID与标题)
在所需要改的按钮上右键----&Properties(属性),
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75817&d=')}}"
图上添加了两个按钮,两个复选框,跟一个滑块.
一些基本功能都差不多了,后面如果还需要,再按以上方法添加.
二、&&找出棋盘数组的地址,读出棋盘数据
1.&&找出棋盘数组地址,我们需要用到一个Cheat&Engine(CE)软件,(这些工具大多都是英文的,对于外语我可是一点也不懂,所以,工具的单词我都去网上翻译一下,如果有不对的地方,请理解,明白意思就行了)。
2.&&这CE工具主要是访问进程地址空间,然后通过枚举的方法找出想要找的地址。
在枚举地址之前,还要用到另外一个工具XueTr.exe,因为QQ游戏大厅如果检测到CE工具在运行的话,就会弹出一个警告的对话框,然后退出游戏。
用XueTr.exe工具能解决弹出警告,步骤:
打开QQ游戏大厅(要先关掉CE,免得又弹出警告),再打开XueTr.exe---&点击&进程&----&右击QQ游戏大厅进程-----&选择&&查看进程模块;
找到&Tersafe.dll,右键点击&&Tersafe.dll&-----&删除模块文件.
然后再返回进程右键点击&QQ游戏大厅-----&选择&查看进程线程----&点击一下&模块&让他排序好----&右键点击&TenSLX.dll-----&选择&结束线程(不管有几个TenSLX.dll,都结束掉).如下图:
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75821&d=')}}"
好了,现在可以关掉XueTr.exe了,打开CE工具就不会再弹出警告了,当然QQ游戏大厅每次重新启动都需要用XueTr.exe来结束那几个线程,才能正常使用CE.(那个Tersafe.dll模块已经删掉了,就没有了,也就不用再次删除了)
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75822&d=')}}"
如上图,我们知道棋盘是一个11*19的二维数组的结构,我们只要找到数组的基地址就能算出整个数组每个棋子的位置,就是说只要枚举出上图做记号的那个位置的地址就行了.每一个棋子都是用一个数值来表示的,不同的棋子,用不同的数表示,空的地方当然就是0了,假如说1代表一种棋子,2又代表另一种棋子,依此类推,那么整个棋盘的最大范围都不会超过255(一个字节范围)所以我们枚举棋盘地址的时候选择数据类型为字节就足够了,如图:
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75823&d=')}}"
4.&&现在进入QQ连连看游戏房间,找位置坐下,点击练习,再打开CE工具----&点击&选择进程打开-----&选择&连连看游戏进程----&Open(打开)
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75824&d=')}}"
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75825&d=')}}"
如上图,我们要找的地址没有棋子,就是为0,那么用CE工具就选择Exact&Value(精确值)再点New&Scan(新扫描)…如下图:
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75826&d=')}}"
我们看到上图左边Found(扫描结果)处:有一个很大的数值,再下来的Address(偏移地址)与Value(值)的地方是表示符合条件的结果;(数值都为0)
然后我们再点游戏的&练习,换一盘棋,
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75827&d=')}}"
请看左上角的地方还是没有棋子,就再选择Exact&Value(精确值)再点Next&Scan(再次扫描)…如下图:
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75828&d=')}}"
我们会发现扫描结果已经在减少;
再次点击&练习,更换棋子.
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75829&d=')}}"
上图左上角已经有一个棋子了,那么CE就要选择Bigger&than(大于)-------&Next&Scan(再次扫描)
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75830&d=')}}"
现在发现扫描结果明显少了很多,然后再点击练习,换棋子,如果左上角没有棋子,就选择Exact&Value(精确值)为0,&-------&Next&Scan(再次扫描);&如果有棋子就选择Bigger&than(大于)-------&Next&Scan(再次扫描).直到扫描结果剩下一个的时候,就是棋盘数组的基地址了(在扫描到后面剩下几个扫描结果的时候,可能遇到再怎么换棋子来扫描,结果都不会减少的情况,这时可以退出游戏,然后换个座位或者换个房间,CE工具再重新选择进程打开,接着扫描)
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75831&d=')}}"
现在找到了棋盘地址,我们验证一下这地址正不正确,双击该地址,该地址就会在如上图出现在CE工具的下面,右键该地址-----&Browse&this&memory&region(浏览此内存区域)&
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75832&d=')}}"
此时,就能看到棋盘地址0x处的内容,再去跟棋盘的棋子对应,就会发现,都是可以吻合的,(当然,如果不吻合,肯定是地址不正确了,)如图:
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75833&d=')}}"
看上图,第一排是空的,第二排的第一格也为空,加起来就一共是20个空的,再看下图,0x处,前面一开始也是有20字节为0的,紧接着两个字节为0C跟08,再看棋盘也刚好有两个棋子.(当你在棋盘上消去一对棋子的时候,会看到地址表中对应的两个字节,也会跟着被设为零)
现在基本上可以肯定这棋盘地址是正确的了:0x&&//棋盘基地址
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75834&d=')}}"
5.&&在读出棋盘数据之前,我们还需要知道窗口的标题,可以用VC++的Spy++工具,打开VC++选择Tools(工具)------&Spy++,然后点击“小望远镜”的图标.如下图:
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75835&d=')}}"
在弹出来的对话框中选定Properties(属性),打开连连看游戏,用鼠标按住Finder&Tool(查找工具)的图标,把它拖动到连连看的游戏窗口中
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75836&d=')}}"
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75837&d=')}}"
然后点击OK,
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75838&d=')}}"
如上图中,在General(常规)中的Window&Caption(窗口标题中)就可以直接把窗口标题复制下来.
首先我们回到VC++,在对话框中多添加一个按钮与编辑框,用来读出棋盘数据.
用来读出棋盘数据的编辑框,大小要调整合适,然后再修改编辑框的属性
选定该编辑框-----&右键------&&Properties(属性)---&Styles(样式),然后把Multiline(多行)与Want&return(这里的意思应该是换行)前面钩上,如下图:
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75840&d=')}}"
(这个属性更改好了,直接关掉属性对话框,或者按回车,就会回到编辑外挂对话框了)
然后再给编辑框关联一个类变量,右键编辑框-----&Class&Wizard(类向导)----&Member&Variables(成员变量)------&选定编辑框ID(我没有更改该编辑框的ID,所以它是IDC_EDIT1)-----&Add&Variables(添加变量).如下图:
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75841&d=')}}"
在弹出的对话框中设置好Member&variable&name(变量类型)跟Variable&type(变量名,因为棋盘数据是一个字符串数组,所以我们要选择字符串类型)----&OK.
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75842&d=')}}"
返回外挂编辑图,双击”读取棋盘数据”按钮就可以转到实现触发该按钮消息的成员函数.我们在这里编写代码实现读出棋盘数据.如下图:
&screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75843&d=')}}"
先为棋盘地址与窗口标题定义一个宏(定义在函数外面,要全局的,)
#define&CHESS_DATA_ADDRESS&0x
#define&GAME_CAPTION&“QQ游戏&-&连连看角色版”
byte&chessdata[11][19];&&//存放棋盘数据的数组,这个数组我们定义为全局变量,因为别的函数还要用到此数组.
void&CQQllkWgDlg::OnButton1()&
&&//&TODO:&Add&your&control&notification&handler&code&here
&&DWORD&&&&&&&//返回窗口进程ID
&&LPVOID&chessaddress&=&(LPVOID)CHESS_DATA_ADDRESS;
&&&&&&&&HWND&hwnd&=&::FindWindow(NULL,GEAM_CAPTION);&&//获取窗口句&&&&&&&&&&&&&&&&&&&&&&&柄
&&&&&&&&if&(hwnd&==&NULL)//如果打开窗口不成功
&&&&&&&&&&&&GetWindowThreadProcessId(hwnd,&prcesssid);&//获取窗口进程ID
&&&&&&&&&&&&HANDLE&processsH&=&OpenProcess(PROCESS_ALL_ACCESS,FALSE,&prcesssid)&;//打开指定进程
&&&&&&&&&&&&ReadProcessMemory(processsH,chessaddress,chessdata,19*11,&byread);&//读取进程指定地址、大小的数据(读出数据到chessaddress数组)
&&&&&&&&&&&&CloseHandle(processsH);//关闭进程&&&&&
&&&&&&&&&}
//(编译代码,看是否有错误,我们往后每完成一小部分代码都应该编译一下,看是否有错,有错误就要先改过来,再继续写下去)
现在已经读出棋盘数据,那么我们再把chessaddress数组的数据显示到之前定义变量为m_ChessData的编辑框,这样我们能更清楚地验证棋盘数据.
&&&&&&[COLOR=&rgb(255,&140,&0)&]CString&chessdata1;
&&&&&&&&&&&&&&&&CString&tempS
&&&&&for(int&x&=&0;x&11;x++)
&&&&&&&for&(int&y&=&0;y&19;y++)
&&&&&&&&&tempStr.Format(&%2x&,chessdata[x][y]);//把数据进行排版
&&&&&&&&&chessdata1&+=&tempS
&&&&&&&&&}
&&&&&&&chessdata1&+=&&\r\n&;
&&&&&m_ChessData&=&chessdata1;
&&&&&&&&&&&UpdateData(FALSE);//更新数据到编辑框[/COLOR]
把上面代码加入到void&CQQllkWgDlg::OnButton1()函数读取棋盘数据的代码后面,编译通过后,运行程序,就能显示棋盘数据到编辑框.如下图:
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75844&d=')}}"
&screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75845&d=')}}"
现在已经能显示棋盘数据了,不过看起来,不是很好看,再修改一下:
把tempStr.Format(&%2x&,chessdata[x][y]);&里面的%2x改成%2.0x;在
chessdata1&+=&tempS后面再上chessdata1&+=&”&”;这样看起来会更加好看.
&screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75847&d=')}}"
这样看起来更加贴切```
既然读出了棋盘数据,那么接下来就是准备实现消去一对棋子代码框架.
在写代码前,我们先建立一个头文件,然后我们就在头文件中编写实现的代码,这样会比较整洁,清晰
点击菜单File(文件)---&New(新建)---&Files---&C/C++&Header&File,写上头文件名,如下图:
&screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75848&d=')}}"
在VC++应用于软件的左边,FileView版块的Header&Files文件夹中我们就能看到刚才新建的头文件了,后面我们的一些函数的实现,都写在这头文件中,如下图:
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75849&d=')}}"
三、&&编写实现消去棋子的代码框架1.&&我们得到了棋盘数据,每个棋子都比较一次,如果相同且不为0,就调用检测函数(这些函数,我们自己写代码实现)检测是否能消去,如果能消去,再调用实现消去棋子函数.
以下代码实现遍历整个棋盘.(这函数我们写到GameProcess.h头文件中去)
[COLOR=&rgb(255,&140,&0)&]BOOL&ClearPiar()
&&UpdateChess();//更新棋盘数据
&&//遍历整个棋盘,找出相同的一对棋子
&&POINT&p1&=&{0};//定义两个POINT型结构的变量来表示两棋子的位置.
&&POINT&p2&=&{0};
&&int&x1&=&0;
&&int&x2&=&0;
&&int&y1&=&0;
&&int&y2&=&0;
&&for&(y1&=&0;y1&&&11;y1++)
&&&&for&(x1&=&0;x1&&&19;x1++)
&&&&&&for&(y2&=&y1;y2&&&11;y2++)//遍历的时候要注意y2&=&y1
&&&&&&&&for&(x2&=&0;x2&&&19;x2++)//x2&=&0
&&&&&&&&&&if&(chessdata[y1][x1]&==&chessdata[y2][x2])//如果棋子相等
&&&&&&&&&&{
&&&&&&&&&&&&if&(chessdata[y1][x1]&!=&NULL)//如果棋子不为零
&&&&&&&&&&&&{
&&&&&&&&&&&&&&if&((x1&!=&x2)||(y1&!=&y2))//两棋子不是同一棋子
&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&p1.x&=&x1;
&&&&&&&&&&&&&&&&p1.y&=&y1;
&&&&&&&&&&&&&&&&p2.x&=&x2;
&&&&&&&&&&&&&&&&p2.y&=&y2;
&&&&&&&&&&&&&&&&if&(checkchess(p1,p2))//检测棋子是否能消除
&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&click2p(p1,p2);//消除一对棋子
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&return&TRUE;
&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&}
&&&&&&&&&&&&}
&&&&&&&&&&}
&&return&FALSE;
我们给上面用到的几个函数先定义个空函数,
[COLOR=&rgb(255,&140,&0)&]BOOL&checkchess(p1,p2)
&&return&TRUE;
BOOL&click2p(p1,p2)
&&return&TRUE;&
Void&UpdateChess()
2.&&这些函数必须定义在ClearPiar()之前,(因为我们在定义ClearPiar()之前没有给这些声明过,)要不编译会出现未定义错误.
现在我们只要完成checkchess(p1,p2)函数跟click2p(p1,p2)函数还有UpdateChess()函数(这更新棋盘数据的函数,其实我们已经完成了,之前我们写过显示棋盘数据的成员函数,只要去掉后面显示棋盘数据部分就行了)的实现,就差不多能实现消除一对棋子了.
这些函数我们都写到GameProcess.h头文件里面,打开该头文件,把QQllkWgDlg.cpp中的
#include&&stdafx.h&复制过去.
[COLOR=&rgb(255,&140,&0)&]#define&CHESS_DATA_ADDRESS&0x
#define&GAME_CAPTION&“QQ游戏&-&连连看角色版”
byte&chessdata[11][19];&&//存放棋盘数据的数组,这个数组我们定义为全局变量,因为别的函数还要用到此数组.
Void&UpdateChess()
&&&&DWORD&&&&&&&//返回窗口进程ID
&&&&LPVOID&chessaddress&=&(LPVOID)CHESS_DATA_ADDRESS;
&&&&DWORD&
&&&&HWND&hwnd&=&::FindWindow(NULL,GEAM_CAPTION);&&//获取窗口句&&&&&&&&&&&&&&&&&&&&&&&柄
&&&&if&(hwnd&!=&NULL)//如果打开窗口不成功
&&&&&&&GetWindowThreadProcessId(hwnd,&prcesssid);&//获取窗口进程ID
&&&&&&&HANDLE&processsH&=&OpenProcess(PROCESS_ALL_ACCESS,FALSE,&prcesssid)&;//打开指定进程
&&&&&&&ReadProcessMemory(processsH,chessaddress,chessdata,19*11,&byread);//读取进程指定地址、大小的数据(读出数据到chessaddress数组)
&&&&&&&CloseHandle(processsH);//关闭进程
3.&&我们已完成UpdateChess()函数了,之前创建的”读出棋盘数据”的按钮跟用来显示棋盘数据的编辑框,已经没有什么用了,先把它们给删除掉.首先点击一下实现读出棋盘数据的void&CQQllkWgDlg::OnButton1()函数,再点击右上角的”GO”图标:
&screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75850&d=')}}"
然后把这函数的声明给注释掉,如下图:
再双击左侧QQllkWgDlg.cpp回到函数的实现,把该函数的代码全都注释掉(包括全局变量与宏定义).点击左侧的ResourceViewt版块---&QQlldWg&resources---&Dialog,双击IDD_QQLLKWG_DIALOG,返回到对话编辑,右键点击”显示棋盘数据的编辑框”,如下图:
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75852&d=')}}"
---&&Class&Wizard(类向导)----&Member&Variables(成员变量)------&选定之前定义的m_ChessData变量---&Delete&Variable(删除变量)---&OK.
&screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75853&d=')}}"
再同时选定”读出棋盘数据”按钮跟显示棋盘数据的对话框,按Delete键删除掉.
再编译看看,有错误,不能通过,双击出错的提示,去到出错的地方,我们看到还有个地方跟”读出棋盘数据”按钮有关系的,把它也注释掉.再编译就能通过了,如下图:
&screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75854&d=')}}"
4.&&在写checkchess(p1,p2)函数之前,先来分析一下连连看游戏规则,玩过连连看游戏的人,都应该知道,要消除两个棋子的条件是最多只能用三条直线(路线)连起
来.如下图:
&screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75855&d=')}}"
&screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75856&d=')}}"
从上图中,我们知道最多只能三条线相连的话,不管怎么变化,无非就那么几种情况(还有两个棋子连在一起的情况),只要我们一一检测一遍,就知道能否消除了,
首先,我们写一个检测一条直线是否能连通的函数,这个比较容易实现(因为一条线能连起来的话,不是x轴相同,就是y轴相同).
&screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75857&d=')}}"
[COLOR=&rgb(255,&140,&0)&]BOOL&Linkchess(POINT&p1,POINT&p2)
&&POINT&temp&=&{0};
&&int&i&=&0;
&&if&(p1.y&==&p2.y)&//如果两棋子同在x轴上
&&&&if&(p1.x&&&p2.x)//始终设左边的棋子为p1,右边的棋子为p2
&&&&&&temp&=&p1;
&&&&&&p1&=&p2;
&&&&&&p2&=&
&&&&for&(i&=&p1.x+1;i&&&p2.x;i++)&//p1.x+1为从左边棋子往右的一个棋子开始检测
&&&&&&if&(chessdata[p1.y][i]&!=&NULL)//如果线路中有一个格子不为0,那么此线路不通
&&&&&&&&return&FALSE;
&&if&(p1.x&==&p2.x)&//如果两棋子同在y轴上
&&&&if&(p1.y&&&p2.y)//始终设上面的棋子为p1,下面的棋子为p2
&&&&&&temp&=&p1;
&&&&&&p1&=&p2;
&&&&&&p2&=&
&&&&for&(i&=&p1.y+1;i&&&p2.y;i++)&//p1.y+1为从最顶的棋子往下面的一个棋子开始检测
&&&&&&if&(chessdata[i][p1.x]&!=&NULL)//如果线路中有一个格子不为0,那么此线路不通
&&&&&&&&return&FALSE;
&&return&TRUE;&&//整条线中都为0,表示能通
如果两棋子是相邻的情况,&Linkchess(p1,p2)函数中的for循环是不能够满足条件的,所以也会返回TRUE.
上面的Linkchess(p1,p2)函数检测了一条线路是否能连通,三条线路的情况,就调用三次Linkchess(p1,p2)函数,只要每次都返回TRUE的话,也就说明能连通;
[COLOR=&rgb(255,&140,&0)&]BOOL&checkchess(POINT&p1,POINT&p2)
&&POINT&A&=&{0};
&&POINT&B&=&{0};
&&int&i&=&0;
&&if&(p1.x&==&p2.x&||&p1.y&==&p2.y)&//如果y轴或者x轴相同,(第一种情况)
&&&&if&(Linkchess(p1,p2))&&&&&&&&&//检测一条线路
&&&&&&return&TRUE;
&&if&((chessdata[p1.y][p2.x]&==&NULL)&||&(chessdata[p2.y][p1.x]&==&NULL))//如果x轴与y轴相交的两个点有一个为0(第二种情况)
&&&&A.x&=&p1.x;
&&&&A.y&=&p2.y;&&//交点赋值于临时变量A
&&&&B.x&=&p2.x;
&&&&B.y&=&p1.y;&&//另一个交点赋值于临时变量B
&&&&if&((Linkchess(p1,A)&&&&Linkchess(p2,A)&&&&(chessdata[p2.y][p1.x]&==&NULL))&//检测两条线路,交点必须为0
&&&&&&||&(Linkchess(p1,B)&&&&Linkchess(p2,B)&&&&(chessdata[p1.y][p2.x]&==&NULL)))
&&&&&&return&TRUE;
&&if&(p1.x&!=&p2.x)&&//两棋子不在同一y轴上(第三种情况纵向检测)
&&&&A&=&p1;
&&&&B&=&p2;
&&&&for&(i&=&0;i&&&11;i++)
&&&&&&A.y&=&B.y&=&i;
&&&&&&if&((chessdata[A.y][A.x]&==&NULL)&&&&(chessdata[B.y][B.x]&==&NULL)&
&&&&&&&&&&&Linkchess(p1,A)&&&&Linkchess(p2,B)&&&&Linkchess(A,B))&//纵向循环检测三条线路,两交点也必须为0
&&&&&&&&return&TRUE;
&&if&(p1.y&!=&p2.y)&//两棋子不在同一x轴上(第三种情况横向检测)
&&&&A&=&p1;
&&&&B&=&p2;
&&&&for&(i&=&0;i&&&19;i++)
&&&&&&A.x&=&B.x&=&i;
&&&&&&if&((chessdata[A.y][A.x]&==&NULL)&&&&(chessdata[B.y][B.x]&==&NULL)&
&&&&&&&&&&&Linkchess(p1,A)&&&&Linkchess(p2,B)&&&&Linkchess(A,B))&//横向循环检测三条线路,两交点必须为0
&&&&&&&&return&TRUE;
&&return&FALSE;
在checkchess(p1,p2)函数中,每一种路线的情况用一个if语句表示(不算嵌套的),
i.&&对于用一条线路就能连接的情况,两棋子肯定是在同一直线上的,&
if&(p1.x&==&p2.x&||&p1.y&==&p2.y)只要y轴相同或者x轴相同,就调用Linkchess(p1,p2)检测路线,能连通返回TRUE.
ii.&&用两条线路的情况,就是p1与p2的x轴跟y轴的两个交点,这两个交点是固定且是可以算出来的,所以检测这两组形成交点的直线是否能连通,以及交点本身是否为0;
if&((chessdata[p1.y][p2.x]&==&NULL)&||&(chessdata[p2.y][p1.x]&==&NULL)),只要有一个为0,再检测线路是否能连通.
if&((Linkchess(p1,A)&&&&Linkchess(p2,A)&&&&(chessdata[p2.y][p1.x]&==&NULL))&
&&&&&&||&(Linkchess(p1,B)&&&&Linkchess(p2,B)&&&&(chessdata[p1.y][p2.x]&==&NULL)))两组路线,只要有一组能连通以及两线的交点为0.返回TRUE.
iii.&&三条线路的话,就有两个交点,还分横向,跟纵向,而且三条线路中,有一条是不确定的,所以用个for循环来检测,按照棋盘数组,纵向循环11次,横向循环19次,检测两个交点是否为0,以及三条线路是否能通,满足这5个条件返回TRUE.
&&for&(i&=&0;i&&&11;i++)
&&&&&&A.y&=&B.y&=&i;
&&&&&&if&((chessdata[A.y][A.x]&==&NULL)&&&&(chessdata[B.y][B.x]&==&NULL)&&&&Linkchess(p1,A)&&&&Linkchess(p2,B)&&&&Linkchess(A,B)).
5.&&能消除的一对棋子已经找出来了,那么我们下面开始实现模拟鼠标点击,消去一对棋子,现在我们要找出棋盘数组相对于窗口的坐标:
这个我们也用Spy++,也很容易能够实现:&这次我们用Spy++查找窗口消息,选定Message(消息),用鼠标按住Finder&Tool(查找工具)的图标,把它拖动到连连看的游戏窗口中,点OK.如下图:
&screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75857&d=')}}"
此时,我们会看到不断地截取到很多消息.如下图:
&screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75858&d=')}}"
然后点击暂停图标,停止获取消息,再点击删除图标,删除掉这些消息,最后点击选项图标,在弹出来的对话框的Messages版块中,点击Clear&All(删除全部)删除所有的消息,如下图:
&screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75859&d=')}}"
然后选中鼠标左键按下与鼠标左键抬起的两个消息,点击OK.如下图:
&screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75860&d=')}}"
然后再点击一下开始截取消息图标:如下图
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75861&d=')}}"
现在把鼠标光标移动到游戏棋盘的第一格(左上角),点击一下鼠标左键,如下图:
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75862&d=')}}"
现在可以看到了两个消息,(一个按下,一个抬起).如下图:
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75863&d=')}}"
图中两个消息表示在相对窗口坐标的x&=&23,y&=&194的位置按下跟抬起了一次鼠标左键,就是说明棋盘第一个棋子相对于窗口的位置是x&=&23,y&=&194.再双击一下两个消息中的一个,就可以看到LParam参数,如下图:
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75864&d=')}}"
LParam&=&0x00C20017是一个32位的值,其实这个值的高16位0x00C2就是十进制的194,那低16位0x0017对应的十进制就是23了;现在只要再知道棋子的高跟宽就能算出棋盘数组中每一个棋子的坐标了,求棋子的宽度与高度,我们用最直接的方法,就是截取一张整个棋盘的图片(大小跟棋盘偏差不要太大),然后用一个图片编辑工具打开(我用的是Windows自带的图片编辑器),就能看到该图片的宽度与高度,然后宽/11,高/19,用四舍五入取整数的商,就是一个棋子的宽与高了,看下图:
&screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75865&d=')}}"
上图中,棋盘的宽为594,高为388.
那么594/19≈31&&&&&388/11≈35
参数LParam就等于(y&&16)&+&x.
那么,如果棋子p1的坐标是x&=&2,y&=&3的话,LParam就表示为(35*3&&16)&+&31*2;还要注意的是这个坐标是相对于棋盘第一格的坐标的,要表示相对窗口的坐标的话,还要加上第一格的坐标,LParam&=&((35*p1.y&+&194)&&16)+(31*p1.x&+&23)这才是表示棋子相对窗口的坐标,有了这些参数,我们就可以编写代码实现消去一对棋子了,
[COLOR=&rgb(255,&140,&0)&]BOOL&click2p(POINT&A,POINT&B)
&&int&x&=&23;
&&int&y&=&193;&&//第一格棋子坐标
&&int&LPARAM&=&((y+35*A.y)&&16)+x+31*A.x;
&&//&TODO:&Add&your&control&notification&handler&code&here
&&HWND&hwnd&=&FindWindow(NULL,GEAM_CAPTION);
&&if&(hwnd&==&NULL)
&&&&return&FALSE;
&&SendMessage(hwnd,WM_LBUTTONDOWN,0,LPARAM);
&&SendMessage(hwnd,WM_LBUTTONUP,0,LPARAM);
&&LPARAM&=&((y+35*B.y)&&16)+x+31*B.x;
&&SendMessage(hwnd,WM_LBUTTONDOWN,0,LPARAM);
&&SendMessage(hwnd,WM_LBUTTONUP,0,LPARAM);
&&return&TRUE;
6.&&现在双击”单消”按钮,去到实现单消的成员函数,在这函数里,只要调用ClearPiar()函数即可,下图:
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75866&d=')}}"
当然还要把头文件夹GeamProcess.h包含进去才能调用,下图:
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75867&d=')}}"
现在单消的功能已经能够实现了
六、&&修补与完善外挂
1.&&既然已经实现了单消,那么实现全消也不是什么问题了,只要循环调用单消就行了,当然这个循环肯定要有个退出的条件,可以用剩余的棋子数作为退出的循环的条件,如图:
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75868&d=')}}"
在棋盘下方的剩余方块的这个地方,就是表示棋盘上的棋子数,只要找出这个地址,然后读出这个地址的数据,我们还是用CE工具来查找,这个很容易找到,因为它是个可见的值,一直用精确值来查找就行了.找到之后就是编写代码读出数据,这跟之前读出棋盘数据差不多.只要把那段代码稍微改一下就好.
#define&ChessNumAddre&(LPVOID)0x001159FC&&//棋子数基址
void&UpChessNum()
&&DWORD&ProcID;
&&HANDLE&ProcH
&&LPCVOID&BackS
&&HWND&hwnd&=&FindWindow(NULL,&GEAM_CAPTION);
&&if&(hwnd&==&NULL)
&&GetWindowThreadProcessId(hwnd,&ProcID);
&&ProcHwnd&=&OpenProcess(PROCESS_ALL_ACCESS,FALSE,ProcID);
&&ReadProcessMemory(ProcHwnd,ChessNumAddre,&chessnumber,1,(LPDWORD)&BackSize);
&&CloseHandle(ProcHwnd);
上面的UpChessNum()函数跟之前的UpdateChess()函数几乎是一样的,只改动了几个地方(读取进程的地址、用来存放进程内容的变量、还有读取的大小).
我们把这个函数插入到实现消去棋子的click2p(POINT&A,POINT&B)函数里面去,(注意:&UpChessNum()函数要定义在click2p(POINT&A,POINT&B)函数的前面)
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75868&d=')}}"
这样只要每消去一对棋子,棋子数就会更新.现在可以编写”全消”按钮的成员函数代码了(双击”全消”按钮就可以去到该函数的定义).
[COLOR=&rgb(255,&140,&0)&]void&CQQllkWgDlg::OnExinction()&
&&//&TODO:&Add&your&control&notification&handler&code&here
&&UpChessNum();//更新棋子数
&&while(chessnumber)
&&&&ClearPiar();
函数中,一开始先更新棋子数,然后再进入while循环.
2.screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75869&d=')}}"
如上图,在游戏的过程中,还会碰到无解的情况,如果点击了”全消”按钮,在这种情况下,while循环就是一个死循环,程序会崩溃掉.解决这个问题,我们可以在游戏窗口中,点击重列的道具,我们也可以自动的模拟鼠标点击重列.首先用Spy++获取到重列图标相对窗口的坐标,(获取的方法,跟前面获取棋盘数组第一格的坐标相同,这里就不再叙述了)然而这个重列道具是有限的,还要用CE工具找到该地址(这个重列道具数也是可见的,它的地址也很容易找得到),然后读出重列道具数.
[COLOR=&rgb(255,&140,&0)&]
&&&&#define&RESTATED_NUM&(LPVOID)0x&&&//重列地址
&&&&LPVOID&
VOID&ReadRestated()
&&DWORD&ProcID;
&&DWORD&BackS
&&HWND&hwnd&=&FindWindow(NULL,GEAM_CAPTION);
&&if&(hwnd&!=&NULL)
&&&&GetWindowThreadProcessId(hwnd,&ProcID);
&&&&HANDLE&ProcH&=&OpenProcess(PROCESS_ALL_ACCESS,FALSE,ProcID);
ReadProcessMemory(ProcH,RESTATED_NUM,&restatednumber,1,&BackSize);
&&&&CloseHandle(ProcH);
&&执行ReadRestated()函数就会在restatednumber返回重列道具数,
&&再编写代码模拟鼠标点击重列图标.
VOID&ReChess()&&//重列棋盘
&&LPARAM&LPARAM&=&RESTATED_COORDINATE;
&&HWND&hwnd&=&FindWindow(NULL,GEAM_CAPTION);
&&if&(hwnd&!=&NULL)
&&&&SendMessage(hwnd,WM_LBUTTONDOWN,0,LPARAM);
&&&&SendMessage(hwnd,WM_LBUTTONUP,0,LPARAM);
现在代码编写好了,要怎么样才能自动调用呢?&ClearPiar()函数是实现消去一个棋子,它还是一个布尔类型的函数,当它的返回值是true时,表示当返回值是false时就表示没有消去棋子,返回false的情况只有两种,一是整个棋盘的棋子都消去了,没有棋子了,二是无解.那么我们就在ClearPiar()函数返回false的语句的前面,插入ReChess()函数,当然,不能直接插入,还要加一些判断.
&&[COLOR=&rgb(255,&140,&0)&]ReadRestated();&&//读出重列道具数
&&if&(chessnumber&&&&restatednumber)//如果棋子数与重列道具都不为零
&&&&ReChess();
&&}[/COLOR]
在ClearPiara()函数中插入以上代码,就可以实现自动重列了,如下图:
&screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75871&d=')}}"
3.&&现在再加上挂机速度跟自动挂机的功能,先把滑块的属性设置好,右键滑块---&Properties(属性)----&Styles(样式)---&钩上Tick&marks(刻度标记)跟Enable&selection(启用选择)还有Auto&ticks(自动刻度)那个Point(点)的选项,随个人爱好选择,如下图:
&screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75872&d=')}}"
然后再给滑块关联一个控件,使用控件调用类成员函数设置滑块的最小值跟最大值,以及初始值,还有刻度值.右键滑块---&ClassWizard(类向导)---&Member&Variables(成员变量)---&双击滑块ID&(IDC_SLIDER1),给滑块添加一个控件类型的变量.如下图:
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75873&d=')}}"
在Category(类别)中选择Control(控件),写入变量名.点OK,如下图:
&screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75874&d=')}}"
在以同样的方法添加一个数值类型的变量,如下图:
screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75875&d=')}}"
现在去初始化对话框的类中,添加设置控件信息的代码,在对话框空白处右键---&Events(事件)---&双击右边的初始化对话框消息,就能去到初始化对话框的成员函数,好下图:
&screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75876&d=')}}"
在函数的尾部,返回值之前,添加上代码.
&&[COLOR=&rgb(255,&140,&0)&]m_Speed_Slider.SetRange(30,2000);//设置滑块范围
&&m_Speed_Slider.SetPos(800);&//设置初始位置
&&m_Speed_Slider.SetTicFreq(100);&//设置移动频率[/COLOR]
以上三个函数,看字面意思就能大概明白意思.添加位置如下图:
&screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75877&d=')}}"
滑块的基本信息设置好了,再给它添加一个编辑框,用来显示出当前滑块的数值(我给该编辑框关联了一个int型的变量m_SheepNum).然后再给滑块关联一个NM_RELEASEDCAPTURER&的消息响应(为什么是NM_RELEASEDCAPTURER消息,我也不是很清楚,找了一些资料,只朦胧的知道它能捕获鼠标抬起的消息,)右键滑块---&Events(事件)---&双击NM_RELEASEDCAPTURER---&OK.如下图:
&screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75878&d=')}}"
再双击右边已经关联的NM_RELEASEDCAPTURER事件去到该成员函数的实现.如下图:
&screen.width*0.6) {this.width=screen.width*0.6;this.alt='';this.onmouseover=this.style.cursor='pointer'; this.onclick=function(){window.open('/attachment.php?s=b4f5e2aab493b12d125eec3&attachmentid=75879&d=')}}"
[COLOR=&rgb(255,&140,&0)&]void&CQQllkWgDlg::OnReleasedcaptureSlider1(NMHDR*&pNMHDR,&LRESULT*&pResult)&
&&//&TODO:&Add&your&control&notification&handler&code&here
&&UpdateData(TRUE);//更新控件的值到变量
&&m_SheepNum&=&m_SliderV
//m_SheepNum&=&m_Speed_Slider.GetPos();//此语句跟上面语句等价
&&UpdateData(FALSE);//更新变量到编辑框
&&*pResult&=&0;
以上代码:当在滑块触发了鼠标抬起消息,用UpdateData(TRUE)函数更新滑块的数值到变量m_SliderVar中,然后再赋值给m_SheepNum,最后再用UpdateData(FALSE)函数把m_SheepNum的值更新到编辑框.
这样我们就能在编辑框上看到滑块的数值了(当移滑块的时候,是鼠标抬起了,才更新数值).下面再来实现把挂机速度跟滑块关联想来,当钩上”挂机速度”的时候,才能够移动滑块,如果不钩上”挂机速度”就不能移动滑块.
4.&&给”自动挂机”复选框关联一个BOOL的变量m_GeamSpeed,然后双击复选框,去到成员函数的实现.
[COLOR=&rgb(255,&140,&0)&]void&CQQllkWgDlg::OnCheck2()&
&&//&TODO:&Add&your&control&notification&handler&code&here
&&UpdateData(TRUE);
&&::EnableWindow(m_Speed_Slider.m_hWnd,m_GeamSpeed);&//该函数允许/禁止指定的窗口或控件接受鼠标和键盘的输入.
这现在再编译的话,就能用m_GeamSpeed变量的值控制滑块是否接收消息,还有一点没完整,就是一开始的时候,&m_GeamSpeed默认是没钩上的,此时滑块还能接收鼠标的消息,这是因为这时没有触发调用void&CQQllkWgDlg::OnCheck2()函数的消息,所以没起到理想的效果.解决问题很简单,只要把m_GeamSpeed变量初始化的值改为true就行了.右键点击m_GeamSpeed变量---&Go&To&Reference&to&m_GeamSpeed(转到参考),可以看到m_GeamSpeed变量初始化为false,把它改成true好了.如下图:
5.&&实现自动挂机.同样的先给”自动挂机”复选框关联一个BOOL类型的变量(m_AutoGame),去到成员函数的实现.
[COLOR=&rgb(255,&140,&0)&]void&CALLBACK&PlayProc(
&&&&&&&&&&&&HWND&hwnd,&
&&&&&&&&&&&&UINT&uMsg,&
&&&&&&&&&&&&UINT&idEvent,&
&&&&&&&&&&&&DWORD&dwTime&
&&&&&&&&&&&&)//定义一个回调函数,
&&ClearPiar();
CONST&AUTOTIME&=&111;//自动挂机定时器(全局变量)
void&CQQllkWgDlg::OnCheck1()&//自动挂机成员函数
&&//&TODO:&Add&your&control&notification&handler&code&here
&&UpdateData(TRUE);&&//更新数据到变量
&&if&(m_AutoGame)
&&&&SetTimer(AUTOTIME,m_SliderVar,&PlayProc);//创建一个定时器
&&&&KillTimer(AUTOTIME);
以上代码先定义一个回调函数(实现单消功能),然后在void&CQQllkWgDlg::OnCheck1()成员函数中建一个定时器,每隔一定的时间(m_SliderVar)就调用回调函数,从而实现自动挂机的功能.随着滑块的变动,&m_SliderVar变量也跟着变动,这样挂机的速度也改变了,其实结果并不像我想的一样,测试的时候发现,当使用自动挂机时,滑动滑块,挂机速度并没有跟着改变,而是要去掉自动挂机重新钩上的时候速度才跟着改变,还要把代码改进一下.在滑块的成员函数中,我们添加一些代码.如下图:
当滑动滑块时,先关掉挂机定时器,然后再判断自动挂机变量,如果钩上了才打开.这样可以说比较完美的实现了自动挂机功能.
6.&&现在再添加上去除游戏倒计时时间,用CE工具找出游戏倒计时地址(开始用大于0扫描,然后它是一直在减小的,所以一直用减少了的值扫描,直到时间跑完了,就用精确值为0扫描),然后再建立一个定时器,不断地把新时间写入到该地址(之前在我看的资料中,是用CE工具找到写入该地址的代码,然后把该代码去掉,这样我测试的时候,的确能实现,但是新版的qq游戏中,只要把这代码改了,就马上弹出警告,然后退出游戏,我一直也没找到解决这个问题的方法,如果有高人知道,希望能指点一二).这样游戏时间就不会减少了,再添加一个”去掉倒计时”的复选框,同时关联一个BOOL类型的变量m_ClearTime.
HANDLE&ProcH;
#define&COUNTDOWNTIME&(LPVOID)0x&&//倒计时地址
CONST&CLEARTIME&=&112;//去除倒计时定时器
[COLOR=&rgb(255,&140,&0)&]void&CALLBACK&CTProc(
&&&&&&&&&&&&&HWND&hwnd,&
&&&&&&&&&&&&&UINT&uMsg,&
&&&&&&&&&&&&&UINT&idEvent,&
&&&&&&&&&&&&&DWORD&dwTime&
&&&&&&&&&&&&)
&&DWORD&BackS
&&DWORD&TimeNum&=&750;//量大时间值
&&WriteProcessMemory(ProcH,COUNTDOWNTIME,&TimeNum,4,&BackSize);写入时间
void&CQQllkWgDlg::OnCheck3()&
&&//&TODO:&Add&your&control&notification&handler&code&here
&&UpdateData(TRUE);
&&DWORD&ProcID;
&&HWND&hwnd&=&::FindWindow(NULL,GEAM_CAPTION);
&&if&(hwnd&!=&NULL)
&&&&GetWindowThreadProcessId(hwnd,&ProcID);
&&&&ProcH&=&OpenProcess(PROCESS_ALL_ACCESS,FALSE,ProcID);
&&if&(m_ClearTime)
&&&&SetTimer(CLEARTIME,100,&CTProc);
&&&&CloseHandle(ProcH);
&&&&KillTimer(CLEARTIME);
上面代码也是前面已经用过的,只是稍微改动一点,首先定义一个回调函数(实现写入时间750),后面就是打开窗口跟进程之类的,然后是判断复选框变量选择性的创建定时器,定时器是每隔100毫秒就调用一次回调函数.
7.&&再添加一个自动开始游戏的功能,添加一个”自动开始”的复选框,同时关联一个BOOL类型的变量用Spy++工具取得游戏中”开始”按键的坐标,然后编写代码模拟鼠标点击开始.
CONST&AUTOPLAY&=&113;&//自动开局定时器
[COLOR=&rgb(255,&140,&0)&]void&CALLBACK&GamePlay(
&&&&&&&&&&&HWND&hwnd,&
&&&&&&&&&&&UINT&uMsg,&
&&&&&&&&&&&UINT&idEvent,&
&&&&&&&&&&&DWORD&dwTime&
&&&&&&&&&&&&)
&&LPARAM&LPARAM&=&GAMEPLAY;
&&hwnd&=&FindWindow(NULL,GEAM_CAPTION);
&&if&(hwnd&!=&NULL)
&&&&PostMessage(hwnd,WM_LBUTTONDOWN,0,LPARAM);
&&&&PostMessage(hwnd,WM_LBUTTONUP,0,LPARAM);
void&CQQllkWgDlg::OnCheck4()&
&&//&TODO:&Add&your&control&notification&handler&code&here
&&UpdateData(TRUE);&&//更新数据到变量,
&&if&(m_AutoPlay&&&&(chessnumber&==&NULL))
&&&&SetTimer(AUTOPLAY,1000,&GamePlay);
&&&&KillTimer(AUTOPLAY);
定义一个回调函数(实现模拟鼠标点击”开始”),值得注意的是这时候我是用PostMessage函数来实现模拟鼠标,而不是像自动重列一样使用SendMessage函数,这是因为使用SendMessage函数的时候,不能实现模拟点击(可能是QQ游戏已经在那个地方屏蔽掉SendMessage的消息了),改用PostMessage函数的时候就没有问题了.当钩上了自动开局棋子数为0,就创建一个时间段为1000毫秒的定定时器.*转载请注明来自看雪论坛@
上传的图像
被 kanxue 最后编辑
共 18 位会员感谢 caigui 发表的文章:
&(), &(), &(), &(), &(), &(), &(), &(), &(), &(), &(), &(), &(), &(), &(), &(), &(), &()
注册日期: Jan 2012
现金: 100 Kx
获感谢文章数:2获会员感谢数:19
, 01:09:51
注册日期: May 2009
现金: 3487 Kx
获感谢文章数:3获会员感谢数:4
, 05:35:57
Very&Good&!
Thanks&for&sharing&!
注册日期: May 2011
现金: 160 Kx
获感谢文章数:0获会员感谢数:0
, 08:27:23
很详细,很励志
对新人非常好
注册日期: Mar 2012
现金: 51 Kx
获感谢文章数:0获会员感谢数:0
, 09:16:26
有时间看看,留个脚印
注册日期: Dec 2010
现金: 26 Kx
获感谢文章数:0获会员感谢数:0
, 09:22:22
liuming学习了
注册日期: Jul 2009
现金: 181 Kx
获感谢文章数:0获会员感谢数:0
, 09:39:25
就是一个郁金香的框架
<img src="images/bar/stars08.gif" border="0" alt="『iOS安全』版主iOS小组(OSG)">
『iOS安全』版主iOS小组(OSG)
注册日期: Feb 2006
现金: 950 Kx
致谢数: 17
获感谢文章数:7获会员感谢数:26
, 09:59:36
不错。很详细,新手一眼就能明白。很多图片,很久才能打开。
被 zhuliang 最后编辑
注册日期: Dec 2010
现金: 44 Kx
获感谢文章数:0获会员感谢数:0
, 11:19:45
很详细。建议版主标记精华以鼓励新手。
注册日期: Nov 2011
现金: 180 Kx
获感谢文章数:2获会员感谢数:3
, 11:42:08
不错,顶个,有空再和连连看过不去
注册日期: Jan 2005
现金: 160 Kx
获感谢文章数:0获会员感谢数:0
, 12:01:46
不错,顶个很详细
注册日期: Jun 2010
现金: 32 Kx
获感谢文章数:0获会员感谢数:0
, 16:02:13
很详细,感谢分享
注册日期: Jun 2007
现金: 198 Kx
获感谢文章数:0获会员感谢数:0
, 09:53:30
看不到图~~可惜啦
注册日期: Oct 2012
现金: 41 Kx
获感谢文章数:0获会员感谢数:0
, 11:13:06
曾经写过一个,确实挺麻烦的
注册日期: Sep 2009
现金: 67 Kx
获感谢文章数:2获会员感谢数:2
, 13:01:14
很少有人寫得這麼詳細
该主题: "【原创】新人入手第一个游戏外挂,附上详细制作过程" 因在一定的时间里没有任何回复而自动关闭。如果您还对该主题感兴趣或者想参与对此主题的讨论,请您重新发表一篇相关的新主题。
您不可以发表主题
您不可以回复帖子
您不可以上传附件
您不可以编辑自己的帖子
论坛论坛启用
用户控制面板
会员在线状态
『求助问答』
『经典问答』
『资料导航』
『Android安全』
『iOS安全』
『软件逆向』
『编程技术』
『加壳脱壳』
『密码算法』
『资源下载』
『WEB安全』
『二进制漏洞』
『看雪众测』
『CrackMe』
『招聘专区』
『职业生涯』
『15PB培训』
『麦洛克菲培训』
『外文翻译』
『茶余饭后』
『安全资讯』
『论坛活动』
6)PEDIY Crackme竞赛2009
7)看雪十周年专版
8)腾讯公司2010软件安全竞赛
9)2011 Exploit Me竞赛
『图书项目版』
《加密与解密(第三版)》
《C++反汇编与逆向分析技术揭秘》
《Android软件安全与逆向分析》
『论坛版务』
所有时间均为北京时间, 现在的时间是 .
&&& 看雪学院()
| 提供带宽资源
|&微信公众帐号:

我要回帖

更多关于 新手纹绣师简介 的文章

 

随机推荐