梦醒暗黑廿年

梦醒暗黑廿年

向抗击疫情的英雄们致敬

宇春秋
卌年未有过的闭门养猪生活,无病呻吟回忆年少游戏时光。
从英雄无敌三到文明四,突然想起廿年前通宵在网吧干暗黑二单机的日子。那时候什么都不会,一点支配骷髅都不点,一个人带着一大群骷髅干普通虫子,整个网吧看着我半个小时磨死都瑞尔的轰动,那一瞬间的满足,到廿年后的今天都历历在目。重玩暗黑二的时候,正好某网开剧毒世界,带着廿年的回忆,懵懂的冲了进去,然后在G战队群里看大佬们普及基本知识,一点一点的从零开始学符文之语,BUG杀,隔河杀…。
肝了一个月之后感觉心潮澎湃,只是每次蛮子手动BO得太麻烦,本想找个能自动BO的地图,网上找了一圈竟然没有,于是捡起OD想自己做个补丁,没想到一补就补了半个月,而且补出了这个系列(暂定叫系列吧,虽然绝大部分数据都有了,但第一次写文章,还不知道能有几篇:D)。
本系列只做学习交流,请不要用于其它非法目地。
另感谢sting大神,第一步的基础研究就是从读d2hackmap开始的,由于网上找了很久都没找到免费的1.13c版本源代码,只能随便找了个对应1.09版本的代码研究,后来还找了个d2hackmap2.24也就是对应1.11b版本的代码研究,两个版本的代码和文件结构变化很大,分析的时候两个版本可能有点混,但最后出的结果是好。
第一篇:地图篇
开始真没想过从地图开始研究起,起初只是想BO得找到玩家,也就是遍历周围数据,结果人物数据还没找到,反而把地图数据找出来了。
一、遍历数据。
首先想到的是不开地图进游戏,小地图上各个怪是没有文字提示的,开地图就有BOSS、精英怪和玩家的名字提示,所以这事肯定是地图做的。
翻d2hackmap的代码,里面正好有DrawAutomapCellPatch这个函数,看函数名应该是做这事的。仔细研读了下,发现…看不懂,对这个游戏的数据一点研究没有,硬来撞代码,肯定头疼。但没办法,先简单的分析一下。
函数的原型是这样的:

void __stdcall DrawAutomapCellPatch(CellContext *pCellContext, DWORD xpos, DWORD ypos, RECT *cliprect, DWORD bright)

里面有xpos和ypos,坐标嘛,那pCellContext肯定就是地图元素的结构了。函数里面有个常量CELLNO_WAYPOINT,定义的是

#define CELLNO_WAYPOINT 307

找个能用地图加载,OD上去在d2hackmap模块中搜索常量0x 133(十进制307)。
在d2hackmap+ 0x15E60(不同的地图偏移不同,请对应自己的地图分析,以后涉及hackmap模块的地址不在做说明)这个函数中有使用到这个常量。
在这个函数中所有的数据都用到了堆栈,而且用到的数据和地址没有关系,果断CTRL+F9返回上一层看有没有收获。
返回到的上一层是D2Client.dll,原型如下:

6FB104D9  |.  8B4424 20     |mov     eax, dword ptr [esp+0x20]
6FB104DD  |.  51            |push    ecx
6FB104DE  |.  8D5424 2C     |lea     edx, dword ptr [esp+0x2C]
6FB104E2  |.  52            |push    edx
6FB104E3  |.  55            |push    ebp
6FB104E4  |.  50            |push    eax
6FB104E5  |.  8D4C24 48     |lea     ecx, dword ptr [esp+0x48]
6FB104E9  |.  51            |push    ecx
6FB104EA  |.  E8 71594CA0   |call    d2hackma.0FFD5E60>>>>>>d2hackmap HOOK
6FB104EF  |.  8B4C24 18     |mov     ecx, dword ptr [esp+0x18]
6FB104F3  |>  3B6E 10       |cmp     ebp, dword ptr [esi+0x10]       ;  D2Client.6FAF4B50
6FB104F6  |.  7F 0F         |jg      short 6FB10507
6FB104F8  |.  8B49 10       |mov     ecx, dword ptr [ecx+0x10]>>>>关键点
6FB104FB  |.  85C9          |test    ecx, ecx
6FB104FD  |.  894C24 18     |mov     dword ptr [esp+0x18], ecx

