2008年10月9日星期四

魔兽3内存修改器V5 For 1.22.0.6328,以及智力修改详解

原贴:http://tctianchi.yo2.cn/articles/%e9%ad%94%e5%85%bd3%e5%86%85%e5%ad%98%e4%bf%ae%e6%94%b9%e5%99%a8v5-for-12206328%ef%bc%8c%e4%bb%a5%e5%8f%8a%e6%99%ba%e5%8a%9b%e4%bf%ae%e6%94%b9%e8%af%a6%e8%a7%a3.html

距离上一次写魔兽修改器(V4在这里)已经有超过一年半的时间了, 这个期间有不少网友来信,但是我完全没有要再写一版的意思。包括像出现在本Blog的hyp发布了他自己的修改器(这里),包括暴雪出了1.22补丁,我都没有改。直到另一位网友给我写信(他没有留言,我假设他不希望自己的名字出现在这里)声称他已经能找到所有单位的HP为止,我顿时 愤怒了 对他刮目相看!

war3trainerv5.gif于是我想写一个集大成的修改器,可以模拟整个游戏界面,看到地图上的所有单位,可以点击任何想改的数据然后改掉,切换到游戏时已经改好……做一个这样的修改器好像难度也不会很大,但是我还很忙,所以就不要这么麻烦了(雾很大)。

这两天我趁glascholar同学帮我去解决电脑上没有导出并口接插件并且寄到苏州来的空档,做了这个V5修改器来针对1.22.0.6328版 的Warcraft 3。为了避免与hyp同学重复,我依然没有用内存搜索的方法来做,而是坚持了我以前的游戏代码篡改的思路。但是与V4不同的是,我花了很大力气实现了只注 入一次就能修改全部属性的特性。也就是说:我已经解决了单位各属性地址之间的关系。所以其他人也应该可以根据我的理论实现自己的内存搜索版的修改器。

修改器+源代码+手册打包:war3trainerv5.rar

[特性]

    V5版修改器具有以下特性:
  • 仅适用于魔兽3 1.22.0.6328,其他版本不用试也知道不行
  • 可以修改前10个玩家的钱,并且不用注入就能修改
  • 可以修改英雄的一些属性,每个英雄只需要注入一次,其他地址可以推算
  • V5的注入对一般单位无效,这反而变成V5修改器的缺陷,而V4版是可以改一般单位的。因为唯一的一次注入放在了英雄状态栏绘制的函数里面,而V4版是每个属性单独注入一次,所以没有这个问题。如果我愿意放下自尊,不去搞什么“只注入一次”的话,就可以解决这个问题,但是这样就无法炫耀技术了,对吧?
  • 智力修改完美解决,现在不可能找不到智力的地址了
  • 新增HP、MP,及其最大值的修改

[详解智力修改]
在V2版(这里)中,我曾经给过一个傻x的修改方案,现在给出更加精确的算法。为什么智力的修改与力量、敏捷不同。这是因为后两者的代码比较集中,而且代码也比较规则。例如力量的绘制代码如下:

6F353D7D mov ecx, [ebp+0D4h] eHeroMultiply = *(pAttribute1 + 0x35);
6F353D83 mov esi, [ebp+94h] vHeroPower = *(pAttribute1 + 0x25);
6F353D89 mov [esp+8ECh+eHeroMultiply], ecx  
6F353D8D lea ecx, [ebp+6Ch] vPowerAdd = sub_6F4634E0(pAttribute1 + 0x1B);
6F353D90 call sub_6F4634E0
6F353D95 lea edx, [esp+8ECh+eHeroMultiply]  
6F353D99 push edx  
6F353D9A mov edx, eax ePowerAdd = ZipNum_Encode_NotSure(&tmpInt1, vPowerAdd);
6F353D9C lea ecx, [esp+8F0h+tmpInt1]
6F353DA0 call ZipNum_Encode_NotSure
6F353DA5 mov edx, eax ePowerSum = ZipNum_Multiply_NotSure(&tmpInt2, ePowerAdd, &eHeroMultiply);
6F353DA7 lea ecx, [esp+8F0h+tmpInt2]
6F353DAB call ZipNum_Multiply_NotSure
6F353DB0 mov ecx, eax vPowerSum = ZipNum_Decode_NotSure(ePowerSum);
6F353DB2 call ZipNum_Decode_NotSure
6F353DB7 add eax, esi s578_SStrPrintf(
Buffer1,
0x40u
,
"%d"
,
vHeroPower
+ vPowerSum - *AttributeBiasCopy1
);
6F353DB9 sub eax, [ebx]
6F353DBB push eax
6F353DBC push offset aD_0 ; "%d"
6F353DC1 lea eax, [esp+8F4h+Buffer1]
6F353DC5 push 40h ; BufferSize
6F353DC7 push eax ; Buffer
6F353DC8 call s578_SStrPrintf
6F353DCD mov esi, [ebx]  
6F353DCF mov ecx, [esp+8FCh+GBufferCopy1] GBufferCopy2 = GBufferCopy1;
6F353DD3 add esp, 10h  
6F353DD6 add esi, [ecx] sumPower = *GBufferCopy1 + *AttributeBiasCopy1;
if
( sumPower )
{
6F353DD8 jz short loc_6F353E1B
6F353DDA test esi, esi
6F353DDC mov eax, offset aCff00ff00_1 ; " |CFF00FF00+" tmpString1 = " |CFF00FF00+";
6F353DE1 jg short loc_6F353DE8 if ( sumPower<= 0 )
6F353DE3 mov eax, offset aCffff0000_1 ; " |CFFFF0000" tmpString1 = " |CFFFF0000";
6F353DE8 push 40h ; MaxLength s503_SStrNCat(Buffer1, tmpString1, 0x40u);
6F353DEA push eax ; pSrc
6F353DEB lea edx, [esp+8F4h+Buffer1]
6F353DEF push edx ; pDest
6F353DF0 call s503_SStrNCat
6F353DF5 push esi s578_SStrPrintf(Buffer2, 0x40u, "%d|R", sumPower);
6F353DF6 push offset aDR ; "%d|R"
6F353DFB lea eax, [esp+8F4h+Buffer2]
6F353DFF push 40h ; BufferSize
6F353E01 push eax ; Buffer
6F353E02 call s578_SStrPrintf
6F353E07 add esp, 10h s503_SStrNCat(Buffer1, Buffer2, 0x40u);
6F353E0A push 40h ; MaxLength
6F353E0C lea ecx, [esp+8F0h+Buffer2]
6F353E10 push ecx ; pSrc
6F353E11 lea edx, [esp+8F4h+Buffer1]
6F353E15 push edx ; pDest
6F353E16 call s503_SStrNCat }

