找回密码
 点一下
查看: 28267|回复: 44

[翻译版]inside MPQ(完结)

[复制链接]
发表于 2008-5-12 03:57:15 | 显示全部楼层 |阅读模式
翻译前声明:
          本翻译对于原文进行了适量删节和修改。
          本翻译只做为学习参考使用,不得用于任何商业目的。
          由于本人能力有限,如有错误请回帖指出
          要转贴本翻译请征得翻译作者的同意。
原文地址:http://www.campaigncreations.org/starcraft/inside_mopaq/
翻译文章地址:http://www.islga.org/bbs/read.php?tid=13735
原文作者:Justin Olbrantz
翻译作者:喀尔硫司之瞳        QQ:526312052

第一章    关于MPQ的历史


MPQ,也称MoPaQ,是Mike O'Brien发明的一种压缩文件格式。
在1996作为,MPQ应用在Diablo(暗黑破坏神)游戏中。
然而它的版权属于 Blizzard 的父公司 Havas Interactive,并且在Mike O'Brien离开暴雪后继续使用。 正是MPQs由于在Diablo(暗黑破坏神)中的出色表现,使其继续应用在Starcraft(星际争霸), Warcraft 2(魔兽争霸2), Diablo 2(暗黑破坏神2), Lords of Magic(魔法大帝)中。

第二章    关于MPQ的介绍


    MPQ内部包含了许多文件,包括坐标算法、声音、动画、字符串、数字数据和故事情节信息。
明显地,MPQ的潜力很大。要想利用MPQ,那么您就需要了解它。

  在有MPQ格式之前,一直使用的是WAR格式,在Warcraft 2,甚至在Warcraft1中存放游戏数据。然而WAR格式是简单的,不精制的,是由缺乏经验的程序员所编写的文件格式(相信我,我知道)。文件在档案中仅使用参考序数和是否被压缩做为唯一可选择调用的方法。
尽管如此它仍然完成了它的任务。它提供了压缩格式下的文件调用。但是,很快缺点开始出现。调用时使用参考序数,意味着一长传文件接口的名单必须被保留和被咨询,当程序员需要使用其中一个文件,那么则需要级长的时间,工作变得越来越繁琐。
当时这些问题并没有那么严重,所以有人坚持使用WAR格式,但是一切在使用Battle.net(网络对战)后,问题变得不能接受。

  
MPQ的特点  
  如被提及以前,MPQ格式一直被用做修正WAR的设计缺陷。但是现在他们也想增加一些全新的特点到MPQ。在暴雪的游戏中,MPQ格式的特点总结为以下几点:

Security. 安全

暴雪一定不希望在游戏中玩家可以修改数据。或许他们提早知道MPQ格式可以为Starcraft使用。 不管怎样,安全是最重要的,由此他们显然做了级大的努力去维护游戏的安全性。

Efficiency. 效率

MPQs要求执行时先简单预先输入的各种各样的任务数据然后实时放出。对于预先输入数据,时间并不重要。 但是实时放出就是另一件事了,其中的数据必须快速地被解压使用。

Multilinguality.多语言的计算机处理

在最开始的时候,暴雪就计划发布其游戏在全球游戏市场,因此他们尽可能的做到多语言。 在创新时,他们决定设计多语种能写入MPQ格式。

Expandability.扩展

显然的,在游戏中需要使用独立的数据。太大的数据不仅是效率低并且减慢游戏速度,如果补丁修改了,也是很麻烦的。暴雪明白这个道理,因而MPQ格式的要求就是有能力完全,高效率的,从多个档案数据中调用需要的数据。

  
什么是strom  
  相比在程序模块中复制函数,多数程序员喜欢把相同代码放到shared libraries(共享程序库)里。shared libraries是包含了任意程序功能的函数模块。不仅能避免多余,并且能缩小程序大小。
正因为如此,暴雪使用一个称为Storm的共享程序库(PC机上为Storm.dll,MAC机为Storm.bin)。
所有现代的暴雪游戏中都使用strom存放重要功能,比如读取MPQ,Battle.net和一些图形化例程。
当暴雪要发布新版本的游戏,只需要增加功能到strom,无需改变原有功能。 这意味着旧版本的游戏只用升级新版本strom就可以了,这就是我们俗称的安装补丁。
就像所有共享程序库,任何想使用它的程序都可以访问到它的函数。这就是为什么strom只包含MPQ读取功能。


什么是 MPQ API Library DLL

虽然 Storm 没有包含任何编写MPQ的功能。
但是 StarEdit 包含,因为 SCM/SCX 文件也是 MoPaQ文件。
但是这些函数被加密了,所以只有知识渊博的黑客们才可以使用。
对于Blizzard 来说不幸的是,有一个这样的黑客,他的名字是 Andrey Lelikov(aka Lelik)。
他发现了一种访问这些宝贵的函数的途径,并把这个复杂的过程封装在
LMPQAPI.DLL(Lelik's MPQ API Library DLL)文件中。该文件自动破解
StarEdit,将这些函数展示在所有的程序员面前。

评分

参与人数 1威望 +10 收起 理由
kook + 10 good job

查看全部评分

 楼主| 发表于 2008-5-12 03:57:31 | 显示全部楼层
第三章      MPQ的基本原理

通过整个计算机发展史来看,绝大多数的进步都是在求解问题中发生的。
那么在这一章中,我们将采取看看一些涉及到MPQ的问题及其解决办法。
  
HASH (散列或哈希)

  问题:你有一个非常大的字符串数组,和一个字符串
        怎么知道字符串是否在数组中?

你可能会开始在数组中与其他字符串比较每个字符串,但是,当进行应用后,你会发现,这种方法在实际使用时是特别慢的。在此之前,你又怎能在没有与其他字符串比较的情况下,确定这个字符串是否存在?

解决方法:hash
hash是规模较小的数据类型(例如数字)能指向其他较大的数据类型(通常是字符串) 。在这种情况下,您可以在数组中先存储hash。然后再计算其他字符串的hash,并比较它存储的hash。通过字符串比较,如果hash在数组相匹配的新的hash,就可以核实存在。这就是所谓的索引查找,可以加快对于不同大小的数组和平均长度的字符串的搜索速度约100倍。

unsigned long HashString(char *lpszString)
{
unsigned long ulHash = 0xf1e2d3c4;

while (*lpszString != 0)
{
ulHash <<= 1;
ulHash += *lpszString++;
}

return ulHash;
}

上面的代码,体现了一个很简单的散列算法。
功能是在每个字符添加前,把哈希值向左移动1bit,并总计字符串中的字符。
使用这种算法,字符串“arr\\ units.dat ”将散列为0x5a858026,“unit\\neutral\\ acritter.grp ”将散列为0x694cd020 。
无可否认,这是一个很简单的算法,但是不是非常实用。因为在较低的数字范围内会产生一个相对可预见的输出,以及出现大量的冲突。当多于一个字符串散列为相同值就会出现冲突。
MPQ格式使用一个非常复杂的散列算法(如下所示),产生完全不可预测的哈希值,这个算法十分有效,这就是所谓的单向散列。
就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值。从预映射,能够简单迅速的得到散列值,而在计算上不可能构造一个预映射,使其散列结果等于某个特定的散列值。
即构造相应的任意长度明文=固定长度散列值-1(固定长度散列值)不可行。
故此使用特别算法,文件名“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(散列表或哈希表)

问题:您尝试在前面的示例中使用相同索引,您的程序一定会有中断现象发生,而且不够快。
      您能做的只有让程序不去查询数组中的所有散列值。或者 您可以只做一次对比就可以得出在列表        中是否存在字符串。
      听起来不错,真的么?   
      骗你的啦!!!

解决方案:a hash table

哈希表是数组中的一种特殊类型,也就是设定指定字符串的偏移量为那个字符串的散列值。
我的意思是,假如您设置一个字符串列表,使用一个单独的固定大小的数组作为哈希表。
您想查看新的字符串是否在前面的哈希表里。
那么您需要先计算要查看字符串的散列值,然后以哈希表大小的散列值为模求余数。
因此,如果您使用上面列出的简单散列算法,"arr\\units.dat"将散列为0x5A858026,使其偏移量
0x26(0x5A858026 divided by 0x400 is 0x16A160, with a remainder of 0x26)。
假如这个地方有字符串,那么就会与被添加的字符串比较。
假如字符串在0x26不匹配,或直接不存在,那说明该添加的字符并不在在数组中。
下面的代码说明了这:
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
}
  

