1. 程式人生 > >基於C++ Qt實現的紅色警戒3修改器(Github開源)

基於C++ Qt實現的紅色警戒3修改器(Github開源)

前言

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

瀏覽了一下網路,形形色色的單機遊戲修改器教程,但是基本只實現了一到兩個功能,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函式

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即可執行

 

寫得很累,希望新人看了我的教程就可以學會製作單機遊戲外掛,知其然,知其所以然。大佬可以隨便看看