欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

基于C++ Qt实现的红色警戒3修改器(Github开源)

程序员文章站 2022-06-01 19:37:17
前言 这部修改器制作有一段时间了,但是一直没出教程。今天利用周末空闲写篇教程,给后来者指路的同时也加深自己对游戏修改器的理解,大佬就随便看看吧 浏览了一下网络,形形色色的单机游戏修改器教程,但是基本只实现了一到两个功能,GUI图形界面也没有。网站上能下载到的实现很多功能的修改器却又不开源,对新手不够 ......

前言

这部修改器制作有一段时间了,但是一直没出教程。今天利用周末空闲写篇教程,给后来者指路的同时也加深自己对游戏修改器的理解,大佬就随便看看吧

浏览了一下网络,形形色色的单机游戏修改器教程,但是基本只实现了一到两个功能,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++ Qt实现的红色警戒3修改器(Github开源)

 

开发环境

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写入内存的时候时常崩溃,所以我用的全屏启动

基于C++ Qt实现的红色警戒3修改器(Github开源)

打开cheat engine,打开游戏进程

基于C++ Qt实现的红色警戒3修改器(Github开源)

进入游戏

基于C++ Qt实现的红色警戒3修改器(Github开源)

以查找金钱基址为例

搜索金钱数字10000,点击first scan

基于C++ Qt实现的红色警戒3修改器(Github开源)

搜索到一系列值,此时改变游戏金钱,建造建筑或单位

基于C++ Qt实现的红色警戒3修改器(Github开源)

 

基于C++ Qt实现的红色警戒3修改器(Github开源)

输入改变后的金钱9200,再点击next scan,可以看到只有四个地址,缩小了范围

 基于C++ Qt实现的红色警戒3修改器(Github开源)

 修改它们的value,看哪个是真的地址,结果第三个是真的(不一定都是第三个,每次搜索都不一样,切勿教条)

基于C++ Qt实现的红色警戒3修改器(Github开源)

游戏金钱发生了变化

基于C++ Qt实现的红色警戒3修改器(Github开源)

 对下方选中的地址,按右键选中find out what writes to this address

出现confirmation,选yes

基于C++ Qt实现的红色警戒3修改器(Github开源)

再次改变游戏的金钱,监测到mov指令

基于C++ Qt实现的红色警戒3修改器(Github开源)

双击进入mov指令,其他操作一概不看,只看标红字的mov指令

我们来分析下这些内容

eax=(0001675b)16 = (91995)10

eax的值就是游戏中的金钱,是所谓的操作数

mov[esi+04],eax

将eax的值传送到以"esi+04"为地址的内存区域

以"esi+04"为地址的内存区域指向eax

ce提示了我们,地址可能是esi,记下esi(源变址寄存器)的地址和偏移量04

 基于C++ Qt实现的红色警戒3修改器(Github开源)

输入esi的十六进制地址值,勾上hex,new scan->first scan

 基于C++ Qt实现的红色警戒3修改器(Github开源)

只搜索到一个地址,对其右键进入"find out what accesses this address",再次改变游戏金钱

基于C++ Qt实现的红色警戒3修改器(Github开源)

mov eax,[ecx+eax*4]

eax*4的结果会非常的大,ce提示的地址和上一步一样,陷入了循环。似乎我们在这就要止步不前了

其实eax = 0,0*4=0,所以偏移量为0

不经我们要思考,为什么eax=0?

我用ollydbg启动红警3,断点调试到这一步,请看右侧变量,eax=00000000

基于C++ Qt实现的红色警戒3修改器(Github开源)

在红色警戒3中,但凡遇到偏移量由乘法组成,如[eca+eax*4],默认eax为0就好了,不要被它吓到,不知道游戏开发商为什么这样子设计偏移量

干嘛偏移量不直接+0?也许就是为了给我们设一道难题吧

为了对新手足够友好,我不说ollydbg,感兴趣的可以了解下

所以这里eax等于以ecx为地址的值,不需要用ce推荐的地址了,

因为这里eax=0,没有意义,记下来ecx的地址和偏移量0

基于C++ Qt实现的红色警戒3修改器(Github开源)

输入上步ecx的地址,搜索到一大堆结果,有点绝望,只能一个个试了,在这里没有技术含量,需要耐心