现在在这方面的解释有一个明显的缺陷。您认为发生冲突时(两个不同的字符串哈希以同等价值的) ?显然,他们不能在哈希表占用相同的接口。通常,解决的方法是在哈希表每个接口作为一个指针到一个链表,然后将链表里所有接口的散列值设置相同。
MPQ使用一个关于文件名称的哈希表记录内部文件,但是这个哈希表的格式与普通哈希表有所不同。
首先,MPQ根本不保存文件名,用三个散列值代替保存散列值的偏移量和为了核查文件名保存真实的文件名。
而是使用三个不同哈希值:一个做为哈希表的偏移量,两个是做为核查。
两个做为核查的哈希值被用来代替真实的文件名称。当然,也有可能两个不同文件名称的散列值相同,
不过这种情况发生的可能性为平均1:18889465931478580854784 ,对于任何人来说这应该足够安全了。


另一种方法:不同于常规的执行情况的mpq哈希表。
代替使用每个接口的链接表。当冲突发生时,把接口移动动下一个序列,并且重复动作,直到找到空闲空间。
下面的代码是在MPQ设置读取的基本方法:
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
}
每条代码反复研究,理论的背后是不难的。
它基本上是如下这个过程:

1.计算3个散列值(一个冲突和两个检查)并将其存储在变量。
2.移动冲突散列值的接口
3.接口未使用的吗?如果是的话,停止搜寻,并传回'文件没有被发现' 。
4.两个检查是否匹配检查我们正在寻找文件的散列值呢?如果是的话,停止搜寻,并传回目前的接口。
5.如果在最后一个接口,移动到列表中的下一个接口,(wrapping around to the beginning ??)。
6.刚移动的借口是否和冲突时的散列值相同(是否检查了整个哈希表? ) ?如果是的话,停止搜寻,并传回'文件没有被发现' 。
7.回到第3步。
如果您很仔细的话,您可能会从我的解释和示例代码注意到,是因为mpq的哈希表已保留所有文件接口在MPQ 。那么您认为每一个哈希表项如何得到填补?答案可能出乎您的意料却显而易见:您不能继续添加文件。几个人都问我为什么有一个上限(所谓的档案限制),在一个MPQ中可以有多少档案, ,是否有任何的方式解决这个限制。那么,您已经有了第一个问题的答案。至于第二项;没有,您不能绕开该文件的限制。对这个问题,哈希表,甚至不能调整大小,除非您重新改造MPQ。在哈希表每个接口因为重新设置大小不同位置可能会改变。而且导致无法获得新的地址,因为地址是文件名的散列值,并且我们还可能不知道档案名称。


Compression  压缩
问题:您有一个很大的程序(比如说, 50 megs ) ,您要分发在互联网上。但50 megs是一个非常大的下载量,而且别人未必有兴趣等待四个半小时去下载这个程序。

解决方法:压缩。
压缩是一门艺术。是在更小的内存中重新放置等量的数据。
有数以百计不同的压缩算法,使用不同的方式。
MPQ实际使用的算法是the Data Compression Library, licensed from PKWare (one of the leaders in applied compression),在此解释太过于复杂。相反,我会尝试解释一个更简单的压缩算法的例子。

                    本章节并不完全 ,因为作者没写完


Encryption  加密

这个世界上总是有喜欢剽窃的人存在,所以我们需要有一个保护资料安全的系统。
千百年来人们一直试图传递信息给他人。从手写的信件进行,信使徒步穿越古希腊,纳粹潜艇的无线电传输,在第二次世界大战,使用信用卡交易,到网络应用的今天,有能力去确保别人无法获得您的信息是必要的。
所谓的加密是复杂的艺术的保护,然而我们不知道设计第一个算法的人,我们也不知道到底有多少的算法。一切从简单的数据加扰,嬗变,甚至算法,其中有解密密钥(有时也称为密码)是不同的加密密钥(在一个方法所谓非对称加密) ,已做了一次又一次。
做为一个全面的权威加密方法,本文章肯定从来没有索赔,也不期望。
您只需要知道加密是你与MPQ直接相关的。
让我们从一个简单的加密算法开始,这是刊登在《Basic Lab Notes》 (为了可读性本人改变了一些变数名称,评论删除) :
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;
}

这是非常简单的哈希代码,不应被用来在一个实际的程序中使用。
即使代码是隐藏的(没有双关语意),这也是简单的 。
不言而喻,这是通过块进行加密的,把每个字节与相应的字节的密码转换为二进制。然后修改字节的密码,加入13 ( 选择13是因为这是一个素数)。这样做是为了使代码的模式,更难以识别。
那么,用此算法,加密字符串“encryption” ( 65 6E 63 72 79 70 74 69 6F 6E),加密的密码“ mpq ” (4D 50 51 ),这样会得到一个无法读取字符串(28 3E 32 28 24 2E 13 03 04 1A)。
现在,这个算法是对称的。这意味着密码是用来加密有相同密码的块。事实上,由于转换为二进制是一个对称的运作,完全相同的算法可以用来解密。请注意,大多数的对称加密算法是不完全对称,所以他们要求加密和解密的功能有所不同。

好吧,下面就就是关键的地方。
如果您想要编写,就必须在哪里都知道加密算法。
教导给您这个方法是我的使命。

MPQ的加密算法混合其他加密技术。它创建了一个加密表(这也是用在散列函数) ,并使用一个文件的加密密钥,以挑选出某些成员的加密表。然后对表中的成员进行转换成二进制数据加密。现在,用一个相当奇怪的方法来做,所以或许有些代码将显示您it is overcomplicated :-p。以下代码生成密码表数组长度为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);
}
}
}

你是不是越来越觉得暴雪聘请了一名心怀不满的微积分教授写这些算法?还好对与我这不是问题,如果你不明白此代码。如果您想要编写,您需要这些功能,你不一定要了解他们。无论如何,在密码表初始化后,我们可以解密MPQ数据,具有下列功能(不要期望我向您解释,我不想知道如何运作自己! ) :
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;
}
}
回复

使用道具 举报

 楼主| 发表于 2008-5-12 03:57:36 | 显示全部楼层
第四章    STROM