在6FB104F8有这样的一行代码:mov ecx, dword ptr [ecx+0x10],翻译成C语言就是:

Addr=*(ULONG *)(Addr+0x10)

看样子不是链表就是二叉树。
那么最初的ecx地址是哪儿来的的呢?回到D2Client的调用函数头6FB10250下断,结果很迷茫,怎么返回的还是这个函数呢?
这种情况只有一种可能:递归调用。如果真是递归调用那么数据结构很大可能就是二叉树了,毕竟二叉树的经典遍历就是递归嘛!
到D2Client模块头,CTRL+F搜索call 6FB10250,看是哪儿最初调用这个递归的。
第一次搜索到的还是在6FB10250函数中,CTRL+L继续搜索,找到原型如下:

6FB10E05  |> \A1 C4C1BC6F   mov     eax, dword ptr [0x6FBCC1C4]
6FB10E0A  |.  8B48 08       mov     ecx, dword ptr [eax+0x8]
6FB10E0D  |.  8D5424 10     lea     edx, dword ptr [esp+0x10]
6FB10E11  |.  E8 3AF4FFFF   call    6FB10250>>>>>>>>调用1
6FB10E16  |.  E8 E55D4CA0   call    d2hackma.0FFD6C00
6FB10E1B  |?  90            nop
6FB10E1C  |.  8B49 0C       mov     ecx, dword ptr [ecx+0xC]
6FB10E1F  |.  8D5424 10     lea     edx, dword ptr [esp+0x10]
6FB10E23  |.  E8 28F4FFFF   call    6FB10250>>>>>>>>调用2
6FB10E28  |.  A1 C4C1BC6F   mov     eax, dword ptr [0x6FBCC1C4]
6FB10E2D  |.  8B48 10       mov     ecx, dword ptr [eax+0x10]
6FB10E30  |.  8D5424 10     lea     edx, dword ptr [esp+0x10]
6FB10E34  |.  E8 17F4FFFF   call    6FB10250>>>>>>>>调用3
6FB10E39  |.  A1 B0C1BC6F   mov     eax, dword ptr [0x6FBCC1B0]
6FB10E3E  |.  8B1D C4C1BC6F mov     ebx, dword ptr [0x6FBCC1C4]
6FB10E44  |.  48            dec     eax
6FB10E45  |.  8B43 04       mov     eax, dword ptr [ebx+0x4]

看样子遍历了三个地址,分别是[[0x6FBCC1C4]+0x08], [[0x6FBCC1C4]+0x0C], [[0x6FBCC1C4]+0x10]。那么基本上可以确定以下三点:
1、遍历的基址是0x6FBCC1C4,也就是D2Client+0x11C1C4。
2、需要遍历三个地址里面的数据,分别是基址的0x08,0x0C,0x10三个偏移里的值。
3、再次回到6FB10250,发现遍历时下一数据分别储存在0x0C和0x10地址,那么可以99%确定是数据二叉树储存。
二、坐标信息
基本数据有了,但最关键的坐标呢?
不急,回到D2Client的6FB10250的函数看代码,原型如下:

6FB102A6  |> \0FBF41 06     |movsx   eax, word ptr [ecx+0x6] 
6FB102AA  |.  8B2D B016BA6F |mov     ebp, dword ptr [0x6FBA16B0]
6FB102B0  |.  8D0480        |lea     eax, dword ptr [eax+eax*4]
6FB102B3  |.  D1E0          |shl     eax, 1
6FB102B5  |.  99            |cdq
6FB102B6  |.  F7FD          |idiv    ebp
6FB102B8  |.  8BF8          |mov     edi, eax
6FB102BA  |.  2B3D F8C1BC6F |sub     edi, dword ptr [0x6FBCC1F8]
6FB102C0  |.  0FBF41 08     |movsx   eax, word ptr [ecx+0x8]
6FB102C4  |.  8D0480        |lea     eax, dword ptr [eax+eax*4]
6FB102C7  |.  D1E0          |shl     eax, 1
6FB102C9  |.  99            |cdq
6FB102CA  |.  F7FD          |idiv    ebp

