DIABLO 板


LINE

之前要用 mpq2k 看某些 mpq 档里的东西, 突然发现 mpq2k 几乎看不到什麽档 案了, 尤其在 d2exp 1.10 以後, patch_d2.mpq 让它找只能找到一个档案, 因 为 mpq2k 的作者已经不再做任何维护的动作了, 只能自己手动来, 所以才有想 了解这个档案格式的动力, 想看看能否突破 mpq2k 现有的限制. (之前有想看, 但就是懒 (-,-))))). 目前针对 mpq2k 的一些嚐试已经告一段落, 想做的事也差不多了, 继续研究的 动力也没了, 所以把目前的东西整理一下, 希望对想研究的人有一些帮助. * 这篇文章最有用的应该是参考资料, 其他都是废话 XD 这篇文章可自由转载, 但请保留出处(ptt.cc diablo 板)及作者(edwar). 参考资料 -------- Inside MoPaQ http://www.campaigncreations.org/starcraft/inside_mopaq/ 这是 mpq2k 作者所写的网页, 但是似乎没有再更新了. (听说作者不希望有人烦他 mpq2k 相关的事) 从 Inside MoPaQ 提供的 Stormless MPQ Editor 连结拿到的 MpqView 原始码也很有用. 网页: http://www.angelfire.com/sc/mpq/ MPQ 档的一些特性 ---------------- * Blizzard 所使用的一种档案格式, 存放一些游戏的资讯档, 如声音、动画 * 用於将多个档案放进一个档案里 * 给一个档名, 可以检查一个 MPQ 档有没有那个档, 但是无演算法可从一个 MPQ 档逆推求得该 MPQ 档含有那些档案. - MPQ 使用三个不同参数得到的 32bit hash 来取代档名这项资讯, 如果 有碰撞产生就认了, 所以无法逆推. * 档名不分大小写 档头 ---- offset |length | content (hex) | | ---------|-------|------------------- 00000000 | 4 | 4D 50 51 1A ('MPQ'?) 00000004 | 4 | 20 00 00 00 00000008 | 4 | mpq file size ? 0000000C | 4 | unknown (00 00 03 00) 00000010 | 4 | htbl offset: hash table 位置 00000014 | 4 | btbl offset: block table 位置 00000018 | 4 | htbl_len: hash table 大小 / 能容纳的档案数 0000001C | 4 | btbl_len: 现有档案数 依 MpqView source code 来看, 4D 50 51 1A 字串只需位於 4-word aligned 开始的地方, 开档案後要先找到这个字串, 之後的其他资讯都是相对於此字串 的相对位置. hash table (htbl) ----------------- hash table 每笔资料含有四个 32bit 的值 offset |length | content (hex) | | ---------|-------|------------------- 00000000 | 4 | hash2 00000004 | 4 | hash3 00000008 | 4 | unknown (好像都是 00 00 00 00 或 FF FF FF FF) 0000000C | 4 | 位於 block table 的那一笔 entry, 从 0 开始算 共有 htbl_len 笔记录, 如果该笔记录未被使用, 四个栏位似乎都会是 FF FF FF FF. 能容纳多少笔记录在该 MPQ 档建立之初就已决定, 只要开始加 档案就无法变动了, 这也是该档能够容纳的档案数. 若是实际上用 hex editor 去看 MPQ 档, hash table 应该是一堆看起来杂乱 无章的资料, 需要经过 decrypt 的过程转成正常的样子. 参考 encrypt / decrypt 部份的说明. 由档名得到 block entry ====================== 由档名可以求得三个 32bit hash (参考 hash function, 该处提到的 $scrc1 / $scrc2 / $scrc3 即为所求, 分别当做这里的 hash1, hash2, hash3), 再 由这三个值找出位於 hash table 何处: 1. hash1 除以 htbl_len 的余数: 此数告诉你要从 hash table 的那一笔记 录开始找. (从零开始算) 2. 检查这笔记录是否已被使用, 可能是看 offset 0008h 的数值: (a) 00000000h 表示有使用, 进行步骤 3. (b) FFFFFFFFh 则是未被使用, 跳至步骤 4. 3. 检查 hash2 和 hash3 是否均符合: (a) 是, 均符合: 找到档案了, offset 000Ch 为该档的 block entry. (b) 否: 还未找到, 跳到步骤 4 4. 看看是否已经整个 hash table 的每笔记录都找过却找不到, 若是, 该档 不存在 -> 跳出. 5. 继续下一笔记录, 跳至步骤 2. 如果是在 hash table 最後一笔, 下一笔 记录则是第 0 笔记录. 由於 mpq 处理 hash collision(碰撞)的方式是继续往後找, 直至有空的位置 可以存放就储存. 如果碰撞後又有做 delete(删除)的动作, 可能会产生空洞. 所以步骤 2-(b) 发现该位置未使用也只是跳过去而已. 接下来 ====== * 如果只是要知道该档名存在与否, 那以上的[由档名得到 block entry]应 该就可以了. * 还要再得到那个档案的内容的话, 由此 block entry, 再到 block table 里找相关的档案储存资讯. block table (btbl) ------------------ block table 每笔资料含有四个 32bit 的值 offset |length | content (hex) | | ---------|-------|------------------- 00000000 | 4 | 存放的位置, 相对於档头开始(4D 50 51 1A) 00000004 | 4 | 压缩後的档案大小; 若未压缩, 则同未压缩的档案大小 00000008 | 4 | 未压缩的档案大小 0000000C | 4 | block 属性, 如有无压缩、有无编码等等 共有 btbl_len 笔记录, 从 0 开始算. 若用 hex editor 去看 MPQ 档, block table 应该像 hash table 一样, 看起 来像一堆杂乱无章的资料, 需要经过 decrypt 的过程转成正常的样子. 参考 encrypt / decrypt 部份的说明. 属性 ==== 目前找到的 block 属性值有: ------ 00000000h 80000000h - 未压缩, 未编码 80000100h - from Diablo I MPQ, DCL 压缩 80000200h - Starcraft MPQ (or Diablo II), 要另外读压缩方式 80010200h 80030000h 80030200h ====== bit value | attribute (hex) | ----------|---------------------------------------- 00000000 | 不知, 也许无任何特性 00000100 | PACK, Diablo I MPQ, always compressed with DCL 00000200 | PACK, Starcraft MPQ (or Diablo II), 某处会记录压缩方式 00010000 | coded (是指 encrypted?) 00020000 | coded (是指 encrypted?) 80000000 | 不知 (各属性值意义在 MpqView 的原始档有更详尽的解释) hash function ------------- 预备知识: hash function 概念 MPQ 档使用一个不错的单向 hash function 将档名转成三个 32bit hash, 此 hash function 也可以用不同的参数就改变输出的结果, 档案转换後的三个 hash 就是三个相异参数得到的. 底下使用 perl 程式码呈现如何产生 hash: (C 的程式码请参考 MpqView 原始档) --- get hash ------------- my @massive_base; sub BuildBaseMassive { my $s1; my ($i, $j); my $rem; $rem = 0x100001; for $i (0 .. 0x100-1) { for $j (0 .. 4) { $rem = ($rem*125+3) % 0x002AAAAB; $s1 = ($rem & 0xFFFF) << 0x10; $rem = ($rem*125+3) % 0x002AAAAB; $s1 |= ($rem & 0xFFFF); $massive_base[$i+0x100*$j] = $s1; } } } sub Crc ($$$) { my $string = shift; my $refMassive_base = shift; my $massive_base_offset = shift; my $byte; my $crc = 0x7fed7fed; my $s1 = 0xEEEEEEEE; foreach (split(//, $string)) { $byte = ord; last if $byte == 0; if ($byte > 0x60 && $byte < 0x7B) { $byte -= 0x20; } $crc = $$refMassive_base[$massive_base_offset+$byte]^($crc+$s1); $crc &= 0xFFFFFFFF; $s1 += $crc+($s1<<5)+$byte+3; $s1 &= 0xFFFFFFFF; } return $crc; } &BuildBaseMassive; my $filename = 'This\is\a\test\file'; my ($scrc1, $scrc2, $scrc3); $scrc1 = &Crc($filename, \@massive_base, 0); $scrc2 = &Crc($filename, \@massive_base, 0x100); $scrc3 = &Crc($filename, \@massive_base, 0x200); === get hash ============= $scrc1, $scrc2, $scrc3 就是由档名转换得到的三个 32bit hash, 分别由调整参数 为 0, 0x100, 0x200 得到. encryption/decryption: hash & block table ----------------------------------------- 如果直接用 hex editors 去看 hash table 和 block table, 看到的资料应该是 很杂乱的, 这两个 table 在写进来 MPQ 档之前, 已经被 Encrypt 过了, 所以想 要看到正确的资料, 就要先 Decrypt. 以下是 Encrypt / Decrypt 的 perl code: (此处 sub Decode 是 Decrypt 程式; sub Encode 是 Encrypt 程式) (C 程式码 Decrypt 部分同样参考 MpqView 原始码) --- Encrypt / Decrypt ----------- sub Decode ($$$$) { my $refDataIn = shift; my $refMassive_base = shift; my $crc = shift; my $lenght = shift; my ($i, $dec); my $s1 = 0xEEEEEEEE; for $i (0 .. $lenght-1) { $s1 += $$refMassive_base[0x400+($crc & 0xFF)]; $s1 &= 0xFFFFFFFF; $dec = $$refDataIn[$i]^($s1+$crc); $dec &= 0xFFFFFFFF; $s1 += $dec+($s1<<5)+3; $s1 &= 0xFFFFFFFF; $$refDataIn[$i] = $dec; $crc = ($crc>>0x0b) | ((0x11111111+(($crc^0x7FF)<<0x15)) & 0xFFFFFFFF); } } sub Encode ($$$$) { my $refDataIn = shift; my $refMassive_base = shift; my $crc = shift; my $lenght = shift; my ($i, $dec); my $s1 = 0xEEEEEEEE; for $i (0 .. $lenght-1) { $s1 += $$refMassive_base[0x400+($crc & 0xFF)]; $s1 &= 0xFFFFFFFF; $dec = $$refDataIn[$i]; $$refDataIn[$i] = ($dec^($s1+$crc)) & 0xFFFFFFFF; $s1 += $dec+($s1<<5)+3; $s1 &= 0xFFFFFFFF; $crc = ($crc>>0x0b) | ((0x11111111+(($crc^0x7FF)<<0x15)) & 0xFFFFFFFF); } } my $name_htable = "(hash table)"; my $name_btable = "(block table)"; seek(MPQ, $offset_mpq+$offset_htbl, 0); read(MPQ, $tmp, 4*4*$lenght_htbl); @hash_table = unpack("L*", $tmp); seek(MPQ, $offset_mpq+$offset_btbl, 0); read(MPQ, $tmp, 4*4*$lenght_btbl); @block_table = unpack("L*", $tmp); $tmp = &Crc($name_htable, \@massive_base, 0x300); &Decode(\@hash_table, \@massive_base, $tmp, 4*$lenght_htbl); $tmp = &Crc($name_btable, \@massive_base, 0x300); &Decode(\@block_table, \@massive_base, $tmp, 4*$lenght_btbl); # my @tblock_table = @block_table; # $tmp = &Crc($name_btable, \@massive_base, 0x300); # &Encode(\@tblock_table, \@massive_base, $tmp, 4*$lenght_btbl); === Encrypt / Decrypt =========== @massive_base 和 &Crc(...) 跟 hash function 用的是一样的. -- 结束了 ^^ --