称为STROM库函数,或者简称为STROM。
它是对于本身的运行系统,拥有庞大的功能库函数。甚至不需要Microsoft支持。
它本身包含了足够强大的功能,甚至不需要调用本地API函数。
事实上,STROM包含了所有暴雪编写的可以重复使用的功能。
但它也拥有一些操作系统特殊的要求 比如那些在GDI,DirectX,QuickDraw等等。
原因很简单,就是为了减轻从一个系统到另一个系统的接口问题。
毕竟,这就是为什么花成千上万的工作时间把数以千计的操作系统函数从Windows源调用Mac一样,为什么在不花时间去做调用,而去改写功能?

根据STROM的多个版本,大约累计了275个实际有用的功能。
正如您看到的,没更新STROM时,仍然使用STROM库函数。同样,更新后依然是旧的STROM库函数,
只是做了更新。这是为了保证游戏在不同版本的兼容性。
这些275个使用功能分为约20个功能集(通常在Windows环境下称为subsystems,在MAC环境下称为managers。
下面所示部分清单:
Memory Subsystem  -记忆体子系统-例行的共同记忆功能,包括分配新的内存,释放分配内存,灌装记忆体,以及更多。STROM没有自己的内存管理,包括内置的错误检查和其他强大的功能。该子系统功能与在PC上与'mem'前缀相等。

  String Subsystem  - 字符串子系统-功能是使用字符串,如复制,合并,搜索等这些职能是大多数部分,相等于'str'的功能。

  File Subsystem    - 文件子系统-功能是存取文件系统。有能力读出(但不包括写入)无论是在磁盘上的可靠文件,还是mpq档案。撇开mpq读取的功能,其他功能都是高级系统功能运行方式。

  Network Subsystem -网络子系统-功能是接入远端的电脑系统,通过使用IPX,调制解调器, TCP/IP和直接电缆。职能是与服务器或在游戏中玩家的通讯。使用高级系统特殊调用。

  Error Subsystem  -错误子系统- 功能是捕捉和处理错误。这些职能大部分没有与任何操作系统的等值。

Registry Subsystem  -登录子系统- 功能是持久性储存数据到计算机中。使用注册表在Windows系统,或MACS系统上。

Bitmap Subsystem    -位图系统-功能是位图文件装载和显示。使用系统特殊调用。

目前为止,大约只记录了40种功能,因为我手边没有足够时间来做,认真来做的话大约需要几个月。
此外这里只只讨论MPQ。



Using the Strom API  使用STROM API函数

说明:其余的这一章是针对Windows平台的!

正如我以前说过,STROM功能任何人都可使用它们。不过,暴雪并不希望如此。
我花了最近两天时间,总结出来:STROM使用一个非常邪恶的方法来对付我们这种想使用它的人。
我花了至少10个小时的努力,试图解除愚蠢的事,而我现在可以很骄傲的说我成功了,我会全力为您解释冗长而复杂的细节。
经过我和Mike O'Brien所谓的the Storm Interface Library(接口库)斗智斗勇。发现这是由一个头文件和导入库组成,所以我做了Storm booby-traps这个工具 。要记得DLLs 101 ,是包含被用来当程序编译连接程序DLL的导入表的导入库 。这意味着什么,就是所有您需要做的就是storm.lib (在STROM接口库)与模块连接在您的程序和#include storm.h头文件。这真令我疯狂,我不得不让您可以轻松使用the Storm Interface Library。
现在,为了以后少点麻烦,让我们现在就看看STROM的功能。

Opening an MPQ Archive- SFileOpenArchive  
打开MPQ存档函数—SFileOpenArchive


BOOL WINAPI SFileOpenArchive(LPCSTR lpFileName, DWORD dwMPQID, DWORD dwUnknown, HANDLE *lphMPQ);
ParameterWhat it is
lpFileName[in] A pointer to NULL terminated string that holds the path of the MPQ to open. SFileOpenArchive will crash if this is NULL.
dwMPQID[in] An ID value that is saved internally in Storm for the MPQ. What this parameter is used for is not clear at this time.
dwUnknownUnknown. Should always be NULL.
lphMPQ[out] Pointer to a HANDLE variable that, upon successful completion, will hold the HANDLE of the MPQ. SFileOpenArchive will fail if this is NULL.

在此之前,您可以先看一个MPQ文件,您必须先打开它。
为此每您必须使用SFileOpenArchive。它会打开一个存档,并给你一个HANDLE,您可以稍后调用SFileOpenFileEx和SFileCloseArchive 。
第一个参数,lpFileName,只不过是mpq公开的名称,绝不能为空。
第二个参数,dwMPQID 是将指派给mpq内部的ID。这并不改变mpq ,目前还不清楚为什么这样做。
第三个参数,dwUnknown,这是唯一我们认为没用的,但是不容忽视。
最后一个参数,lphMPQ,是一个HADLE的指针(您必须先声明)。
如果SFileOpenArchive成功完成,this HANDLE will be that of the MPQ。
如果SFileOpenArchive成功,其返回值将为零。
但是,有几种情况可能导致SFileOpenArchive失败。
如果它失败,将返回一个值是假的。在这种情况下,您可以调用GetLastError获得更进一步的信息,为什么失败。如果lpFileName是一个零长度字符串或phMPQ是Null ,GetLastError 将返回ERROR_INVALID_PARAMETER 。如果该文件lpFileName不存在, GetLastError 将返回 ERROR_FILE_NOT_FOUND。在一些非常罕见的情况下, GetLastError可能会返回其他一些潜错误值。


Closing an Archive - SFileCloseArchive
关闭存档函数 - SFileCloseArchive


BOOL WINAPI SFileCloseArchive(HANDLE hMPQ);
ParameterWhat 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.

一旦您开启一个mpq存档,你必须记住它关闭时,您就大功告成了!SFileCloseArchive是SFileOpenArchive 的产物。
作为与SFileOpenArchive ,SFileCloseArchive返回一个非零值,那就是成功的
如果返回一个假值,那就失败了。
然而,在这种情况下,GetLastError不会提供任何有用的信息。
-所以你只能假设原因是hMPQ参数是无效的。

Opening a File Inside an MPQ - SFileOpenFileEx
打开MPQ内的文件函数 - SFileOpenFileEx



BOOL WINAPI SFileOpenFileEx(HANDLE hMPQ, LPCSTR lpFileName, DWORD dwSearchScope, HANDLE *lphFile);
ParameterWhat it is
hMPQ[in] The HANDLE of the MPQ previously opened with SFileOpenArchive that contains the file you want to open. SFileOpenFileEx will fail or crash if this is NULL or a HANDLE not obtained with SFileOpenArchive.
lpFileName[in] A pointer to a NULL terminated string that holds name of the file within the MPQ to be opened. SFileOpenFileEx will crash if this is NULL.
dwSearchScope[in] Specifies where to look for a file when opening a file on the hard drive. Must be NULL when working with MPQs.
lphFile[out] A pointer to a HANDLE variable that, upon successful completion, will hold the HANDLE of the requested file. SFileOpenFileEx will fail if this is NULL.

只是因为你用SFileOpenArchive并不意味着您就可以从它立即开始读取。请记住,MPQ只不过是包含其他文件的多档案文件。在您可以阅读任何一个mpq ,您必须打开一个(或多个)的MPQ内部档案。 SFileOpenFileEx就是让您使用这一任务的函数;它将打开在一个mpq所请求的文件并对其返回一个HANDLE。
再次, sfileopenfile将返回一个非零值就成功,是假值的就失败,您可以调用getlasterror获得的原因。如果lpfilename是一个零长度字符串或lphfile是Null , getlasterror将返回error_invalid_parameter 。如果该文件不存在于mpq , getlasterror会报告error_file_not_found 。在一些罕见的情况下, getlasterror可能会报告error_file_invalid ,并就极为罕见的情况下,它可能会返回其他一些模糊的错误值。
重要注意事项:当您调用sfileclosearchive关闭mpq ,同样会关闭所有MPQ内部打开的文件,您获取到来自sfileopenfileex的HANDLEs成为无效。如果您调用sfilereadfile , sfilegetfilesize , sfilesetfilepointer ,或sfileclosefile,这些其中一个无效的HANDL,调用将失败,并且STROM甚至可能崩溃。

Closing a File Inside an MPQ - SFileCloseFile
关闭MPQ内的文件函数 - SFileCloseFile

BOOL WINAPI SFileCloseFile(HANDLE hFile);
ParameterWhat 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, 用来关闭一个已经打开的文件.
也想sfileclosearchive , sfileclosefile将返回一个非零值说明成功的,假值的就失败, getlasterror将不提供帮助。所幸的是,多于sfileclosearchive , sfileclosefile只在hFlie是NULL或一个无效的HANDLE。

Reading from a File in an MPQ - SFileReadFile
读取MPQ内文件函数 - SFileReadFile

BOOL WINAPI SFileReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped);
ParameterWhat 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.

