2008年10月27日星期一

魔兽3内存修改器 v6

原文

由于hsluoyz同学强烈要求我研究一下魔兽3 1.20e的HP摆放位置,以便达到他某个不可告人的目的,加上本人确实无法接受v5版修改器不能改一般单位的事实,所以昨天晚上作了这个v6修改器。这一次的重点是对3个版本的支持以及无注入的新思路。

war3trainerv6_2.gif

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

[新特性]
    v6版区别于v5版的特征:
  • 无注入的新思路:这一版实现了不修改游戏代码,当然更不是内存搜索,全部直接定位。游戏怎么运行,我就怎么拿到数据,十分猥琐。
  • 同时改多个单位:现在你可以一次选中几个单位,我会列出你选择了什么。
  • 又可以改单位了:既然单位可以改,建筑也就可以改。
  • 多版本支持:受不了某些不肯升级游戏的朋友了,现在支持1.20e、1.21、1.22.0.6328三个版本。
  • 修改单位的坐标:下图就演示了主基地平移。
  • 英雄技能:按照hyp的指示,加了最简单的技能修改,更加复杂的修改没有做。

war3trainerv6_1.jpg
主基地平移, 采矿速度提升;
注意到树丛中的大法师了吗?
另外建筑的阴影明显只是一张贴图

[关于选中单位ESI的获得]
以下以1.22为例说明我是怎么拿到玩家选择的所有单位。

另外我要正告hsluoyz同学:我怀疑你找来的那个静态指针不具有普遍意义。我愿意以一顿晚饭的代价跟你打赌。

获得选中列表并不困难,如果你不愿意用我的思路,你也可以直接在内存中搜索“.?AUCUnitListNode@@”获得ESI队列,或者用.\CPlayerWar3.cpp所在的段+90拿到链表基地址然后推算单位地址,都可以。但是我是不做内存搜索的,所以步骤略多:

  1. [6FAA2FFC] 记为A1
    参考:
    6F416B0A mov edi, 6FAA2FFC
    6F416B10 movzx ebx, word ptr [edi + 28]
    6F416B14 call 6F52F4A0
  2. [A1 + 58 + 4 * a2] 记为A2,a2取0
    参考:6F3A0564 mov eax, [ecx + eax * 4 + 58]
  3. [A2 + 34] 记为UnitListRoot
    参考:6F2CC0AE mov ebp, [eax + 34]
  4. 这个UnitListRoot的数据结构:
    struct UnitListRoot
    {
    BYTE NotSure[0x1F0];
    UnitListNode* Head, End;
    DWORD Length;
    }
    参考:6f415b95 mov eax, [ebx+1F4]
  5. UnitListNode的数据结构:
    struct UnitListNode
    {
    UnitListNode* Next;
    DWORD notNext;
    UnitBase* thisUnit;
    }
    其中notNext总是等于~Next,而thisUnit就是上次要找的ESI

[后记]
这一次我会说得比较小心:我不知道我还会不会更新这个修改器了。

2008年10月25日星期六

vb的kbhit()实现

这里是原文

数值分析考得如此简单,以至于感觉白复习了。

晚上,因为一些原因我很希望串口的数据能及时dump到硬盘上,可恶的是手头的串口调试工具都不怎么顺手,我气愤了,所以决定写一个题为《不就是个串口嘛!》的程序。很多功能还没有来得及加上,以后再完善,先用起来再说。

网上的一些vb6高手似乎很懒,实现了控制台、读取、写入、颜色等等功能,唯独没有加上这个关键的kbhit()函数。那怎么能行呢!所以这篇文章特别dump了c语言的库函数:

Public Function kbHit() As Boolean
' Dim m_StdInput As Long
' m_StdInput = CreateFile("CONIN$", _
' GENERIC_READ Or GENERIC_WRITE, _
' FILE_SHARE_READ Or FILE_SHARE_WRITE, _
' 0, _
' OPEN_EXISTING, _
' 0, _
' 0)
' 或者,m_StdInput = GetStdHandle(STD_INPUT_HANDLE)

Dim nRet As Long
Dim
CharsRead As Long
Dim
nEvents As Long
Dim
Events() As INPUT_KEY_EVENT_RECORD
nRet = GetNumberOfConsoleInputEvents(m_StdInput, nEvents)
If nRet = 0 Or nEvents = 0 Then
kbHit = False
Exit Function
End If
ReDim
Events(0 To nEvents - 1)
nRet = PeekConsoleInput(m_StdInput, Events(0), nEvents, CharsRead)
If nRet And CharsRead <> 0 And CharsRead <= nEvents Then
Dim
i As Long
For
i = 0 To nEvents - 1
If Events(i).EventType = KEY_EVENT And _
Events(i).bKeyDown Then
' [tc]Caution: Events(i).AsciiChar is ignored
' [tc]Caution: _getextendedkeycode is ignored
kbHit = True
Exit Function
End If
Next
End If
kbHit = False
End Function

相关定义我就不贴了,网上满大街都是。这个程序中间我注有两个Caution,如果检测到的按键并不是键盘上的按键,理论上kbhit应该返回False,但是这段代码不是这样。所以这个函数其实是不完美的,为什么我不去实现_getextendedkeycode呢?因为我比较懒……

2008年10月13日星期一

[tc]的Blogger重新开张

此前去年由于Blogspot域名被封,本人将Blog搬迁到了yo2。今天决定依然以tctianchi.yo2.cn作为主站,偶尔把好一点的文章转到这里来,07年级以前发布的文章不变。

所以这里就要改名了,现在叫做《绵掌集散地潜水版》。所以还是欢迎网友到主站去玩!

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了吧!