对每个地址右键"find out what accesses this address",从上往下找,列举在上面的地址可能性最大,运气好第一个就是真实地址。记住,只看传送指令mov

基于C++ Qt实现的红色警戒3修改器(Github开源)

果然,第一个是真实地址,记下ecx的地址和偏移量e4

基于C++ Qt实现的红色警戒3修改器(Github开源)

输入10c815a0,并搜索,又是一堆

基于C++ Qt实现的红色警戒3修改器(Github开源)

老规矩,"find out what accesses this address",在这记录下偏移量2c,我们看到ecx地址和上一步搜索的地址一致,陷入了循环

弃用之,采用ce推荐的基址10ca2728。这里你就要自己做判断,一般地址为0,地址和上步骤是一样的,不能用他们,

灵活地选用其他地址

基于C++ Qt实现的红色警戒3修改器(Github开源)

输入10ca2728搜索,可以看到绿色结果,此地址为一级基址,也就是静态基址,我们终于找到了

基于C++ Qt实现的红色警戒3修改器(Github开源)

在ce中手动添加基址来测试找到的基址是否正确,单机add address manually,输入我们刚才寻找的偏移量和基址

 基于C++ Qt实现的红色警戒3修改器(Github开源)

可以看到,一级基址经过四次偏移指向的地址,是五级地址,就是我们第一个扫描出来的地址

基于C++ Qt实现的红色警戒3修改器(Github开源)

 

现在我们来总结

金钱的基址和偏移量如下

[[[[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

基于C++ Qt实现的红色警戒3修改器(Github开源)

大功告成,找其他基址方法类似,不做赘述。

 

我在找金钱基址花了四十分钟,在eax=0那里卡了好久,被第二次的偏移量坑了,最后终于找到。

找电力基址花了我将近一个小时才找到,所以我不建议大家在寻找基址上花费大量时间。在这里获取现成的基址

我们应该把精力放在高级语言如何实现功能上

 

c++重要函数详解

readprocessmemory

读取内存我们要用到readprocessmemory函数

函数功能:该函数从指定的进程中读入内存信息,被读取的区域必须具有访问权限。

函数原型:bool readprocessmemory(handle hprocess,lpcvoid lpbaseaddress,lpvoid lpbuffer,dword nsize,lpdword lpnumberofbytesread);

 

參数:

hprocess:进程句柄

lpbaseaddress:读出数据的地址

lpbuffer:存放读取数据的地址

nsize:读入数据的字节数

lpnumberofbytesread:数据的实际大小

 

writeprocessmemory

写入内存我们需要writeprocessmemory函数

bool writeprocessmemory(handle hprocess,lpvoid lpbaseaddress,lpvoid lpbuffer,dword nsize,lpdword lpnumberofbyteswritten
);

参数:

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, 4, null))
    {
        return null;
    }

    if (!readprocessmemory(g_hprocess, (lpvoid)(ibase + p1), &ip1, 4, 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, 4, null))
    {
        return null;
    }

    if (!readprocessmemory(g_hprocess, (lpvoid)(ibase + p1), &ip1, 4, null))
    {
        return null;
    }

    if (!readprocessmemory(g_hprocess, (lpvoid)(ip1 + p2), &ip2, 4, 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, 4, null))
    {
        return null;
    }

    if (!readprocessmemory(g_hprocess, (lpvoid)(ibase + p1), &ip1, 4, null))
    {
        return null;
    }

    if (!readprocessmemory(g_hprocess, (lpvoid)(ip1 + p2), &ip2, 4, null))
    {
        return null;
    }

    if (!readprocessmemory(g_hprocess, (lpvoid)(ip2 + p3), &ip3, 4, null))
    {
        return null;
    }
    ip4 = (int *)(ip3 + p4);
    return ip4;
}

//改变电力
void modifyelectricity()
{
    //获取电力所在地址
    int *pelec = get3point(g_nbaseaddr, 0x2c, 0x74, 0x4);
    //将电力修改为目标值
    int nelecvalue = 9999;
    //修改
    writeprocessmemory(g_hprocess, pelec, &nelecvalue, 4, null);
}

//修改策略值
void modifystrategy()
{
    //获取策略所在地址
    int *pstrategy = get3point(g_nbaseaddr, 0x2c, 0x1320, 0x2c);
    //将策略修改为目标值
    //策略值类型为float
    float nelecstrategy = 4320;
    //修改
    writeprocessmemory(g_hprocess, pstrategy, &nelecstrategy, 4, null);
}

//修改金钱
void modifymoney()
{
    //获取金钱所在地址
    int *pmoney = get4point(g_nbaseaddr, 0x2c, 0xe4, 0x0, 0x4);
    //将金钱修改为目标值
    int nelecmoney = 11111;
    //修改
    writeprocessmemory(g_hprocess, pmoney, &nelecmoney, 4, null);
}

//修改选取单位的大小
//支持选择单个单位对大小进行修改,多选会导致错乱
void modifysizeofunit()
{
    //获取单位大小所在地址
    int *psizeofunit = get3point(g_otherbaseaddr, 0x50, 0x8, 0x25c);
    //将单位大小修改为目标值
    float nelecsizeofunit = 2;
    //修改
    writeprocessmemory(g_hprocess, psizeofunit, &nelecsizeofunit, 4, null);
}

int _tmain(int argc, _tchar* argv[])
{
    //获取游戏窗口所在进程的进程id,也就是pid
    hwnd hwnd = findwindow(null, text("終極動員令:紅色警戒3"));
    if (null == hwnd)
    {
        cout<<"查找窗口失败"<<endl;
        getchar();
        return 0;
    }

    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 0;
    }

    modifyelectricity();
    modifymoney();
    modifystrategy();
    modifysizeofunit();
    getchar();
    return 0;
}

 