当然,您要先打开再读取文件。
一旦你获得一个有效的文件HANDLE从sfileopenfileex ,这就是这个函数的功能。 sfilereadfile会读取指定的字节数,然后推进文件指针。这意味着,如果您有一个文件,您调用sfilereadfile会读取的一半的档案,当您再次调用sfilereadfile,你会得到另一半的档案。如果您需要再次读取上半部分的文件,你会需要调用sfilesetfilepointer 。
sfilereadfile将提供一个非零的返回值就成功,或虚假的就失败。不过,你必须记住,只是因为它返回一个非零值,并不等于它实际上读取了什么,这只是说明没有错误发生。
如果在该文件hfile有不足未读字节(字节从文件指针到文件结束)比要求数量少, sfilereadfile会读不到的数目要求字节;
如果档案hfile的文件指针是在该文件的末尾, sfilereadfile会读什么,设置lpnumberofbytesread为0 ,返回真。因此,检查lpnumberofbytesread 是非常重要的。

Getting a File's Size - SFileGetFileSize
获得文件大小函数- SFileGetFileSize

DWORD WINAPI SFileGetFileSize(HANDLE hFile, LPDWORD lpFileSizeHigh);
ParameterWhat it is
hFile[in] The HANDLE of the file whose size is to be determined. SFileGetFileSize will crash if this is NULL or a HANDLE not obtained with SFileOpenFileEx.
lpdwFileSizeHigh[out] A pointer to a DWORD that, upon successful completion, will hold the high 32-bits (a DWORD) of the file's size. However, it is not possible for a file in an MPQ to be this large (over 4 gigabytes), so this is virtually unused, and may safely be NULL.

这是普遍认为是最坏的编程实践,因为完全可以进行修改,在开始读取时让不明长度的文件不会造成崩溃。sfilegetfilesize在这里只是把戏,因为它在用sfileopenfileex打开文件后才可以撷取文件的大小 。
也有一些重大的故障点在sfilegetfilesize 。首先是含糊不清错误。当sfilegetfilesize成功,它将返回文件的大小(可0 ! ) 。但是,当发生错误时,它将返回0xFFFFFFFF的,而且同一件事,它会返回为4294967295字节( 4 GB的) 。所幸的是,这不是一个很大的问题,正如您可能从未真正看到一个文件这么大。第二个问题,更危险的是sfilegetfilesize缺乏错误检查。 sfilegetfilesize不检查是否hfile是有效或是否为零。这意味着,如果你给它一个hfile的无效的HANDLE ,程序崩溃,电脑也会崩溃。因此,底线是这样的:无比谨慎使用此功能。


Moving the File Pointer - SFileSetFilePointer
移动文件指针函数- SFileSetFilePointer

DWORD WINAPI SFileSetFilePointer(HANDLE hFile, long nDistanceToMove, long *lpDistanceToMoveHigh, DWORD dwMoveMethod);
ParameterWhat 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:
FILE_BEGINThe file pointer will be set to nDistanceToMove bytes from the beginning of the file. nDistanceToMove must be positive.
FILE_CURRENTThe file pointer will be set to nDistanceToMove bytes from the current location of the file pointer.
FILE_ENDThe file pointer will be set to nDistanceToMove bytes from the end of the file. nDistanceToMove must be negative.

要想从文件任意位置读取,就要先在该文件提出移动指针,这个功能函数就是sfilesetfilepointer。
文件指针指向了下次读取时的读取位置。每次读取和编写将会移动文件指针到读写区域的最底部。

sfilesetfilepointer实际上并不移动文件指针,只是在文件中对应ndistancetomove。相反, sfilesetfilepointer移动文件指针到一个相对位置,无论是开头或结尾的文件。
举例来说,假设您有一个千字节的文件。当文件第一次打开,它的文件指针设置为0 ,则指的是第一个字节。然后,您读100个字节从该文件。然后,您调用sfilesetfilepointer设置ndistancetomove 500 。如果您调用dwmovemethod 设置为file_begin,文件指针将被设定为500 。如果你曾dwmovemethod作为file_current ,文件指针将是600 ,因为文件指针被转移到了当您从档案读取后100字节。但如果设置dwmovemethod以file_end , sfilesetfilepointer会失败,因为它将尝试设置档案指针一千四百九十九(文件中的最后字节, 999 ,加500 )不存在。如果您多用几次,会发现这是个非常简单的函数。

假如失败, sfilesetfilepointer返回0xFFFFFFFF ,作为sfilegetfilesize也有同样的陷阱 。但是,当圆满完成, sfilesetfilepointer会返回档案hfile新的绝对位置的文件指针 。这意味着您可以简单地获取当前的立场文件指针调用sfilesetfilepointer设置ndistancetomove 0 , dwmovemethod设置为file_current 。事实上,这是为什么说不存在sfilegetfileposition功能。

就像sfilegetfilesize , sfilesetfilepointer会肆无忌惮地使用任何你给它hfile的HANDLE,不会进行任何错误检查。这意味着,确保获得一个有效的文件处理,您必须谨慎,否则,您的电脑又会崩溃。


Choosing a Language - SFileSetLocale
选择语言- SFileSetLocale


LCID WINAPI SFileSetLocale(LCID lcNewLocale);
ParameterWhat 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:
0Language Neutral/English
0x407German
0x409English
0x40aSpanish
0x40cFrench
0x410Italian
0x416Portuguese

SFileSetLocale 是功能简单,背后复杂的代表.它使用了暴雪的multilinguality系统。感谢它,一个单一的函数调用,保证所有文件读出一个mpq的语言。它的唯一参数是the entire Storm MPQ subsystem。它绝不会失败,并且它传回的语言代码你给它,不过其返回值毫无价值。

