试着写一下MMORPG游戏游戏的自动挂机

因为,视频里教到了植物大战僵尸的自动放置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像属性的东东
    void on_Btn_Browser_GameUrl_clicked();      // 选择文件按钮的click事件
    void on_Btn_Run_Game_clicked();            // 运行游戏的click事件

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>
#include <QFileDialog>


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


void Home::on_Btn_Browser_GameUrl_clicked()
{
    //  getOpenFileName参数:
    //      1.parent,用于指定父组件。
    //      2.caption,是对话框的标题;
    //      3.dir,是对话框显示时默认打开的目录
    //      4.filter,是对话框的后缀名过滤器
    //      5.selectedFilter,是默认选择的过滤器
    //      6.options,是对话框的一些参数设定
    QString url = QFileDialog::getOpenFileName(this,"请打开游戏进程",NULL,"应用程序(*.exe);;*.exe");
    // 在lineEdit控件中打印出来
    ui->Input_Game_Url->setText(url);
}


void Home::on_Btn_Run_Game_clicked()
{
    QString url = ui->Input_Game_Url->text();
    std::wstring wlpstr = url.toStdWString();
    if(url.isEmpty()){  // 为空弹窗
        QMessageBox::critical(this,"错误","路径不允许为空");
    }else{
        // 他这个视频上用的是CString截取字符串,然后一系列操作,好像是确定当前进程目录。我查了一点QString的相关信息,然后同等与视频里所教的内容
        // 而MFC里他的获取到的路径是c:\\..\\x.exe但是QT里是这样的c:/../x.exe所以这块代码就和视频里的不太一样。
        QString nurl = url.left(url.lastIndexOf("/"));
        std::wstring nurl_wlpstr =  nurl.toStdWString();
        //  SetCurrentDirectory :将应用程序的当前工作目录设置为指定的目录。
        //  这块很重要,反正我是这么理解的一个文件打开要在本目录下打开,因为他依赖了很多文件,比如dll之类的东东,
        //  如果不设置工作目录的话呢,就相当于在win+e的地址栏里直接打开了该文件,他并没有执行任何依赖所以会报错。
        int a = ::SetCurrentDirectory(nurl_wlpstr.c_str());
        ::ShellExecute(NULL,NULL,wlpstr.c_str(),NULL,NULL,SW_SHOW);
        // 这段代码无所谓,习惯用JS的console.log调试,所以用了一个lable把我想看到的值打印出来。
        ui->test_lable->setText(a == 0 ? "失败" + nurl :"成功" + nurl);
    }
}

这里的控件都是拖动的,所以直接粘贴代码还是不行的,需要有点QT的基础。。这个也简单。随便找个QT教程看3级 知道槽信号,怎么给button绑定事件就好了。

我就是这样,这次用到了QTableWidget , 所以在百度上搜一下怎么调用怎么遍历怎么赋值,怎么取值就好了。根本不需要去找个视频去重新走一边,那样很浪费事件。而且又不是找工作,实现就好。用到的时候在学,把痛苦留给明天

这里看一下程序暂时的样子吧。

这里大概就是这个样子。

学过前端框架 像VUE和react,dva之类的会觉得,这个玩意简直不要太简单。除了语法和某些逻辑来说。比他们方便的不要太多。

不多说了。总之以后的路线会跟各种学习视频走,等跟视频多了,在提升自己的逻辑思维,看了这么多 其实重要的并不是代码怎么写而是基址怎么找 call怎么找。这才是重要的。

所以找call的经验还是要学习。比如说FPS的自动瞄准。。是怎么做到的,LOL里的自动躲避技能又是怎么弄得。

加油~~~

posted @ 2024-03-10 12:55  淡定君  阅读(16)  评论(0编辑  收藏  举报