※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 220.138.156.138
1F:推 Forcast:◆ 这一篇文章值 230 银 02/07 23:31
2F:推 jimmylee7775:推 02/07 23:31
3F:推 waterfantasy:我的眼睛花了 02/07 23:32
4F:推 red0210:眼睛都花了 02/07 23:33
5F:推 MeauD2:这篇 很难>"< 应属於大内高手流 02/07 23:33
6F:推 micado:我只看到一推程式设定码 02/07 23:34
7F:推 ericinttu:虽然我看不懂,但真是高手才办得到 Orz 02/07 23:32
8F:推 GGL:@@" 02/07 23:34
9F:推 pocky0511:第一行就看不懂 第二页就按END 02/07 23:35
10F:推 micado:不过我还是想说"帅ㄚ~老皮!" 02/07 23:36
11F:→ edwar:大量中英夹杂, 很难排版 >"< 我自己留的每段分开, 较不伤眼 02/07 23:35
12F:推 omarandy:完全...不懂ˊˋ 02/07 23:43
13F:推 ScorAlan:大师让小弟拜一下Orz 02/08 00:00
14F:推 Joybo:我走到了Programming版了吗 02/08 00:17
15F:推 Joybo:没学过perl,很好奇他怎知道Encode / Decode的方式,组语? 02/08 00:21
16F:→ edwar:perl 的部分是留着我自己以後要看的 XD 02/08 00:24
17F:→ edwar:Encode/Decode 也许是 trace 出来的? 02/08 00:24
18F:→ edwar:楼上有人看不懂没关系, 只要有人能看懂再写好用的工具程式 02/08 00:26
19F:→ edwar:就好了 XD 02/08 00:27