multilinguality系统原理是这样的:每个MPQ档案有一个语言代码,并只要他们有不同的语言代码就可以有多个文件具有相同名称的。当调用sfileopenfileex时, sfileopenfileex寻找一个文件具有相同的语言代码储存,然后调用sfilesetlocale (如果sfilesetlocale从来没有被调用,语言的代码为0 ) 。如果一个文件匹配的语言代码无法找到, sfileopenfileex将打开中立语言(有一个语言代码0 )版本的文件。

Putting it All Together - DumbExtractor
全部合到一起的工具 - DumbExtractor
回复

使用道具 举报

 楼主| 发表于 2008-5-12 03:57:40 | 显示全部楼层
第五章
THE STARCRAFT CAMPAIGN EDITOR AND THE MPQ API LIBRARY
星际争霸编辑器和MPQ API LIBRARY

Starcraft Campaign Editor 是什么呢?其实就是一个能让您自己作出星际争霸地图的程序。
并把地图以SCM格式保存,或者保存为SCX文件。
可是这些个SCM/SCXs文件 和Warcraft 2是一样的原始文件?
假如您用hex editor 看过这些文件,那么您会得到一个否定的答案。
实际上SCM/SCXs就是MPQ文件!!
那么你也许认为很简单,那你就错了,StarEdit使用了一套难以捉摸的MPQ编写套路。
不过在您仔细阅读下面的内容,您将会得到答案。


Using StarEdit - The MPQ API Library
使用编辑器- The MPQ API Library


注意:这章是针对WINDOWS平台的,适用于THE MPQ API LIBRARY 2.0或更高!


在这里我们并不能很快的编译,因为编辑器有的功能我们并不能直接使用。
不像STROM之类的shared libraries ,StarEdit拥有复杂的操作系统保护机智,而不只是对于文件的保护。就算您是一位很好的程序员,您一样无法直接修改它。对于这种高难度的熟练的对运行系统的改写,还没有人能完成过。 就在这个时候Andrey Lelikov (简称Lelik)横空出世。

Lelik是一位熟悉系统内部工作机制的俄罗斯程序员。他设计了一个能够使用 StarEdit MPQ 的方法。
他把自己写的详细功能放进了MPQ API Library 。

就像STROM,MPQ API Library(又名LMPQAPI),它包含了共享库(可惜的是,像STROM接口库,现在在MAC机上还没有)。LMPQAPI不仅包括了StarEdit的MPQ编写功能,而且提供接口读写STROM。
如果您想同时使用STROM和StarEdit,您不需要同时使用LMPQAPI和STROM接口库。
一个LMPQAPI足已。

好吧。我提醒您一件事,您想在使用LMPQAPI的时候区分是用STROM还是StarEdit么?
STROM功能有就像使用STROM接口库时一样有一个前缀'SFile',在使用StarEdit时,前缀是'Mpq'。
这是很重要的,因为这说明了STROM和StarEdit的功能不兼容。
这意味着您无法用SFileOpenArchive获得的MPQ HANDLE去在StarEdit('Mpq'前缀的函数)里调用,反之亦然。 如果您还是调用的话,调用会失败,程序会崩溃。记住这点。

&iquest;Sé Habla Espa&ntilde;ol?
让人讲西班牙语?


因为75%以上的星际争霸或暗黑的玩家是以英语为母语,所以大多数MPQ开发测试都基于这些游戏的英文版。对于使用英文版游戏的玩家,MPQ会运行良好。但事实上,98%的标准MPQ文件使用的是language-neutral (比如图象文件等等)。甚至有人用全非英语的MPQ玩游戏也没有问题。
不过,很明显这里是有问题的,只是还要等些时间才有人能发现吧。


做为上面两章的解释,MPQ格式具有强大的多国语言功能。
但是,您完全没必要做多语言的SCM/SCXs。
这就是说,您完全不必要让StarEdit支持多语言功能,就连暴雪的程序员都懒的做。
但是我们都有兴趣研究MPQ,除非没必要,其中的许多语言功能还是有用的。

在技术方面说,所有的StarEdit功能都只运行有语言代码为0或language-neutral代码的文件。
也就是说,MpqAddFileToArchive和MpqAddWAVToArchive只增加language- neutral files, MpqDeleteFile只删除language-neutral files, MpqRenameFile 只会重命名language-neutral files.

这种设计决定了,执行结果并不明显。在前一章也提到过,假如在打开有相同名称不同语言的文件时,STROM使用了SFileSetLocale做为语言过滤器,用来决定到底打开哪个文件。
假设在StarEdit使用一个MPQ文件代替patch_rt.mpq ,而且在那个MPQ文件里有英语/language-neutral解析度文件rez\\gluAll.tbl (这文件内有多个语言版本),但是不能有葡萄牙语版本(选定任意语言)。
当您运行这个游戏的葡萄牙语版时,程序会查询在您MPQ文件中的该语言版。结果就是失败,而且默认又为英语版本的,对不对?
好吧,不是这样的。STROM允许您同时打开几个MPQ文件,并且STROM会搜索已经加载的MPQ,然后自动加载最新的MPQ到文件里。但是在这个过程中,STROM在系统为language-neutral以前,会检查已经打开的所有特殊语言的MPQ。
这意味着,前面STROM从broodat.mpq 载入葡萄牙语版本,而不是您自己的language-neutral 版本。
很不幸,现在还没有解决的办法,希望新版本的LMPQAPI能被解决吧。



Initializing the MPQ API Library - MpqInitialize
初始化MPQ API Library函数- MpqInitialize

BOOL WINAPI MpqInitialize();

不像STROM,LMPQAPI在控制StarEdit时有巨大的复杂的任务要做。
因此,无法在启动LMPQAPI时做完一切。你必须告诉LMPQAPI什么时候运行。所幸,这很简单。
你要做的就是在您启动程序前调用MpqInitialize,LMPQAPI会自动做完剩下的。
请确定在调用LMPQAPI其他函数前,您调用了MpqInitialize。此外,即使您对STROM接口库什么都不做,也必须在任何STROM函数调用前被调用。
一次调用会同时初始化STROM和STAREIDT。

1.Starcraft/Brood War 1.07 必须已经安装,Storm.dll和StarEdit.exe必须在程序目录中
2.StarEdit不能和LMPQAPI一起运行.

为了调用MpqInitialize初始化上面的两个要求必须满足。尽管还有其他原因,上面两个却是最常见的。
不管什么原因,MpqInitialize调用失败后,都会返回FLASH,您可以检索GetLastError设置一个错误值。
如果LMPQAPI无法在游戏目录或在您程序的目录里无法找到StarEdit.exe ,那么GetLastError会返回MPQ_ERROR_NO_STAREDIT;
如果StarEdit.exe的版本不匹配,GetLastError会返回MPQ_ERROR_BAD_STAREDIT;
如果StarEdit已经运行GetLastError会返回MPQ_ERROR_STAREDIT_RUNNING;
如果是因为其他原因GetLastError通常会返回MPQ_ERROR_INIT_FAILED。

但是,无论为什么GetLastError调用失败,返回了什么,您要做的就是尽快的关闭程序。
不要调用任何LMPQAPI功能(STROM或者StarEdit的功能),当然也不要在调用MpqInitialize了。


Opening an MPQ for Editing - MpqOpenArchiveForUpdate
打开MPQ- MpqOpenArchiveForUpdate