智力的代码在6F0DA9D0,内存位置不完全相同,感兴趣可以去看,本质上还是一样的。下面的步骤供修改器使用。但是我要说,如果只注入一次的话,最难改的不是智力,而是移动速度的话,你相信吗?这件事情详见附件里面的手册。这里讲智力。

第1步:搞到英雄的基址,可以在6F353D25拿到ESI,并且解开(即所谓解引用,或者寻址),并且做如下定义:
ESI记为ThisUnit
○ [ThisUnit + 1E4] 记为 UnitAttributes(这里用不到,改别的东西要用)
○ [ThisUnit + 1EC] 记为 HeroAttributes
这个1EC是怎么来的呢?
参考的代码:6F353D00
即:int __thiscall DrawHeroProperty(int *GameContext, int **Attributes, int *AttributeBias, unsigned int *GBuffer)
 

6F353D51 mov ebp, [eax + 1ECh] pAttribute = Attributes[0x7B]; // 7B * 4 = 1EC
…… …… ……
6F353EDE mov ecx, ebp vIntellect = getHeroIntellect_NotSure(pAttribute);
6F353EE0 call 6F0DA9D0

第2步:获得智力的游戏全局索引,定义:
○ [HeroAttributes + 7C + 2 * 4] 记为 Index1
○ [HeroAttributes + 7C + 3 * 4] 记为 ReferenceNumber1

参考的代码:6F0DA9D0
即:int __fastcall getHeroIntellect_NotSure(int* pAttribute)

6F0DA9DE lea ecx, [esi + 7Ch] vHeroIntellect = sub_6F4634E0(pAttribute + 0x1F);
// 1F * 4 = 7C
6F0DA9E1 call 6F4634E0

参考的代码:6F4634E0
即:int __fastcall sub_6F4634E0(int *base)
 

6F4634E0 mov edx, [ecx + 0Ch] return *(_DWORD *)(
getValueFromGame
(*(base + 2), *(base + 3))
+ 0x78
);
6F4634E3 mov ecx, [ecx + 8]
6F4634E6 call 6F03F180
6F4634EB mov eax, [eax + 78h]
6F4634EE retn

第3步:使用所谓<算法1>(6F4634E0),获得智力的地址。

步3.1:[6FAA4178] 记为 ThisGame
步3.2:[ThisGame + C 记为 ThisGameMemory
步3.3:[ThisGameMemory + Index1 * 8 + 4] 记为 Address1
步3.4:[Address1 + 18] 应当等于ReferenceNumber1,不相等游戏会异常(访问地址0),所以做修改器不用考虑这里
步3.5:Address1 + 78 作为地址,里面是智力

参考的代码:6F03F180
即:int __fastcall getValueFromGame(unsigned int nIndex, int ReferenceNumber)

…… …… result = *(_DWORD *)
(
*(_DWORD *)(dword_6FAA4178 + 0xC)
+ 8 * nIndex + 4
) &
(
(*(_DWORD *)
(*(_DWORD *)
(*(_DWORD *)(dword_6FAA4178 + 0xC)
+ 8 * nIndex + 4
)
+ 24
) != ReferenceNumber)
- 1
);
6F03F1DA mov eax, [esi + 0Ch]
6F03F1DD mov ecx, [eax + ecx * 8 + 4]
6F03F1E1 xor eax, eax
6F03F1E3 cmp [ecx + 18h], edx
6F03F1E6 pop edi
6F03F1E7 setnz al
6F03F1EA pop esi
6F03F1EB sub eax, 1
6F03F1EE and eax, ecx
6F03F1F0 retn return result;

需要特别指出的是,还有一个所谓<算法2>(6F468A20)也很重要,改移动速度要用到,详见我的手册。

[后记]
这两天我在game.dll、Storm.dll阅读了超过万行汇编。在这里严重感谢Hex-Rays这款插件,虽然有的时候也会越帮越忙,但是如果没有她,我肯定无法在短时间内完成这样的工作量。

有鉴于我此刻已经厌倦修改魔兽,所以这一回,应该不会再出V6了吧!

没有评论: