012 背包二叉树遍历分析和代码编写

本文详细解析了游戏背包系统的逆向工程过程,包括如何遍历背包数据结构、获取物品属性及名称等关键信息,并提供了具体的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背包属性遍历

在这里插入图片描述

从物品数量入手,搜索2字节

在这里插入图片描述

筛选出唯一的值

在这里插入图片描述

下两字节的访问断点,鼠标移动到物品上面,断点断下

物品数量=r14+0x10

这里就能得到一个+0x10的偏移。然后记录下每层的返回地址

在这里插入图片描述

r14来源于r9

在这里插入图片描述

r9来源于rsi

在这里插入图片描述

rsi来源于r9

在这里插入图片描述

r9来源于r14

在这里插入图片描述

r14来源于rax

在这里插入图片描述

rax来源于rbx,rbx来源于上面的call

在这里插入图片描述

继续进call里面追,里面有个循环就是二叉树

在这里插入图片描述

追rax的来源

物品数量=[rax+0x20]+0x10

在这里插入图片描述

物品数量=[[rax]+0x20]+0x10

在这里插入图片描述

物品数量=[rdx+0x20]+0x10

在这里插入图片描述

rdx来源于rax,然后这个位置是一个二叉树

在这里插入图片描述

rax来源于rdx+8

在这里插入图片描述

rdx来源于一个数组

[[r9+rcx*8+0x9FC]+0x8]

在这里插入图片描述

r9来源于rcx

在这里插入图片描述

rcx来源于一个基地址

[[0x00007FF637D57050+rcx*8+0x9FC]+0x8]

继续追rcx

在这里插入图片描述

rcx来源于rax+rax*4

[[0x00007FF637D57050+rax+rax*4*8+0x9FC]+0x8]

在这里插入图片描述

rax来源于dx

在这里插入图片描述

dx来源于[rbp+0x30],这个地址往上追会追到一个常量。这个数值1代表背包,0代表装备栏

物品名字库遍历

接着我们需要去找到物品名称,一般物品名称是通过一个call来查找的,这个call会传入物品ID,返回物品的名称。我们从物品的名称入手来找到这个call

在这里插入图片描述

直接搜索物品名称

在这里插入图片描述

下断,让断点断下

在这里插入图片描述

返回发现是一个系统函数,我们要追r10的来源。

在这里插入图片描述

r10来源于rdx,返回上层

在这里插入图片描述

rdx来源于rsi

在这里插入图片描述

rsi来源于r8

在这里插入图片描述

返回上层,r8来源于[rdi]

在这里插入图片描述

rdi来源于rcx

在这里插入图片描述

再返回上层,rcx来源于r10

在这里插入图片描述

r10来源于rdx

在这里插入图片描述

rdx来源于[rcx+0xBC]

[[rcx+0xBC]+0]

在这里插入图片描述

rcx来源于rax,rax来源于rdx

在这里插入图片描述

再返回,rdx来源于r12

在这里插入图片描述

r12来源于r8

在这里插入图片描述

r8来源于r13

在这里插入图片描述

r13来源于rax。接着来分析这个call

在这里插入图片描述

这个call有两个参数,rcx是一个基地址,edx传入的是物品ID。那么我们就可以通过这个call,来拿到返回值

[[rax+0xBC]+0]

然后再通过偏移表达式,拿到物品名称。

继续追下rax

在这里插入图片描述

rax来源于[rax+0x20],来源于[rax]

在这里插入图片描述

来源于rsp+0x8

在这里插入图片描述

来源于r8

[[[r8+0x20]+0xBC]+0]

在这里插入图片描述

r8来源于rax,然后就到了一个二叉树,rax来源于[r8+0x8],r8来源于[rcx+0x1A14]

在这里插入图片描述

然后来源于一个基地址

名称二叉树:[[0x00007FF66C047050+0x1A14]+0x8]
+0 左子树
+0x10 右子树
+0x29 结束标志
+0x18 物品ID
+0x20 物品对象

名称偏移:[[[root+0x20]+0xBC]+0]

物品的名字可以通过调用call来获取,也可以通过遍历拿到整个名字库

数据整理

mov edx,r15d                                 |
lea rcx,qword ptr ds:[0x7FF66C047050]        | 名字库二叉树基地址
call 0x7FF66A16DDA0                          | 通过物品ID取物品名称-----------------------------背包物品的二叉树----------------------------
二叉树根节点:[[0x00007FF637D57050+rax*5*8+0x9FC]+0x8]
二叉树基地址偏移:0x2F67050

rax是背包序号
0 装备栏
1 主背包
2 资源包1
3 资源包2
4 资源包3

+0 左子树
+0x10 右子树
+0x29 结束标志
+0x18 物品的下标


