inside MPQ
?
翻譯前聲明:
? ? ? ? ? 本翻譯對(duì)于原文進(jìn)行了適量刪節(jié)和修改。
? ? ? ? ? 本翻譯只做為學(xué)習(xí)參考使用,不得用于任何商業(yè)目的。
? ? ? ? ? 由于本人能力有限,如有錯(cuò)誤請(qǐng)回帖指出
? ? ? ? ? 要轉(zhuǎn)貼本翻譯請(qǐng)征得翻譯作者的同意。
原文地址:http://www.campaigncreations.org/starcraft/inside_mopaq/
翻譯文章地址:http://www.islga.org/bbs/read.php?tid=13735
原文作者:Justin Olbrantz
翻譯作者:喀爾硫司之瞳? ? ? ? QQ:526312052
第一章? ? 關(guān)于MPQ的歷史
MPQ,也稱MoPaQ,是Mike O'Brien發(fā)明的一種壓縮文件格式。
在1996作為,MPQ應(yīng)用在Diablo(暗黑破壞神)游戲中。
然而它的版權(quán)屬于 Blizzard 的父公司 Havas Interactive,并且在Mike O'Brien離開(kāi)暴雪后繼續(xù)使用。 正是MPQs由于在Diablo(暗黑破壞神)中的出色表現(xiàn),使其繼續(xù)應(yīng)用在Starcraft(星際爭(zhēng)霸), Warcraft 2(魔獸爭(zhēng)霸2), Diablo 2(暗黑破壞神2), Lords of Magic(魔法大帝)中。
第二章? ? 關(guān)于MPQ的介紹
? ? MPQ內(nèi)部包含了許多文件,包括坐標(biāo)算法、聲音、動(dòng)畫、字符串、數(shù)字?jǐn)?shù)據(jù)和故事情節(jié)信息。
明顯地,MPQ的潛力很大。要想利用MPQ,那么您就需要了解它。
? 在有MPQ格式之前,一直使用的是WAR格式,在Warcraft 2,甚至在Warcraft1中存放游戲數(shù)據(jù)。然而WAR格式是簡(jiǎn)單的,不精制的,是由缺乏經(jīng)驗(yàn)的程序員所編寫的文件格式(相信我,我知道)。文件在檔案中僅使用參考序數(shù)和是否被壓縮做為唯一可選擇調(diào)用的方法。
盡管如此它仍然完成了它的任務(wù)。它提供了壓縮格式下的文件調(diào)用。但是,很快缺點(diǎn)開(kāi)始出現(xiàn)。調(diào)用時(shí)使用參考序數(shù),意味著一長(zhǎng)傳文件接口的名單必須被保留和被咨詢,當(dāng)程序員需要使用其中一個(gè)文件,那么則需要級(jí)長(zhǎng)的時(shí)間,工作變得越來(lái)越繁瑣。
當(dāng)時(shí)這些問(wèn)題并沒(méi)有那么嚴(yán)重,所以有人堅(jiān)持使用WAR格式,但是一切在使用Battle.net(網(wǎng)絡(luò)對(duì)戰(zhàn))后,問(wèn)題變得不能接受。
?
MPQ的特點(diǎn)?
? 如被提及以前,MPQ格式一直被用做修正WAR的設(shè)計(jì)缺陷。但是現(xiàn)在他們也想增加一些全新的特點(diǎn)到MPQ。在暴雪的游戲中,MPQ格式的特點(diǎn)總結(jié)為以下幾點(diǎn):
Security. 安全
暴雪一定不希望在游戲中玩家可以修改數(shù)據(jù)。或許他們提早知道MPQ格式可以為Starcraft使用。 不管怎樣,安全是最重要的,由此他們顯然做了級(jí)大的努力去維護(hù)游戲的安全性。
Efficiency. 效率
MPQs要求執(zhí)行時(shí)先簡(jiǎn)單預(yù)先輸入的各種各樣的任務(wù)數(shù)據(jù)然后實(shí)時(shí)放出。對(duì)于預(yù)先輸入數(shù)據(jù),時(shí)間并不重要。 但是實(shí)時(shí)放出就是另一件事了,其中的數(shù)據(jù)必須快速地被解壓使用。
Multilinguality.多語(yǔ)言的計(jì)算機(jī)處理
在最開(kāi)始的時(shí)候,暴雪就計(jì)劃發(fā)布其游戲在全球游戲市場(chǎng),因此他們盡可能的做到多語(yǔ)言。 在創(chuàng)新時(shí),他們決定設(shè)計(jì)多語(yǔ)種能寫入MPQ格式。
。
Expandability.擴(kuò)展
顯然的,在游戲中需要使用獨(dú)立的數(shù)據(jù)。太大的數(shù)據(jù)不僅是效率低并且減慢游戲速度,如果補(bǔ)丁修改了,也是很麻煩的。暴雪明白這個(gè)道理,因而MPQ格式的要求就是有能力完全,高效率的,從多個(gè)檔案數(shù)據(jù)中調(diào)用需要的數(shù)據(jù)。
?
什么是strom?
? 相比在程序模塊中復(fù)制函數(shù),多數(shù)程序員喜歡把相同代碼放到shared libraries(共享程序庫(kù))里。shared libraries是包含了任意程序功能的函數(shù)模塊。不僅能避免多余,并且能縮小程序大小。
正因?yàn)槿绱?#xff0c;暴雪使用一個(gè)稱為Storm的共享程序庫(kù)(PC機(jī)上為Storm.dll,MAC機(jī)為Storm.bin)。
所有現(xiàn)代的暴雪游戲中都使用strom存放重要功能,比如讀取MPQ,Battle.net和一些圖形化例程。
當(dāng)暴雪要發(fā)布新版本的游戲,只需要增加功能到strom,無(wú)需改變?cè)泄δ堋?這意味著舊版本的游戲只用升級(jí)新版本strom就可以了,這就是我們俗稱的安裝補(bǔ)丁。
就像所有共享程序庫(kù),任何想使用它的程序都可以訪問(wèn)到它的函數(shù)。這就是為什么strom只包含MPQ讀取功能。
什么是 MPQ API Library DLL
雖然 Storm 沒(méi)有包含任何編寫MPQ的功能。
但是 StarEdit 包含,因?yàn)?SCM/SCX 文件也是 MoPaQ文件。
但是這些函數(shù)被加密了,所以只有知識(shí)淵博的黑客們才可以使用。
對(duì)于Blizzard 來(lái)說(shuō)不幸的是,有一個(gè)這樣的黑客,他的名字是 Andrey Lelikov(aka Lelik)。
他發(fā)現(xiàn)了一種訪問(wèn)這些寶貴的函數(shù)的途徑,并把這個(gè)復(fù)雜的過(guò)程封裝在
LMPQAPI.DLL(Lelik's MPQ API Library DLL)文件中。該文件自動(dòng)破解
StarEdit,將這些函數(shù)展示在所有的程序員面前。
第三章? ? ? MPQ的基本原理
通過(guò)整個(gè)計(jì)算機(jī)發(fā)展史來(lái)看,絕大多數(shù)的進(jìn)步都是在求解問(wèn)題中發(fā)生的。
那么在這一章中,我們將采取看看一些涉及到MPQ的問(wèn)題及其解決辦法。
?
HASH (散列或哈希)
? 問(wèn)題:你有一個(gè)非常大的字符串?dāng)?shù)組,和一個(gè)字符串
? ? ? ? 怎么知道字符串是否在數(shù)組中?
你可能會(huì)開(kāi)始在數(shù)組中與其他字符串比較每個(gè)字符串,但是,當(dāng)進(jìn)行應(yīng)用后,你會(huì)發(fā)現(xiàn),這種方法在實(shí)際使用時(shí)是特別慢的。在此之前,你又怎能在沒(méi)有與其他字符串比較的情況下,確定這個(gè)字符串是否存在?
解決方法:hash
hash是規(guī)模較小的數(shù)據(jù)類型(例如數(shù)字)能指向其他較大的數(shù)據(jù)類型(通常是字符串) 。在這種情況下,您可以在數(shù)組中先存儲(chǔ)hash。然后再計(jì)算其他字符串的hash,并比較它存儲(chǔ)的hash。通過(guò)字符串比較,如果hash在數(shù)組相匹配的新的hash,就可以核實(shí)存在。這就是所謂的索引查找,可以加快對(duì)于不同大小的數(shù)組和平均長(zhǎng)度的字符串的搜索速度約100倍。
| unsigned long HashString(char *lpszString) { | ||
| ? | unsigned long ulHash = 0xf1e2d3c4; while (*lpszString != 0) { | |
| ? | ? | ulHash <<= 1; ulHash += *lpszString++; |
| ? | } return ulHash; | |
| } | ||
上面的代碼,體現(xiàn)了一個(gè)很簡(jiǎn)單的散列算法。
功能是在每個(gè)字符添加前,把哈希值向左移動(dòng)1bit,并總計(jì)字符串中的字符。
使用這種算法,字符串“arr\ units.dat ”將散列為0x5a858026,“unit\neutral\ acritter.grp ”將散列為0x694cd020 。
無(wú)可否認(rèn),這是一個(gè)很簡(jiǎn)單的算法,但是不是非常實(shí)用。因?yàn)樵谳^低的數(shù)字范圍內(nèi)會(huì)產(chǎn)生一個(gè)相對(duì)可預(yù)見(jiàn)的輸出,以及出現(xiàn)大量的沖突。當(dāng)多于一個(gè)字符串散列為相同值就會(huì)出現(xiàn)沖突。
MPQ格式使用一個(gè)非常復(fù)雜的散列算法(如下所示),產(chǎn)生完全不可預(yù)測(cè)的哈希值,這個(gè)算法十分有效,這就是所謂的單向散列。
就是把任意長(zhǎng)度的輸入(又叫做預(yù)映射, pre-image),通過(guò)散列算法,變換成固定長(zhǎng)度的輸出,該輸出就是散列值。這種轉(zhuǎn)換是一種壓縮映射,也就是,散列值的空間通常遠(yuǎn)小于輸入的空間,不同的輸入可能會(huì)散列成相同的輸出,而不可能從散列值來(lái)唯一的確定輸入值。從預(yù)映射,能夠簡(jiǎn)單迅速的得到散列值,而在計(jì)算上不可能構(gòu)造一個(gè)預(yù)映射,使其散列結(jié)果等于某個(gè)特定的散列值。
即構(gòu)造相應(yīng)的任意長(zhǎng)度明文=固定長(zhǎng)度散列值-1(固定長(zhǎng)度散列值)不可行。
故此使用特別算法,文件名“arr\ units.dat ”將散列為0xf4e6c69d ,和“unit\neutral\ acritter.grp ”將散列為0xa26067f3 。
| unsigned long HashString(char *lpszFileName, unsigned long dwHashType) { | ||
| ? | unsigned char *key = (unsigned char *)lpszFileName; unsigned long seed1 = 0x7FED7FED, seed2 = 0xEEEEEEEE; int ch; while(*key != 0) { | |
| ? | ? | ch = toupper(*key++); seed1 = cryptTable[(dwHashType << 8) + ch] ^ (seed1 + seed2); seed2 = ch + seed1 + seed2 + (seed2 << 5) + 3; |
| ? | } return seed1; | |
| } | ||
HASH TABLES(散列表或哈希表)
問(wèn)題:您嘗試在前面的示例中使用相同索引,您的程序一定會(huì)有中斷現(xiàn)象發(fā)生,而且不夠快。
? ? ? 您能做的只有讓程序不去查詢數(shù)組中的所有散列值。或者 您可以只做一次對(duì)比就可以得出在列表? ? ? ? 中是否存在字符串。
? ? ? 聽(tīng)起來(lái)不錯(cuò),真的么?? ?
? ? ? 騙你的啦!!!
解決方案:a hash table
哈希表是數(shù)組中的一種特殊類型,也就是設(shè)定指定字符串的偏移量為那個(gè)字符串的散列值。
我的意思是,假如您設(shè)置一個(gè)字符串列表,使用一個(gè)單獨(dú)的固定大小的數(shù)組作為哈希表。
您想查看新的字符串是否在前面的哈希表里。
那么您需要先計(jì)算要查看字符串的散列值,然后以哈希表大小的散列值為模求余數(shù)。
因此,如果您使用上面列出的簡(jiǎn)單散列算法,"arr\units.dat"將散列為0x5A858026,使其偏移量
0x26(0x5A858026 divided by 0x400 is 0x16A160, with a remainder of 0x26)。
假如這個(gè)地方有字符串,那么就會(huì)與被添加的字符串比較。
假如字符串在0x26不匹配,或直接不存在,那說(shuō)明該添加的字符并不在在數(shù)組中。
下面的代碼說(shuō)明了這:
| int GetHashTablePos(char *lpszString, SOMESTRUCTURE *lpTable, int nTableSize) { | ||
| ? | int nHash = HashString(lpszString), nHashPos = nHash % nTableSize; if (lpTable[nHashPos].bExists && !strcmp(lpTable[nHashPos].pString, lpszString)) | |
| ? | ? | return nHashPos; |
| ? | else | |
| ? | ? | return -1; //Error value |
| } | ||
?
現(xiàn)在在這方面的解釋有一個(gè)明顯的缺陷。您認(rèn)為發(fā)生沖突時(shí)(兩個(gè)不同的字符串哈希以同等價(jià)值的) ?顯然,他們不能在哈希表占用相同的接口。通常,解決的方法是在哈希表每個(gè)接口作為一個(gè)指針到一個(gè)鏈表,然后將鏈表里所有接口的散列值設(shè)置相同。
MPQ使用一個(gè)關(guān)于文件名稱的哈希表記錄內(nèi)部文件,但是這個(gè)哈希表的格式與普通哈希表有所不同。
首先,MPQ根本不保存文件名,用三個(gè)散列值代替保存散列值的偏移量和為了核查文件名保存真實(shí)的文件名。
而是使用三個(gè)不同哈希值:一個(gè)做為哈希表的偏移量,兩個(gè)是做為核查。
兩個(gè)做為核查的哈希值被用來(lái)代替真實(shí)的文件名稱。當(dāng)然,也有可能兩個(gè)不同文件名稱的散列值相同,
不過(guò)這種情況發(fā)生的可能性為平均1:18889465931478580854784 ,對(duì)于任何人來(lái)說(shuō)這應(yīng)該足夠安全了。
另一種方法:不同于常規(guī)的執(zhí)行情況的mpq哈希表。
代替使用每個(gè)接口的鏈接表。當(dāng)沖突發(fā)生時(shí),把接口移動(dòng)動(dòng)下一個(gè)序列,并且重復(fù)動(dòng)作,直到找到空閑空間。
下面的代碼是在MPQ設(shè)置讀取的基本方法:
| int GetHashTablePos(char *lpszString, MPQHASHTABLE *lpTable, int nTableSize) { | |||
| ? | const int HASH_OFFSET = 0, HASH_A = 1, HASH_B = 2; int nHash = HashString(lpszString, HASH_OFFSET), nHashA = HashString(lpszString, HASH_A), nHashB = HashString(lpszString, HASH_B), nHashStart = nHash % nTableSize, nHashPos = nHashStart; while (lpTable[nHashPos].bExists) { | ||
| ? | ? | if (lpTable[nHashPos].nHashA == nHashA && lpTable[nHashPos].nHashB == nHashB) | |
| ? | ? | ? | return nHashPos; |
| ? | ? | else | |
| ? | ? | ? | nHashPos = (nHashPos + 1) % nTableSize; |
| ? | ? | if (nHashPos == nHashStart) | |
| ? | ? | ? | break; |
| ? | } return -1; //Error value | ||
| } | |||
每條代碼反復(fù)研究,理論的背后是不難的。
它基本上是如下這個(gè)過(guò)程:
1.計(jì)算3個(gè)散列值(一個(gè)沖突和兩個(gè)檢查)并將其存儲(chǔ)在變量。
2.移動(dòng)沖突散列值的接口
3.接口未使用的嗎?如果是的話,停止搜尋,并傳回'文件沒(méi)有被發(fā)現(xiàn)' 。
4.兩個(gè)檢查是否匹配檢查我們正在尋找文件的散列值呢?如果是的話,停止搜尋,并傳回目前的接口。
5.如果在最后一個(gè)接口,移動(dòng)到列表中的下一個(gè)接口,(wrapping around to the beginning ??)。
6.剛移動(dòng)的借口是否和沖突時(shí)的散列值相同(是否檢查了整個(gè)哈希表? ) ?如果是的話,停止搜尋,并傳回'文件沒(méi)有被發(fā)現(xiàn)' 。
7.回到第3步。
如果您很仔細(xì)的話,您可能會(huì)從我的解釋和示例代碼注意到,是因?yàn)閙pq的哈希表已保留所有文件接口在MPQ 。那么您認(rèn)為每一個(gè)哈希表項(xiàng)如何得到填補(bǔ)?答案可能出乎您的意料卻顯而易見(jiàn):您不能繼續(xù)添加文件。幾個(gè)人都問(wèn)我為什么有一個(gè)上限(所謂的檔案限制),在一個(gè)MPQ中可以有多少檔案, ,是否有任何的方式解決這個(gè)限制。那么,您已經(jīng)有了第一個(gè)問(wèn)題的答案。至于第二項(xiàng);沒(méi)有,您不能繞開(kāi)該文件的限制。對(duì)這個(gè)問(wèn)題,哈希表,甚至不能調(diào)整大小,除非您重新改造MPQ。在哈希表每個(gè)接口因?yàn)橹匦略O(shè)置大小不同位置可能會(huì)改變。而且導(dǎo)致無(wú)法獲得新的地址,因?yàn)榈刂肥俏募纳⒘兄?#xff0c;并且我們還可能不知道檔案名稱。
Compression? 壓縮
問(wèn)題:您有一個(gè)很大的程序(比如說(shuō), 50 megs ) ,您要分發(fā)在互聯(lián)網(wǎng)上。但50 megs是一個(gè)非常大的下載量,而且別人未必有興趣等待四個(gè)半小時(shí)去下載這個(gè)程序。
解決方法:壓縮。
壓縮是一門藝術(shù)。是在更小的內(nèi)存中重新放置等量的數(shù)據(jù)。
有數(shù)以百計(jì)不同的壓縮算法,使用不同的方式。
MPQ實(shí)際使用的算法是the Data Compression Library, licensed from PKWare (one of the leaders in applied compression),在此解釋太過(guò)于復(fù)雜。相反,我會(huì)嘗試解釋一個(gè)更簡(jiǎn)單的壓縮算法的例子。
? ? ? ? ? ? ? ? ? ? 本章節(jié)并不完全 ,因?yàn)樽髡邲](méi)寫完
Encryption? 加密
這個(gè)世界上總是有喜歡剽竊的人存在,所以我們需要有一個(gè)保護(hù)資料安全的系統(tǒng)。
千百年來(lái)人們一直試圖傳遞信息給他人。從手寫的信件進(jìn)行,信使徒步穿越古希臘,納粹潛艇的無(wú)線電傳輸,在第二次世界大戰(zhàn),使用信用卡交易,到網(wǎng)絡(luò)應(yīng)用的今天,有能力去確保別人無(wú)法獲得您的信息是必要的。
所謂的加密是復(fù)雜的藝術(shù)的保護(hù),然而我們不知道設(shè)計(jì)第一個(gè)算法的人,我們也不知道到底有多少的算法。一切從簡(jiǎn)單的數(shù)據(jù)加擾,嬗變,甚至算法,其中有解密密鑰(有時(shí)也稱為密碼)是不同的加密密鑰(在一個(gè)方法所謂非對(duì)稱加密) ,已做了一次又一次。
做為一個(gè)全面的權(quán)威加密方法,本文章肯定從來(lái)沒(méi)有索賠,也不期望。
您只需要知道加密是你與MPQ直接相關(guān)的。
讓我們從一個(gè)簡(jiǎn)單的加密算法開(kāi)始,這是刊登在《Basic Lab Notes》 (為了可讀性本人改變了一些變數(shù)名稱,評(píng)論刪除) :
| void EncryptBlock(void *lpvBlock, int nBlockLen, char *lpszPassword) { | ||
| ? | int nPWLen = strlen(lpszPassword), nCount = 0; char *lpsPassBuff = (char *)_alloca(nPWLen); memcpy(lpsPassBuff, lpszPassword, nPWLen); for (int nChar = 0; nCount < nBlockLen; nCount++) { | |
| ? | ? | char cPW = lpsPassBuff[nCount]; lpvBlock[nChar] ^= cPW; lpsPassBuff[nCount] = cPW + 13; nCount = (nCount + 1) % nPWLen; |
| ? | } return; | |
| } | ||
這是非常簡(jiǎn)單的哈希代碼,不應(yīng)被用來(lái)在一個(gè)實(shí)際的程序中使用。
即使代碼是隱藏的(沒(méi)有雙關(guān)語(yǔ)意),這也是簡(jiǎn)單的 。
不言而喻,這是通過(guò)塊進(jìn)行加密的,把每個(gè)字節(jié)與相應(yīng)的字節(jié)的密碼轉(zhuǎn)換為二進(jìn)制。然后修改字節(jié)的密碼,加入13 ( 選擇13是因?yàn)檫@是一個(gè)素?cái)?shù))。這樣做是為了使代碼的模式,更難以識(shí)別。
那么,用此算法,加密字符串“encryption” ( 65 6E 63 72 79 70 74 69 6F 6E),加密的密碼“ mpq ” (4D 50 51 ),這樣會(huì)得到一個(gè)無(wú)法讀取字符串(28 3E 32 28 24 2E 13 03 04 1A)。
現(xiàn)在,這個(gè)算法是對(duì)稱的。這意味著密碼是用來(lái)加密有相同密碼的塊。事實(shí)上,由于轉(zhuǎn)換為二進(jìn)制是一個(gè)對(duì)稱的運(yùn)作,完全相同的算法可以用來(lái)解密。請(qǐng)注意,大多數(shù)的對(duì)稱加密算法是不完全對(duì)稱,所以他們要求加密和解密的功能有所不同。
好吧,下面就就是關(guān)鍵的地方。
如果您想要編寫,就必須在哪里都知道加密算法。
教導(dǎo)給您這個(gè)方法是我的使命。
MPQ的加密算法混合其他加密技術(shù)。它創(chuàng)建了一個(gè)加密表(這也是用在散列函數(shù)) ,并使用一個(gè)文件的加密密鑰,以挑選出某些成員的加密表。然后對(duì)表中的成員進(jìn)行轉(zhuǎn)換成二進(jìn)制數(shù)據(jù)加密。現(xiàn)在,用一個(gè)相當(dāng)奇怪的方法來(lái)做,所以或許有些代碼將顯示您it is overcomplicated :-p。以下代碼生成密碼表數(shù)組長(zhǎng)度為0x500:
| void prepareCryptTable() { | |||
| ? | unsigned long seed = 0x00100001, index1 = 0, index2 = 0, i; for(index1 = 0; index1 < 0x100; index1++) { | ||
| ? | ? | for(index2 = index1, i = 0; i < 5; i++, index2 += 0x100) { | |
| ? | ? | ? | unsigned long temp1, temp2; seed = (seed * 125 + 3) % 0x2AAAAB; temp1 = (seed & 0xFFFF) << 0x10; seed = (seed * 125 + 3) % 0x2AAAAB; temp2 = (seed & 0xFFFF); cryptTable[index2] = (temp1 | temp2); |
| ? | ? | } | |
| ? | } | ||
| } | |||
你是不是越來(lái)越覺(jué)得暴雪聘請(qǐng)了一名心懷不滿的微積分教授寫這些算法?還好對(duì)與我這不是問(wèn)題,如果你不明白此代碼。如果您想要編寫,您需要這些功能,你不一定要了解他們。無(wú)論如何,在密碼表初始化后,我們可以解密MPQ數(shù)據(jù),具有下列功能(不要期望我向您解釋,我不想知道如何運(yùn)作自己! ) :
| void DecryptBlock(void *block, long length, unsigned long key) { | ||
| ? | unsigned long seed = 0xEEEEEEEE, unsigned long ch; unsigned long *castBlock = (unsigned long *)block; // Round to longs length >>= 2; while(length-- > 0) { | |
| ? | ? | seed += stormBuffer[0x400 + (key & 0xFF)]; ch = *castBlock ^ (key + seed); key = ((~key << 0x15) + 0x11111111) | (key >> 0x0B); seed = ch + seed + (seed << 5) + 3; *castBlock++ = ch; |
| ? | } | |
| } | ||
?
第四章? ? STROM
稱為STROM庫(kù)函數(shù),或者簡(jiǎn)稱為STROM。
它是對(duì)于本身的運(yùn)行系統(tǒng),擁有龐大的功能庫(kù)函數(shù)。甚至不需要Microsoft支持。
它本身包含了足夠強(qiáng)大的功能,甚至不需要調(diào)用本地API函數(shù)。
事實(shí)上,STROM包含了所有暴雪編寫的可以重復(fù)使用的功能。
但它也擁有一些操作系統(tǒng)特殊的要求 比如那些在GDI,DirectX,QuickDraw等等。
原因很簡(jiǎn)單,就是為了減輕從一個(gè)系統(tǒng)到另一個(gè)系統(tǒng)的接口問(wèn)題。
畢竟,這就是為什么花成千上萬(wàn)的工作時(shí)間把數(shù)以千計(jì)的操作系統(tǒng)函數(shù)從Windows源調(diào)用Mac一樣,為什么在不花時(shí)間去做調(diào)用,而去改寫功能?
根據(jù)STROM的多個(gè)版本,大約累計(jì)了275個(gè)實(shí)際有用的功能。
正如您看到的,沒(méi)更新STROM時(shí),仍然使用STROM庫(kù)函數(shù)。同樣,更新后依然是舊的STROM庫(kù)函數(shù),
只是做了更新。這是為了保證游戲在不同版本的兼容性。
這些275個(gè)使用功能分為約20個(gè)功能集(通常在Windows環(huán)境下稱為subsystems,在MAC環(huán)境下稱為managers。
下面所示部分清單:
Memory Subsystem? -記憶體子系統(tǒng)-例行的共同記憶功能,包括分配新的內(nèi)存,釋放分配內(nèi)存,灌裝記憶體,以及更多。STROM沒(méi)有自己的內(nèi)存管理,包括內(nèi)置的錯(cuò)誤檢查和其他強(qiáng)大的功能。該子系統(tǒng)功能與在PC上與'mem'前綴相等。
? String Subsystem? - 字符串子系統(tǒng)-功能是使用字符串,如復(fù)制,合并,搜索等這些職能是大多數(shù)部分,相等于'str'的功能。
? File Subsystem? ? - 文件子系統(tǒng)-功能是存取文件系統(tǒng)。有能力讀出(但不包括寫入)無(wú)論是在磁盤上的可靠文件,還是mpq檔案。撇開(kāi)mpq讀取的功能,其他功能都是高級(jí)系統(tǒng)功能運(yùn)行方式。
? Network Subsystem -網(wǎng)絡(luò)子系統(tǒng)-功能是接入遠(yuǎn)端的電腦系統(tǒng),通過(guò)使用IPX,調(diào)制解調(diào)器, TCP/IP和直接電纜。職能是與服務(wù)器或在游戲中玩家的通訊。使用高級(jí)系統(tǒng)特殊調(diào)用。
? Error Subsystem? -錯(cuò)誤子系統(tǒng)- 功能是捕捉和處理錯(cuò)誤。這些職能大部分沒(méi)有與任何操作系統(tǒng)的等值。
Registry Subsystem? -登錄子系統(tǒng)- 功能是持久性儲(chǔ)存數(shù)據(jù)到計(jì)算機(jī)中。使用注冊(cè)表在Windows系統(tǒng),或MACS系統(tǒng)上。
Bitmap Subsystem? ? -位圖系統(tǒng)-功能是位圖文件裝載和顯示。使用系統(tǒng)特殊調(diào)用。
目前為止,大約只記錄了40種功能,因?yàn)槲沂诌厸](méi)有足夠時(shí)間來(lái)做,認(rèn)真來(lái)做的話大約需要幾個(gè)月。
此外這里只只討論MPQ。
Using the Strom API? 使用STROM API函數(shù)
說(shuō)明:其余的這一章是針對(duì)Windows平臺(tái)的!
正如我以前說(shuō)過(guò),STROM功能任何人都可使用它們。不過(guò),暴雪并不希望如此。
我花了最近兩天時(shí)間,總結(jié)出來(lái):STROM使用一個(gè)非常邪惡的方法來(lái)對(duì)付我們這種想使用它的人。
我花了至少10個(gè)小時(shí)的努力,試圖解除愚蠢的事,而我現(xiàn)在可以很驕傲的說(shuō)我成功了,我會(huì)全力為您解釋冗長(zhǎng)而復(fù)雜的細(xì)節(jié)。
經(jīng)過(guò)我和Mike O'Brien所謂的the Storm Interface Library(接口庫(kù))斗智斗勇。發(fā)現(xiàn)這是由一個(gè)頭文件和導(dǎo)入庫(kù)組成,所以我做了Storm booby-traps這個(gè)工具 。要記得DLLs 101 ,是包含被用來(lái)當(dāng)程序編譯連接程序DLL的導(dǎo)入表的導(dǎo)入庫(kù) 。這意味著什么,就是所有您需要做的就是storm.lib (在STROM接口庫(kù))與模塊連接在您的程序和#include storm.h頭文件。這真令我瘋狂,我不得不讓您可以輕松使用the Storm Interface Library。
現(xiàn)在,為了以后少點(diǎn)麻煩,讓我們現(xiàn)在就看看STROM的功能。
Opening an MPQ Archive- SFileOpenArchive?
打開(kāi)MPQ存檔函數(shù)—SFileOpenArchive
| ? |
| ||||||||||||||||||||||||
在此之前,您可以先看一個(gè)MPQ文件,您必須先打開(kāi)它。
為此每您必須使用SFileOpenArchive。它會(huì)打開(kāi)一個(gè)存檔,并給你一個(gè)HANDLE,您可以稍后調(diào)用SFileOpenFileEx和SFileCloseArchive 。
第一個(gè)參數(shù),lpFileName,只不過(guò)是mpq公開(kāi)的名稱,絕不能為空。
第二個(gè)參數(shù),dwMPQID 是將指派給mpq內(nèi)部的ID。這并不改變mpq ,目前還不清楚為什么這樣做。
第三個(gè)參數(shù),dwUnknown,這是唯一我們認(rèn)為沒(méi)用的,但是不容忽視。
最后一個(gè)參數(shù),lphMPQ,是一個(gè)HADLE的指針(您必須先聲明)。
如果SFileOpenArchive成功完成,this HANDLE will be that of the MPQ。
如果SFileOpenArchive成功,其返回值將為零。
但是,有幾種情況可能導(dǎo)致SFileOpenArchive失敗。
如果它失敗,將返回一個(gè)值是假的。在這種情況下,您可以調(diào)用GetLastError獲得更進(jìn)一步的信息,為什么失敗。如果lpFileName是一個(gè)零長(zhǎng)度字符串或phMPQ是Null ,GetLastError 將返回ERROR_INVALID_PARAMETER 。如果該文件lpFileName不存在, GetLastError 將返回 ERROR_FILE_NOT_FOUND。在一些非常罕見(jiàn)的情況下, GetLastError可能會(huì)返回其他一些潛錯(cuò)誤值。
Closing an Archive - SFileCloseArchive
關(guān)閉存檔函數(shù) - SFileCloseArchive
| BOOL WINAPI SFileCloseArchive(HANDLE hMPQ); | |||
| ? | Parameter | What it is | ? |
| ? | hMPQ | [in] The HANDLE of the MPQ to close, which was acquired earlier with SFileOpenArchive. SFileCloseArchive will fail (or worse) if this is NULL or a HANDLE not obtained with SFileOpenArchive. | |
一旦您開(kāi)啟一個(gè)mpq存檔,你必須記住它關(guān)閉時(shí),您就大功告成了!SFileCloseArchive是SFileOpenArchive 的產(chǎn)物。
作為與SFileOpenArchive ,SFileCloseArchive返回一個(gè)非零值,那就是成功的
如果返回一個(gè)假值,那就失敗了。
然而,在這種情況下,GetLastError不會(huì)提供任何有用的信息。
-所以你只能假設(shè)原因是hMPQ參數(shù)是無(wú)效的。
Opening a File Inside an MPQ - SFileOpenFileEx
打開(kāi)MPQ內(nèi)的文件函數(shù) - SFileOpenFileEx
| ? |
| ||||||||||||||||||||||||
只是因?yàn)槟阌肧FileOpenArchive并不意味著您就可以從它立即開(kāi)始讀取。請(qǐng)記住,MPQ只不過(guò)是包含其他文件的多檔案文件。在您可以閱讀任何一個(gè)mpq ,您必須打開(kāi)一個(gè)(或多個(gè))的MPQ內(nèi)部檔案。 SFileOpenFileEx就是讓您使用這一任務(wù)的函數(shù);它將打開(kāi)在一個(gè)mpq所請(qǐng)求的文件并對(duì)其返回一個(gè)HANDLE。
再次, sfileopenfile將返回一個(gè)非零值就成功,是假值的就失敗,您可以調(diào)用getlasterror獲得的原因。如果lpfilename是一個(gè)零長(zhǎng)度字符串或lphfile是Null , getlasterror將返回error_invalid_parameter 。如果該文件不存在于mpq , getlasterror會(huì)報(bào)告error_file_not_found 。在一些罕見(jiàn)的情況下, getlasterror可能會(huì)報(bào)告error_file_invalid ,并就極為罕見(jiàn)的情況下,它可能會(huì)返回其他一些模糊的錯(cuò)誤值。
重要注意事項(xiàng):當(dāng)您調(diào)用sfileclosearchive關(guān)閉mpq ,同樣會(huì)關(guān)閉所有MPQ內(nèi)部打開(kāi)的文件,您獲取到來(lái)自sfileopenfileex的HANDLEs成為無(wú)效。如果您調(diào)用sfilereadfile , sfilegetfilesize , sfilesetfilepointer ,或sfileclosefile,這些其中一個(gè)無(wú)效的HANDL,調(diào)用將失敗,并且STROM甚至可能崩潰。
Closing a File Inside an MPQ - SFileCloseFile
關(guān)閉MPQ內(nèi)的文件函數(shù) - SFileCloseFile
| BOOL WINAPI SFileCloseFile(HANDLE hFile); | |||
| ? | Parameter | What it is | ? |
| ? | hFile | [in] The HANDLE of the file to close, which was acquired earlier with SFileOpenFileEx. SFileCloseFile will fail (or worse) if this is NULL or a HANDLE not obtained with SFileOpenFileEx. | |
就像SFileCloseArchive is to SFileOpenArchive, SFileCloseFile is the natural compliment of SFileOpenFileEx, 用來(lái)關(guān)閉一個(gè)已經(jīng)打開(kāi)的文件.
也想sfileclosearchive , sfileclosefile將返回一個(gè)非零值說(shuō)明成功的,假值的就失敗, getlasterror將不提供幫助。所幸的是,多于sfileclosearchive , sfileclosefile只在hFlie是NULL或一個(gè)無(wú)效的HANDLE。
Reading from a File in an MPQ - SFileReadFile
讀取MPQ內(nèi)文件函數(shù) - SFileReadFile
| BOOL WINAPI SFileReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped); | |||
| ? | Parameter | What it is | ? |
| ? | hFile | [in] The HANDLE of the file to read from, which was acquired earlier with SFileOpenFileEx. SFileReadFile will crash if this is NULL or a HANDLE not obtained with SFileOpenFileEx. | ? |
| ? | lpBuffer | [out] A pointer to a buffer in memory where SFileReadFile will place the data read from the file. This buffer must be at least as large as nNumberOfBytesToRead. SFileReadFile will fail if this is NULL. | ? |
| ? | nNumberOfBytesToRead | [in] The number of bytes for SFileReadFile to read from the file. SFileReadFile may crash if this is larger than the size of lpBuffer. | ? |
| ? | lpNumberOfBytesRead | [out] A pointer to a DWORD that will hold the number of bytes actually read from the file. The number of bytes read will never be more than nNumberOfBytesToRead, but may be less if the number of unread bytes in the file is less than nNumberOfBytesToRead. It is not recommended to let this be NULL. | ? |
| ? | lpOverlapped | [in] A pointer to an OVERLAPPED structure. This is used for asynchronous reading of files on a disk, and must be NULL when reading files in MPQs. | |
當(dāng)然,您要先打開(kāi)再讀取文件。
一旦你獲得一個(gè)有效的文件HANDLE從sfileopenfileex ,這就是這個(gè)函數(shù)的功能。 sfilereadfile會(huì)讀取指定的字節(jié)數(shù),然后推進(jìn)文件指針。這意味著,如果您有一個(gè)文件,您調(diào)用sfilereadfile會(huì)讀取的一半的檔案,當(dāng)您再次調(diào)用sfilereadfile,你會(huì)得到另一半的檔案。如果您需要再次讀取上半部分的文件,你會(huì)需要調(diào)用sfilesetfilepointer 。
sfilereadfile將提供一個(gè)非零的返回值就成功,或虛假的就失敗。不過(guò),你必須記住,只是因?yàn)樗祷匾粋€(gè)非零值,并不等于它實(shí)際上讀取了什么,這只是說(shuō)明沒(méi)有錯(cuò)誤發(fā)生。
如果在該文件hfile有不足未讀字節(jié)(字節(jié)從文件指針到文件結(jié)束)比要求數(shù)量少, sfilereadfile會(huì)讀不到的數(shù)目要求字節(jié);
如果檔案hfile的文件指針是在該文件的末尾, sfilereadfile會(huì)讀什么,設(shè)置lpnumberofbytesread為0 ,返回真。因此,檢查lpnumberofbytesread 是非常重要的。
Getting a File's Size - SFileGetFileSize
獲得文件大小函數(shù)- SFileGetFileSize
| ? |
| ||||||||||||||||
這是普遍認(rèn)為是最壞的編程實(shí)踐,因?yàn)橥耆梢赃M(jìn)行修改,在開(kāi)始讀取時(shí)讓不明長(zhǎng)度的文件不會(huì)造成崩潰。sfilegetfilesize在這里只是把戲,因?yàn)樗谟胹fileopenfileex打開(kāi)文件后才可以擷取文件的大小 。
也有一些重大的故障點(diǎn)在sfilegetfilesize 。首先是含糊不清錯(cuò)誤。當(dāng)sfilegetfilesize成功,它將返回文件的大小(可0 ! ) 。但是,當(dāng)發(fā)生錯(cuò)誤時(shí),它將返回0xFFFFFFFF的,而且同一件事,它會(huì)返回為4294967295字節(jié)( 4 GB的) 。所幸的是,這不是一個(gè)很大的問(wèn)題,正如您可能從未真正看到一個(gè)文件這么大。第二個(gè)問(wèn)題,更危險(xiǎn)的是sfilegetfilesize缺乏錯(cuò)誤檢查。 sfilegetfilesize不檢查是否hfile是有效或是否為零。這意味著,如果你給它一個(gè)hfile的無(wú)效的HANDLE ,程序崩潰,電腦也會(huì)崩潰。因此,底線是這樣的:無(wú)比謹(jǐn)慎使用此功能。
Moving the File Pointer - SFileSetFilePointer
移動(dòng)文件指針函數(shù)- SFileSetFilePointer
| DWORD WINAPI SFileSetFilePointer(HANDLE hFile, long nDistanceToMove, long *lpDistanceToMoveHigh, DWORD dwMoveMethod); | ||||||||
| ? | Parameter | What it is | ? | |||||
| ? | hFile | [in] The HANDLE of the file whose file pointer is to be moved. SFileSetFilePointer will crash if this is NULL or a HANDLE not obtained with SFileOpenFileEx. | ? | |||||
| ? | nDistanceToMove | [in] The low-order 32-bits of the number of bytes for SFileSetFilePointer to move the file pointer, with positive numbers moving the pointer forward and negative numbers moving the pointer backward. This value can also be 0. | ? | |||||
| ? | lpDistanceToMoveHigh | [in] A pointer to the high-order 32-bits of the distance for SFileSetFilePointer to move the file pointer. But, because MPQs do not support files this large, this is unused and must be NULL or SFileSetFilePointer will fail. | ? | |||||
| ? | dwMoveMethod | [in] Specifies the relative location the file pointer will be moved to. Must be one of these following values in Windows.h:
| ||||||
要想從文件任意位置讀取,就要先在該文件提出移動(dòng)指針,這個(gè)功能函數(shù)就是sfilesetfilepointer。
文件指針指向了下次讀取時(shí)的讀取位置。每次讀取和編寫將會(huì)移動(dòng)文件指針到讀寫區(qū)域的最底部。
sfilesetfilepointer實(shí)際上并不移動(dòng)文件指針,只是在文件中對(duì)應(yīng)ndistancetomove。相反, sfilesetfilepointer移動(dòng)文件指針到一個(gè)相對(duì)位置,無(wú)論是開(kāi)頭或結(jié)尾的文件。
舉例來(lái)說(shuō),假設(shè)您有一個(gè)千字節(jié)的文件。當(dāng)文件第一次打開(kāi),它的文件指針設(shè)置為0 ,則指的是第一個(gè)字節(jié)。然后,您讀100個(gè)字節(jié)從該文件。然后,您調(diào)用sfilesetfilepointer設(shè)置ndistancetomove 500 。如果您調(diào)用dwmovemethod 設(shè)置為file_begin,文件指針將被設(shè)定為500 。如果你曾dwmovemethod作為file_current ,文件指針將是600 ,因?yàn)槲募羔槺晦D(zhuǎn)移到了當(dāng)您從檔案讀取后100字節(jié)。但如果設(shè)置dwmovemethod以file_end , sfilesetfilepointer會(huì)失敗,因?yàn)樗鼘L試設(shè)置檔案指針一千四百九十九(文件中的最后字節(jié), 999 ,加500 )不存在。如果您多用幾次,會(huì)發(fā)現(xiàn)這是個(gè)非常簡(jiǎn)單的函數(shù)。
假如失敗, sfilesetfilepointer返回0xFFFFFFFF ,作為sfilegetfilesize也有同樣的陷阱 。但是,當(dāng)圓滿完成, sfilesetfilepointer會(huì)返回檔案hfile新的絕對(duì)位置的文件指針 。這意味著您可以簡(jiǎn)單地獲取當(dāng)前的立場(chǎng)文件指針調(diào)用sfilesetfilepointer設(shè)置ndistancetomove 0 , dwmovemethod設(shè)置為file_current 。事實(shí)上,這是為什么說(shuō)不存在sfilegetfileposition功能。
就像sfilegetfilesize , sfilesetfilepointer會(huì)肆無(wú)忌憚地使用任何你給它hfile的HANDLE,不會(huì)進(jìn)行任何錯(cuò)誤檢查。這意味著,確保獲得一個(gè)有效的文件處理,您必須謹(jǐn)慎,否則,您的電腦又會(huì)崩潰。
Choosing a Language - SFileSetLocale
選擇語(yǔ)言- SFileSetLocale
| LCID WINAPI SFileSetLocale(LCID lcNewLocale); | ||||||||||||||||
| ? | Parameter | What it is | ? | |||||||||||||
| ? | lcNewLocale | [in] The language code (LCID) that SFileSetLocale will make the new default. The following codes are ones that I've found in Starcraft MPQs:
| ||||||||||||||
SFileSetLocale 是功能簡(jiǎn)單,背后復(fù)雜的代表.它使用了暴雪的multilinguality系統(tǒng)。感謝它,一個(gè)單一的函數(shù)調(diào)用,保證所有文件讀出一個(gè)mpq的語(yǔ)言。它的唯一參數(shù)是the entire Storm MPQ subsystem。它絕不會(huì)失敗,并且它傳回的語(yǔ)言代碼你給它,不過(guò)其返回值毫無(wú)價(jià)值。
multilinguality系統(tǒng)原理是這樣的:每個(gè)MPQ檔案有一個(gè)語(yǔ)言代碼,并只要他們有不同的語(yǔ)言代碼就可以有多個(gè)文件具有相同名稱的。當(dāng)調(diào)用sfileopenfileex時(shí), sfileopenfileex尋找一個(gè)文件具有相同的語(yǔ)言代碼儲(chǔ)存,然后調(diào)用sfilesetlocale (如果sfilesetlocale從來(lái)沒(méi)有被調(diào)用,語(yǔ)言的代碼為0 ) 。如果一個(gè)文件匹配的語(yǔ)言代碼無(wú)法找到, sfileopenfileex將打開(kāi)中立語(yǔ)言(有一個(gè)語(yǔ)言代碼0 )版本的文件。
Putting it All Together - DumbExtractor
全部合到一起的工具 - DumbExtractor
?
第五章
THE STARCRAFT CAMPAIGN EDITOR AND THE MPQ API LIBRARY
星際爭(zhēng)霸編輯器和MPQ API LIBRARY
Starcraft Campaign Editor 是什么呢?其實(shí)就是一個(gè)能讓您自己作出星際爭(zhēng)霸地圖的程序。
并把地圖以SCM格式保存,或者保存為SCX文件。
可是這些個(gè)SCM/SCXs文件 和Warcraft 2是一樣的原始文件?
假如您用hex editor 看過(guò)這些文件,那么您會(huì)得到一個(gè)否定的答案。
實(shí)際上SCM/SCXs就是MPQ文件!!
那么你也許認(rèn)為很簡(jiǎn)單,那你就錯(cuò)了,StarEdit使用了一套難以捉摸的MPQ編寫套路。
不過(guò)在您仔細(xì)閱讀下面的內(nèi)容,您將會(huì)得到答案。
Using StarEdit - The MPQ API Library
使用編輯器- The MPQ API Library
注意:這章是針對(duì)WINDOWS平臺(tái)的,適用于THE MPQ API LIBRARY 2.0或更高!
在這里我們并不能很快的編譯,因?yàn)榫庉嬈饔械墓δ芪覀儾⒉荒苤苯邮褂谩?
不像STROM之類的shared libraries ,StarEdit擁有復(fù)雜的操作系統(tǒng)保護(hù)機(jī)智,而不只是對(duì)于文件的保護(hù)。就算您是一位很好的程序員,您一樣無(wú)法直接修改它。對(duì)于這種高難度的熟練的對(duì)運(yùn)行系統(tǒng)的改寫,還沒(méi)有人能完成過(guò)。 就在這個(gè)時(shí)候Andrey Lelikov (簡(jiǎn)稱Lelik)橫空出世。
Lelik是一位熟悉系統(tǒng)內(nèi)部工作機(jī)制的俄羅斯程序員。他設(shè)計(jì)了一個(gè)能夠使用 StarEdit MPQ 的方法。
他把自己寫的詳細(xì)功能放進(jìn)了MPQ API Library 。
就像STROM,MPQ API Library(又名LMPQAPI),它包含了共享庫(kù)(可惜的是,像STROM接口庫(kù),現(xiàn)在在MAC機(jī)上還沒(méi)有)。LMPQAPI不僅包括了StarEdit的MPQ編寫功能,而且提供接口讀寫STROM。
如果您想同時(shí)使用STROM和StarEdit,您不需要同時(shí)使用LMPQAPI和STROM接口庫(kù)。
一個(gè)LMPQAPI足已。
好吧。我提醒您一件事,您想在使用LMPQAPI的時(shí)候區(qū)分是用STROM還是StarEdit么?
STROM功能有就像使用STROM接口庫(kù)時(shí)一樣有一個(gè)前綴'SFile',在使用StarEdit時(shí),前綴是'Mpq'。
這是很重要的,因?yàn)檫@說(shuō)明了STROM和StarEdit的功能不兼容。
這意味著您無(wú)法用SFileOpenArchive獲得的MPQ HANDLE去在StarEdit('Mpq'前綴的函數(shù))里調(diào)用,反之亦然。 如果您還是調(diào)用的話,調(diào)用會(huì)失敗,程序會(huì)崩潰。記住這點(diǎn)。
?Sé Habla Espa?ol?
讓人講西班牙語(yǔ)?
因?yàn)?5%以上的星際爭(zhēng)霸或暗黑的玩家是以英語(yǔ)為母語(yǔ),所以大多數(shù)MPQ開(kāi)發(fā)測(cè)試都基于這些游戲的英文版。對(duì)于使用英文版游戲的玩家,MPQ會(huì)運(yùn)行良好。但事實(shí)上,98%的標(biāo)準(zhǔn)MPQ文件使用的是language-neutral (比如圖象文件等等)。甚至有人用全非英語(yǔ)的MPQ玩游戲也沒(méi)有問(wèn)題。
不過(guò),很明顯這里是有問(wèn)題的,只是還要等些時(shí)間才有人能發(fā)現(xiàn)吧。
做為上面兩章的解釋,MPQ格式具有強(qiáng)大的多國(guó)語(yǔ)言功能。
但是,您完全沒(méi)必要做多語(yǔ)言的SCM/SCXs。
這就是說(shuō),您完全不必要讓StarEdit支持多語(yǔ)言功能,就連暴雪的程序員都懶的做。
但是我們都有興趣研究MPQ,除非沒(méi)必要,其中的許多語(yǔ)言功能還是有用的。
在技術(shù)方面說(shuō),所有的StarEdit功能都只運(yùn)行有語(yǔ)言代碼為0或language-neutral代碼的文件。
也就是說(shuō),MpqAddFileToArchive和MpqAddWAVToArchive只增加language- neutral files, MpqDeleteFile只刪除language-neutral files, MpqRenameFile 只會(huì)重命名language-neutral files.
這種設(shè)計(jì)決定了,執(zhí)行結(jié)果并不明顯。在前一章也提到過(guò),假如在打開(kāi)有相同名稱不同語(yǔ)言的文件時(shí),STROM使用了SFileSetLocale做為語(yǔ)言過(guò)濾器,用來(lái)決定到底打開(kāi)哪個(gè)文件。
假設(shè)在StarEdit使用一個(gè)MPQ文件代替patch_rt.mpq ,而且在那個(gè)MPQ文件里有英語(yǔ)/language-neutral解析度文件rez\gluAll.tbl (這文件內(nèi)有多個(gè)語(yǔ)言版本),但是不能有葡萄牙語(yǔ)版本(選定任意語(yǔ)言)。
當(dāng)您運(yùn)行這個(gè)游戲的葡萄牙語(yǔ)版時(shí),程序會(huì)查詢?cè)谀鶰PQ文件中的該語(yǔ)言版。結(jié)果就是失敗,而且默認(rèn)又為英語(yǔ)版本的,對(duì)不對(duì)?
好吧,不是這樣的。STROM允許您同時(shí)打開(kāi)幾個(gè)MPQ文件,并且STROM會(huì)搜索已經(jīng)加載的MPQ,然后自動(dòng)加載最新的MPQ到文件里。但是在這個(gè)過(guò)程中,STROM在系統(tǒng)為language-neutral以前,會(huì)檢查已經(jīng)打開(kāi)的所有特殊語(yǔ)言的MPQ。
這意味著,前面STROM從broodat.mpq 載入葡萄牙語(yǔ)版本,而不是您自己的language-neutral 版本。
很不幸,現(xiàn)在還沒(méi)有解決的辦法,希望新版本的LMPQAPI能被解決吧。
Initializing the MPQ API Library - MpqInitialize
初始化MPQ API Library函數(shù)- MpqInitialize
BOOL WINAPI MpqInitialize();
不像STROM,LMPQAPI在控制StarEdit時(shí)有巨大的復(fù)雜的任務(wù)要做。
因此,無(wú)法在啟動(dòng)LMPQAPI時(shí)做完一切。你必須告訴LMPQAPI什么時(shí)候運(yùn)行。所幸,這很簡(jiǎn)單。
你要做的就是在您啟動(dòng)程序前調(diào)用MpqInitialize,LMPQAPI會(huì)自動(dòng)做完剩下的。
請(qǐng)確定在調(diào)用LMPQAPI其他函數(shù)前,您調(diào)用了MpqInitialize。此外,即使您對(duì)STROM接口庫(kù)什么都不做,也必須在任何STROM函數(shù)調(diào)用前被調(diào)用。
一次調(diào)用會(huì)同時(shí)初始化STROM和STAREIDT。
1.Starcraft/Brood War 1.07 必須已經(jīng)安裝,Storm.dll和StarEdit.exe必須在程序目錄中
2.StarEdit不能和LMPQAPI一起運(yùn)行.
為了調(diào)用MpqInitialize初始化上面的兩個(gè)要求必須滿足。盡管還有其他原因,上面兩個(gè)卻是最常見(jiàn)的。
不管什么原因,MpqInitialize調(diào)用失敗后,都會(huì)返回FLASH,您可以檢索GetLastError設(shè)置一個(gè)錯(cuò)誤值。
如果LMPQAPI無(wú)法在游戲目錄或在您程序的目錄里無(wú)法找到StarEdit.exe ,那么GetLastError會(huì)返回MPQ_ERROR_NO_STAREDIT;
如果StarEdit.exe的版本不匹配,GetLastError會(huì)返回MPQ_ERROR_BAD_STAREDIT;
如果StarEdit已經(jīng)運(yùn)行GetLastError會(huì)返回MPQ_ERROR_STAREDIT_RUNNING;
如果是因?yàn)槠渌騁etLastError通常會(huì)返回MPQ_ERROR_INIT_FAILED。
但是,無(wú)論為什么GetLastError調(diào)用失敗,返回了什么,您要做的就是盡快的關(guān)閉程序。
不要調(diào)用任何LMPQAPI功能(STROM或者StarEdit的功能),當(dāng)然也不要在調(diào)用MpqInitialize了。
Opening an MPQ for Editing - MpqOpenArchiveForUpdate
打開(kāi)MPQ- MpqOpenArchiveForUpdate
| HANDLE WINAPI MpqOpenArchiveForUpdate(LPCSTR lpFileName, DWORD dwCreationDisposition, DWORD dwHashTableSize); | |||||||||||||
| ? | Parameter | What it is | ? | ||||||||||
| ? | lpFileName | [in] A pointer to NULL-terminated string that holds the path of the MPQ to open. MpqOpenArchiveForUpdate will fail if this is NULL. | ? | ||||||||||
| ? | dwCreationDisposition | [in] Specifies what MpqOpenArchiveForUpdate should do with the archive if it does/doesn't exist. Must be one of the following values defined in lmpqapi.h:
| ? | ||||||||||
| ? | dwHashTableSize | [in] Whenever MpqOpenArchiveForUpdate creates a new archive (see the dwCreationDisposition for details on when this occurs), dwHashTableSize specifies how large the hash table for the new archive will be, with a minimum size of 16, and a maximum size of 262,144 (if dwHashTableSize is not within these values, MpqOpenArchiveForUpdate will change it). This parameter does not affect archives that already exist. | |||||||||||
與STROM一樣,您在使用前必須先打開(kāi)它.
MpqOpenArchiveForUpdate 打開(kāi)(或創(chuàng)建)一個(gè)存檔,這是為了您使用其他StarEdit功能的,并返回存檔的HANDLE.
但是不像SFileOpenArchive,MpqOpenArchiveForUpdate需要您在時(shí)間上做出選擇.
第一選擇是dwcreationdisposition參數(shù)。它告訴mpqopenarchiveforupdate是否應(yīng)該創(chuàng)建一個(gè)新的存檔還是打開(kāi)一個(gè)現(xiàn)有的,或兩者之間。其他的決定性參數(shù)是dwhashtablesize 。 dwhashtablesize告訴mpqopenarchiveforupdate創(chuàng)建什么大小存檔的哈希表(這也是該文件限制),在
mpqopenarchiveforupdate事件中必須創(chuàng)建要一個(gè)新的存檔。
做為一個(gè)存檔的哈希表大小差不多是1000,除非你知道存檔一定會(huì)超過(guò)1000個(gè)文件。
但同時(shí)請(qǐng)記住,每個(gè)哈希表項(xiàng)和存檔文件將新增16個(gè)字節(jié),(見(jiàn)第2章的哈希表,或第5章關(guān)于mpq哈希表的更多信息) 。
在上面的進(jìn)程中MpqOpenArchiveForUpdate可能調(diào)用失敗.
MpqOpenArchiveForUpdate將返回INVALID_HANDLE_VALUE或者NULL.
我們根據(jù)調(diào)用GetLastError通常可以獲取有用的信息,但不是絕對(duì).
如果lpfilename參數(shù)為空, getlasterror將返回error_invalid_parameter 。
如果dwcreationdisposition返回moau_open_existing或者lpfilename不存在, getlasterror將返回error_file_not_found 。反過(guò)來(lái)說(shuō),如果dwcreationdisposition返回moau_create_new和檔案lpfilename已經(jīng)存在, getlasterror將返回error_already_exists 。
最后,如果mpq存檔存在,但是是無(wú)效或損壞的, getlasterror將返回mpq_error_mpq_invalid 。
在一些罕見(jiàn)的情況下, getlasterror將返回其他一些錯(cuò)誤代碼。
Closing a Modified Archive - MpqCloseUpdatedArchive
關(guān)閉修改過(guò)的存檔- MpqCloseUpdatedArchive
| ? |
| ||||||||||||||||
這里和在STROM中一樣,您在哪用SFileOpenArchive打開(kāi)MPQ,那么就在修改它的地方用SFileCloseArchive 關(guān)閉.
那么MPQ存檔打開(kāi)時(shí)用MpqOpenArchiveForUpdate,關(guān)閉時(shí)用MpqCloseUpdatedArchive.
然而在這時(shí)候有一點(diǎn)區(qū)別.STROM不會(huì)修改實(shí)際MPQ,所以關(guān)閉MPQ HANDLE時(shí)沒(méi)有什么特別需要做的.
但是StarEdit 確實(shí)修改了MPQ.而且MPQ的散列值和文件表是在沒(méi)有調(diào)用MpqCloseUpdatedArchive關(guān)閉MPQ HANDLE前不能寫在MPQ分區(qū)的.
這意味著要快速關(guān)閉StarEdit MPQ HANDLEs,要不然您有可能再次程序崩潰,而無(wú)法保存修改的MPQ.
Adding a File - MpqAddFileToArchive
添加文件函數(shù)- MpqAddFileToArchive
| ? |
| ||||||||||||||||||||||||||||||
往往大約 95%的時(shí)候會(huì)在使用 StarEdit MPQ功能時(shí)候添加文件. 對(duì)于這個(gè)任務(wù), 您會(huì)用到的函數(shù)有MpqAddFileToArchive和它的姊妹功能MpqAddWAVToArchive (稍后討論).
MpqAddFileToArchive在MPQ hMPQ分區(qū)添加文件lpSourceFileName 使用名稱lpDestFileName, 并在這個(gè)過(guò)程中壓縮 或/和 加密它.
由于一些設(shè)計(jì)上的疏漏,在您并沒(méi)有完全明白前,MpqAddFileToArchive會(huì)是個(gè)大麻煩.
1.MpqAddFileToArchive并不檢查lpSourceFileName和lpDestFileName是否為空.
就是說(shuō)假如任一參數(shù)為空,您會(huì)再次看到程序崩潰.
2.覆蓋MPQ中已有文件時(shí)MpqAddFileToArchive的運(yùn)行機(jī)制.
當(dāng)您調(diào)用MpqAddFileToArchive去添加的文件已經(jīng)存在MPQ中(在這種情況下,你就不得不指定dwflags為mafa_replace_existing ),MpqAddFileToArchive會(huì)在不確定文件lpSourceFileName是否存在前就不分青紅皂白地刪除存在的文件lpDestFileName.
解決的辦法很簡(jiǎn)單:確保lpsourcefilename和lpdestfilename是有效的(非空) ,以及確保lpsourcefilename存在之前,調(diào)用mpqaddfiletoarchive 。
現(xiàn)在您應(yīng)該可以饒過(guò)所有的障礙,用mpqaddfiletoarchive成功添加文件了吧.
這時(shí)mpqaddfiletoarchive將允許返回TURE.
如果有錯(cuò)誤,它將返回FALSE 。在這種情況下,少量的信息可調(diào)用getlasterror 。
如果該文件lpsourcefilename不存在, getlasterror將返回error_file_not_found (盡管它的有點(diǎn)晚) 如果哈希表是FULL(見(jiàn)第2章) , getlasterror將返回mpq_error_hash_table_full 。
如果該文件lpdestfilename已經(jīng)存在 和在dwflags中未指定mafa_replace_existing , getlasterror將返回mpq_error_already_exists 。
但是,在很多情況下getlasterror將返回其他一些錯(cuò)誤代碼或沒(méi)有代碼。
Adding a File with WAV Compression - MpqAddWAVToArchive
添加WAV壓縮文件函數(shù)- MpqAddWAVToArchive
| ? |
| ||||||||||||||||||||||||||||||||||||||
毫無(wú)疑問(wèn),最流行的新功能的lmpqapi 2.0版(就是我做的) ,便有功能 mpqaddwavtoarchive 。
雖然mpqaddfiletoarchive能壓縮約80 %的文件(非wav文件) ,它對(duì)WAV文件的壓縮只有平均約5 % 。這是由于wav數(shù)據(jù)性質(zhì)和它的不可壓縮性。
在這里,對(duì)于wav壓縮的壓縮是必要的,而 mpqaddwavtoarchive就實(shí)現(xiàn)了這個(gè)功能。
盡管工作原理不同,MpqAddWAVToArchive的界面與 MpqAddFileToArchive卻是幾乎相同的。
唯一的區(qū)別就是多了一個(gè)新函數(shù)dwQuality。
參數(shù)設(shè)置后WAV將會(huì)被壓縮。
不過(guò)不像mpqaddfiletoarchive的標(biāo)準(zhǔn)壓縮, mpqaddwavtoarchive的wav壓縮,實(shí)際上降低了wav的質(zhì)量 。質(zhì)量降低多少依賴于dwquality 。
如果您有一個(gè)音樂(lè)WAV而又想保證質(zhì)量,那您就使用mawa_quality_high ,因?yàn)樗詈玫谋A魒av的質(zhì)量;而同一個(gè)聲音wav , mawa_quality_low通常是不行的,因?yàn)槁曊{(diào)比較容易壓縮失真; mawa_quality_medium往往是最有效的。
Deleting a File - MpqDeleteFile
刪除文件函數(shù) - MpqDeleteFile
| ? |
| ||||||||||||||||
少數(shù)情況下,您可能需要?jiǎng)h除MPQ中的某個(gè)文件,那么您就需要用到MpqDeleteFile。
MpqDeleteFile能從已經(jīng)打開(kāi)的存檔hMPQ中刪除文件lpFileName。
不過(guò)這不是看上去那么簡(jiǎn)單的。
mpqdeletefile為lpfilename刪除哈希和文件表項(xiàng) ,使其無(wú)法進(jìn)入。
但是除非文件在MPQ的physical end ,否則MpqDeleteFile無(wú)法清除內(nèi)存。
這就是說(shuō),通常情況下MPQ文件是不會(huì)減小大小的。
不過(guò)空間可以添加新文件循環(huán)再利用。稍后為您介紹MpqAddFileToArchive 和MpqAddWAVToArchive.
就像STROM和STAREDIT幾乎所有的功能一樣,調(diào)用MpqDeleteFile成功就返回TURE,失敗就返回FALSE。
失敗的話,您可以調(diào)用GetLastError去獲得一些關(guān)于失敗原因的信息(假如有的話)。
在這種情況下GetLastError是驚人的有效。
因?yàn)閹缀踔挥幸环N會(huì)造成MpqDeleteFile失敗的原因(除了hMPQ無(wú)效):
該文件lpFileName不存在于MPQ,GetLastError將返回MPQ_ERROR_FILE_NOT_FOUND。
Renaming a File - MpqRenameFile
重命名文件函數(shù)- MpqRenameFile
| BOOL WINAPI MpqRenameFile(HANDLE hMPQ, LPCSTR lpOldFileName, LPCSTR lpNewFileName); | |||
| ? | Parameter | What it is | ? |
| ? | hMPQ | [in] The HANDLE of the MPQ that holds the file to be renamed, and was acquired earlier with MpqOpenArchiveForUpdate. MpqRenameFile will fail if this is NULL or a HANDLE not obtained with MpqOpenArchiveForUpdate. | ? |
| ? | lpOldFileName | [in] A pointer to a NULL-terminated string containing the name of the file in the MPQ to be renamed. MpqRenameFile will fail if this is NULL. | ? |
| ? | lpNewFileName | [in] A pointer to a NULL-terminated string containing the name that the file lpOldFileName will be changed to. MpqRenameFile will fail if this is NULL | |
在我慢慢研究MPQ后發(fā)現(xiàn)StarEdit缺少一對(duì)非常有用的功能。
而且那個(gè)時(shí)候我對(duì)與STROM和StarEdit的運(yùn)做有了相當(dāng)?shù)牧私?#xff0c;所以我決定寫個(gè)自己的功能。
MpqRenameFile 非常的簡(jiǎn)單;它重新把hMPQ中的文件名 由lpOldFileName變?yōu)閘pNewFileName.
mpqrenamefile返回給您的依舊是簡(jiǎn)單的信息。
mpqrenamefile返回true就成功,FLASH失敗,并讓您調(diào)用getlasterror獲得失敗的原因 。
如果hMPQ HANDLE 是NULL或無(wú)效,lpoldfilename或lpnewfilename是Null , getlasterror將返回error_invalid_parameter 。
如果該文件lpoldfilename不存在于MPQ hMPQ中, getlasterror將返回mpq_error_file_not_found 。
如果該文件lpnewfilename已經(jīng)存在mpq中 , getlasterror將返回mpq_error_already_exists 。
Compacting an MPQ - MpqCompactArchive
壓縮MPQ函數(shù)- MpqCompactArchive
| ? |
| ||||||||||||||||
記得我剛才說(shuō)過(guò)調(diào)用mpqdeletefile實(shí)際上并不刪除文件;它只是使它們無(wú)法使用。那么, mpqaddfiletoarchive和mpqaddwavtoarchive也是一樣。
當(dāng)您添加一個(gè)已經(jīng)存在的文件或添加比舊文件大的新文件,新的文件將附加,和原來(lái)被占用的空間不會(huì)被釋放。正因?yàn)槿绱?#xff0c;每當(dāng)您建立一個(gè)大的復(fù)雜的包含許多覆蓋/刪除的檔案的mpq,將會(huì)有很大的體積,并且沒(méi)有辦法從零重建MPQ。
解決這個(gè)問(wèn)題的方法是調(diào)用mpqcompactarchive :這是我設(shè)計(jì)的第三個(gè),也是最后一個(gè),最困難的mpq功能。 mpqcompactarchive的功能是把數(shù)據(jù)壓縮到一個(gè)緩沖文件,然后再傳回。這就是說(shuō)在壓縮的時(shí)候,在磁盤上的緩沖文件必須有足夠的可用空間(這取決于MPQ的大小)。
雖然mpqcompactarchive可以讓幾乎所有的文件變得緊湊,但是還是有不能成功的類型:有mafa_encrypt和mafa_modcryptkey屬性,但缺乏mafa_compress和mafa_compress2屬性的文件(如需詳細(xì)資訊,請(qǐng)參閱第5章) 。當(dāng)mpqcompactarchve調(diào)用這種文件,getlasterror會(huì)返回錯(cuò)誤代碼mpq_error_compact_error ,然后刪除違規(guī)檔案,并繼續(xù)壓縮過(guò)程。
mpqcompactarchive返回TURE就成功,FLASH就失敗。您可以調(diào)用getlasterror獲得錯(cuò)誤信息 。
如果hmpq是Null或無(wú)效的staredit HANDLE, getlasterror將返回error_invalid_parameter 。
如果沒(méi)有足夠的可用記憶體為mpqcompactarchive撥出2.5 MB的讀取緩沖區(qū), getlasterror將返回error_outofmemory 。
如果沒(méi)有足夠的可用磁盤空間mpqcompactarchive分配緩沖文件, GetLastError返回error_disk_full 。在某些特殊的情況下, getlasterror可能會(huì)返回其他一些奇怪的錯(cuò)誤代碼。
Getting Information About a File - SFileGetFileInfo
獲取文件有關(guān)信息函數(shù) - SFileGetFileInfo
| DWORD WINAPI SFileGetFileInfo(HANDLE hMPQorFile, DWORD dwInfoType); | ||||||||||||||
| ? | Parameter | What it is | ? | |||||||||||
| ? | hMPQorFile | [in] The HANDLE of the either the MPQ or file inside an MPQ to obtain info about, which was acquired earlier with either SFileOpenArchive or SFileOpenFileEx. SFileGetFileInfo will fail (or worse) if this is NULL or a HANDLE not obtained with SFileOpenArchive or SFileOpenFileEx. | ? | |||||||||||
| ? | dwInfoType | [in] The type of info to obtain about the file or MPQ. Must be one of the following values defined in lmpqapi.h:
| ||||||||||||
SFileGetFileInfo 是 LMPQAPI中最古怪的功能函數(shù).
不僅是唯一可以通過(guò)LMPQAPI而不能通過(guò)STROM接口庫(kù)的STROM功能。
它唯一的函數(shù)就是能夠接受MPQ或者文件HANDLE,但在大多數(shù)情況下它根本不存在于STROM里。
為了補(bǔ)救這個(gè),我自己寫了了這個(gè)SFileGetFileInfo。
sfilegetfileinfo是專門設(shè)計(jì)來(lái)提供有用的mpq和其內(nèi)檔案的信息;包括mpq中壓縮文件的大小,文件指針的位置。它使用簡(jiǎn)單,但相當(dāng)有效。它使用已經(jīng)打開(kāi)了的mpq或文件的HANDLE,以獲取有關(guān)hmpqorfile的信息 ,以及在dwinfotype中需要的信息類型代碼。
如果sfilegetfileinfo成功,返回值就是有關(guān)文件或MPQ的資料 。
但是,如果失敗的話,它會(huì)返回0xFFFFFFFF,并設(shè)置一個(gè)錯(cuò)誤代碼,您可以調(diào)用getlasterror查看 。
如果因?yàn)閔mpqorfile HANDLE是無(wú)效的, getlasterror將返回error_invalid_parameter 。
如果因?yàn)閐winfotype是一個(gè)無(wú)效的信息代碼,getlasterror將返回error_unknown_property 。 sfilegetfileinfo只在一種情況下會(huì)調(diào)用失敗(除了嚴(yán)重的內(nèi)部lmpqapi錯(cuò)誤)。
因?yàn)橐粋€(gè)mpq HANDLE和dwinfotype指定的信息只能得到一個(gè)文件HANDLE,或反之亦然,在這種情況下, getlasterror將返回error_unknown_property。
總結(jié)
以上是生活随笔為你收集整理的inside MPQ的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 智能工厂信息系统架构设计-WMS、ERP
- 下一篇: 计算机开机壁纸能不能更换,如何修改电脑开