梦醒暗黑廿年
夢(mèng)醒暗黑廿年
向抗擊疫情的英雄們致敬
宇春秋
卌年未有過的閉門養(yǎng)豬生活,無病呻吟回憶年少游戲時(shí)光。
從英雄無敵三到文明四,突然想起廿年前通宵在網(wǎng)吧干暗黑二單機(jī)的日子。那時(shí)候什么都不會(huì),一點(diǎn)支配骷髏都不點(diǎn),一個(gè)人帶著一大群骷髏干普通蟲子,整個(gè)網(wǎng)吧看著我半個(gè)小時(shí)磨死都瑞爾的轟動(dòng),那一瞬間的滿足,到廿年后的今天都?xì)v歷在目。重玩暗黑二的時(shí)候,正好某網(wǎng)開劇毒世界,帶著廿年的回憶,懵懂的沖了進(jìn)去,然后在G戰(zhàn)隊(duì)群里看大佬們普及基本知識(shí),一點(diǎn)一點(diǎn)的從零開始學(xué)符文之語(yǔ),BUG殺,隔河殺…。
肝了一個(gè)月之后感覺心潮澎湃,只是每次蠻子手動(dòng)BO得太麻煩,本想找個(gè)能自動(dòng)BO的地圖,網(wǎng)上找了一圈竟然沒有,于是撿起OD想自己做個(gè)補(bǔ)丁,沒想到一補(bǔ)就補(bǔ)了半個(gè)月,而且補(bǔ)出了這個(gè)系列(暫定叫系列吧,雖然絕大部分?jǐn)?shù)據(jù)都有了,但第一次寫文章,還不知道能有幾篇:D)。
本系列只做學(xué)習(xí)交流,請(qǐng)不要用于其它非法目地。
另感謝sting大神,第一步的基礎(chǔ)研究就是從讀d2hackmap開始的,由于網(wǎng)上找了很久都沒找到免費(fèi)的1.13c版本源代碼,只能隨便找了個(gè)對(duì)應(yīng)1.09版本的代碼研究,后來還找了個(gè)d2hackmap2.24也就是對(duì)應(yīng)1.11b版本的代碼研究,兩個(gè)版本的代碼和文件結(jié)構(gòu)變化很大,分析的時(shí)候兩個(gè)版本可能有點(diǎn)混,但最后出的結(jié)果是好。
第一篇:地圖篇
開始真沒想過從地圖開始研究起,起初只是想BO得找到玩家,也就是遍歷周圍數(shù)據(jù),結(jié)果人物數(shù)據(jù)還沒找到,反而把地圖數(shù)據(jù)找出來了。
一、遍歷數(shù)據(jù)。
首先想到的是不開地圖進(jìn)游戲,小地圖上各個(gè)怪是沒有文字提示的,開地圖就有BOSS、精英怪和玩家的名字提示,所以這事肯定是地圖做的。
翻d2hackmap的代碼,里面正好有DrawAutomapCellPatch這個(gè)函數(shù),看函數(shù)名應(yīng)該是做這事的。仔細(xì)研讀了下,發(fā)現(xiàn)…看不懂,對(duì)這個(gè)游戲的數(shù)據(jù)一點(diǎn)研究沒有,硬來撞代碼,肯定頭疼。但沒辦法,先簡(jiǎn)單的分析一下。
函數(shù)的原型是這樣的:
里面有xpos和ypos,坐標(biāo)嘛,那pCellContext肯定就是地圖元素的結(jié)構(gòu)了。函數(shù)里面有個(gè)常量CELLNO_WAYPOINT,定義的是
#define CELLNO_WAYPOINT 307找個(gè)能用地圖加載,OD上去在d2hackmap模塊中搜索常量0x 133(十進(jìn)制307)。
在d2hackmap+ 0x15E60(不同的地圖偏移不同,請(qǐng)對(duì)應(yīng)自己的地圖分析,以后涉及hackmap模塊的地址不在做說明)這個(gè)函數(shù)中有使用到這個(gè)常量。
在這個(gè)函數(shù)中所有的數(shù)據(jù)都用到了堆棧,而且用到的數(shù)據(jù)和地址沒有關(guān)系,果斷CTRL+F9返回上一層看有沒有收獲。
返回到的上一層是D2Client.dll,原型如下:
在6FB104F8有這樣的一行代碼:mov ecx, dword ptr [ecx+0x10],翻譯成C語(yǔ)言就是:
Addr=*(ULONG *)(Addr+0x10)看樣子不是鏈表就是二叉樹。
那么最初的ecx地址是哪兒來的的呢?回到D2Client的調(diào)用函數(shù)頭6FB10250下斷,結(jié)果很迷茫,怎么返回的還是這個(gè)函數(shù)呢?
這種情況只有一種可能:遞歸調(diào)用。如果真是遞歸調(diào)用那么數(shù)據(jù)結(jié)構(gòu)很大可能就是二叉樹了,畢竟二叉樹的經(jīng)典遍歷就是遞歸嘛!
到D2Client模塊頭,CTRL+F搜索call 6FB10250,看是哪兒最初調(diào)用這個(gè)遞歸的。
第一次搜索到的還是在6FB10250函數(shù)中,CTRL+L繼續(xù)搜索,找到原型如下:
看樣子遍歷了三個(gè)地址,分別是[[0x6FBCC1C4]+0x08], [[0x6FBCC1C4]+0x0C], [[0x6FBCC1C4]+0x10]。那么基本上可以確定以下三點(diǎn):
1、遍歷的基址是0x6FBCC1C4,也就是D2Client+0x11C1C4。
2、需要遍歷三個(gè)地址里面的數(shù)據(jù),分別是基址的0x08,0x0C,0x10三個(gè)偏移里的值。
3、再次回到6FB10250,發(fā)現(xiàn)遍歷時(shí)下一數(shù)據(jù)分別儲(chǔ)存在0x0C和0x10地址,那么可以99%確定是數(shù)據(jù)二叉樹儲(chǔ)存。
二、坐標(biāo)信息
基本數(shù)據(jù)有了,但最關(guān)鍵的坐標(biāo)呢?
不急,回到D2Client的6FB10250的函數(shù)看代碼,原型如下:
看6FB102A6的movsx eax, word ptr [ecx+0x6] ,還記得嗎?ecx是地圖元素的地址,word ptr [ecx+0x6]應(yīng)該是個(gè)short型的數(shù)據(jù),對(duì)應(yīng)的就是xpos,那么word ptr [ecx+0x8]對(duì)應(yīng)的就是ypos了。然后還有一些shl,div計(jì)算就不管了,大概應(yīng)該是xpos(ypos)*5<<1/ [0x6FBA16B0],直接套匯編吧。
沒讀過什么書,沒專門學(xué)過編程,所以代碼很挫,看個(gè)大概意思就好(英語(yǔ)不好,很多單詞都是BAIDU查的:L)。代碼如下:
三、畫出地圖
1、由于暗黑2的坐標(biāo)有負(fù)數(shù),所以找出最小坐標(biāo)和最大坐標(biāo),方便畫出圖形。
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是墻的數(shù)據(jù)for(ULONG i=0;i<arrMapCell.size();i++){ULONG Addr=arrMapCell[i];if (IsBadReadPtr((LPVOID)Addr,0x100) == 0){int x=0,y=0;_asm{pushadmov ecx,Addrmovsx eax, word ptr [ecx+6]mov ebx, MiniMapCellLenlea eax, dword ptr [eax+eax*4]shl eax, 1cdqidiv ebxmov edi, eaxmov x,edimov ecx,Addrmovsx eax, word ptr [ecx+8]mov ebx, MiniMapCellLenlea eax, dword ptr [eax+eax*4]shl eax, 1cdqidiv ebxmov edi, eaxmov y,edipopad}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{pushadmov ecx,Addrmovsx eax, word ptr [ecx+6]mov ebx, MiniMapCellLenlea eax, dword ptr [eax+eax*4]shl eax, 1cdqidiv ebxmov edi, eaxmov x,edimov ecx,Addrmovsx eax, word ptr [ecx+8]mov ebx, MiniMapCellLenlea eax, dword ptr [eax+eax*4]shl eax, 1cdqidiv ebxmov edi, eaxmov y,edipopad}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{pushadmov ecx,Addrmovsx eax, word ptr [ecx+6]mov ebx, MiniMapCellLenlea eax, dword ptr [eax+eax*4]shl eax, 1cdqidiv ebxmov edi, eaxmov x,edimov ecx,Addrmovsx eax, word ptr [ecx+8]mov ebx, MiniMapCellLenlea eax, dword ptr [eax+eax*4]shl eax, 1cdqidiv ebxmov edi, eaxmov y,edipopad}x=x-MinX;y=y-MinY;image.SetPixel(x,y,RGB(0,255,0));}}image.ReleaseDC();image.Save("C:\\map.bmp"); }3、畫完之后發(fā)現(xiàn)大概能看出地圖的樣子了,但每個(gè)點(diǎn)之間的差距有點(diǎn)大,不能成一個(gè)封閉的地圖圖形,我的做法是把每個(gè)地圖坐標(biāo)點(diǎn)除以一個(gè)值,走廊窄的地圖/2,大地圖/4。然后判斷斜角點(diǎn)是否有數(shù)據(jù),如果有數(shù)據(jù)就把周圍點(diǎn)補(bǔ)齊,地圖就封閉起來了。望高手指點(diǎn)怎么處理更好!結(jié)果如下:
本人原創(chuàng),轉(zhuǎn)載請(qǐng)注明。
第一篇地圖篇完。(第二篇NPC篇待續(xù))
總結(jié)
- 上一篇: extremeComponents资料
- 下一篇: 提供博客里提到的几个程序的下载地址