like.gif 您可能会有兴趣的文章
icon.png[问题/行为] 猫晚上进房间会不会有憋尿问题
icon.pngRe: [闲聊] 选了错误的女孩成为魔法少女 XDDDDDDDDDD
icon.png[正妹] 瑞典 一张
icon.png[心得] EMS高领长版毛衣.墨小楼MC1002
icon.png[分享] 丹龙隔热纸GE55+33+22
icon.png[问题] 清洗洗衣机
icon.png[寻物] 窗台下的空间
icon.png[闲聊] 双极の女神1 木魔爵
icon.png[售车] 新竹 1997 march 1297cc 白色 四门
icon.png[讨论] 能从照片感受到摄影者心情吗
icon.png[狂贺] 贺贺贺贺 贺!岛村卯月!总选举NO.1
icon.png[难过] 羡慕白皮肤的女生
icon.png阅读文章
icon.png[黑特]
icon.png[问题] SBK S1安装於安全帽位置
icon.png[分享] 旧woo100绝版开箱!!
icon.pngRe: [无言] 关於小包卫生纸
icon.png[开箱] E5-2683V3 RX480Strix 快睿C1 简单测试
icon.png[心得] 苍の海贼龙 地狱 执行者16PT
icon.png[售车] 1999年Virage iO 1.8EXi
icon.png[心得] 挑战33 LV10 狮子座pt solo
icon.png[闲聊] 手把手教你不被桶之新手主购教学
icon.png[分享] Civic Type R 量产版官方照无预警流出
icon.png[售车] Golf 4 2.0 银色 自排
icon.png[出售] Graco提篮汽座(有底座)2000元诚可议
icon.png[问题] 请问补牙材质掉了还能再补吗?(台中半年内
icon.png[问题] 44th 单曲 生写竟然都给重复的啊啊!
icon.png[心得] 华南红卡/icash 核卡
icon.png[问题] 拔牙矫正这样正常吗
icon.png[赠送] 老莫高业 初业 102年版
icon.png[情报] 三大行动支付 本季掀战火
icon.png[宝宝] 博客来Amos水蜡笔5/1特价五折
icon.pngRe: [心得] 新鲜人一些面试分享
icon.png[心得] 苍の海贼龙 地狱 麒麟25PT
icon.pngRe: [闲聊] (君の名は。雷慎入) 君名二创漫画翻译
icon.pngRe: [闲聊] OGN中场影片:失踪人口局 (英文字幕)
icon.png[问题] 台湾大哥大4G讯号差
icon.png[出售] [全国]全新千寻侘草LED灯, 水草

请输入看板名称,例如:Boy-Girl站内搜寻

TOP