看6FB102A6的movsx eax, word ptr [ecx+0x6] ,还记得吗?ecx是地图元素的地址,word ptr [ecx+0x6]应该是个short型的数据,对应的就是xpos,那么word ptr [ecx+0x8]对应的就是ypos了。然后还有一些shl,div计算就不管了,大概应该是xpos(ypos)*5<<1/ [0x6FBA16B0],直接套汇编吧。
没读过什么书,没专门学过编程,所以代码很挫,看个大概意思就好(英语不好,很多单词都是BAIDU查的:L)。代码如下:

void GetMapCell(ULONG Addr,std::vector<ULONG> &arrMapCell)
{
	if (IsBadReadPtr((LPVOID)Addr,0x100) != 0) return ;
	arrMapCell.push_back(Addr);
	GetMapCell(*(ULONG *)(Addr+0x0c),arrMapCell);
	GetMapCell(*(ULONG *)(Addr+0x10),arrMapCell);
	return;
}

void GetD2DllBase()
{
	uD2ClientAddr=(ULONG)::GetModuleHandleA("D2Client.dll");
	uD2CommonAddr=(ULONG)::GetModuleHandleA("D2Common.dll");
	uD2WinAddr=(ULONG)::GetModuleHandleA("D2Win.dll");
	uD2Multi=(ULONG)::GetModuleHandleA("D2Multi.dll");
	uD2Launch = (ULONG)::GetModuleHandleA("d2launch.dll");
......
}

三、画出地图
1、由于暗黑2的坐标有负数,所以找出最小坐标和最大坐标,方便画出图形。

void InitCurMapInfo()
{
	std::vector<ULONG> arrMapCell;
	ULONG MapBase=*(ULONG *)(uD2ClientAddr+0x11C1C4);
	if (IsBadReadPtr((LPVOID)MapBase, 0x20) != 0)return;
	GetMapCell(*(ULONG *)(MapBase+0x0C),arrMapCell);//实际上0x0C是墙的数据
	int MiniMapCellLen=*(int *)(uD2ClientAddr+0xF16B0);
	int MinX=65535,MinY=65535;
	int MaxX=0,MaxY=0;
	for(ULONG i=0;i<arrMapCell.size();i++)
	{
		ULONG Addr=arrMapCell[i];
		if (IsBadReadPtr((LPVOID)Addr,0x100) == 0)
		{
			int x=0,y=0;
			_asm
			{
				pushad
					mov		ecx,Addr
					movsx   eax, word ptr [ecx+6]
					mov     ebx, MiniMapCellLen
					lea     eax, dword ptr [eax+eax*4]
					shl     eax, 1
					cdq
					idiv    ebx
					mov     edi, eax
					mov		x,edi
					mov		ecx,Addr
					movsx   eax, word ptr [ecx+8]
					mov     ebx, MiniMapCellLen
					lea     eax, dword ptr [eax+eax*4]
					shl     eax, 1
					cdq
					idiv    ebx
					mov     edi, eax
					mov		y,edi
					popad
			}
			if(x<MinX)MinX=x;
			if(y<MinY)MinY=y;
			if(x>MaxX)MaxX=x;
			if(y>MaxY)MaxY=y;
		}
	}
	siCurMapMinX=MinX;
	siCurMapMinY=MinY;
	siCurMapMaxX=MaxX;
	siCurMapMaxY=MaxY;
}

2、保存地图