物品数量=[root+0x20]+0x10
+0x20 对象
对象+0x10 物品数量 DWORD	
对象+0x14 最大数量 DWORD	
对象+0xE 物品位置 BYTE
对象+8 物品ID DWORD

通过二叉树拿对象 通过对象拿属性

-----------------------------物品名称的二叉树----------------------------
名称二叉树:[[0x00007FF66C047050+0x1A14]+0x8]
偏移:0x2F67050
+0 左子树
+0x10 右子树
+0x29 结束标志
+0x18 物品ID
+0x20 物品对象

名称偏移:[[[root+0x20]+0xBC]+0]
通过二叉树拿ID 通过ID拿名称



----------------------------通过物品ID获取物品名称----------------------------
mov edx,r15d                                 |
lea rcx,qword ptr ds:[0x7FF66C047050]        | 名字库二叉树基地址
call 0x7FF66A16DDA0                          | 通过物品ID取物品名称


名字库二叉树基地址偏移:0x2F67050
通过物品ID取物品名称call偏移:0x108DDA0
名称:[[rax+0xBC]+0]

代码编写

背包遍历的代码和周围遍历很相似,这里直接给出相关代码

GameData.h

//背包遍历
_stuObjs GetBagDatas();

//进入背包遍历
void EnterBagDataTree(DWORD dwRoot, _stuObjs& alllist);

//获取每个节点的背包数据
void GetBagDataObjInfo(DWORD dwNode, _stuObjs& alllist);

GameData.cpp

//背包遍历
_stuObjs GetBagDatas()
{
	_stuObjs baglist;

	//获取二叉树根节点(这里只遍历主背包 1代表主背包)
	QWORD dwRootAddr = g_GameAddr + AroundAndBagDataTree + 1 * 5 * 8 + 0x9FC;
	DWORD dwBagTreeToot = ReadDword(dwRootAddr);
	dwBagTreeToot = ReadDword(dwBagTreeToot + 0x8);

	//进入背包遍历
	EnterBagDataTree(dwBagTreeToot, baglist);

	return baglist;
}

//进入背包遍历
void EnterBagDataTree(DWORD dwRoot, _stuObjs& alllist)
{
	//左子树
	DWORD dwLeftNode = ReadDword(dwRoot + 0);
	//右子树
	DWORD dwRightNode = ReadDword(dwRoot + 0x10);
	//标志位
	BYTE bFlag = ReadBYTE(dwRoot + 0x29);

	//标志位为0开始遍历
	if (bFlag == 0)
	{
		//获取对象里面的数据
		GetBagDataObjInfo(dwRoot, alllist);

		//递归遍历左子树
		EnterBagDataTree(dwLeftNode, alllist);

		//递归遍历右子树
		EnterBagDataTree(dwRightNode, alllist);
	}
}

//获取每个节点的背包数据
void GetBagDataObjInfo(DWORD dwNode, _stuObjs& alllist)
{
	_stuObj stuBagData;

	//设置类型
	stuBagData.m_StuType = Em_Item;

	//对象
	stuBagData.m_Obj = ReadDword(dwNode + 0x20);

	//下标
	stuBagData.m_Item_Index = ReadDword(dwNode + 0x18);

	//物品ID
	stuBagData.m_ID = ReadDword(stuBagData.m_Obj + 0x8);

	//物品数量
	stuBagData.m_Item_Nownum = ReadDword(stuBagData.m_Obj + 0x10);

	//物品名字
	stuBagData.m_Name = Fn_GetGoodsNameByID(stuBagData.m_ID);

	//保存到容器
	alllist.m_data.push_back(stuBagData);
}

GameFunction.h

//通过物品ID获取物品名称
wstring Fn_GetGoodsNameByID(DWORD ID);

GameFunction.cpp

//通过物品ID获取物品名称
std::wstring Fn_GetGoodsNameByID(DWORD ID)
{
	//1.1 先拿到二叉树的地址 也就是ECX的地址
	QWORD GoodsTreeAddr = g_GameAddr + AroundAndBagDataTree;
	//1.2 再拿到call地址
	QWORD GetGoodsNameCall = g_GameAddr + GetBagNameCall;

	//调用call
	QWORD qGoodObj = GameCall2(GoodsTreeAddr, ID, GetGoodsNameCall);

	//第一层偏移
	qGoodObj = ReadDword(qGoodObj + 0xBC);

	try
	{
		//1.5 读取名字
		if (qGoodObj)
		{
			return ReadWChar(qGoodObj);
		}
	}
	catch (...)
	{
		OutputDebugStringA("读取名字出错了");
	}


	return L"NULL";
}

实际效果如图:

在这里插入图片描述

到这里自动吃药所需要的数据都已经找齐了,下一节我们来完成自动吃药的功能。

Github:https://github.com/TonyChen56/GameReverseNote

完整代码:https://download.csdn.net/download/qq_38474570/79498815

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鬼手56

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值