pvzclass是如何實現(xiàn)的?pvzclass源代碼初步分析(9)Extensions.h & utils.h
Extensions.h?和?utils.h?都可以實現(xiàn)一些常用的修改功能。
Extensions.h?大部分修改都是功能類的,而?utils.h?的修改都是數(shù)值類的。
不同于之前介紹的頭文件,這兩個頭文件直接定義了函數(shù),而非先聲明,再由其他文件完成定義。
建議在閱讀本篇前先閱讀第4篇的內(nèi)容。
注:本文以2021.8.12更新的版本為準(zhǔn)。


Extensions.h?
Extensions.h?開頭有這樣一段宏定義:

前三個宏比較簡單,是一個帶控制的 WriteMemory() ,適配三種整數(shù)型變量?!?/p>
剩下的五個宏學(xué)過匯編的一眼就能看出來,這是把匯編語言翻譯成機(jī)器碼。
Extensions.h?實現(xiàn)的部分功能僅僅在跳轉(zhuǎn)條件上進(jìn)行調(diào)整,另外定義一個無參數(shù)版本的宏也可以方便修改。
Extensions.h?的大部分功能都是這樣的(有的還需要其他參數(shù)):

可以看出,之前 MEMMOD?類宏中的"b"就是函數(shù)的參數(shù)。顯然它就是個開關(guān)。
而圖示函數(shù)在 MEMMOD?類宏中直接使用數(shù)值,從寫入的地址來看,這應(yīng)該是機(jī)器碼。
當(dāng)然,作者顯然沒有把機(jī)器碼直接背下來,而是利用其他軟件,將匯編代碼轉(zhuǎn)化為機(jī)器碼。
對于一段匯編代碼,作者大部分采取轉(zhuǎn)化為機(jī)器碼后拼接的方法。因此,Extensions.h?中會出現(xiàn)這種乍一看很反常的代碼:

其實大部分時候這還是在修改代碼,只是形式上有些偷懶而已。
當(dāng)然,也有使用其他方法實現(xiàn)自身功能的函數(shù)。比如?VasePerspect(b),就是用常規(guī)方法進(jìn)行代碼注入:

雖然形式變了,但基本的實現(xiàn)方式并未變化。
utils.h
和 events 組件一樣,utils.h 也是由 YouTheB(github名)補(bǔ)充到 pvzclass 中的。
實際上這個文件并未包含在 pvzclass.h 中。
utils.h?中所有函數(shù)都包含在命名空間?Utils?中。
utils.h?和大部分頭文件一樣,開頭都是 #pragma once?和包含的頭文件。
然后是變量類型定義和宏定義:

果然,pvzclass?的絕大部分功能都有這倆。
但在文件末尾,兩個宏定義被移除了:

前六個是修改植物基本屬性的函數(shù),原理都很簡單。

在PVZ中,這些基本屬性是用結(jié)構(gòu)體數(shù)組(當(dāng)然也可能是二維數(shù)組)存儲的。
地址變量 address?的第一部分是統(tǒng)一的 0x69F2C??,實際上就是指定了某一屬性(或第一維)。第二部分就是下標(biāo)(或第二維)了。
SetSunValue(sun)?更簡單,單純 Write(x,y)?而已。
readmemory(t)?則是個套皮?Read(t) 。

剩余的三個函數(shù)中,isvalid(s,l,r)?和 t(a)?都是 GetAddress(s,l,r)?的輔助函數(shù)。

不難看出,isvalid(s,l,r)?判斷一個字符串(或其子串)是否滿足中括號匹配
而 t(a)?則是套皮的 isalnum(a) 。
isalnum(a)?可以檢查某一字符是否為數(shù)字或英文字母(包括大小寫),如果是就返回 true ,否則返回 false。
下面分析?GetAddress(s,l,r)。
GetAddress()
在匯編語言中,常用 [address]?表示指針 address?所指向的值。
但這種表示形式不能嵌套,對于多級指針,只能用多個語句實現(xiàn)。
而 GetAddress(s,l,r)?可以直接識別這種表達(dá)式(但不能有減法運(yùn)算)并給出結(jié)果,即使表達(dá)式中有多層嵌套。
我們從頭開始分析:

這段代碼的作用很明顯,是在一開始判斷表達(dá)式是否有括號失配的低級錯誤。
時間復(fù)雜度。

這里 top?只有定義而沒有應(yīng)用,它在下一段代碼才能派上用場。
這一部分中 l?和 r?的作用應(yīng)該很明確了。它們表示的是子串的范圍。
這一部分的作用為:判定 s(或它的一個子串)是否表示?16?進(jìn)制數(shù)值。如果是,就將其轉(zhuǎn)為 10?進(jìn)制并返回。
這一部分的時間復(fù)雜度也是。

從這部分代碼開始,s?肯定是一個中括號括起的表達(dá)式。
開頭出現(xiàn)了一個名為?v?的 vector<int>。
結(jié)合開頭將 l-1、中間將 r?分別加入 v,以及每當(dāng)有同級?+?就將編號加入 v,可以初步判定,這是在根據(jù) +?將字符串分割為若干子串。
這一段的時間復(fù)雜度為。
又因為這個表達(dá)式默認(rèn)只有?+?一種二元運(yùn)算符,這些子串要么是?16?進(jìn)制數(shù),要么是中括號括起的表達(dá)式。
接下來的代碼也不難理解,就是遞歸處理各個子串。
如果是 16?進(jìn)制數(shù),就會在自調(diào)用的第二部分代碼算出數(shù)值,并立刻返回;如果是表達(dá)式,就會繼續(xù)遞歸,直到求得數(shù)值為止。
在運(yùn)算部分的代碼中,need?表示這個子串是否由中括號擴(kuò)起。結(jié)合中括號的作用和代碼內(nèi)容,不難理解該變量的意義。

GetAddress()?前有這樣一句注釋:

這個大概率指的是時間復(fù)雜度。
實際上,對于部分字符串,如括號失配的字符串、純16進(jìn)制數(shù)串,時間復(fù)雜度確實是,原因顯然。
不過對于另一部分字符串,實際上時間復(fù)雜度就有可能大于。
在第三部分代碼中,每次遞歸都只會去除一層中括號,剩下的子串會在遞歸調(diào)用第三部分的代碼時重復(fù)遍歷,拉低計算效率。
對于形如"[[[[[[[[[[[[......6A9EC0]]]]]]]]]]]]......."(左右中括號數(shù)量相等,6A9EC0可以換為其他 16?進(jìn)制數(shù)),該算法的時間復(fù)雜度甚至?xí)_(dá)到,而且還有大量開 vector?導(dǎo)致棧溢出的可能。
不過一般情況下不會很大,而且
的情況也不容易出現(xiàn),還是可以接受的。

下一篇將分析events,可能是pvzclass(時間上)最多變的部分。