HANDLE WINAPI MpqOpenArchiveForUpdate(LPCSTR lpFileName, DWORD dwCreationDisposition, DWORD dwHashTableSize);
ParameterWhat 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:
MOAU_CREATE_NEWMpqOpenArchiveForUpdate will create a brand new archive. If lpFileName already exists, MpqOpenArchiveForUpdate will fail.
MOAU_CREATE_ALWAYSMpqOpenArchiveForUpdate will create a new archive if lpFileName doesn't exist. If lpFileName does exist, it will be deleted and overwritten.
MOAU_OPEN_EXISTINGMpqOpenArchiveForUpdate will open the archive lpFileName. MpqOpenArchiveForUpdate will fail if lpFileName does not exist.
MOAU_OPEN_ALWAYSIf lpFileName exists, MpqOpenArchiveForUpdate will open it. If it doesn't, MpqOpenArchiveForUpdate will create a new archive.
MOAU_MAINTAIN_LISTFILEThis flag specifies that as MpqOpenArchiveForUpdate uses the archive lpFileName, it should update the internal listfile, if one exists. This is a flag, and must be ORed (with the | operator) with one of the other options for dwCreationDisposition
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一样,您在使用前必须先打开它.
MpqOpenArchiveForUpdate 打开(或创建)一个存档,这是为了您使用其他StarEdit功能的,并返回存档的HANDLE.

但是不像SFileOpenArchive,MpqOpenArchiveForUpdate需要您在时间上做出选择.
第一选择是dwcreationdisposition参数。它告诉mpqopenarchiveforupdate是否应该创建一个新的存档还是打开一个现有的,或两者之间。其他的决定性参数是dwhashtablesize 。 dwhashtablesize告诉mpqopenarchiveforupdate创建什么大小存档的哈希表(这也是该文件限制),在
mpqopenarchiveforupdate事件中必须创建要一个新的存档。
做为一个存档的哈希表大小差不多是1000,除非你知道存档一定会超过1000个文件。
但同时请记住,每个哈希表项和存档文件将新增16个字节,(见第2章的哈希表,或第5章关于mpq哈希表的更多信息) 。


在上面的进程中MpqOpenArchiveForUpdate可能调用失败.
MpqOpenArchiveForUpdate将返回INVALID_HANDLE_VALUE或者NULL.
我们根据调用GetLastError通常可以获取有用的信息,但不是绝对.
如果lpfilename参数为空, getlasterror将返回error_invalid_parameter 。
如果dwcreationdisposition返回moau_open_existing或者lpfilename不存在, getlasterror将返回error_file_not_found 。反过来说,如果dwcreationdisposition返回moau_create_new和档案lpfilename已经存在, getlasterror将返回error_already_exists 。
最后,如果mpq存档存在,但是是无效或损坏的, getlasterror将返回mpq_error_mpq_invalid 。
在一些罕见的情况下, getlasterror将返回其他一些错误代码。

Closing a Modified Archive - MpqCloseUpdatedArchive
关闭修改过的存档- MpqCloseUpdatedArchive


BOOL WINAPI MpqCloseUpdatedArchive(HANDLE hMPQ, DWORD dwUnknown);
ParameterWhat it is
hMPQ[in] The HANDLE of the MPQ to close, which was acquired earlier with MpqOpenArchiveForUpdate. MpqCloseUpdatedArchive will fail (or worse) if this is NULL or a HANDLE not obtained with MpqOpenArchiveForUpdate.
dwUnknownUnknown. Should always be NULL.

这里和在STROM中一样,您在哪用SFileOpenArchive打开MPQ,那么就在修改它的地方用SFileCloseArchive 关闭.
那么MPQ存档打开时用MpqOpenArchiveForUpdate,关闭时用MpqCloseUpdatedArchive.
然而在这时候有一点区别.STROM不会修改实际MPQ,所以关闭MPQ HANDLE时没有什么特别需要做的.
但是StarEdit 确实修改了MPQ.而且MPQ的散列值和文件表是在没有调用MpqCloseUpdatedArchive关闭MPQ HANDLE前不能写在MPQ分区的.
这意味着要快速关闭StarEdit MPQ HANDLEs,要不然您有可能再次程序崩溃,而无法保存修改的MPQ.


Adding a File - MpqAddFileToArchive
添加文件函数- MpqAddFileToArchive


BOOL WINAPI MpqAddFileToArchive(HANDLE hMPQ, LPCSTR lpSourceFileName, LPCSTR lpDestFileName, DWORD dwFlags);
ParameterWhat it is
hMPQ[in] The HANDLE of the MPQ to add the file to, which was acquired earlier with MpqOpenArchiveForUpdate. MpqAddFileToArchive will fail if this is NULL or a HANDLE not obtained with MpqOpenArchiveForUpdate.
lpSourceFileName[in] A pointer to a NULL-terminated string containing the path of the file on disk to add. MpqAddFileToArchive will crash if this is NULL.
lpDestFileName[in] A pointer to a NULL-terminated string containing the name that the file will be given in the MPQ. MpqAddFileToArchive will crash if this is NULL.
dwFlags[in] Flags specifying properties that MpqAddFileToArchive will apply to the file inside the MPQ. Must be a combination of the following flags specified in lmpqapi.h:
MAFA_ENCRYPTThe file will be encrypted.
MAFA_COMPRESSThe file will be compressed.
MAFA_REPLACE_EXISTINGIf the file lpDestFileName already exists in the MPQ, it will be replaced with the new file to be added

往往大约 95%的时候会在使用 StarEdit MPQ功能时候添加文件. 对于这个任务, 您会用到的函数有MpqAddFileToArchive和它的姊妹功能MpqAddWAVToArchive (稍后讨论).
MpqAddFileToArchive在MPQ hMPQ分区添加文件lpSourceFileName 使用名称lpDestFileName, 并在这个过程中压缩 或/和 加密它.

由于一些设计上的疏漏,在您并没有完全明白前,MpqAddFileToArchive会是个大麻烦.
1.MpqAddFileToArchive并不检查lpSourceFileName和lpDestFileName是否为空.
就是说假如任一参数为空,您会再次看到程序崩溃.
2.覆盖MPQ中已有文件时MpqAddFileToArchive的运行机制.
当您调用MpqAddFileToArchive去添加的文件已经存在MPQ中(在这种情况下,你就不得不指定dwflags为mafa_replace_existing ),MpqAddFileToArchive会在不确定文件lpSourceFileName是否存在前就不分青红皂白地删除存在的文件lpDestFileName.
解决的办法很简单:确保lpsourcefilename和lpdestfilename是有效的(非空) ,以及确保lpsourcefilename存在之前,调用mpqaddfiletoarchive 。

现在您应该可以饶过所有的障碍,用mpqaddfiletoarchive成功添加文件了吧.
这时mpqaddfiletoarchive将允许返回TURE.
如果有错误,它将返回FALSE 。在这种情况下,少量的信息可调用getlasterror 。
如果该文件lpsourcefilename不存在, getlasterror将返回error_file_not_found (尽管它的有点晚) 如果哈希表是FULL(见第2章) , getlasterror将返回mpq_error_hash_table_full 。
如果该文件lpdestfilename已经存在 和在dwflags中未指定mafa_replace_existing , getlasterror将返回mpq_error_already_exists 。
但是,在很多情况下getlasterror将返回其他一些错误代码或没有代码。