void GetMapInfo()
{
	std::vector<ULONG> arrMapCell;
	ULONG MapBase=*(ULONG *)(uD2ClientAddr+0x11C1C4);
	int MiniMapCellLen=*(int *)(uD2ClientAddr+0xF16B0);

	InitCurMapInfo();
	int MinX=siCurMapMinX,MaxX=siCurMapMaxX,MinY=siCurMapMinY,MaxY=siCurMapMaxY;
	CImage image;
	image.Create(MaxX-MinX,MaxY-MinY,24);//Create的背景是黑色的
	for (int y=0; y<MaxY-MinY; y++)
	{
		BYTE *p=(BYTE *)image.GetPixelAddress(0,y);//黑色背景不好看,改成白色
		memset(p,255,(MaxX-MinX)*3);
	}
	HDC hdc=image.GetDC();
	SetBkMode(hdc,TRANSPARENT); 
	CFont font;
	font.CreateFontA(20,0,0,0,200,FALSE,FALSE,0,ANSI_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH|FF_SWISS,"Arial");
	::SelectObject(hdc,font.GetSafeHandle());

	GetMapCell(*(ULONG *)(MapBase+0x0C),arrMapCell);//0x0C是墙的数据
	for(ULONG i=0;i<arrMapCell.size();i++)
	{
		ULONG Addr=arrMapCell[i];
		if (IsBadReadPtr((LPVOID)Addr,0x100) == 0)
		{
			int x=0,y=0;
			_asm
			{
				pushad
					mov		ecx,Addr
					movsx   eax, word ptr [ecx+6]
					mov     ebx, MiniMapCellLen
					lea     eax, dword ptr [eax+eax*4]
					shl     eax, 1
					cdq
					idiv    ebx
					mov     edi, eax
					mov		x,edi
					mov		ecx,Addr
					movsx   eax, word ptr [ecx+8]
					mov     ebx, MiniMapCellLen
					lea     eax, dword ptr [eax+eax*4]
					shl     eax, 1
					cdq
					idiv    ebx
					mov     edi, eax
					mov		y,edi
					popad
			}
			x=x-MinX;
			y=y-MinY;
			image.SetPixel(x,y,RGB(255,0,0));
		}
	}

	arrMapCell.clear();
	GetMapCell(*(ULONG *)(MapBase+0x08),arrMapCell);//是地图附加元素,如水面
	for(ULONG i=0;i<arrMapCell.size();i++)
	{
		ULONG Addr=arrMapCell[i];
		if (IsBadReadPtr((LPVOID)Addr,0x100) == 0)
		{
			int x=0,y=0;
			_asm
			{
				pushad
					mov		ecx,Addr
					movsx   eax, word ptr [ecx+6]
					mov     ebx, MiniMapCellLen
					lea     eax, dword ptr [eax+eax*4]
					shl     eax, 1
					cdq
					idiv    ebx
					mov     edi, eax
					mov		x,edi

					mov		ecx,Addr
					movsx   eax, word ptr [ecx+8]
					mov     ebx, MiniMapCellLen
					lea     eax, dword ptr [eax+eax*4]
					shl     eax, 1
					cdq
					idiv    ebx
					mov     edi, eax
					mov		y,edi
					popad
			}

			x=x-MinX;
			y=y-MinY;
			if(*(WORD *)(Addr+4)>=4&&*(WORD *)(Addr+4)<=8)//0x08是地图元素,4-8是河水走不过去,需要画在地图上,其它的都画上去太乱不好看
			{
				image.SetPixel(x,y,RGB(0,0,255));
			}
		}
	}

	arrMapCell.clear();
	GetMapCell(*(ULONG *)(MapBase+0x10),arrMapCell);//不知道是什么,没注意
	for(ULONG i=0;i<arrMapCell.size();i++)
	{
		ULONG Addr=arrMapCell[i];
		if (IsBadReadPtr((LPVOID)Addr,0x100) == 0)
		{
			int x=0,y=0;
			_asm
			{
				pushad
					mov		ecx,Addr
					movsx   eax, word ptr [ecx+6]
					mov     ebx, MiniMapCellLen
					lea     eax, dword ptr [eax+eax*4]
					shl     eax, 1
					cdq
					idiv    ebx
					mov     edi, eax
					mov		x,edi

					mov		ecx,Addr
					movsx   eax, word ptr [ecx+8]
					mov     ebx, MiniMapCellLen
					lea     eax, dword ptr [eax+eax*4]
					shl     eax, 1
					cdq
					idiv    ebx
					mov     edi, eax
					mov		y,edi
					popad
			}
			x=x-MinX;
			y=y-MinY;
			image.SetPixel(x,y,RGB(0,255,0));
		}
	}
	image.ReleaseDC();
	image.Save("C:\\map.bmp");
}

3、画完之后发现大概能看出地图的样子了,但每个点之间的差距有点大,不能成一个封闭的地图图形,我的做法是把每个地图坐标点除以一个值,走廊窄的地图/2,大地图/4。然后判断斜角点是否有数据,如果有数据就把周围点补齐,地图就封闭起来了。望高手指点怎么处理更好!结果如下:
在这里插入图片描述
本人原创,转载请注明。
第一篇地图篇完。(第二篇NPC篇待续)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值