試著寫一下MMORPG遊戲遊戲的自動掛機

淡定君發表於2024-03-10

因為,影片裡教到了植物大戰殭屍的自動放置Call就結束了,所以暫且先跟著影片走。而影片就開始研究mmorpg遊戲了。

所以我打算跟著影片走。而上個專案大體能夠理解其實就是用CE找基址,然後透過程式碼注入的方式實行自動指令碼之類的東東。

至於CE找基址OD找call這些設計經驗的東西我會慢慢學習。先跟著影片走熟悉一下C++的語法,先了解一下mmo遊戲的機制了。

我學習的影片是B站的,地址是:https://www.bilibili.com/video/BV1vy4y147s5/

總結一下吧。影片每一集大概都3個小時 建議彙編部分可以去別的UP主去看。。影片是2016年的了好像,雖然很老,但是老師講的還是很細的。

我的彙編是在這裡學習的,地址:https://www.bilibili.com/video/BV1Rs411c7HG

這個怎麼說呢,如果有程式設計基礎的看的話,就不會那麼乏味可以看得懂,但是如果一點程式設計基礎都沒有的話,我感覺會跟天書一樣。

咋說呢3個小時的影片,雖然說長吧。但是貴在細緻吧。。只不過乏味,屬於網課系列,所有一些遇到的問題就需要自己找解答方法了。就比如我從MFC轉到QT遇到的ATT彙編和Intel彙編。QString和CString ,QTimer 和Timer,和一些事件繫結都需要查百度。

但是也算是多學了知識吧。

對於沒有基礎來說,跳著看是看不懂的,所以快進看是錯誤的。。但是有些函式只是實現邏輯方法,邏輯無所謂,實現就好。這方面也就不需要看了。就比如說我這次貼出來的。如果自己可以看到最後可以實現是什麼樣的,那麼則可以快進。

也不多說貼程式碼。

home.h

// home.h

#ifndef HOME_H
#define HOME_H
#include <QWidget>


QT_BEGIN_NAMESPACE
namespace Ui {
class Home;
}
QT_END_NAMESPACE

class Home : public QWidget
{
    Q_OBJECT

public:
    Home(QWidget *parent = nullptr);
    ~Home();

public: // 自己寫的
    void initTableBox();    // 初始化程序表資料
    void getProcessList(QString strName ,QVector<QString>& vecProcess);   // 獲取程序列表,把結果寫在形參的第二個引數裡
    void removeProcessTItem(QVector<QString> vecProcess);    //根據需要去除processTable中已經退出的程序
    int findProcess(QString pid );
    int findVecProcess(QString pid ,QVector<QString> vecProcess);
public:
    QTimer *tim;    //定義一個定時器
private slots:
    void realTimeMonitoringProcessTable();      // 用來做槽實時監控processTable像屬性的東東

private:
    Ui::Home *ui;

};
#endif // HOME_H

home.cpp

// home.cpp

#include "home.h"
#include <windows.h>
#include <tlhelp32.h>
#include "ui_home.h"
#include <QMessageBox>
#include <QTimer>


Home::Home(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Home)
{
    ui->setupUi(this);

    initTableBox();  // 這個表格空間裡寫有程序列表的資訊,這裡設定他的預設資料。
    // 定義一個定時器 定義一個槽函式initTableBox對應他的訊號timeout;其實按照js來說就是給他繫結一個事件
    // 用JS語言來理解他 就相當於定義了一個 setTimeout(() =>{ 我裡面寫了realTimeMonitoringProcessTable的程式碼。}) 唯一不同的是JS裡有兩個引數且只呼叫一次
    // 而這裡time->setInterval(1000)的意思好像就和JS裡setInterval差不多了結合後邊的connect繫結槽和訊號的關係,實現迴圈執行函式。大概吧,我是這麼理解的
    tim = new QTimer;
    tim->setInterval(1000);
    connect(tim,SIGNAL(timeout()),this,SLOT(realTimeMonitoringProcessTable()));
    tim->start();
}

Home::~Home()
{
    delete ui;
}



// 預設程序表資訊,自動呼叫
void Home::initTableBox(){
    QStringList headerText;
    headerText<< "程序ID" << "程序名稱" << "遊戲名";
    ui->processTable->setColumnCount(headerText.count());       // table列的數量與表頭對其
    ui->processTable->setHorizontalHeaderLabels(headerText);    // 設定table表頭

    ui->processTable->horizontalHeader()->setStyleSheet("QHeaderView::section{background:#F3F3F3;border:1px solid #CCCCCC; border-top:0px;}");     // 設定表頭顏色
    ui->processTable->verticalHeader()->setHidden(true);        // 去除編號

    ui->processTable->setSelectionBehavior(QAbstractItemView::SelectRows); //設定選中狀態為一整行
    ui->processTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);   // 設定自適應等寬
    ui->processTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Interactive);   // 表示第0列可以手動設定,其他列自動分配寬度
    ui->processTable->setColumnWidth(0,80);     // 設定第一列寬度

}