Adding a File with WAV Compression - MpqAddWAVToArchive
添加WAV压缩文件函数- MpqAddWAVToArchive


BOOL WINAPI MpqAddWAVToArchive(HANDLE hMPQ, LPCSTR lpSourceFileName, LPCSTR lpDestFileName, DWORD dwFlags, DWORD dwQuality);
ParameterWhat it is
hMPQ[in] The HANDLE of the MPQ to add the file to, which was acquired earlier with MpqOpenArchiveForUpdate. MpqAddWAVToArchive will fail if this is NULL or a HANDLE not obtained with MpqOpenArchiveForUpdate.
lpSourceFileName[in] A pointer to a NULL-terminated string containing the path of the file on disk to add. MpqAddWAVToArchive will crash if this is NULL.
lpDestFileName[in] A pointer to a NULL-terminated string containing the name that the file will be given in the MPQ. MpqAddWAVToArchive will crash if this is NULL.
dwFlags[in] Flags specifying properties that MpqAddWAVToArchive will apply to the file inside the MPQ. Must be a combination of the following flags specified in lmpqapi.h:
MAFA_ENCRYPTThe file will be encrypted.
MAFA_REPLACE_EXISTINGIf the file lpDestFileName already exists in the MPQ, it will be replaced with the new file to be added
dwQuality[in] Specifies the quality the WAV file will be compressed to. Must be one of the following values defined in lmpqapi.h:
MAWA_QUALITY_HIGHBest sound quality, least compression. WAV lpDestFileName in MPQ will occupy the most space after compression (but still less space than if you had added the file with MpqAddFileToArchive instead)
MAWA_QUALITY_MEDIUMMedium sound quality, medium compression. Balance between quality and compression.
MAWA_QUALITY_LOWHighest compression, worst sound quality. WAV lpDestFileName in MPQ will occupy the least space after compression.

毫无疑问,最流行的新功能的lmpqapi 2.0版(就是我做的) ,便有功能 mpqaddwavtoarchive 。
虽然mpqaddfiletoarchive能压缩约80 %的文件(非wav文件) ,它对WAV文件的压缩只有平均约5 % 。这是由于wav数据性质和它的不可压缩性。
在这里,对于wav压缩的压缩是必要的,而 mpqaddwavtoarchive就实现了这个功能。
尽管工作原理不同,MpqAddWAVToArchive的界面与 MpqAddFileToArchive却是几乎相同的。
唯一的区别就是多了一个新函数dwQuality。
参数设置后WAV将会被压缩。
不过不像mpqaddfiletoarchive的标准压缩, mpqaddwavtoarchive的wav压缩,实际上降低了wav的质量 。质量降低多少依赖于dwquality 。
如果您有一个音乐WAV而又想保证质量,那您就使用mawa_quality_high ,因为它最好的保留wav的质量;而同一个声音wav , mawa_quality_low通常是不行的,因为声调比较容易压缩失真; mawa_quality_medium往往是最有效的。


Deleting a File - MpqDeleteFile
删除文件函数 - MpqDeleteFile


BOOL WINAPI MpqDeleteFile(HANDLE hMPQ, LPCSTR lpFileName);
ParameterWhat it is
hMPQ[in] The HANDLE of the MPQ to close, which was acquired earlier with MpqOpenArchiveForUpdate. MpqDelete will fail (or worse) if this is NULL or a HANDLE not obtained with MpqOpenArchiveForUpdate.
lpFileName[in] A pointer to a NULL-terminated name of the file in the MPQ to delete. MpqDeleteFile will crash if this is NULL.

少数情况下,您可能需要删除MPQ中的某个文件,那么您就需要用到MpqDeleteFile。
MpqDeleteFile能从已经打开的存档hMPQ中删除文件lpFileName。
不过这不是看上去那么简单的。
mpqdeletefile为lpfilename删除哈希和文件表项 ,使其无法进入。
但是除非文件在MPQ的physical end ,否则MpqDeleteFile无法清除内存。
这就是说,通常情况下MPQ文件是不会减小大小的。
不过空间可以添加新文件循环再利用。稍后为您介绍MpqAddFileToArchive 和MpqAddWAVToArchive.

就像STROM和STAREDIT几乎所有的功能一样,调用MpqDeleteFile成功就返回TURE,失败就返回FALSE。
失败的话,您可以调用GetLastError去获得一些关于失败原因的信息(假如有的话)。
在这种情况下GetLastError是惊人的有效。
因为几乎只有一种会造成MpqDeleteFile失败的原因(除了hMPQ无效):
该文件lpFileName不存在于MPQ,GetLastError将返回MPQ_ERROR_FILE_NOT_FOUND。


Renaming a File - MpqRenameFile
重命名文件函数- MpqRenameFile



BOOL WINAPI MpqRenameFile(HANDLE hMPQ, LPCSTR lpOldFileName, LPCSTR lpNewFileName);
ParameterWhat 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后发现StarEdit缺少一对非常有用的功能。
而且那个时候我对与STROM和StarEdit的运做有了相当的了解,所以我决定写个自己的功能。
MpqRenameFile 非常的简单;它重新把hMPQ中的文件名 由lpOldFileName变为lpNewFileName.

mpqrenamefile返回给您的依旧是简单的信息。
mpqrenamefile返回true就成功,FLASH失败,并让您调用getlasterror获得失败的原因 。
如果hMPQ HANDLE 是NULL或无效,lpoldfilename或lpnewfilename是Null , getlasterror将返回error_invalid_parameter 。
如果该文件lpoldfilename不存在于MPQ hMPQ中, getlasterror将返回mpq_error_file_not_found 。
如果该文件lpnewfilename已经存在mpq中 , getlasterror将返回mpq_error_already_exists 。



Compacting an MPQ - MpqCompactArchive
压缩MPQ函数- MpqCompactArchive


BOOL WINAPI MpqCompactArchive(HANDLE hMPQ, BOOL bFailOnBadFile);
ParameterWhat it is
hMPQ[in] The HANDLE of the MPQ to compact, which was acquired earlier with MpqOpenArchiveForUpdate. MpqCompactArchive will fail if this is NULL or a HANDLE not obtained with MpqOpenArchiveForUpdate.
bFailOnBadFile[in] Explained below. Should usually be FALSE


记得我刚才说过调用mpqdeletefile实际上并不删除文件;它只是使它们无法使用。那么, mpqaddfiletoarchive和mpqaddwavtoarchive也是一样。
当您添加一个已经存在的文件或添加比旧文件大的新文件,新的文件将附加,和原来被占用的空间不会被释放。正因为如此,每当您建立一个大的复杂的包含许多覆盖/删除的档案的mpq,将会有很大的体积,并且没有办法从零重建MPQ。

解决这个问题的方法是调用mpqcompactarchive :这是我设计的第三个,也是最后一个,最困难的mpq功能。 mpqcompactarchive的功能是把数据压缩到一个缓冲文件,然后再传回。这就是说在压缩的时候,在磁盘上的缓冲文件必须有足够的可用空间(这取决于MPQ的大小)。

虽然mpqcompactarchive可以让几乎所有的文件变得紧凑,但是还是有不能成功的类型:有mafa_encrypt和mafa_modcryptkey属性,但缺乏mafa_compress和mafa_compress2属性的文件(如需详细资讯,请参阅第5章) 。当mpqcompactarchve调用这种文件,getlasterror会返回错误代码mpq_error_compact_error ,然后删除违规档案,并继续压缩过程。

