前前言
上半年疫情在家的时候实在无聊,文艺复兴,把PVZ几个版本又玩了一遍,本来打算写个修改器,结果鸽到现在因为各种事情耽搁到现在。以后有时间就尽量更新,估计这个系列要写很久
另:PVZ各版本合集搜“植物大战僵尸相关”就可以找到(还是西游版和β版本好玩。
前言
和网上大部分教程一样,本文也是通过CE寻找阳光基址,然后用C语言实现阳光数量的修改。
一、CE寻找阳光基址
CE还算是蛮有意思的一个软件,有兴趣学习CE使用方法的同学,可以在B站搜CE嗨入门,不过重点还是要动手去实践。
用CE寻找阳光基址的方法有很多,这里不再赘述,只讲我用的方法(可能是最笨的方法了。
首先要知道,每次打开游戏,游戏所占用的内存空间是不一样的,所以为了能够每次打开游戏都能成功修改我们想要的游戏道具的数量,我们就要找到基址。
基址是不会因为游戏进程的打开或关闭而改变的,所以只要通过基址和偏移量,我们就能够实现对游戏道具的修改。
1、添加PVZ窗口到CE菜单栏,搜索阳光初始值,改变阳光数量并搜索,最后就能确定第一个地址和一级偏移量(基操直接略过
2、确定第一个地址后,点击“找出什么访问了这个地址”,返回游戏,当阳光数量发生变化时,会发现窗口内出现了新的函数,锁定后点击更多信息,即可确定下一个地址和下一级偏移量
3、阳光数量只有二级偏移,所以搜索第二步给出的可能地址后,重复第二步即可确定基址(绿色的
4、手动添加指针,通过基址+偏移量找到存储阳光的地址
添加后尝试修改地址存储内容,发现阳光数量发生改变,说明成功找到基址。
二、C语言代码实现阳光数量的修改
这里要用到windows.h文件里的API,墙裂建议去MSDN了解一下,这里只做简单介绍,因为懒
一点题外话:虽然平时可能接触不到windows.h,但是要知道windows.h其实是Windows API的头文件,它包含的东西非常有用,可以实现很多有趣的小功能,比如打开网页,实现按键,鼠标点击,QQ消息轰炸 等等
先放代码
#include <windows.h>
#include <stdio.h>
#include <pthread.h>
int main()
{
HWND hwnd = FindWindow(NULL, "植物大战僵尸中文版"); //找到窗口
if(hwnd == 0)
{
printf("获取句柄失败");
return -1;
}
DWORD dwPID = 0;
GetWindowThreadProcessId(hwnd, &dwPID); //获取进程标识
if (dwPID == 0)
{
printf("获取PID失败");
return -1;
}
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, dwPID);
if (hProcess == NULL)
{
printf("进程打开失败\n");
return -1;
}
DWORD dwSize = 0;
DWORD SunShineBaseAddress = 0x006A9F38; //基址
DWORD SunShineBaseAddressValue = 0; //基址值
if (0 == ReadProcessMemory(hProcess, (LPVOID)SunShineBaseAddress, &SunShineBaseAddressValue, sizeof(DWORD), &dwSize))
{
printf("静态址获取失败\n");
return -1;
}
DWORD SunShineOffsetFirst = 0x768; //一级偏移
DWORD SunShineOffsetFirstValue = 0; //一级偏移值
if (0 == ReadProcessMemory(hProcess, (LPVOID)(SunShineBaseAddressValue + SunShineOffsetFirst), &SunShineOffsetFirstValue, sizeof(DWORD), &dwSize))
{
printf("一级偏移获取失败\n");
return -1;
}
DWORD SunShineOffsetSecond = 0x5560; //二级偏移
DWORD SunShineNum=0; //二级偏移值,即阳光数量
if (0 == ReadProcessMemory(hProcess, (LPVOID)(SunShineOffsetFirstValue + SunShineOffsetSecond), &SunShineNum, sizeof(DWORD), &dwSize))
{
printf("二级偏移获取失败\n");
return -1;
}
int modifySunshine;
printf("SunShineNum:%d\n", SunShineNum);
printf("Please input the amount of sunshine you want:");
scanf("%d", &modifySunshine);
WriteProcessMemory(hProcess, (LPVOID)(SunShineOffsetFirstValue + SunShineOffsetSecond), &modifySunshine, sizeof(DWORD), &dwSize);
printf("modified successfully!");
CloseHandle(hProcess);
return 0;
}
可以看到,代码思路非常简单:获取对应窗口句柄——获取进程标识——打开进程——通过基址和偏移量找到存放阳光数量的地址——对地址存储内容修改——成功修改并退出。
下面是对用到的函数的一些简单介绍:
HWND FindWindow(LPCTSTR lpClassName,LPCTSTR lpWindowName):
返回与指定字符串相匹配的窗口类名或窗口名的最顶层窗口的窗口句柄;
第一个参数 lpClassName 通常为NULL;第二个参数 lpWindowName 为窗口名称,可用中文表示。
若查找成功,则返回对应的窗口句柄;否则返回INVALID_HANDLE_VALUE。
DWORD GetWindowThreadProcessId(HWND hWnd,LPDWORD lpdwProcessId):
一种计算机函数,功能是找出某个窗口的创建者(线程或进程),返回创建者的标志符。
第二个参数 lpdwProcessId 是存放进程号的变量。
而返回值是对应的线程号。
HANDLE OpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId):
打开一个已存在的进程对象,并返回进程的句柄.
第一个参数 dwDesiredAccess 表示用户想要得到的访问权限(标志)。
第二个参数 bInheritHandle 判断是否继承句柄。
第三个参数 dwProcessId 获取进程标示符。
BOOL ReadProcessMemory(HANDLE hProcess,LPCVOID lpBaseAddress,LPVOID lpBuffer,DWORD nSize,LPDWORD lpNumberOfBytesRead):
一种内存操作函数,其作用为根据进程句柄读入该进程的某个内存空间。
第一个参数 hProcess 为远程进程句柄,即被读取者。
第二个参数 lpBaseAddress 为远程进程中内存地址,通过这个参数确定从具体何处读取。
第三个参数 lpBuffe 为本地进程中内存地址. 函数将读取的内容写入此处。
第四个参数 nSize 为要写入的字节数。
第五个参数 lpNumberOfBytesRead 为实际传送的字节数,函数返回时报告实际写入多少。
BOOL WriteProcessMemory(HANDLE hProcess,LPVOID lpBaseAddress,LPVOID lpBuffer,DWORD nSize,LPDWORD lpNumberOfBytesWritten):
往往与 ReadProcessMemory()一起使用,常用来写入某一进程的内存区域。
第一个参数 hProcess 为进程的句柄。
第二个参数 lpBaseAddress 要写入的内存首地址。
第三个参数 lpBuffer 指向要写入的数据的指针。
第四个参数 nSize 为要写入的字节数,通常以字节为单位。
第五个参数 lpNumberOfBytesWritten 为实际数据的长度。
BOOL CloseHandle(HANDLE hObject):
关闭一个内核对象。其中包括文件、文件映射、进程、线程、安全和同步对象等。
若成功关闭,则返回true。
三、结果展示
结束语
CE+C语言制作PVZ修改器系列的第一篇就到这里了,虽然只是之前的一时心血来潮想写这样一个系列,但还是要有始有终,指只要我忘得彻底就可以结束了 。
CE+C语言制作PVZ修改器(二)准备写“分别用OD和C语言实现阳光的自动收集”,不定期更新。