pvzclass是如何實現(xiàn)的?pvzclass源代碼初步分析(5)Plant類 & Zombie類
PVZ類的成員類,除了Memory類外都大同小異。
它們都有自己的構(gòu)造函數(shù),使用指針,通過基址+偏移的方法實現(xiàn)修改PVZ對象的功能。
本篇以Plant類和Zombie類為例,解讀pvzclass如何實現(xiàn)除Memory類外的成員類。
強(qiáng)烈建議在閱讀本篇前先閱讀第3篇和第4篇的內(nèi)容。



雖然有定義成員類的代碼,但是PVZ類下并沒有定義類型為成員類的變量。以Plant類為例:

同時可以看到,pvzclass中,返回值為成員類的方法,返回的都是指針。
再加上幾乎每個成員類都有構(gòu)造函數(shù),實例化成員類時,建議使用指針和new。
Plant類
Plant類包括的內(nèi)容,自然是修改PVZ中植物的屬性用的。
Plant類的內(nèi)容可以分為三部分:基址和構(gòu)造函數(shù)、對象屬性、方法。
(以下展示的代碼不包括原代碼中的注釋部分)

BaseAddress變量存儲的,是Plant對象對應(yīng)的植物對象在PVZ本體中的內(nèi)存基址。它的賦值由構(gòu)造函數(shù)完成:

構(gòu)造函數(shù)的參數(shù)是"indexoraddress",即"index"或"address"。其中"index"指的是植物對象的序號,不會大于1024;而"address"則是上文提到的,植物對象所在的內(nèi)存基址,必然大于1024。這種數(shù)值分布是PVZ內(nèi)部決定的。
pvzclass可以依照這條特性確認(rèn)參數(shù)是"index"還是"address",最終確認(rèn)基址并完成初始化。
DebugType的初始化涉及Plant類的屬性,接下來講。


對象屬性部分的代碼幾乎完全使用INT_PROPERTY宏和T_PROPERTY宏實現(xiàn)。
這些成員變量可以像常規(guī)變量一樣調(diào)用和賦值,并且因為宏定義的調(diào)節(jié),可以直接反映到PVZ本體的對應(yīng)變量上。
使用INT_PROPERTY宏定義的變量,其名稱為第一個"參數(shù)";使用T_PROPERTY宏定義的變量,其名稱為第二個"參數(shù)"。
比如,如果你要將某一植物的生命值(即"Hp")改為23333,你的代碼應(yīng)當(dāng)為:
如果你沒能從變量名中推測出變量的作用,可以打開MemoryAddressList,利用變量對應(yīng)的偏移量(就是最后一個參數(shù)),在植物對象的成員變量中找到對應(yīng)的變量。


使用這些方法可以對植物進(jìn)行一些操作。
方法的具體定義在Classes文件夾下的"Plant.cpp"中。
GetAnimationPart1()和GetAnimationPart2()可以獲取植物對象的動畫部件。它們的實現(xiàn)非常簡單:

Light()和Flash()的實現(xiàn)也很簡單:

查詢MemoryAddressList可知,0xB8和0xBC對應(yīng)的變量分別是發(fā)光倒計時和閃光倒計時。
這兩個方法的原理也很簡單,就是通過修改倒計時實現(xiàn)相應(yīng)效果。
剩下的四個方法都使用了注入?yún)R編代碼的方式實現(xiàn)功能。它們直接調(diào)用PVZ內(nèi)部的函數(shù),以實現(xiàn)一般情況下難以通過修改(或讀?。┥贁?shù)變量實現(xiàn)的功能。


在第3篇中已經(jīng)講過,SETARG宏用于修改byte數(shù)組(也就是待注入的匯編碼)中的部分內(nèi)容,這里用于調(diào)整指令用的參數(shù)。
這些匯編代碼的聲明在AsmFuntions.h,定義則在AsmFunctions.cpp中。
Shoot()的架構(gòu)基本一致,不過因為自身的功能(即立刻令植物發(fā)射子彈),代碼上有一些不同:

在PVZ中,香蒲的子彈可以跟蹤攻擊場上的僵尸。Shoot命令的targetid參數(shù)可以為生成的子彈指定僵尸作為攻擊目標(biāo),并完成其他屬性的設(shè)置。
容易看出,代碼的前半段是生成子彈,后半段是設(shè)置子彈的跟蹤情況。
這里出現(xiàn)了第4篇中提到過的Memory::Variable,它在這里的作用是暫時存儲子彈的基址,這個基址最終會被Execute()函數(shù)抓取。
因為注意:對楊桃使用Shoot()方法時,pvzclass不會抓取基址,而是直接留空。
這里牽扯到了一些子彈的屬性,這個后面會講。
還剩下一個SetAnimation(),它的代碼是這樣的:

其中的lstrcpyA()可以視為一種特殊的strcpy()。
看上去和前面幾個都不一樣。
然而,我們把Execute()的代碼拿出來,比對一下:

為什么SetAnimation()沒有使用Execute()?
我們看一眼匯編碼部分,它長這樣:

這一部分相當(dāng)于如下的代碼:
SetAnimation()中有一個動態(tài)設(shè)置的參數(shù),這一參數(shù)根據(jù)申請的內(nèi)存地址進(jìn)行確定,但PUSHDWORD宏的參數(shù)是絕對引用的。
因此SetAnimation()不得不采取改寫Execute()的方法。
如果使用pvzclass寫匯編代碼,需要動態(tài)為與申請的內(nèi)存地址相關(guān)的變量賦值,可以考慮用類似的方法來實現(xiàn)。
Zombie類
Zombie類包括的內(nèi)容用于修改PVZ中僵尸的屬性。
類似地,Zombie類的內(nèi)容可以分為三部分:基址和構(gòu)造函數(shù)、對象屬性、方法。


這一部分和Plant類基本一樣,跳過。
屬性方面,除了Index(僵尸對象序列的基址)意外,依然是用T_PROPERTY宏及其變種定義成員變量。
同樣地,你可以打開MemoryAddressList,利用變量對應(yīng)的偏移量在僵尸對象的成員變量中找到對應(yīng)的變量,以確認(rèn)變量對應(yīng)的僵尸屬性。
方法方面,Zombie類包含的方法更多,但原理也就那么幾類:




這里出現(xiàn)的CollisionBox(碰撞箱)、AccessoriesType1(I類護(hù)甲)和AccessoriesType2(II類護(hù)甲)三種結(jié)構(gòu)體,都是在PVZ類之外定義的。定義它們的代碼在PVZ.h開頭的部分。


Zombie::SetAnimation()和Plant::SetAnimation()相似,都是用類似Execute()的代碼完成自身的功能。
各位創(chuàng)作者想要實現(xiàn)類似的功能時,也可以模仿上面的代碼,自己編寫出同類型功能的代碼。

下一篇將解讀PVZ類直屬的成員變量和直屬方法。
陽光數(shù)、出怪倒計時等場景性的屬性,大部分由直屬的成員變量進(jìn)行調(diào)控。
實例化PVZ類成員類的方法,大都是直屬方法。
都是地位比較高的內(nèi)容。
敬請期待。