原贴:
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为止,我顿时 愤怒了 对他刮目相看!
于是我想写一个集大成的修改器,可以模拟整个游戏界面,看到地图上的所有单位,可以点击任何想改的数据然后改掉,切换到游戏时已经改好……做一个这样的修改器好像难度也不会很大,但是我还很忙,所以就不要这么麻烦了(雾很大)。
这两天我趁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了吧!