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

来源:https://www.cnblogs.com/Java-Starter/archive/2018/11/18/9979565.html
-Advertisement-
Play Games

前言 這部修改器製作有一段時間了,但是一直沒出教程。今天利用周末空閑寫篇教程,給後來者指路的同時也加深自己對游戲修改器的理解,大佬就隨便看看吧 瀏覽了一下網路,形形色色的單機游戲修改器教程,但是基本只實現了一到兩個功能,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++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即可運行

 

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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 多線程 利用條件變數實現線程安全的隊列 背景:標準STL庫的隊列queue是線程不安全的。 利用條件變數(Condition variable)簡單實現一個線程安全的隊列。 代碼: c++ include include include include include include templat ...
  • 這次我們繼續探險,來搞定 python 中的模塊(module)。**兵馬未動,糧草先行**,開工之前先看看基礎是否補齊了。 ...
  • 之前一直用的mysql5.5,最近發現Mysql8更新了很多新特性以及查詢效率的提升,覺得很有必要更新下開發版本,好,廢話不多說: 1、下載安裝包,下載地址:mysql8.0 。如果你想要下載其它版本可以選擇:mysql歷史版本地址。 2、下載好,刪除phpstudy的mysql目錄。如果數據重要的 ...
  • 自己做開發也有兩年多了吧,其中也關註過許多大牛的博客,買過許多的書看. 自己也是個比較愛閱讀的人,從小的時候被老爸逼著每次寒暑假看書,到後來慢慢長大愛上了閱讀,習慣了看書. 農村的小孩嗎,那時候又不像現在.只有通過閱讀去認知這個世界. 做開發以來陸陸續續的看過幾篇文章介紹相關書籍的,自己通過百度,也 ...
  • 偏函數應用舉例:路燈指示牌 ...
  • devtools模塊,是為開發者服務的一個模塊。主要的功能就是代碼修改後一般在5秒之內就會自動重新載入至伺服器,相當於restart成功。與JRebel不同的是,JRebel是一款商業插件,devtools是免費的。是boot的一個熱部署工具,當我們修改了classpath下的文件(包括類文件、屬性... ...
  • Python基礎知識(7):數據基本類型之元組、字典 一、元組 用括弧把元素括起來中間用逗號隔開。用逗號分開一些值便可創建元組 結果: (1, 2, 3) 空元組可以用沒有包含任何內容的兩個圓括弧表示,如()。 1、元素不可被修改,不能進行增加、刪除操作 2、建議寫元組的時候在右括弧的左邊加一個逗號 ...
  • sorted()方法 sorted()可用於任何一個可迭代對象。 原型為sorted(iterable, cmp=None, key=None, reverse=False) iterable:一個可迭代對象; cmp:用於比較的函數,比較什麼由key決定; key:用列表元素的某個屬性或函數進行作 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...