// 根據程序名獲取所有同名的程序列表,例如遊戲多開,但程序名一樣。要注入不同的程序
// 這裡第二個引數,我理解的是地址傳遞,因為JS裡沒有所以我特地去查了查。可以百度上了解以下c++的指標。
// 簡單的說就是,形參2 引用了 實參2的地址,修改他即修改了實參的值,不會C++的會雲裡霧裡,我也是看了好久。所以這塊還是建議找c++指標的資料看一下
void Home::getProcessList(QString strName ,QVector<QString>& vecProcess)
{
    // 建立一個快照
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,NULL);
    PROCESSENTRY32 pe32;//存放程序快照的結構體,和Process32First,Process32Next配合使用
    pe32.dwSize = sizeof(PROCESSENTRY32);
    BOOL bRet = Process32First(hSnapshot,&pe32);        //  Process32First用來獲得程序快照的第一個程序
    // 每次進入這個函式,先清空原來舊的程序列表。然後將tableWidget的行數初始為0,如果不初始為0,他會一直增加原來的資料還會保留,不懂他什麼機制,設定了就沒事了。
    while (bRet) {
        QString strProcess = QString::fromWCharArray(pe32.szExeFile);       // wchar_t 轉 QString
        strProcess.toLower();   //  因為程序名有大寫小寫。但是程序名並不區分大小寫。所以統一轉換為小寫比較
        if(strProcess == strName){
            QString pid = QString::number(pe32.th32ProcessID);
            vecProcess.append(pid);
        }

        bRet = Process32Next(hSnapshot,&pe32);
    }
    CloseHandle(hSnapshot);     // 關閉控制代碼
}

void Home::realTimeMonitoringProcessTable(){
    QVector<QString> vecProcess;
    // 迴圈了程序快照,把需要的所有程序放到了vecProcess這個引數裡
    getProcessList("mhmain.exe",vecProcess);
    // 迴圈需要的程序也就是vecProcess引數,把他的值列印在QTableWidget控制元件中,程序快照中的值不存在於QTableWidget中,才會向QTableWidget中寫入項,否則不會寫入
    for (int index = 0; index < vecProcess.size(); ++index) {
        QString pid = vecProcess[index];
        if(findProcess(pid) == -1){
            int row = ui->processTable->rowCount();
            ui->processTable->insertRow(row);
            ui->processTable->setItem(row,0,new QTableWidgetItem(pid));       // 獲取程序ID
            ui->processTable->setItem(row,1,new QTableWidgetItem("mhmain.exe"));    // 程序名稱
            ui->processTable->setItem(row,2,new QTableWidgetItem("暫無"));
        }
    }
    // 這會出現一種狀況,未新增的程序可以實時新增,但是已退出的程序還在列表裡,所以需要剔除他
    removeProcessTItem(vecProcess);
}


void Home::removeProcessTItem(QVector<QString> vecProcess){
    int totalRow = ui->processTable->rowCount();
    for (int index = 0; index < totalRow; ++index) {
        QString item_pid = ui->processTable->item(index,0)->text();
        if(findVecProcess(item_pid ,vecProcess) == -1){
            // 當程序已經不在了那麼,在table中移除他
            ui->processTable->removeRow(index);
            // 這句很重要,是退出當前迴圈,大多數情況不退出也可以迴圈會繼續迴圈,也就浪費資源而已,但是這裡則不一樣
            // ui->processTable->removeRow(index)會移除當前行,也就是移除第一行的時候 第二行會變成第一行。
            // 但如果迴圈不終止就會產生異常,這裡假設資料有兩條totalRow=2
            // 刪除了之後當刪除之後total還是等於2,但是此時的table裡卻只有一條資料了,假設我刪除了下標為0的資料,那麼此時0被刪除,下標1變成了0,但是此時index變成了1
            // 所以當QString item_pid = ui->processTable->item(index,0)->text();再次呼叫的時候index是1。但是第1行已經變成了第0行,所以直接崩潰
            // 這裡因為自己出現了這個錯誤,所以提醒一下
            break;
        }

    }
}

// 如果找到了返回該id所在行下表,如沒找到返回-1,主要用來判斷QTableWidget是否新增該項
int Home::findProcess(QString pid){
    int total = ui->processTable->rowCount();
    for (int i = 0; i < total; i++) {
        QString item_pid = ui->processTable->item(i,0)->text();
        if(pid == item_pid){
            return i;
        }
    }
    return -1;
}
// 在vecProcess陣列裡尋找Pid找不到返回-1找到了返回下標,主要用來判斷是否程序已經消失所以要去除該程序所在的行。
int Home::findVecProcess(QString pid ,QVector<QString> vecProcess){
    for (int index = 0; index < vecProcess.size(); ++index) {
        if(pid == vecProcess[index]){
            return index;
        }
    }
    return -1;
}

這裡的控制元件都是拖動的,所以直接貼上程式碼還是不行的,需要有點QT的基礎。。這個也簡單。隨便找個QT教程看3級 知道槽訊號,怎麼給button繫結事件就好了。

我就是這樣,這次用到了QTableWidget , 所以在百度上搜一下怎麼呼叫怎麼遍歷怎麼賦值,怎麼取值就好了。根本不需要去找個影片去重新走一邊,那樣很浪費事件。而且又不是找工作,實現就好。用到的時候在學,把痛苦留給明天

這裡看一下程式暫時的樣子吧。

這裡大概就是這個樣子。

學過前端框架 像VUE和react,dva之類的會覺得,這個玩意簡直不要太簡單。除了語法和某些邏輯來說。比他們方便的不要太多。

不多說了。總之以後的路線會跟各種學習影片走,等跟影片多了,在提升自己的邏輯思維,看了這麼多 其實重要的並不是程式碼怎麼寫而是基址怎麼找 call怎麼找。這才是重要的。

所以找call的經驗還是要學習。比如說FPS的自動瞄準。。是怎麼做到的,LOL裡的自動躲避技能又是怎麼弄得。

加油~~~


相關文章