MPQ Storm库 源码分析 一
MPQ是什么?
? ? ? ??MPQ維基上說(shuō)的很明白。簡(jiǎn)而言之,它是暴雪公司用于游戲數(shù)據(jù)打包的工具,星際爭(zhēng)霸,魔獸爭(zhēng)霸游戲中都有使用。該工具內(nèi)含游戲資源加密和壓縮等功能。
? ? ? ? git下載地址:https://github.com/stormlib/StormLib
? ? ? ??MPQ文件總共有四種格式:
? ? ? ? ? ? ?第一種:FLAT格式,線性存儲(chǔ)。
? ? ? ? ? ? ?第二種:Partial格式,分包的存儲(chǔ)。
? ? ? ? ? ? ?第三種:MPQE格式,加密存儲(chǔ)。
? ? ? ? ? ? ?第四種:BLOCK格式,模塊存儲(chǔ)。
? ? ? ??
FLAT格式最簡(jiǎn)單。不過(guò)該格式也支持對(duì)單個(gè)文件的加密和壓縮。
本文主要分析FLAT格式,主要包含以下五點(diǎn):
? ? ? ??一、創(chuàng)建一個(gè)MPQ文件。
? ? ? ??二、添加資源文件。
? ? ? ??三、封裝MPQ文件。
? ? ? ??四、讀取MPQ文件。
? ? ? ??五、讀取資源文件。
? ? ? ??圖一、圖二、圖三為FLAT格式的內(nèi)部格式。
圖一
? ? ? ??一、創(chuàng)建一個(gè)MPQ文件。
? ? ? ? ? ? ?主要過(guò)程是,調(diào)用SFileCreateArchive接口創(chuàng)建MPQ文件,創(chuàng)建成功之后,該接口會(huì)返回成功與否,同時(shí)將MPQ文件的句柄賦值給傳進(jìn)來(lái)的變量。
? ? ? ? ? ? ?創(chuàng)建MPQ文件詳細(xì)流程是:
? ? ? ? ? ? ?1》在內(nèi)存中初始化一個(gè)加密塊StormBuffer;
? ? ? ? ? ? ?2》嘗試打開(kāi)一個(gè)同名的MPQ文件。假如已經(jīng)存在的同名文件不是MPQ文件,MPQ工具就會(huì)嘗試用MPQ文件的格式讀取該文件。為了避免同名的錯(cuò)誤,在StormLib的例子中,有先刪除同一目錄想的同名文件,再創(chuàng)建MPQ文件的邏輯。
? ? ? ? ? ? ?3》創(chuàng)建一個(gè)空的MPQ文件;
? ? ? ? ? ? ?4》重置文件大小。
? ? ? ? ? ? ?5》寫(xiě)入一個(gè)假的MPQ文件頭部,即圖一的A指向的區(qū)域。MPQ的頭部有四種不同的格式,分別擁有不同的長(zhǎng)度,不過(guò)默認(rèn)有一個(gè)最大長(zhǎng)度是208字節(jié)。寫(xiě)這個(gè)假的MPQ文件頭部,最主要的目的是占位,保證第一個(gè)數(shù)據(jù)文件的寫(xiě)入位置是在正確的。
? ? ? ? ? ? ?6》如果是大文件,則創(chuàng)建大文件的索引表HetTable。
? ? ? ? ? ? ?7》創(chuàng)建HashTabl,用來(lái)索引文件的。
? ? ? ? ? ? ?以上七個(gè)過(guò)程,主要實(shí)現(xiàn)了兩個(gè)結(jié)果,1、創(chuàng)建了MPQ文件,2、在內(nèi)存中生成需要用到的MPQ數(shù)據(jù)。
? ? ? ??二、添加資源文件。
? ? ? ? ? ? ?主要過(guò)程是,調(diào)用SFileCreateFile接口,在MPQ文件中創(chuàng)建一個(gè)新的文件句柄。通過(guò)SFileWriteFile接口寫(xiě)入數(shù)據(jù)。調(diào)用SFileCloseFile釋放內(nèi)存資源。
? ? ? ? ? ? ?詳細(xì)流程:
? ? ? ? ? ? ?1》檢查是不是內(nèi)部文件,MPQ主要有兩個(gè)內(nèi)部文件,一個(gè)是listfile,另外一個(gè)attributes。如果不是內(nèi)部文件,則向MPQ文件中添加新的文件。
? ? ? ? ? ? ?2》找到MPQ文件中可以寫(xiě)入的位置,如果是第一次寫(xiě)入,則此時(shí)是圖一B指向的區(qū)域。但此時(shí)還沒(méi)有寫(xiě)入。
? ? ? ? ? ? ?3》通過(guò)HashString方法,將資源文件名轉(zhuǎn)化為hash索引值,以新的hash索引值從當(dāng)前的HashTable中取一個(gè)TMPQHash數(shù)據(jù)結(jié)構(gòu)。
? ? ? ? ? ? ?4》假如不是第一次建立新文件。則此時(shí),通過(guò)TMPQHash的dwBlockIndex值,在FileTable做dwBlockIndex偏移。獲取新文件的入口TFileEntry。假如是第一次建立新文件,則會(huì)先到FileTable里面取出一個(gè)空閑的位置,分配一個(gè)TFileEntry。默認(rèn)是取FileTable位置做dwFileTableSize偏移,而此時(shí)dwFileTableSize值是0,所以就是FileTable表中第一個(gè)位置,即圖一B指向的區(qū)域。同時(shí)分配一個(gè)TMPQHash(hash索引的取值與文件名有關(guān)),分配完之后,與FileEntry關(guān)聯(lián)。在FileEntry里通過(guò)dwHashIndex索引到hash表中的位置,TMPQHash通過(guò)dwBlockIndex索引到FileEntry。
? ? ? ? ? ? ?5》拿到了FileEntry以后,在SFileAddFile_Init函數(shù)中初始化FileEntry的其他值。經(jīng)過(guò)這些邏輯。就可以拿到一個(gè)TMPQFile文件指針,用來(lái)寫(xiě)入數(shù)據(jù)。
? ? ? ? ? ? ?6》通過(guò)SFileWriteFile接口寫(xiě)入文件數(shù)據(jù),可以將資源文件分批寫(xiě)到MPQ文件中,也可以將資源文件一次寫(xiě)入MPQ文件中,兩種都不會(huì)有問(wèn)題。
圖二
? ? ? ? ? ? ?7》在將要寫(xiě)文件的前,會(huì)先寫(xiě)入一個(gè)扇區(qū),默認(rèn)有8字節(jié),扇區(qū)與將要寫(xiě)入的數(shù)據(jù)的加密有關(guān)聯(lián),然后寫(xiě)入
數(shù)據(jù),數(shù)據(jù)有選項(xiàng),可以選擇是否加密,是否壓縮,是否選擇數(shù)據(jù)作為單元文件存儲(chǔ)。寫(xiě)入完畢之后,會(huì)再次寫(xiě)入當(dāng)
前扇區(qū),具體為什么要兩次寫(xiě)入扇區(qū),下一篇會(huì)繼續(xù)分析。圖二中,L指向的是真正的資源文件數(shù)據(jù),K、M分別是扇
區(qū)。資源文件可選擇加密和壓縮。
圖三
? ? ? ??三、封裝MPQ文件。
? ? ? ? 封裝挺重要的,封裝時(shí)會(huì)將新生成的文件相關(guān)的數(shù)據(jù)更新到MPQ文件頭部,覆蓋原先的假頭部。封裝的主要過(guò)程是,將與文件相關(guān)的HET表和BET表,以及HASH表,文件FileTable,按順序?qū)懙阶詈笠粋€(gè)資源文件的結(jié)尾位置。封裝邏輯執(zhí)行完之后,新的MPQ文件內(nèi)部結(jié)構(gòu)如圖三。
? ? ? ??具體流程是: ? ? ? ? ? ? ? ?
? ? ? ? ? ? ?1》確定是否要寫(xiě)入listfile、attributes等文件,如果要,則會(huì)在MPQ文件中創(chuàng)建,并寫(xiě)入。
? ? ? ? ? ? ?2》調(diào)用SFileCloseArchive,找到可以寫(xiě)入HET、BET、HASH、FILETABLE表的位置,然后將這些表的數(shù)據(jù)寫(xiě)入到新的MPQ文件中。寫(xiě)入后MPQ文件內(nèi)部如圖三。表的數(shù)據(jù)會(huì)寫(xiě)在第N個(gè)數(shù)據(jù)文件之后。最后,回到頭部更新MPQ文件頭部。
? ? ? ? ? ? ?3》釋放內(nèi)存中的資源。
? ? ? ??四、讀取MPQ文件。
? ? ? ??主要流程是,打開(kāi)MPQ文件,先讀取頭部,在讀取正確之后,解析各個(gè)表的位置。獲取數(shù)據(jù)文件位置。并且任意一個(gè)環(huán)節(jié)出錯(cuò),都會(huì)退出。
? ? ? ??具體流程:
? ? ? ? ? ? ?1》初始化一個(gè)StormBuff(與數(shù)據(jù)加密有關(guān))。然后用208字節(jié)的長(zhǎng)度,從MPQ文件頭部0位置開(kāi)始讀取,一次讀取208字節(jié)。讀取完之后強(qiáng)制轉(zhuǎn)化為T(mén)MPQHeader數(shù)據(jù)結(jié)構(gòu)。若TMPQHeader內(nèi)部的數(shù)據(jù)是正確的,則開(kāi)始讀取文件,為了判斷文件是否正確,MPQ會(huì)有許多檢測(cè)。不過(guò)MPQ文件頭部默認(rèn)有四種格式。每一種格式的長(zhǎng)度都不一樣。在確定頭部之后,會(huì)調(diào)用memset,將超出的部分置0,確保新生成的MPQHeader數(shù)據(jù)正確。
? ? ? ? ? ? ?2》通過(guò)新獲得的頭部數(shù)據(jù)結(jié)構(gòu),讀取HASH表。Hash表通過(guò)TMPQHeader的dwHashTablePos屬性,找到其在文件中的位置,并讀取出。接著將文件中的數(shù)據(jù),讀取到內(nèi)存里面,完成Hash表數(shù)據(jù)的讀取。
? ? ? ? ? ? ?3》建立文件數(shù)據(jù)表。通過(guò)讀取Hash表,初始化文件數(shù)據(jù)表FileTable,有了FileTable就可以得到FileEntry,有了文件入口,就能讀取數(shù)據(jù)了。
? ? ? ? ? ? ?4》載入listfile和attributes文件。目前我沒(méi)用到這兩個(gè)選項(xiàng)。
? ? ? ? ? ? ?5》返回MPQ文件句柄。
? ? ? ??五、讀取資源文件。
? ? ? ? 有兩種讀取方式。第一種是取出其中某一個(gè)特定的文件,第二種遍歷MPQ文件內(nèi)部所有的數(shù)據(jù)文件。
? ? ? ??第一種取出一個(gè)文件是比較簡(jiǎn)單的。
? ? ? ??具體流程是:
? ? ? ? ? ? ?1》調(diào)用SFileOpenFileEx接口(Ex即外部文件的意思),傳入文件名給該接口,接口內(nèi)部會(huì)將文件名轉(zhuǎn)化為hash數(shù)據(jù),通過(guò)hash數(shù)據(jù)的dwBlockIndex屬性,算出FileEntry數(shù)據(jù),有了FileEntry,就可以獲得要讀取的文件的數(shù)據(jù)的位置,并且獲取該數(shù)據(jù)文件的句柄。也可以先調(diào)用SFileHasFile判斷是否有該文件。
? ? ? ? ? ? ?2》調(diào)用SFileGetFileSize獲取文件大小,申請(qǐng)內(nèi)存空間。調(diào)用SFileGetFileInfo獲取文件信息,不是數(shù)據(jù)。
? ? ? ? ? ? ?3》調(diào)用SFileReadFile獲取MPQ文件中特定資源文件的數(shù)據(jù)。
? ? ? ? ? ? ?4》調(diào)用SFileCloseFile釋放申請(qǐng)的內(nèi)存。
? ? ? ??第二種稍微復(fù)雜一些。
? ? ? ??具體流程是:
? ? ? ? ? ? ?1》調(diào)用SFileFindFirstFile,匹配符為*,則會(huì)全部匹配。返回一個(gè)hFind值。
? ? ? ? ? ? ?2》執(zhí)行讀取一個(gè)文件的流程,上面已經(jīng)說(shuō)明,此處不再贅述。
? ? ? ? ? ? ?3》調(diào)用SFileFindNextFile獲取下一個(gè)文件的入口。
? ? ? ? ? ? ?4》循環(huán)執(zhí)行1==>3流程。直至沒(méi)有文件可以讀取。
總結(jié)
以上是生活随笔為你收集整理的MPQ Storm库 源码分析 一的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 解决页面报错 500-内部服务器错误
- 下一篇: 一些俗语,不思八九,常想一二