只需要一点点C++基础,新手也可以制作单机游戏内存修改器
声明:本文只是为了初学C++的,能够做出一些实用的东西,跳出管理系统的束缚,提升学习的兴趣,在这里选取了单机游戏,请不要尝试在线游戏,违发而已未必可行。
序:首先我们需要一个Qt+VS环境
Qt从http://download.qt.io/archive/中下载,第一个和第三个,在里面选择对应版本。然后就是配环境了,这里提供2013+Qt5.5.1的环境配置,如果环境不同,请自行百度。这点解决问题能力都没有,就别学C++了...
我的环境是2013+Qt5.5.1,不同版本可能略有差异,不过大同小异。
首先打开VS创建工程,点确定,然后一直下一步
选择QWidget,点完成
然后就创建了一个工程,我们来说说这个工程。
一:main函数和C++的很像
然后再看看刚才创建的Qt窗口类
先是头文件
然后是CPP文件
再看看一些“奇怪”的文件
下面这两个是Qt的moc文件,是编译时自动生成和更新的,所以不用管
下面第一个是资源文件的代码CPP,自动生成的,不用管
第二个是UI文件的头文件,自动生成的,不用管
资源文件,用来加载图片等一些资源,这里没用到,不用管
这个就是上面提到的UI文件,相当于可视化界面设计器,用来设计界面的。
双击点开XXXXX.UI文件
二:接下来开始界面设计
在控件盒子中左键选中一个文件标签,一个文字输入框,一个按钮,然后往界面设计器里面拖。文件标签在左,文字输入框在中,一个按钮在右。
ctrl+鼠标左键点选三个控件,然后在任意一个控件上右键,选择布局->水平布局
开始界面布局
右键大窗口,选择布局->垂直布局
然后鼠标放到界面设计器的边框边缘,按住左键拖动到合适大小
在对象查看器里左键点选大窗口,然后属性窗口往下拖,在WindowTitle里修改窗口标题
双击控件修改控件的文本
记录控件的对应关系,把金钱技能和属性对应的输入框和按钮记录下来
在这里我金钱的输入框是lineEdit,金钱修改按钮是pushButton,技能点则分别是lineEdit_2和pushButton_2,属性点则是lineEdit_3和pushButton_3
然后点保存,注意一定要保存
三:实现前的知识普及
1:游戏内存修改的知识普及
一般游戏数据有一个地址值,但是这个地址值是动态的,每次游戏重启都会发生变化,所以我们要找到不变的一级基址,和两个不变的偏移量,来得到最新的游戏数据地址。
2:Qt信号槽知识普及
①Qt信号
信号是指一种通知,形象地比喻下:比如你带了许多巧克力去公司,然后在群里告诉大家,“我带了很多巧克力,要的来我工位拿”,这里公司群就是你的应用程序,群员就是程序里的实例化对象,你说的话这就是一种信号;可能有些人会无视,有些人根本没看见,有些人会来要,有些人会转告其他人,你只负责发出一个通知,你不关心别人看到你的通知会作何反应。
② Qt槽函数
槽指的是一种行为函数,定义了收到信号通知后,应该做出何种反应,上面巧克力的例子,无视,转告和要巧克力,都是一种对于信号通知的响应行为。
③ Qt的connect函数
就是对信号和槽进行关联,A发的信号通知B做出某种响应行为。
④ Qt的QTimer
定时器,按照你设置的时间间隔,不间断发出timeout()信号通知。
⑤ QMessageBox::information()
显示一个提示窗口
⑥ ui控件的指针怎么找
UI控件的指针和objectname同名,而objectname就是在界面设计器点选对应控件,属性里第一个
使用的时候用ui.objectname或者ui->objectname,用哪种取决于h文件里的ui变量是对象还是指针。这里就是ui.objectname。
四:基址和偏移量查找
①现在我们要用到一个软件,名字叫cheat engine,我的是6.6中文版。游戏以骑马与砍杀为例,首先修改金钱。
②把金钱数据输入ce,点击新的扫描
③想办法改变金钱数,输入CE,点击再次扫描,不断重复这条,直到数据只有一个
(注意:有可能会遇到一直有2个的情况,这样的情况试着改下数据就行了,哪个生效就是哪个)
④这里得到的就是一个游戏数据内存,可以改游戏数据值,但他是动态的,游戏重启就失效了,我们需要找的是基址。
⑤鼠标右键这个游戏数据地址,查找什么改变了这个值。
⑥然后出现这个界面,一开始是没有数据的,需要改变下游戏数据(这里是金钱数)
⑦双击这条数据,这里的5D0就是第一个偏移量,4B4C1024就是下一个要查找的地址。
⑧开启一个新的扫描
⑨选择需要的的地址查找是什么访问了这个地址,有时候有很多个,一般是比较特殊的那个(就是其他地址开头都是一样的,就他不一样),或者一个个看,有数据的就是我们需要的那个地址(注意无需改变游戏数据就有数据)
⑩随便双击一个mov指令数据,这里的140EC就是第二个偏移值,48D2E010就是下一个要查找的地址
①①用新拿到的推荐地址重复第⑧步,查找的绿色地址就是一级基址了
①②开始效验这个基址
①③ 修改这个地址的数值,如果钱发生变化的话就找对了
同理,用这个方法查找技能点
找出来的一级基址是009D5E2C,偏移是5D0 2BC,发现没有,一级基址和第二次偏移是一样的,所以之后查找,找一次偏移就可以了。
【特别注意】网上有些攻略说一级基址+第二次偏移量+第一次偏移量就是游戏数据地址,其实是错的,应该是一级基址里保存的值+第二次偏移量得到二级基址,二级基址里保存的值+第一次偏移量才是游戏数据地址。
五:代码实现部分,教程以注释展现
只实现了技能点和金钱
- #ifndef GAMEEDITOR_H
- #define GAMEEDITOR_H
- #include <QtWidgets/QWidget>
- #include "ui_gameeditor.h"
- //读写游戏内存所必须的头文件
- #include <windows.h>
- class QTimer;
- class GameEditor : public QWidget
- {
- Q_OBJECT
- public:
- GameEditor(QWidget *parent = 0);
- ~GameEditor();
- //slots就是表示槽函数
- protected slots:
- void connectGame();
- void updateGameMoney();
- void updateGamePerks();
- private:
- Ui::GameEditorClass ui;
- //计时器指针,见5-2-4
- QTimer* timer;
- //金钱地址
- DWORD moueyAdress;
- //技能点地址
- DWORD perksAdress;
- //进程PID
- DWORD pid;
- HWND hwnd;
- //进程句柄
- HANDLE handle;
- };
- #endif // GAMEEDITOR_H
- #include "gameeditor.h"
- //定时器头文件
- #include <QTimer>
- //提示框头文件
- #include <QMessageBox>
- GameEditor::GameEditor(QWidget *parent)
- : QWidget(parent)
- {
- ui.setupUi(this);
- pid = 0;
- hwnd = 0;
- handle = 0;
- //金钱一级基址
- moueyAdress = 0x009D5E2C;
- //技能点一级基址
- perksAdress = 0x009D5E2C;
- //创建一个定时器,见5-2-4
- timer = new QTimer;
- //设置时间间隔为1000毫秒
- timer->setInterval(1000);
- //timeout为计时器内置信号,时间一到自动发送
- //connect为关联信号槽,详细见前面的知识普及内容
- //connect(信号发送者指针,SIGNAL(信号), this, SLOT(槽实现函数));
- connect(timer, SIGNAL(timeout()), this, SLOT(connectGame()));
- //clicked为按钮内置信号,点击自动发送
- //还记得吗,ui.pushButton是金钱修改按钮,ui.pushButton_2是技能点修改按钮
- connect(ui.pushButton, SIGNAL(clicked()), this, SLOT(updateGameMoney()));
- connect(ui.pushButton_2, SIGNAL(clicked()), this, SLOT(updateGamePerks()));
- ui.label_4->setText(QString::fromLocal8Bit("正在等待游戏程序...."));
- //计时器开始计时
- timer->start();
- }
- GameEditor::~GameEditor()
- {
- }
- void GameEditor::connectGame()
- {
- //查找窗口并返回窗口句柄
- hwnd = FindWindow(0, L"Mount&Blade Warband");
- if (!hwnd)
- {
- return;
- }
- //通过窗口句柄获取pid
- GetWindowThreadProcessId(hwnd, &pid);
- if (!pid)
- {
- return;
- }
- //通过pid打开一个进程并获取进程句柄
- handle = OpenProcess(PROCESS_ALL_ACCESS, false, pid);
- if (!handle)
- {
- return;
- }
- //通过金钱一级基址读取里面的数据
- DWORD newMoneyAdress = 0;
- ReadProcessMemory(handle, (LPVOID)moueyAdress, &newMoneyAdress, sizeof(newMoneyAdress), 0);
- //读取不到说明你开了游戏,但是没有开始
- if (moueyAdress <= 0)
- {
- return;
- }
- ui.label_4->setText(QString::fromLocal8Bit("游戏程序连接成功!"));
- //停止计时器
- timer->stop();
- //金钱一级基址里的值+第二个偏移量=金钱二级基址
- moueyAdress = newMoneyAdress;
- moueyAdress += 0x140EC;
- //通过金钱二级基址读取里面的数据
- ReadProcessMemory(handle, (LPVOID)moueyAdress, &newMoneyAdress, sizeof(newMoneyAdress), 0);
- //金钱二级基址里的值+第一个偏移量=游戏数据地址(动态),游戏数据地址里的值就是游戏数据
- moueyAdress = newMoneyAdress;
- moueyAdress += 0x5D0;
- //同理通过技能点一级基址和偏移量获取游戏数据地址
- DWORD newPerksAdress = 0;
- ReadProcessMemory(handle, (LPVOID)perksAdress, &newPerksAdress, sizeof(newPerksAdress), 0);
- perksAdress = newPerksAdress;
- perksAdress += 0x140EC;
- ReadProcessMemory(handle, (LPVOID)perksAdress, &newPerksAdress, sizeof(newPerksAdress), 0);
- perksAdress = newPerksAdress;
- perksAdress += 0x2BC;
- }
- //金钱的修改按钮被点击
- void GameEditor::updateGameMoney()
- {
- //还记得吗,ui.lineEdit就是金钱的文本输入框,text()表示获取输入框的文本,toInt()表示转化为int数据
- DWORD money = ui.lineEdit->text().toInt();
- //修改游戏数据地址里的值
- DWORD pref=WriteProcessMemory(handle, (LPVOID)moueyAdress, &money, sizeof(money), 0);
- //QString是Qt里的字符串
- QString informationStr;
- if (pref)
- {
- informationStr = QString::fromLocal8Bit("修改成功");
- }
- else
- {
- informationStr = QString::fromLocal8Bit("修改失败");
- }
- //信息提示框,第一个参数是父窗口,填写this或者0都可以,第二个参数是标题,第三个参数是提示内容
- QMessageBox::information(this, QString::fromLocal8Bit("提示"), informationStr);
- }
- //技能点的修改按钮被点击
- void GameEditor::updateGamePerks()
- {
- DWORD perks = ui.lineEdit_2->text().toInt();
- DWORD pref = WriteProcessMemory(handle, (LPVOID)perksAdress, &perks, sizeof(perks), 0);
- QString informationStr;
- if (pref)
- {
- informationStr = QString::fromLocal8Bit("修改成功");
- }
- else
- {
- informationStr = QString::fromLocal8Bit("修改失败");
- }
- //信息提示框,第一个参数是父窗口,填写this或者0都可以,第二个参数是标题,第三个参数是提示内容
- QMessageBox::information(this, QString::fromLocal8Bit("提示"), informationStr);
- }
只需要一点点C++基础,新手也可以制作单机游戏内存修改器的更多相关文章
- Android基础新手教程——3.1 基于监听的事件处理机制
Android基础新手教程--3.1.1 基于监听的事件处理机制 标签(空格分隔): Android基础新手教程 本节引言: 第二章我们学习的是Android的UI控件,我们能够利用这些控件构成一个精 ...
- Android基础新手教程——1.5.2 Git之使用GitHub搭建远程仓库
Android基础新手教程--1.5.2 Git之使用GitHub搭建远程仓库 标签(空格分隔): Android基础新手教程 本节引言: 在上一节中.我们学习了怎样使用Git.构建我们的本地仓库.轻 ...
- Android基础新手教程——3.8 Gestures(手势)
Android基础新手教程--3.8 Gesture(手势) 标签(空格分隔): Android基础新手教程 本节引言: 周六不歇息,刚剪完了个大平头回来.继续码字~ 好的,本节给大家带来点的是第三章 ...
- Android基础新手教程——1.10 反编译APK获代替码&资源
Android基础新手教程--1.10 反编译APK获代替码&资源 标签(空格分隔): Android基础新手教程 本节引言: "反编译Apk".看上去好像好像非常高端的样 ...
- Android基础新手教程——1.6 .9(九妹)图片怎么玩
Android基础新手教程--1.6 .9(九妹)图片怎么玩 标签(空格分隔): Android基础新手教程 1.本节引言: 可能有的一些疑问: 1.什么是.9图片? 答:图片后缀名前有.9的图片,如 ...
- Android基础新手教程——4.1.3 Activity登堂入室
Android基础新手教程--4.1.3 Activity登堂入室 标签(空格分隔): Android基础新手教程 本节引言: 好的,在学习了两节的Activity后相信大家已经知道怎样去使用Acti ...
- Android基础新手教程——4.1.2 Activity初窥门径
Android基础新手教程--4.1.2 Activity初窥门径 标签(空格分隔): Android基础新手教程 本节引言: 上一节中我们对Activity一些主要的概念进行了了解,什么是Activ ...
- Android基础新手教程——4.4.1 ContentProvider初探
Android基础新手教程--4.4.1 ContentProvider初探 标签(空格分隔): Android基础新手教程 本节引言: 本节给大家带来的是Android四大组件中的最后一个--Con ...
- Android基础新手教程——3.7 AnsyncTask异步任务
Android基础新手教程--3.7 AnsyncTask异步任务 标签(空格分隔): Android基础新手教程 本节引言: 本节给大家带来的是Android给我们提供的一个轻量级的用于处理异步任务 ...
随机推荐
- 自己动手写一个自动登录脚本gg
1.下载一个sshpass工具 2.安装sshpass,安装到tools文件夹 3.把tools文件夹的路径加入到/etc/bashrc vim /etc/bashrc 最后一行 : expor ...
- asp.net core 中灵活的配置方式
asp.net core支持外部文件和命令行参数方式来配置系统运行所需要的配置信息,我们从下面两个常用场景来具体说下具体使用方法. 一.监听地址及端口配置 1,命令行方式 asp.net core系统 ...
- Python 操作 MYSQL
本文介绍了 Python 操作 MYSQL.执行 SQL 语句.获取结果集.遍历结果集.取得某个字 段.获取表字段名.将图片插入数据库.执行事务等各种代码实例和详细介绍,代码居多, 是一桌丰盛唯美的代 ...
- 简单地总结几种常见web攻击手段及其防御方式
web攻击手段有几种,本文简单介绍几种常见的攻击手段及其防御方式 XSS(跨站脚本攻击) CSRF(跨站请求伪造) SQL注入 DDOS XSS 概念 全称是跨站脚本攻击(Cross Site Scr ...
- js实现两个输入框中的数字相乘并自动将结果显示在第三个输入框
<script type="text/javascript"> function cal(ida,idb,idc) { var numa=Number(document ...
- (转)ManyToMany注解
@ManyToMany 注释:表示此类是多对多关系的一边,mappedBy 属性定义了此类为双向关系的维护端,注意:mappedBy 属性的值为此关系的另一端的属性名. 例如,在Student类中有 ...
- Django学习(八)---修改文章和添加文章
博客页面的修改文章和添加新文章 从主页点击不同文章的超链接进入文章页面,就是传递了一个id作为参数,然后后台代码根据这个参数从数据库中取出来对应的文章,并把它传递到前端页面 修改文章和添加新文章,是要 ...
- Linux常用命令及shell技巧
这里列出一些个人在工作中常使用的各种linux命令,每一个不详细讲参数,只写经常用的参数.希望快速获得在linux命令行工作的能力的朋友可以看看.本人一直觉的,不使用linux 图形界面,以xshel ...
- 【学习笔记】C# 封装和继承
封装 封装是实现面向对象程序设计的第一步 封装就是将数据.方法等集合在一个个单元中,我们称之为类 封装的意义在于保护代码/数据,屏蔽复杂性 继承 继承是所有面向对象语言不可缺少的部分 继承是为了实现类 ...
- nyoj_61: 传纸条(一)
题目链接 使用双线dp,假设两个人同时从左上角移动到右下角,且满足路线不交叉,另k=x1+y1=x2+y2压缩状态进行优化.每次状态转移满足 x1,x2,y1,y2都在矩阵范围内,且(x2,y2)在相 ...