ukyoは激怒した。ukyoにはMsEdgeがわからぬ。けれどもzipに対しては、人一倍に敏感であった
$ unzip MsEdge.Win10.VMware.zip
Archive: MsEdge.Win10.VMware.zip
warning [MsEdge.Win10.VMware.zip]: 1031667094 extra bytes at beginning or within zipfile
(attempting to process anyway)
error [MsEdge.Win10.VMware.zip]: start of central directory not found;
zipfile corrupt.
(please check that you have transferred or created the zipfile in the
appropriate BINARY mode and that you have compiled UnZip properly)
なにやらVirtual Machine (VM), Windows Virtual PC & BrowserStack : Microsoft Edge Devよりダウンロードしたzipアーカイブが展開できない、エラーをみるとどうやらメタデータが壊れているようなのでzipの仕様書を見ながらファイルを救出することにした。
zipに格納された各ファイルのオフセットとサイズを取得する
zipの構造については仕様書にも書かれていますし、いろいろなサイトで解説されてますので詳しくは解説しません。以下のサイト等を参考にして下さい。
通常zipはファイル末尾にあるメタデータからデータを展開していきます。が、それが壊れているようなので、前から読み込んでいきます。リトルエンディアンなのでバイナリエディタの表示とは逆になっていることに注意して下さい。
ファイル先頭から、以下のような情報が読み取れます。
-
0x04034B50
local file headerのシグニチャです -
0xFFFFFFFF
ファイルサイズを表しますが、この場合は4GBで収まらないファイルということになります。その場合は、拡張フィールドにサイズが書いてあります -
0x0014
拡張フィールドのバイト長 -
0x0001
拡張フィールドには複数の要素が格納されている可能性があります。これは要素の種類を表します、この場合は64bitで表されたファイルのサイズが入っています -
0x0010
要素のサイズ -
0x000000013D7DF66C
ファイルの圧縮後のサイズ
MsEdge-Win10-VMware/MsEdge-Win10-VMware-disk1.vmdkのオフセットが0x64
byte、サイズが0x000000013D7DF66C
byteだと判明しました。次のファイルのlocal file headerの先頭は0x000000013D7DF6D0
byteにあるはずなのでそこまで飛びます。
同様に読み込んでいきます。1画面に収まる範囲にあるので半透明な範囲で覆ってみました。ファイル名がEdge-Win10-VMware/MsEdge-Win10-VMware.mfでlocal file headerの先頭からのオフセットは0x48
byteはサイズはサイズは0x7A
byteです。
次のファイルも同様に読み込んでいきます。これは省略。3つ目のファイルを読み終わって次のファイルを読み込もうとしたときシグニチャが0x04034B50
でなくて0x02014B50
なことに気づきます。これはcentral directory headerのそれ、local file headerのオフセット等のメタデータで、つまりこれ以上ファイルはないということにもなります。
合計3つのファイルがありました。まとめると以下のようになります。
ファイル名 | local file header先頭からのオフセット(byte) | 圧縮されたファイルのサイズ(byte) |
---|---|---|
MsEdge-Win10-VMware/MsEdge-Win10-VMware-disk1.vmdk | 0x64 |
0x013D7DF66C |
MsEdge-Win10-VMware/MsEdge-Win10-VMware.mf | 0x48 |
0x7A |
MsEdge-Win10-VMware/MsEdge-Win10-VMware.ovf | 0x49 |
0x056E |
展開する
各ファイルのオフセットとサイズが取れたので各ファイルを展開していきます。jsで書きます。別に言語はなんでもいいんですが、慣れているのでこれで。jsだと浮動小数点のアレがあるんですが、今回のだとセーフですかね。
var fs = require('fs');
var zlib = require('zlib');
var start;
var end;
function unzipFile(name, start, end) {
var rs = fs.createReadStream('MsEdge.Win10.VMware.zip', {start: start, end: end});
var inflateStream = zlib.createInflateRaw();
var ws = fs.createWriteStream(name);
rs.pipe(inflateStream).pipe(ws);
}
start = 0x6E;
end = start + 0x013D7DF66C;
unzipFile('MsEdge-Win10-VMware-disk1.vmdk', start, end);
start = end + 0x48;
end = start + 0x7A;
unzipFile('MsEdge-Win10-VMware.mf', start, end);
start = end + 0x49;
end = start + 0x056E;
unzipFile('MsEdge-Win10-VMware.ovf', start, end);
適当にdeflateで圧縮されていると決めつけましたが、zipの圧縮アルゴリズムはdeflate以外も使えます。ただ、deflateじゃないのを見たことがないので意識する必要はないかと思います。
その他、救出する方法を考えてみる
zipはストリームで出力するためにlocal file header内のcompressed size(とuncomressed size, crc32)の部分に0x00000000
を入れておいて、圧縮されたファイルのバイト列のおしりにdata descriptorとして書くことができます。ただ、それだと前からファイルのサイズが取得できないので詰みます。そんなときどうしたらよいか考えてみます。
ディレクトリ名で検索する
今回の例だとMsEdge-Win10-VMware。案外単一ディレクトリに入っているzipが多い印象があります。ディレクトリ名で検索してちょっと戻ってシグニチャを探すのはありだと思います。
central directory headerが生存しているか確認する
今回の例だとcentral directory headerの先頭バイトが見つからないみたいなエラーだったのでそれ自体は生き残っているかもしれません。実際生き残っていましたし。残っていればそこから普通にオフセットを計算すればOKですね。
local file headerらしきものを探す
これは割りと力技です。local file headerのシグニチャを一個ずつ検索していき、直前の12byte(data descriptorのはず)を解析します。compressed sizeが正しいものなら一個前のlocal file headerの末尾にたどり着くはずです。まぁ、ここまで来たらそういうプログラムを作るべきですね。
zip -F
実はzip -F in --out out
でzipファイルを修復することができます。最初からそれ使えよ、というかんじですが、これは終わった後に知ったことです。ただ、これやってもなぜか修復できなかったので結局手動でやるしかなかったのですが。