免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!
本次游戏没法给
内容参考于:微尘网络安全
工具下载:
链接:https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd=6tw3 提取码:6tw3 复制这段内容后打开百度网盘手机App,操作更方便哦
上一个内容:37.x86游戏实战-XXX遍历怪物数组
为了后续方便,现在要做一些准备工作
首先打开我们的C++项目
添加一个类,如下图
类名wetool
然后来到wetool.cpp文件里写下图中的代码(完整的代码放到后面了,可以复制粘贴)
然后右击函数名,然后选择创建声明/定义,所有函数都点一遍
点完之后wetool.h文件里就有了函数声明
然后来到pch.h文件中,写下图红框里的内容 #include "wetool.h"
然后写测试代码,如下图红框
然后重新编译
编译完然后使用管理员打开Dbgview.exe
然后用管理员打开wctool.exe注入dll
效果图:
代码:
pch.h文件现在的内容
// pch.h: 这是预编译标头文件。
// 下方列出的文件仅编译一次,提高了将来生成的生成性能。
// 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。
// 但是,如果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。
// 请勿在此处添加要频繁更新的文件,这将使得性能优势无效。
#ifndef PCH_H
#define PCH_H
// 添加要在此处预编译的标头
#include "framework.h"
#include "wetool.h"
#endif //PCH_H
新加wetool.h文件,现在的内容
#pragma once
void call_logW(wchar_t* pformat, ...);
void call_loaA(char* pszFormat, ...);
DWORD ReadDword(DWORD* a);
WORD ReadWord(WORD* a);
FLOAT ReadFloat(FLOAT* a);
新加wetool.cpp文件,现在的内容:
#include "pch.h"
/*
... 这样的写法call_log函数是可以传入无数个参数(这种参数被称为可变参数)
这种有可变参数的函数它的参数最少要有两个,因为可变参数在设计的时候是
以函数有一个必传参数和可不必传参数(也就是可变参数)的思路进行的设计
所以要用可变参数最少要有两个参数一个必传一个可变参数
带入下方函数必传参数就是pformat,不必传参数或位置个数参数就是...
*/
void call_logW(wchar_t * pformat, ...) {
// 用来存放格式化之后的字符串
wchar_t spformat[1024];
// 可变参数列表,也就是...的类型
va_list va;
/*
通过 pformat 它获取...的位置,va_start宏会把pformat后面一个参数
的地址给 va(这样就找到了可变参数列表)
*/
va_start(va, pformat);
/*
处理字符串,wvsprintfW函数让call_logW有了printf的功能
也就是会自动把%s、%ws、%d等这些符号转成变量里的值
然后%s、%ws、%d等这些东西后续用的时候会逐步写说明
%s单字节字符串,也就是char*类型的字符串
%ws宽字节字符串,也就是wchar_t类型的字符串
%d是4字节整数
*/
wvsprintfW(spformat, pformat,va);
// 打印日志
OutputDebugStringW(spformat);
// 可变参数指针设置为空(也就是NULL)
va_end(va);
}
// ... 这样的写法call_log函数是可以传入多个参数的,详情看使用
void call_loaA(char * pszFormat, ...) {
// 用来存放格式化之后的字符串
char szbufFormat[0x1000];
// 可变参数列表,也就是...的类型
va_list argList;
/*
通过 pszFormat 它获取...的位置,va_start宏会把pszFormat后面一个参数
的地址给 argList(这样就找到了可变参数列表)
*/
va_start(argList, pszFormat);
/*
处理字符串,vsprintf_s函数让call_logA有了printf的功能
*/
vsprintf_s(szbufFormat, pszFormat, argList);
// 打印日志
OutputDebugStringA(szbufFormat);
// 可变参数指针设置为空(也就是NULL)
va_end(argList);
}
// 给 ReadDword 函数传一个地址,它给返回一个int的数据(4字节大小的数字)
DWORD ReadDword(DWORD* a) {
// IsBadReadPtr判断一个内存地址是否可读
if (IsBadReadPtr(a, sizeof(DWORD)) == 0)
// 内存地址可读就把内存地址里放的数据拿出来
return *(DWORD*)a;
return 0;
}
// 给 ReadWord 函数传一个地址,它给返回一个short的数据(2字节大小的数字)
WORD ReadWord(WORD* a) {
// IsBadReadPtr判断一个内存地址是否可读
if (IsBadReadPtr(a, sizeof(WORD)) == 0)
// 内存地址可读就把内存地址里放的数据拿出来
return *(WORD*)a;
return 0;
}
// 给 ReadFloat 函数传一个地址,它给返回一个float的数据(小数、浮点型据)
FLOAT ReadFloat(FLOAT* a) {
// IsBadReadPtr判断一个内存地址是否可读
if (IsBadReadPtr(a, sizeof(FLOAT)) == 0)
// 内存地址可读就把内存地址里放的数据拿出来
return *(FLOAT*)a;
return 0;
}
MyStrust.cpp文件现在的内容,修改了InitMy函数
#include "pch.h"
#include "MyStrust.h"
void MyStrust::InitMy()
{
// [[0x1AB7CDC]+0x258] 名字
/*
*这个符号表示地址,在c++中被称为指针或指针类型
int表示4字节数字
int*就表示指针类型的int
(int*)0x1AB7CDC;这样表示0x1AB7CDC地址里的内容是4字节的数字
*/
int* address = (int*)0x1AB7CDC;
/*
取出地址中的值,也就是取出0x1AB7CDC里的内容
*address这样在左边只写一个*表示取内存地址里的值
也就是取address它的值,address是0x1AB7CDC,也就是取0x1AB7CDC它的值
*/
int addressValue = *address;
/*
(*(int*)addressValue)意思是
把addressValue转成int*,也就是把
addressValue的值当成内存地址,addressValue的值现在是[0x1AB7CDC]+0x258这个
现在这个地址里面的值是名字的地址,所以在左边加了一个*让把名字的地址去除了出来
取出来之后就得到了名字,名字是UNICODE类型,UNICODE又被称为宽字节,宽字节的数据是用两个字节描述一个文字或字母
在c++里wchar_t类型就是UNICODE
然后在c++中名字这种数据被称为字符串,如果要用字符串必须用指针类型也就是wchar_t*
右边加上*让wchar_t变成指针类型的wchar_t,才能在c++中使用字符串
*/
this->My.Name = (wchar_t*)(*(int*)(addressValue+0x258));
// 一般函数名后面是W就表示有UNICODE,也就是要用宽字节
// OutputDebugStringW(this->My.Name);
// L""这两个"之间表示字符串,L""表示这个字符串是宽字节(使用Unicode编码)
call_logW(L"人物姓名=%ws,测试=%ws", this->My.Name,L" 52am");
// [[0x1AB7CDC]+0x18C]x坐标
// [[0x1AB7CDC]+0x190]y坐标
/*
取出地址中的值,也就是取出0x1AB7CDC里的内容
*address这样在左边只写一个*表示取内存地址里的值
也就是取address它的值,address是0x1AB7CDC,也就是取0x1AB7CDC它的值
*/
this->My.X= *(float*)(addressValue + 0x18C);
this->My.Y = *(float*)(addressValue + 0x190);
char buf[256] = { 0 };
// 拼接文字,%f表示拼接一个小数(单浮点数)
sprintf(buf, "x=%f;y=%f", this->My.X, this->My.Y);
OutputDebugStringA(buf);
// [[0x1AB7CDC]+0x36A0]血量
this->My.Blood = *(int*)(addressValue + 0x36A0);
// 拼接文字,%d表示拼接一个整数(32位的整数)
sprintf(buf, "血量 = %d", this->My.Blood);
OutputDebugStringA(buf);
}
void MyStrust::UseObject(DWORD object)
{
object += 3; // 背包物品序号
/*
try的作用
如果
__asm {
pushad
push object
mov ecx, 0x1A5FB24 // 背包基址
mov ecx, [ecx]
mov eax, 0x7B9130 // 使用物品的函数地址
call eax
popad
}
这个代码运行过程中出现错误了,我不会让游戏崩溃,出现错误之后会执行
catch (...) {
OutputDebugStringA("MyStrust::UseObject error");
}
这个catch里面的代码,现在也就是执行OutputDebugStringA("MyStrust::UseObject error");这一行
*/
try {
__asm {
pushad
push object
mov ecx, 0x1A5FB24 // 背包基址
mov ecx, [ecx]
mov eax, 0x7B9130 // 使用物品的函数地址
call eax
popad
}
}
catch (...) {
OutputDebugStringA("MyStrust::UseObject error");
}
}
void MyStrust::ChangeBlooad(int v)
{
try {
__asm {
pushad
push 0
push 0
push 0
push v
mov ecx, 0x1AB7CDC // 里面有我们玩家角色数据的基址
mov ecx, [ecx]
mov eax, 0x8174E0 // 使用物品的函数地址
call eax
popad
}
}
catch (...) {
OutputDebugStringA("MyStrust::ChangeBlooad error");
}
}
上方的代码不全,只有手写的代码
完整代码:以 36.x86游戏实战-C++代码实现锁血 它的代码为基础进行修改
链接:https://pan.baidu.com/s/1W-JpUcGOWbSJmMdmtMzYZg?pwd=q9n5
提取码:q9n5
复制这段内容后打开百度网盘手机App,操作更方便哦