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

2007年7月20日星期五

C&C3:怎样零伤亡打赢最难电脑

原贴:http://tctianchi.yo2.cn/articles/cnc3singleplay.html

  C&C3的最难电脑以疯狂的经济基础而闻名,不过这样的电脑也并不是不可战胜的。观察表明,如果电脑没有矿场或矿车,钱是不会自动增加的。所以压制电脑的经济是越早越好的。以下针对1.05版以GDI为例说明打法,其他阵营仅给出录像。

[地图]
今天的战场是《竞技场》,著名2人地图。我先解释一下这张地图。当前位置为我方出生点,左上角为NOD。中间有两个分矿,对角上有两个蓝矿,电脑不会去用,但是会占领两个地堡。中间的上方和下方各有一个白点,那是油井,我们不管它。


&Lang;竞技场&Rang;地图


  任务目标:不使用BUG,不使用工程师逼退策略,赢下最难电脑。争取最短时间、最少伤亡、最多资源。

[初期]
双矿场开局。
主基地:F10生产线-F2矿场-F3兵营-F1电厂
生产线:   F1电厂-F2矿场-F5雷达-F8高科
下雷达的时候,兵营造好一个机枪兵,收起基地车,赶往对手分矿。


sshot0003.jpg


   路上遇到一工程师,不要客气。


sshot0007.jpg


  如图所示,放下两个声波塔。补上一个电厂,升级催化。


sshot0012.jpg


   电脑对这种战术感到茫然无措。


sshot0019.jpg


  注意:声波塔到达5个以前非常脆弱,一定要控一下,以最大伤害范围为主旨。

[中期]
过渡时期需要在家里补一两个声波。电脑的偷袭可能从上面、右边、左下进行,根据实际情况安排。


sshot0024.jpg


  电脑迅速做出反应,建造了空军来偷袭,不过我已经备好了防空。那我怎么知道电脑会有毒液的呢?因为意识好!

sshot0026.jpg


  ——刚才是吹牛,因为[tc]曾经因此损失矿车一部,没有实现0伤亡。

注意:发现火焰坦克一定要退出里面的机枪兵。

sshot0027.jpg


  电脑看到我防空不多,所以会继续毒液,所以我也继续回应之。

sshot0029.jpg


  现在开始疯狂地扩张(看小地图)。

sshot0033.jpg


[后期]
出一些MM的同时,把红血的单位拿来修。

sshot0036.jpg


  注意:最后关头也请时刻注意微操,MM并不是不会挂的。[tc]曾有多次在最后几分钟失去获得零伤亡录像的机会的惨痛经历。


sshot0042.jpg


  用MM来结束战斗会不会太俗套了呢?


sshot0055.jpg


  注意:一旦开始生产MM就要升级轨道炮。[tc]居然忘记升级了,这是本录像的重大失误。

[结局]
后半段[tc]打得并不好看,主要是因为[tc]太过谨慎了。比赛越是到后期,越是有可能损失一两个单位造成遗憾。

sshot0057.jpg


  以上战略实际上10分钟左右就可以结束,但是要保证0部队伤亡+0建筑损失,是非常不容易的一件事。[tc]我打了整整一个晚上,才成功了一次。

先附上本教学录像:replay-gdi-perf.rar

第二个录像是之前打的版本,除了一不小心挂掉一个炮台之外,应该说比上面的录像打得好。replay-gdi.rar

[其他阵营]
先说NOD。NOD的高机动性、隐形在电脑的作弊下黯然失色,但是NOD拥有本游戏最多的技能,应当加以利用。可惜[tc]暂时还打不出比较好的录像。replay-nod.rar

这个录像和上面一样是Tower Rush,但是没有实现零伤亡。

Scim,1.05版的MotherShip对射程作了调整,所以利用一下。

虽然只要3分钟就可以解决问题,但是录像没有实现零伤亡。replay-sc.rar

NOD和外星人的零伤亡录像我以后再做,现在先摆在这里。

2007年4月17日星期二

说明:我搬过家了

由于本Blog日前域名被封,[tc]已于2007-03-25搬迁至

http://tctianchi.yo2.cn/

2007年3月17日星期六

[tc]魔兽3 1.21内存修改器 V4

  由于Blood+缺了一集,一边等候下载,一边就写了这个V4版。有些代码写的实在垃圾,懒得改了,于是又贴了几个补丁。
  这次除了补完上次说的“攻击力”、“攻击类型”、“防御类型”之外,“防御力”也被我“一不小心”改出来了。“智力”还是像以前一样,有找不到的几率,暂时还没有想到解决方案。

  因为此次更新并不完全是一时兴起,所以那个修改金钱的难题是一定要解决的。
  6F088E78: mov eax,[edx+78] 8b 42 78
  金钱和智力一样,代码是公用的,有一定的几率会变成无用的地址,好在几率不大,大部分时间是在一个区域内不停的变动,[tc]仔细一看:edx的低16位居然是固定的!比如P1的金钱始终是[0190],P2是[1410],只有高16位是不固定的。这就好办了,取下上16位,补上我们自己的下16位即可!该问题完美解决!
  编程的时候值得一提的是:这次[tc]决意尽可能少的修改原来游戏的代码。此前的修改存在冗余!比如“力量”、“敏捷”,我是分开搜索的。但是明眼人很快会发现,这两个地址是相关的,只要找到其中一个,另一个也能推算出来。这次修改金钱也是一样的道理。
  [tc]修改了Player 1-10(注:魔兽最多可以有12个在线玩家,我一时偷懒只作了10个)的金、木、人口、最大人口,但是这些数据是相关的:对于同一个人而言,偏移地址是这样的:
    金      0x 0
    木      0x 80
    最大人口   0x180
    当前人口   0x200

  不出意外的话,这是我最后一次更新这个修改器了。因为基本上能想到的东西都已经写了。