用QT写植物大战僵尸的无线阳光

 从一开始学习制作辅助,

为啥是一因为,本人有点JS的底子,但是c++ 和QT还是并没有接触过。

但是网上的教程基本上都是用易语言或者c++写的。所以没办法还是要看一下c++

但是因为本人太心急,所以没有看过c++的学习视频 就连QT的基本上也就是看了个安装,然后看了一个btn按钮是怎么实现的。。因为有js的底子,知道vue这种绑定机制所以。我就当QT也是这样的了。。就不学习一些其他的空间了。。至于以后用到的话,在学习其他的控件把。。自学起来有些吃力,有的20分钟的视频不懂的可能反复琢磨2个小时都不一定琢磨出来。。

因为网上教程都是那种上古教程都是用的MFC,但是听他们说这是老掉牙的,我也不太懂所以就在网上找啊找 发现了QT。觉得这个写窗体程序应该很不错把。。

所以以后的代码都不是以c++逻辑写的代码,而是以JavaScript的逻辑写的。。为啥说逻辑是因为比如我需要声明一个变量 JS是 var a = 0;但是我不知道c++怎么声明的,,然后一查 是int a = 0;所以他们语法不通。。所以以下写的c++代码可能和shi一样。

就比如说下边有一个循环偏移的 由于并不知道 word类型的这个 遍历循环后会出现问题,具体啥原因也不懂,就去找了个list泛型来代替他。。还好搞定了。找了俩小时呢 。。因为真的一点都看不懂c++ 除了一些简单声明变量和调用函数 ,稍微语法复杂的循环都看不太懂。但是并不影响写一些简单的代码。至于以后需要用到的话 以后在学吧。

所以本系列是从一开始的。

奥对了 汇编也是写这篇文章的时候刚看了王爽老师的书。。学的是8086汇编 ,大概也可以看的明白OD反编译后的代码。但是看懂就够呛了。

慢慢学习,也不知道能坚持多久。菜鸟代码。大佬勿喷。

同是初学者的话可以一起学习。大佬请指教。

#include "widget.h"
#include "ui_widget.h"
#include <windows.h>
#include <QMessageBox>


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

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

// 根据程序标题获得该窗口的句柄
// 成功返回 进程ID,失败返回0
DWORD Widget::findGameProcessByWndTitle(QString title){
    DWORD pid;
    HWND hwnd  = ::FindWindow(NULL,(LPCWSTR)title.unicode());
    if(!hwnd){
        QMessageBox::critical(NULL, "错误", "未查找到" + title +"窗口");
        return 0;
    }

    // 根据窗口句柄,获得该窗口的进程标识符即ID,将结果赋予给dip变量中
    GetWindowThreadProcessId(hwnd,&pid);
    return pid;
}

DWORD Widget::getFinalAddress(HANDLE hProcess ,DWORD base_address,QList<DWORD> offsetAddress,int offset_urls_length){

    // 读取指定进程的空间数据
    // 为了提示以后的自己逐段讲解,函数ReadProcessMemory是:读取指定进程的空间数据
    // 作为新手,不习惯汇编语言总会看错代码的意思。
    // 在汇编里 mov ax,[0xFF]  代替基址赋值,这里的base_address就是0xFF ,这段代码就是我在CE里看到他的样子,证明他是基址
    // 但是ax寄存器里存放的并不是0xFF,0xFF只是一个地址, ax存放的应该是0xFF地址中的值,以8086CPU为例此时内存应该是这样的
    // 2000:00FF 01 23 00 00 00 00
    // 那么这个时候ax寄存器存的值就应该是 2301 ,提醒一下自己ax是16位,高八位23 低八位 01
    // 所以以后会有一些编译地址如 mov esi ,[ax+200] 就相当于 mov esi ,[ 2301 + 200 ] 而不是 mov esi,[0xFF + 200]
    // 同理esi存放的也是[2301+200]这个地址的值,在提醒笨笨的自己,此时内存数据段应该是这样的
    // 2000:2501 50 80 00 00 00 00 也就是[2501]这个地址存放着8050这个值如果以后继续有偏移那么是同理的状况
    // 作此提示,是因为初学汇编有点看不懂ReadProcessMemory这个函数。。看别人写的时候明明可以把基址写成[基址+偏移地址+偏移地址]
    // 而别人偏移两次就要重复两次ReadProcessMemory这个函数
    // 实际上每次获取到的[地址+偏移地址]都要在找一下这个地址的值。所以才需要每次都调用这个函数
    // 而看样子这个函数的第三个参数就是根据第二个参数也就是地址得到的值
    // 所以我定义了变量url_value,让他每次与偏移地址加加
    DWORD url_value = 0;
    if(!ReadProcessMemory(hProcess,(LPCVOID)base_address,(LPVOID)&url_value,sizeof(DWORD),NULL)){

        CloseHandle(hProcess);
        return 0;
    }
    // 看懂以上注释那么做循环。offset_urls数组里存放着偏移地址做循环
    // 因为我不咋会写c++代码。所以优化不好。能出来结果就好了。因为有js的底子所以大概的语法百度一下能稍微写写。
    // 做个for循环 循环偏移地址的数组 因为每次地址都是[原来地址的值+偏移地址] 新地址=原来地址的值+偏移地址 等同于url_value += value;
    for (DWORD value : offsetAddress) {
        url_value += value;
        if(!ReadProcessMemory(hProcess,(LPCVOID)url_value,&url_value,sizeof(DWORD),NULL)){
            CloseHandle(hProcess);
            return 0;
        }
    }
    // 最后返回,偏移后的真正值,也是真正的地址,这里也许失败返回0,但是地址万一是0呢。c++也不像js一样可以弱类型可以返回一个空之类的 ,也不能错误返回-1
    // 作为初学者不知道dword有没有补码会不会也可以判断-1是有值的。不管了。自当他0就错误
    // 我理解大白话就是:如果把扫把比喻成进程,把把手当作句柄。那么OpenProcess返回的是一个句柄,也就是扫把的把手。我可以拿着这个把手抡起扫把扫地或者干些什么
    // 所以我每次扫地的时候都要拿起扫把的把手才能扫地。同样的我要对内存的修改或读取也要拿起进程的把手。也就是什么专业术语中的句柄
    // CloseHandle 看人家解释是:只是关闭了一个线程句柄对象,表示我不再使用该句柄,即不对这个句柄对应的线程做任何干预了。并没有结束线程
    // 看不太懂但是大致换成大白话好像句柄是要归还的。比喻系统发了好多扫把,但是系统资源是不变的。扫把就那么几个你用了就要还。
    // CloseHandle 就相当于我手离开了扫把。但是扫把还有把手。只是我不用他了。至少我那么理解的
    // 野路子出身看不懂什么专业术语。代码能运行就是干。
    // 如果能以白话的理解解读计算机的各种操作。并且自我感觉语句还真有通顺的那个意思的话。
    // 就像用扫把来理解句柄的话。。不管他正不正确。对不对至少逻辑上是通顺的。
    // 没老师教又没有文化。看一些人给的专业名词术语,真的很难看懂更不理解。
    // 如果不理解的话,这段代码就算会写也是死记硬背。但是如果理解了哪怕用自己的方式错误的理解了只要能理的通。写出来的代码依旧是有灵魂的而不是死记硬背
    // CloseHandle(hProcess);这里删除了因为感觉后边还需要用到窗口句柄 就是给地址赋值的时候 。所以不关闭句柄
    return url_value;
}


