基于C++ Qt实现的红色警戒3修改器
前言
这部修改器制作有一段时间了,但是一直没出教程。今天利用周末空闲写篇教程,给后来者指路的同时也加深自己对游戏修改器的理解,大佬就随便看看吧
浏览了一下网络,形形色色的单机游戏修改器教程,但是基本只实现了一到两个功能,GUI图形界面也没有。网站上能下载到的实现很多功能的修改器却又不开源,对新手不够友好
为什么选择红警3而不是其他游戏呢?
其一,它是单机游戏,制作网络游戏修改器(外挂)是违法的,根据《计算机信息网络国际联网安全保护管理办法》第六条规定:“任何单位和个人不得从事下列危害计算机信息网络安全的活动",尤其不能制作网游外挂并拿它去盈利
我不知道做网游外挂开源算不算违法,总之,与违法沾边的事我们别去触碰
其二,是一种情结,我玩的第一部真正意义上的游戏是红色警戒2尤里的复仇(扫雷,三维弹球不算),那时候是2002年,我在上一年级,接触到这种RTSG游戏是爱不释手,从那时起,我就想成为一名游戏开发工程师,然而现在并不是
其三,画面还可以,本来想做红警2外挂的,奈何画面太老,观赏性差
其四,难度适中,网上有很多外挂入门教程是按照植物大战僵尸这款游戏制作的,难度过于入门,基址偏移量太少,偏移一般是直接偏移,只能说是小游戏,稍微大点的单机游戏,基址偏移次数可能会超过10次,比如在红色警戒3中,基址偏移次数最多达到9次,并且偏移量有坑等我们踩
最后一点,红警3在单机游戏中具有很强的代表性,只要学会了制作该外挂,其他单机游戏外挂原理是一样的
答疑解惑:
Q:这是脚本吗?
A:不是,这是通过修改内存,改变指定地址操作数实现的修改器,通俗地说,可以直接改变游戏数据。我写过简单的回合制脚本,请参考这篇博文
Q:这种外挂可以在红警3联机的时候使用吗?
A:不行,仅限于单人模式
Q:这款外挂支持的红警3版本
A:红色警戒3原版Version 1.00
已完成的GUI(基于C++Qt5.7)如下,支持中英德三种语言;同时,我为萌新准备了C++实现的控制台版,不需要了解Qt即可实现本教程外挂的功能
开发环境
C++11
Qt 5.7 mingw53_32(控制台版不需要)
工具
QtCreator:制作Qt图形界面所需的开发工具,支持C++库
Visual Studio(版本最好2010以后):为控制台版而准备
Cheat Engine:寻找游戏基址所使用的工具,找基址的过程是枯燥乏味的,不用担心,我们有现成的基址大全
红色警戒3原版V1.00:逗游上可以下载
汇编知识准备
我只讲解制作该外挂过程中需要用到的汇编知识,不展开叙述。扩展知识园友可以自己去了解下
汇编语言MOV指令:
基本传送指令,Movement,把源操作数传送到目的操作数中
如MOV EAX 1000H,将十六进制数1000H传送给EAX(累加器)
寻址方式:
说明操作数所在地址的方法,有若干种寻址方式,不展开叙述,我们主要用到寄存器相对寻址
寄存器相对寻址:
有效地址 = 基址+变址+位移量
操作数的有效地址为基址寄存器(EBX)或变址寄存器(ESI或者EDI)的内容和指令中指定的位移量之和
如MOV ESI,[EDI + 00000768]
游戏基址:
也叫作基地址,顾名思义就可以理解为基本地址,他是相对偏移量的计算基准
在实模式下,通常都是以段+偏移来定位地址,因此说,这时,段地址是基地址的一种
"----->"表示"指针指向"
基址(存放的内容是一级基址起始地址)——>一级基址(存放的内容是二级基址的起始地址:假定为a)
[一级基址(a) + 偏移量]------>二级基址(存放的内容是三级基址的起始地址:假定为b);
[二级基址(b)+偏移量]-------->三级基址
······
n级基址-------->游戏界面
自己制作游戏修改器必须要找到一级基址
Cheat Engine的思路是根据偏移量从n级基址逆向找到一级基址
寻找基址
右键图标属性,添加“ -win -xres 1024 -yres 768”参数,1024 768是分辨率,自由指定你的分辨率,窗口启动红警3,方便调试
不知为何,我这台电脑窗口运行红警3写入内存的时候时常崩溃,所以我用的全屏启动
打开Cheat Engine,打开游戏进程
进入游戏
以查找金钱基址为例
搜索金钱数字10000,点击First Scan
搜索到一系列值,此时改变游戏金钱,建造建筑或单位
输入改变后的金钱9200,再点击Next Scan,可以看到只有四个地址,缩小了范围
修改它们的Value,看哪个是真的地址,结果第三个是真的(不一定都是第三个,每次搜索都不一样,切勿教条)
游戏金钱发生了变化
对下方选中的地址,按右键选中Find out what writes to this address
出现Confirmation,选yes
再次改变游戏的金钱,监测到MOV指令
双击进入MOV指令,其他操作一概不看,只看标红字的MOV指令
我们来分析下这些内容
EAX=(0001675B)16 = (91995)10
EAX的值就是游戏中的金钱,是所谓的操作数
MOV[ESI+04],EAX
将EAX的值传送到以"ESI+04"为地址的内存区域
以"ESI+04"为地址的内存区域指向EAX
CE提示了我们,地址可能是ESI,记下ESI(源变址寄存器)的地址和偏移量04
输入ESI的十六进制地址值,勾上Hex,New Scan->First Scan
只搜索到一个地址,对其右键进入"Find out what accesses this Address",再次改变游戏金钱
MOV EAX,[ECX+EAX*4]
EAX*4的结果会非常的大,CE提示的地址和上一步一样,陷入了循环。似乎我们在这就要止步不前了
其实EAX = 0,0*4=0,所以偏移量为0
不经我们要思考,为什么EAX=0?
我用OllyDbg启动红警3,断点调试到这一步,请看右侧变量,EAX=00000000
在红色警戒3中,但凡遇到偏移量由乘法组成,如[ECA+EAX*4],默认EAX为0就好了,不要被它吓到,不知道游戏开发商为什么这样子设计偏移量
干嘛偏移量不直接+0?也许就是为了给我们设一道难题吧
为了对新手足够友好,我不说OllyDbg,感兴趣的可以了解下
所以这里EAX等于以ECX为地址的值,不需要用CE推荐的地址了,
因为这里EAX=0,没有意义,记下来ECX的地址和偏移量0
输入上步ECX的地址,搜索到一大堆结果,有点绝望,只能一个个试了,在这里没有技术含量,需要耐心
对每个地址右键"Find out what accesses this Address",从上往下找,列举在上面的地址可能性最大,运气好第一个就是真实地址。记住,只看传送指令MOV
果然,第一个是真实地址,记下ECX的地址和偏移量E4
输入10C815A0,并搜索,又是一堆
老规矩,"Find out what accesses this Address",在这记录下偏移量2C,我们看到ECX地址和上一步搜索的地址一致,陷入了循环
弃用之,采用CE推荐的基址10CA2728。这里你就要自己做判断,一般地址为0,地址和上步骤是一样的,不能用他们,
灵活地选用其他地址
输入10CA2728搜索,可以看到绿色结果,此地址为一级基址,也就是静态基址,我们终于找到了
在CE中手动添加基址来测试找到的基址是否正确,单机Add Address Manually,输入我们刚才寻找的偏移量和基址
可以看到,一级基址经过四次偏移指向的地址,是五级地址,就是我们第一个扫描出来的地址
现在我们来总结
金钱的基址和偏移量如下
[[[[00DFBD74]+2C]+e4]+0]+4
[00DFBD74]是一个值,这个值不是00DFBD74,00DFBD74值存放的地址
在高级语言C++中,可以理解为
int *p; p=00DFBD74; *p=10CA2728;
一级基址:
[00DFBD74]=10CA2728
二级基址:
[一级基址]+偏移
[10CA2728+2C]=10237DB0
三级基址:
[[一级基址]+偏移]+偏移
[10237DB0+E4]=10C815A0
四级基址:
[[[一级基址]+偏移]+偏移]+偏移
[10C815A0+0]=1169BBF0
五级基址:
[[[[一级基址]+偏移]+偏移]+偏移]+偏移
1169BBF0+4=1169BBF4
大功告成,找其他基址方法类似,不做赘述。
我在找金钱基址花了四十分钟,在EAX=0那里卡了好久,被第二次的偏移量坑了,最后终于找到。
找电力基址花了我将近一个小时才找到,所以我不建议大家在寻找基址上花费大量时间。在这里获取现成的基址
我们应该把精力放在高级语言如何实现功能上
C++重要函数详解
ReadProcessMemory
读取内存我们要用到ReadProcessMemory函数
函数功能:该函数从指定的进程中读入内存信息,被读取的区域必须具有访问权限。
函数原型:BOOL ReadProcessMemory(HANDLE hProcess,LPCVOID lpBaseAddress,LPVOID lpBuffer,DWORD nSize,LPDWORD lpNumberOfBytesRead);
參数:
hProcess:进程句柄
lpBaseAddress:读出数据的地址
lpBuffer:存放读取数据的地址
nSize:读入数据的字节数
lpNumberOfBytesRead:数据的实际大小
WriteProcessMemory
写入内存我们需要WriteProcessMemory函数
参数:
hProcess:由OpenProcess返回的进程句柄。如参数传数据为 INVALID_HANDLE_VALUE 【即-1】目标进程为自身进程
lpBaseAddress:要写的内存首地址,在写入之前,此函数将先检查目标地址是否可用,并能容纳待写入的数据、
lpBuffer:指向要写的数据的指针
nSize:要写入数据的字节数
lpNumberOfBytesWritten:写入数据的大小
C++控制台版
全部代码
#include <atlstr.h>
#include <Windows.h>
#include <iostream>
using namespace std;
/*
作者:Jonas
时间:2018/11/17
*/
//游戏基址1
int g_nBaseAddr = 0x00DFBD74;
//游戏基址2
int g_otherBaseAddr = 0x00DEEA3C;
//游戏句柄
HANDLE g_hProcess; //根据基址计算出两次偏移后的地址
int *get2Point(int g_nBaseAddr, int p1, int p2)
{
//iBase存储基地址指向的值,即iBase = [g_nBaseAddr]
//iP1存储以iBase指向的值+偏移为地址所指向的值,即iP1 = [iBase]+p1
//iP2存储最终地址
int iBase, iP1, *iP2;
if (!ReadProcessMemory(g_hProcess, (LPVOID)g_nBaseAddr, &iBase, , NULL))
{
return NULL;
} if (!ReadProcessMemory(g_hProcess, (LPVOID)(iBase + p1), &iP1, , NULL))
{
return NULL;
} //返回最终地址
iP2 = (int *)(iP1 + p2);
return iP2;
} //根据基址计算出三次偏移后的地址
int *get3Point(int g_nBaseAddr, int p1, int p2, int p3)
{
//原理同上,以此类推
int iBase, iP1, iP2, *iP3; if (!ReadProcessMemory(g_hProcess, (LPVOID)g_nBaseAddr, &iBase, , NULL))
{
return NULL;
} if (!ReadProcessMemory(g_hProcess, (LPVOID)(iBase + p1), &iP1, , NULL))
{
return NULL;
} if (!ReadProcessMemory(g_hProcess, (LPVOID)(iP1 + p2), &iP2, , NULL))
{
return NULL;
}
iP3 = (int *)(iP2 + p3);
return iP3;
} //根据基址计算出四次偏移后的地址
int *get4Point(int g_nBaseAddr, int p1, int p2, int p3, int p4)
{
////原理同上,以此类推
int iBase, iP1, iP2, iP3, *iP4; if (!ReadProcessMemory(g_hProcess, (LPVOID)g_nBaseAddr, &iBase, , NULL))
{
return NULL;
} if (!ReadProcessMemory(g_hProcess, (LPVOID)(iBase + p1), &iP1, , NULL))
{
return NULL;
} if (!ReadProcessMemory(g_hProcess, (LPVOID)(iP1 + p2), &iP2, , NULL))
{
return NULL;
} if (!ReadProcessMemory(g_hProcess, (LPVOID)(iP2 + p3), &iP3, , NULL))
{
return NULL;
}
iP4 = (int *)(iP3 + p4);
return iP4;
} //改变电力
void ModifyElectricity()
{
//获取电力所在地址
int *pElec = get3Point(g_nBaseAddr, 0x2c, 0x74, 0x4);
//将电力修改为目标值
int nElecValue = ;
//修改
WriteProcessMemory(g_hProcess, pElec, &nElecValue, , NULL);
} //修改策略值
void ModifyStrategy()
{
//获取策略所在地址
int *pStrategy = get3Point(g_nBaseAddr, 0x2c, 0x1320, 0x2c);
//将策略修改为目标值
//策略值类型为float
float nElecStrategy = ;
//修改
WriteProcessMemory(g_hProcess, pStrategy, &nElecStrategy, , NULL);
} //修改金钱
void ModifyMoney()
{
//获取金钱所在地址
int *pMoney = get4Point(g_nBaseAddr, 0x2c, 0xe4, 0x0, 0x4);
//将金钱修改为目标值
int nElecMoney = ;
//修改
WriteProcessMemory(g_hProcess, pMoney, &nElecMoney, , NULL);
} //修改选取单位的大小
//支持选择单个单位对大小进行修改,多选会导致错乱
void ModifySizeOfUnit()
{
//获取单位大小所在地址
int *pSizeOfUnit = get3Point(g_otherBaseAddr, 0x50, 0x8, 0x25c);
//将单位大小修改为目标值
float nElecSizeOfUnit = ;
//修改
WriteProcessMemory(g_hProcess, pSizeOfUnit, &nElecSizeOfUnit, , NULL);
} int _tmain(int argc, _TCHAR* argv[])
{
//获取游戏窗口所在进程的进程ID,也就是PID
HWND hWnd = FindWindow(NULL, TEXT("終極動員令:紅色警戒3"));
if (NULL == hWnd)
{
cout<<"查找窗口失败"<<endl;
getchar();
return ;
} DWORD dwProcessId;
GetWindowThreadProcessId(hWnd, &dwProcessId);
cout<<"进程ID:"<<dwProcessId<<endl; //获取进程句柄
g_hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (NULL == g_hProcess)
{
cout<<"打开进程失败"<<endl;
getchar();
return ;
} ModifyElectricity();
ModifyMoney();
ModifyStrategy();
ModifySizeOfUnit();
getchar();
return ;
}
运行效果如下,黑灯瞎火
打开游戏看看,金钱变成了11111,策略点加满,电力变成了9999,我选中的发电厂的大小是不是有些违和?
图形界面Qt版
好,接下来,进入重头戏
ui界面布局设计如下
要点解析
初始化句柄
运行前要初始化句柄和检测进程是否打开
否则修改金钱、电力等方法句柄为空
//查看当前游戏进程是否打开
//初始化游戏句柄
void Ra3Window::checkProcessState()
{
//获取游戏窗口所在进程的进程ID,也就是PID
HWND hWnd = FindWindow(NULL, TEXT("終極動員令:紅色警戒3"));
if (NULL == hWnd)
{
//qDebug()<<"查找窗口失败"<<endl;
QMessageBox::information(this,"警告","未找到红色警戒3窗口");
} DWORD dwProcessId;
GetWindowThreadProcessId(hWnd, &dwProcessId);
qDebug()<<"进程ID:"<<dwProcessId<<endl; //获取进程句柄
g_hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (NULL == g_hProcess)
{
QMessageBox::information(this,"警告","打开红色警戒3进程失败");
}
}
Qt国际化
在项目.pro添加
TRANSLATIONS = Translate_EN.ts\
Translate_CN.ts\
Translate_DE.ts
工具-外部-Qt语言家-更新翻译
用以在项目根目录下生成刚才指定名称的ts文件
打开Linguist
打开ts文件,开始翻译吧。。。有道,德语助手各显神通。在译文出输入你的翻译内容
翻译过后,点击文件-发布全部。会在项目根目录下生成qm文件
代码中引用这些qm文件
//语言comboBox触发
void Ra3Window::on_comboBox_2_activated(int index)
{
switch(index)
{
case :
m_Translator->load("./Translate_CN.qm");
break;
case :
m_Translator->load("./Translate_EN.qm");
break;
case :
m_Translator->load("./Translate_DE.qm");
break;
default :
break;
}
qApp->installTranslator(m_Translator);
}
重写retranslateUi
修复comboBox更换语言重置ui后不能保持原来选中的状态,意思是我在语言comboBox选中了英语,界面语言变化了,但是语言comboBox还是简体中文
观察源码发现,该函数把comboBox清空了
//重写retranslateUi
//注释掉语言comboBox清空,修复语言状态错乱(只显示简体中文)
//不建议直接修改源码,复制出来重写
void Ra3Window::retranslateUi(QMainWindow *Ra3Window)
{
Ra3Window->setWindowTitle(QApplication::translate("Ra3Window", "Ra3Window", Q_NULLPTR));
ui->label_3->setText(QApplication::translate("Ra3Window", "\347\255\226\347\225\245\345\212\240\346\273\241", Q_NULLPTR));
ui->pushButton_3->setText(QApplication::translate("Ra3Window", "\347\253\213\345\215\263\347\224\237\346\225\210", Q_NULLPTR));
ui->label_4->setText(QApplication::translate("Ra3Window", "\351\200\211\345\217\226\345\215\225\344\275\215", Q_NULLPTR));
ui->comboBox->clear();
ui->comboBox->insertItems(, QStringList()
<< QApplication::translate("Ra3Window", "\345\260\217", Q_NULLPTR)
<< QApplication::translate("Ra3Window", "\346\240\207\345\207\206", Q_NULLPTR)
<< QApplication::translate("Ra3Window", "\345\244\247", Q_NULLPTR)
);
ui->pushButton_4->setText(QApplication::translate("Ra3Window", "\347\253\213\345\215\263\347\224\237\346\225\210", Q_NULLPTR));
ui->textEdit->setHtml(QApplication::translate("Ra3Window", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:'SimSun'; font-size:9pt; font-weight:400; font-style:normal;\">\n"
"<p align=\"center\" style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">\350\257\264\346\230\216</p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">\351\207\221\351\222\261\357\274\232\350\276\223\345\205\245\351\207\221\351\222\261\346\225\260\351\242\235\357\274\214\345\206\215\347\202\271\345\207\273\345\217\263\344\276\247\342\200\234\347\253\213\345\215\263\347\224\237\346\225\210\342\200\235\346\214\211\351\222\256\357\274\214\345\215\263\345\217\257\350\216\267\345\276\227\346\214\207\345\256\232\347\232\204\351\207\221\351\222\261\346"
"\225\260</p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">\347\224\265\345\212\233\357\274\232\350\276\223\345\205\245\347\224\265\345\212\233\345\200\274\357\274\214\345\206\215\347\202\271\345\207\273\345\217\263\344\276\247\342\200\234\347\253\213\345\215\263\347\224\237\346\225\210\342\200\235\346\214\211\351\222\256\357\274\214\345\215\263\345\217\257\350\216\267\345\276\227\346\214\207\345\256\232\347\232\204\347\224\265\345\212\233\345\200\274</p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">\347\255\226\347\225\245\345\212\240\346\273\241\357\274\232\344\270\215\351\234\200\350\246\201\346\214\207\345\256\232\357\274\214\351\273\230\350\256\244\345\212\240\346\273\241</p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">\351\200\211\345\217\226\345\215\225\344\275\215\357"
"\274\232\345\205\210\351\200\211\346\213\251\344\270\200\344\270\252\345\215\225\344\275\215\357\274\214\344\270\213\346\213\211\346\241\206\351\200\211\346\213\251\345\244\247\345\260\217\357\274\214\345\206\215\347\202\271\345\207\273\345\217\263\344\276\247\342\200\234\347\253\213\345\215\263\347\224\237\346\225\210\342\200\235\346\214\211\351\222\256\357\274\214\345\217\257\344\273\245\347\234\213\345\210\260\345\215\225\344\275\215\347\232\204\345\244\247\345\260\217\345\217\230\345\214\226\357\274\233\346\240\207\345\207\206\357\274\232\346\255\243\345\270\270\345\244\247\345\260\217\343\200\202</p>\n"
"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p>\n"
"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p>\n"
"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0p"
"x; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p>\n"
"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p>\n"
"<p align=\"right\" style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:8pt;\">\345\215\232\345\256\242\345\234\260\345\235\200\357\274\232https://www.cnblogs.com/Java-Starter/</span></p>\n"
"<p align=\"right\" style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:8pt;\">\344\273\205\344\276\233\345\255\246\344\271\240\344\272\244\346\265\201\357\274\214\344\270\245\347\246\201\345\225\206\347\224\250</span></p>\n"
"<p align=\"right\" style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:8pt;\"> \344\275\234\350"
"\200\205\357\274\232Jonas</span></p></body></html>", Q_NULLPTR));
ui->label_5->setText(QApplication::translate("Ra3Window", "\350\257\255\350\250\200", Q_NULLPTR));
//清空comboBox_2 ui->comboBox_2->clear();
// ui->comboBox_2->insertItems(0, QStringList()
// << QApplication::translate("Ra3Window", "\347\256\200\344\275\223\344\270\255\346\226\207", Q_NULLPTR)
// << QApplication::translate("Ra3Window", "\350\213\261\350\257\255", Q_NULLPTR)
// << QApplication::translate("Ra3Window", "\345\276\267\350\257\255", Q_NULLPTR)
// );
ui->lineEdit->setPlaceholderText(QApplication::translate("Ra3Window", "\350\276\223\345\205\245\351\207\221\351\222\261\346\225\260\351\242\235", Q_NULLPTR));
ui->label->setText(QApplication::translate("Ra3Window", "\351\207\221\351\222\261", Q_NULLPTR));
ui->pushButton->setText(QApplication::translate("Ra3Window", "\347\253\213\345\215\263\347\224\237\346\225\210", Q_NULLPTR));
ui->label_2->setText(QApplication::translate("Ra3Window", "\347\224\265\345\212\233", Q_NULLPTR));
ui->lineEdit_2->setPlaceholderText(QApplication::translate("Ra3Window", "\350\276\223\345\205\245\347\224\265\345\212\233\345\200\274", Q_NULLPTR));
ui->pushButton_2->setText(QApplication::translate("Ra3Window", "\347\253\213\345\215\263\347\224\237\346\225\210", Q_NULLPTR));
} // retranslateUi
配置启动程序图标
在项目根目录下新建ico.rc
记事本编辑之
IDI_ICON1 ICON DISCARDABLE "./images/ra3.ico"
准备好ico图标
在项目.pro加入
OTHER_FILES += ico.rc
RC_FILE += ico.rc
Qt relase模式编译,即可看到生成图标
Qt项目打包发布
打包Qt会给我们项目加上依赖环境,使项目在其他电脑上也可运行
园友可以试试不打包直接打开exe程序,会报各种dll找不到的错误
打开Qt 5.7 for Desktop
键入命令
windeployqt RA3_Cheat.exe
圆满完成,结束。
Qt版运行效果
修改金钱、电力,加满策略值,修改单位大小没什么用,就是好玩,可以改的非常大,设置10以上比将军刽子手还大,设置成0单位会“消失”
Qt源码
Qt源码在这里:https://github.com/cjy513203427/RedAlert3_Cheater
哈哈,我怎么放在CSDN上呢?。。。
直接可执行程序在/release目录下,打开RA3_Cheat.exe即可运行
写得很累,希望新人看了我的教程就可以学会制作单机游戏外挂,知其然,知其所以然。大佬可以随便看看
基于C++ Qt实现的红色警戒3修改器的更多相关文章
- 红色警戒2CE修改教程
在大学的时候特别喜欢玩游戏,尤其偏爱单机游戏.在玩一些单机游戏的时候,特意使用了一些修改工具.本来是打算做成一个系列的,但是现在由于时间问题,仅介绍一些.(大概包括rimworld,饥荒,放逐之城,缺 ...
- 一款基于jQuery仿淘宝红色分类导航
今天给大家分享一款基于jQuery仿淘宝红色分类导航.这款分类导航适用浏览器:IE8.360.FireFox.Chrome.Safari.Opera.傲游.搜狗.世界之窗.效果图如下: 在线预览 ...
- 两个基于C++/Qt的开源WEB框架
1.tufao 项目地址: https://github.com/vinipsmaker/tufao 主页: http://vinipsmaker.github.io/tufao/ 介绍: Tufão ...
- [开源]基于ffmpeg和libvlc的视频剪辑、播放器
[开源]基于ffmpeg和libvlc的视频剪辑.播放器 以前研究的时候,写过一个简单的基于VLC的视频播放器.后来因为各种项目,有时为了方便测试,等各种原因,陆续加了一些功能,现在集成了视频播放.视 ...
- 最简单的基于FFmpeg的移动端例子:IOS 推流器
转至:http://blog.csdn.net/leixiaohua1020/article/details/47072519 ================================== ...
- 基于jplayer实现歌词同步的JS音乐播放器效果
分享一款基于jplayer实现歌词同步的JS音乐播放器效果.这是一款基于jQuery实现的音乐播放器功能代码.效果图如下: 在线预览 源码下载 实现的代码. html代码: <textare ...
- 基于React实现的【绿色版电子书阅读器】,支持离线下载
代码地址如下:http://www.demodashi.com/demo/12052.html MyReader 绿色版电子书阅读器 在线地址:http://myreader.linxins.com ...
- 【Qt开发】Qt Creator在Windows上的调试器安装与配置
Qt Creator在Windows上的调试器安装与配置 如果安装Qt时使用的是Visual Studio的预编译版,那么很有可能就会缺少调试器(Debugger),而使用MSVC的Qt对应的原生调试 ...
- 基于Cmake+QT+VS的C++项目构建开发编译简明教程
目录 一.工具下载与安装 1. Qt 2. Visual Studio 2015 3. Cmake 二.C++及Qt项目构建 1. 基于VS构建Qt项目 2. ...
随机推荐
- hdu 4961 数论?
http://acm.hdu.edu.cn/showproblem.php?pid=4961 给定ai数组; 构造bi, k=max(j | 0<j<i,a j%ai=0), bi=ak; ...
- ajax点击加载更多图片
<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>& ...
- uva10905同一思路的两种做法,前一种WA,后一种AC
这道题应该算一道普通的排序吧,实际上就是另一种形式地比大小,自己最开始是用int型存,后来觉着不行,改用long,结果还是WA,这是第一个程序. 第二个程序是改用string处理,确实比int方便很多 ...
- ASP.NET Web API 框架研究 ASP.NET 路由
ASP.NET Web API 如果采用Web Host方式来寄宿,在请求进入Web API 消息处理管道之前,就会用ASP.NET 自身的路由系统根据注册的路由表,解析出当前请求的HttpContr ...
- Swift简单实现一个常规条款、免责声明文字+带有链接的展示形式
效果: IMG_F08DABE063A6-1.jpeg class DisclamerView: UIView { //@objc weak var vc:UIViewController? // ...
- centos下完全卸载mysql(别人写的,我仅仅为了学习记录)
yum方式安装的mysql 1.yum remove mysql mysql-server mysql-libs compat-mysql51 2.rm -rf /var/lib/mysql 3.rm ...
- unigui导出EXCEL使用NATIVEEXCEL
unigui导出EXCEL使用NATIVEEXCEL // 需要nativeexcel控件// cxg 2017-9-9 unit myExcel; interface uses System.Sys ...
- ASP.NET自定义错误页并返回正确的500、404状态码
在项目中,我们常常需要自定义错误页面,但往往返回的状态码都变成了200,对SEO很不友好.我尝试过在百度上寻找解决方案,但找到的资料中说的方法都试过了,发现都是无法返回正确的状态码的. 最后,只好自已 ...
- dubbo-admin 出现警告(不影响使用)
<dubbo:application name="pyg-sellergoods-s" />. <dubbo:application name="pyg ...
- 修改windows远程默认端口
修改windows远程默认端口 windows端口修改rdp 1 远程服务器运行窗口调出注册表编辑器 注册表编辑器regeidt 2 修改两个注册表 1,在注册表HKEY_LOCAL_MACHINE\ ...