运行效果如下,黑灯瞎火

基于C++ Qt实现的红色警戒3修改器(Github开源)

打开游戏看看,金钱变成了11111,策略点加满,电力变成了9999,我选中的发电厂的大小是不是有些违和?

基于C++ Qt实现的红色警戒3修改器(Github开源)

 

图形界面qt版

好,接下来,进入重头戏

ui界面布局设计如下

基于C++ Qt实现的红色警戒3修改器(Github开源)

要点解析

初始化句柄

运行前要初始化句柄和检测进程是否打开

否则修改金钱、电力等方法句柄为空

//查看当前游戏进程是否打开
//初始化游戏句柄
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文件

 基于C++ Qt实现的红色警戒3修改器(Github开源)

打开linguist

基于C++ Qt实现的红色警戒3修改器(Github开源)

打开ts文件,开始翻译吧。。。有道,德语助手各显神通。在译文出输入你的翻译内容

基于C++ Qt实现的红色警戒3修改器(Github开源)

翻译过后,点击文件-发布全部。会在项目根目录下生成qm文件

代码中引用这些qm文件

//语言combobox触发
void ra3window::on_combobox_2_activated(int index)
{
    switch(index)
    {
        case 0:
            m_translator->load("./translate_cn.qm");
            break;
        case 1:
            m_translator->load("./translate_en.qm");
            break;
        case 2:
            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(0, 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图标

基于C++ Qt实现的红色警戒3修改器(Github开源)

 

在项目.pro加入

other_files += ico.rc
rc_file += ico.rc

 qt relase模式编译,即可看到生成图标

 

qt项目打包发布

打包qt会给我们项目加上依赖环境,使项目在其他电脑上也可运行

园友可以试试不打包直接打开exe程序,会报各种dll找不到的错误

打开qt 5.7 for desktop

基于C++ Qt实现的红色警戒3修改器(Github开源)

键入命令

windeployqt ra3_cheat.exe

圆满完成,结束。

基于C++ Qt实现的红色警戒3修改器(Github开源)

qt版运行效果

 基于C++ Qt实现的红色警戒3修改器(Github开源)

修改金钱、电力,加满策略值,修改单位大小没什么用,就是好玩,可以改的非常大,设置10以上比将军刽子手还大,设置成0单位会“消失”

基于C++ Qt实现的红色警戒3修改器(Github开源)

qt源码

qt源码在这里:https://github.com/cjy513203427/redalert3_cheater

哈哈,我怎么放在csdn上呢?。。。

直接可执行程序在/release目录下,打开ra3_cheat.exe即可运行

基于C++ Qt实现的红色警戒3修改器(Github开源)

 

写得很累,希望新人看了我的教程就可以学会制作单机游戏外挂,知其然,知其所以然。大佬可以随便看看