void Widget::on_addSunBtn_clicked()
{
    // 获取进程ID,错误退出函数
    DWORD pid = findGameProcessByWndTitle("植物大战僵尸汉化版");
    if(!pid){
        return;
    }

    // 获取进程句柄
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid);
    if(!hProcess){
        QMessageBox::critical(NULL, "错误", "打开进程失败");
        return;
    }

    DWORD SunShineBaseAddress = 0x6A9EC0; //阳光基址
    QList<DWORD> offsetAddress = { 0x768 };  //所有偏移地址
    // 找阳光其实最终地址 应该是 基址 + 偏移地址 768 + 偏移地址 5560
    // 但是这里list集合只传了一个是因为如果传2个就会获得阳光的数量了。
    // 而此时此刻我并不需要阳光最后地址的值,所以我读内存操作[基址的值+768]就够了。
    // 这样获取到[基址的值+768]的值在+5560就是我要修改的地址
    // 所以在下边调用WriteProcessMemory函数的时候+=0x5560因为他是最终地址。就不需要读内存地址,而需要写内存地址了
    // 教训:
    //      因为先开始我 QList<DWORD> offsetAddress = { 0x768 ,0x5560 } 但是 final_address返回的值是 50 也就是我电脑里阳光的值
    //      然后走WriteProcessMemory这个函数的时候失败了。调试之后才知道他对地址000050的地方赋值。肯定失败。
    //      在网上看的教程,为了优化一下代码并加深理解使以后多层偏移就不用重复读读写了。然后就各种bug
    //      不会玩c++先开始用的dword 变量[]循环的时候取的还是字节。。怎么都失败后来找到了list集合。
    //      总结还是不太好用,至少对新手不友好,不像JS一样一个数组啥都能干。能当集合能当函数的主要是简单。
    //      因为学完辅助打算学学单片机。所以感觉还是用c++开发通用一些。不然还是选择学python了。
    //      所以在python和c++中选择了c++,
    DWORD final_address = getFinalAddress(hProcess,SunShineBaseAddress,offsetAddress,2);
    //  得到真正的地址,这个时候将值给这个地址从而修改阳光数量
    if(!final_address){
        QMessageBox::critical(NULL, "错误", "最终地址获取失败");
        return;
    }
    DWORD sunValue = 99999;
    final_address += 0x5560;
    //  有了地址之后该需要对这个地址进行数据写入了。
    if(!WriteProcessMemory(hProcess,(LPVOID)final_address,&sunValue,sizeof(DWORD),NULL)){
        QMessageBox::critical(NULL, "错误", "添加阳光失败");
        CloseHandle(hProcess);
        return;
    }
    CloseHandle(hProcess);
    QMessageBox::about(NULL, "成功", "OK");
}
  

 

posted @ 2024-03-03 02:42  淡定君  阅读(22)  评论(0编辑  收藏  举报