基於C++ Qt實現的紅色警惕3修改器

前言

這部修改器製做有一段時間了,可是一直沒出教程。今天利用週末空閒寫篇教程,給後來者指路的同時也加深本身對遊戲修改器的理解,大佬就隨便看看吧css

瀏覽了一下網絡,形形色色的單機遊戲修改器教程,可是基本只實現了一到兩個功能,GUI圖形界面也沒有。網站上能下載到的實現不少功能的修改器卻又不開源,對新手不夠友好html

爲何選擇紅警3而不是其餘遊戲呢?ios

其一,它是單機遊戲,製做網絡遊戲修改器(外掛)是違法的,根據《計算機信息網絡國際聯網安全保護管理辦法》第六條規定:「任何單位和我的不得從事下列危害計算機信息網絡安全的活動",尤爲不能製做網遊外掛並拿它去盈利git

我不知道作網遊外掛開源算不算違法,總之,與違法沾邊的事咱們別去觸碰github

其二,是一種情結,我玩的第一部真正意義上的遊戲是紅色警惕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函數

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

 

運行效果以下,黑燈瞎火

打開遊戲看看,金錢變成了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 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圖標

 

在項目.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便可運行

 

寫得很累,但願新人看了個人教程就能夠學會製做單機遊戲外掛,知其然,知其因此然。大佬能夠隨便看看

相關文章
相關標籤/搜索