mpqcompactarchive返回TURE就成功,FLASH就失败。您可以调用getlasterror获得错误信息 。
如果hmpq是Null或无效的staredit HANDLE, getlasterror将返回error_invalid_parameter 。
如果没有足够的可用记忆体为mpqcompactarchive拨出2.5 MB的读取缓冲区, getlasterror将返回error_outofmemory 。
如果没有足够的可用磁盘空间mpqcompactarchive分配缓冲文件, GetLastError返回error_disk_full 。在某些特殊的情况下, getlasterror可能会返回其他一些奇怪的错误代码。


Getting Information About a File - SFileGetFileInfo
获取文件有关信息函数 - SFileGetFileInfo



DWORD WINAPI SFileGetFileInfo(HANDLE hMPQorFile, DWORD dwInfoType);
ParameterWhat 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:
SFILE_INFO_HASH_TABLE_SIZESFileGetFileInfo will return the hash table size of hMPQorFile (see chapters 2 and 5 for more info). hMPQorFile must be a HANDLE of an MPQ.
SFILE_INFO_NUM_FILESSFileGetFileInfo will return the number of files inside hMPQorFile. hMPQorFile must be a HANDLE of an MPQ.
SFILE_INFO_SIZESFileGetFileInfo will return the size of the MPQ hMPQorFile or the uncompressed size of the file hMPQorFile. This is functionally equivalent to the SFileGetFileSize function. hMPQorFile can be either a HANDLE of an MPQ or a HANDLE of a file inside.
SFILE_INFO_COMPRESSED_SIZESFileGetFileInfo will return the compressed size (or uncompressed size if the file isn't compressed) of the file hMPQorFile in an MPQ. hMPQorFile must be a HANDLE of a file inside an MPQ.
SFILE_INFO_FLAGSSFileGetFileInfo will return the flags (see chapter 5 for the meaning of specific flags) of the file hMPQorFile in an MPQ. hMPQorFile must be a HANDLE of a file inside an MPQ.
SFILE_INFO_POSITIONSFileGetFileInfo will return the absolute position of the file pointer for the file hMPQorFile in an MPQ. hMPQorFile must be a HANDLE of a file inside an MPQ.


SFileGetFileInfo 是 LMPQAPI中最古怪的功能函数.
不仅是唯一可以通过LMPQAPI而不能通过STROM接口库的STROM功能。
它唯一的函数就是能够接受MPQ或者文件HANDLE,但在大多数情况下它根本不存在于STROM里。
为了补救这个,我自己写了了这个SFileGetFileInfo。

sfilegetfileinfo是专门设计来提供有用的mpq和其内档案的信息;包括mpq中压缩文件的大小,文件指针的位置。它使用简单,但相当有效。它使用已经打开了的mpq或文件的HANDLE,以获取有关hmpqorfile的信息 ,以及在dwinfotype中需要的信息类型代码。

如果sfilegetfileinfo成功,返回值就是有关文件或MPQ的资料 。
但是,如果失败的话,它会返回0xFFFFFFFF,并设置一个错误代码,您可以调用getlasterror查看 。
如果因为hmpqorfile HANDLE是无效的, getlasterror将返回error_invalid_parameter 。
如果因为dwinfotype是一个无效的信息代码,getlasterror将返回error_unknown_property 。 sfilegetfileinfo只在一种情况下会调用失败(除了严重的内部lmpqapi错误)。
因为一个mpq HANDLE和dwinfotype指定的信息只能得到一个文件HANDLE,或反之亦然,在这种情况下, getlasterror将返回error_unknown_property。
回复

使用道具 举报

 楼主| 发表于 2008-5-12 06:36:34 | 显示全部楼层
原文作者就写了这么多。。
所以本文到此结束了。。
有点遗憾    第5,6章估计我这辈子看不到了
不过由于兴趣原因  我会在后面继续翻译些文章
(其实已经找到了,只是太长。。长。。。长。。长了)

下面提供下载 资源

the Storm Interface Library.zip (3 KB, 下载次数: 422)
DumbExtractor with Source.zip (5 KB, 下载次数: 391)
the MPQ API Library 2.0.zip (27 KB, 下载次数: 452)
回复

使用道具 举报

traxex 该用户已被删除
发表于 2008-5-12 06:53:51 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽
回复

使用道具 举报

发表于 2008-5-12 07:53:24 | 显示全部楼层
感谢楼主的辛勤劳动!
回复

使用道具 举报

发表于 2008-5-12 08:38:45 | 显示全部楼层
很有毅力 于是继续加油
回复

使用道具 举报

发表于 2008-5-12 09:10:06 | 显示全部楼层
很好很强大...都没看过呢,看过也不懂..

加油,受众不是我.5555
回复

使用道具 举报

发表于 2008-5-12 11:06:20 | 显示全部楼层
啊呀,现在开始流行通宵了么

ps:由于lz和某失踪已久的疑难区斑竹做了类似的工作,所以稍微查看了一下

再补充,引用的代码部分可以用[codes=xx]的格式来高亮
回复

使用道具 举报

发表于 2008-5-12 11:10:56 | 显示全部楼层
[trigger]
看MPQ内幕
事件
  任意单位 - 看贴
条件
  (看帖单位) 等于 (凝雪霜风) and (帖子的标题) 等于 ([翻译版]MPQ内幕(完结))
动作
  单位 - 对 (看帖单位) 发布 (仔细看帖) 到 (帖子内容)
  等待 99999 秒 看帖时间
  回帖 - 对 (喀尔硫司之瞳) 回复文本信息 :真的真的看不懂···支持···
[/trigger]

  
回复

使用道具 举报

发表于 2008-5-12 11:57:37 | 显示全部楼层
记得很久以前有过此文的翻译,不管怎么说汉化是苦力,支持
回复

使用道具 举报

发表于 2008-5-12 12:40:34 | 显示全部楼层
樓主台灣人?
回复

使用道具 举报

traxex 该用户已被删除
发表于 2008-5-12 12:43:11 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽
回复

使用道具 举报

发表于 2008-5-12 15:57:29 | 显示全部楼层
楼主可不要翻一半就扔下啊。这样可对不起精华啊。
回复

使用道具 举报

发表于 2008-5-12 16:02:10 | 显示全部楼层
素口牙,快点翻译了编辑进来~~~
回复

使用道具 举报

发表于 2008-5-12 16:30:36 | 显示全部楼层
看了一半,有一些错别字和一些翻译不够友好的地方。
但是做为初版,这些都没关系,以后慢慢优化就好了。
回复

使用道具 举报

发表于 2008-5-12 18:13:17 | 显示全部楼层
支持下LZ....
回复

使用道具 举报

发表于 2008-5-12 20:54:56 | 显示全部楼层
既然看不懂~~就占楼把~~
回复

使用道具 举报

发表于 2008-5-12 20:59:48 | 显示全部楼层
http://www.cnblogs.com/sephil/archive/2006/09/13/503319.html
很早以前的翻译版本
不知道一样不
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 点一下

本版积分规则

Archiver|移动端|小黑屋|地精研究院

GMT+8, 2024-4-19 19:16 , Processed in 0.321392 second(s), 23 queries .

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表