読者です 読者をやめる 読者になる 読者になる

zipファイルの拡張領域 0xE57A について

要約
  • ヘッダID0xE57Aを持つzip拡張ヘッダはファイル名のコードポイントを指定するものである。

  • このヘッダはALZipにより自動で付加されるものである。


zipファイルのヘッダには拡張領域と呼ばれる領域が存在し、ヘッダに標準で含まれる情報以外も記述することができる。

例えば、zip標準ヘッダに存在する貧弱なタイムスタンプ(ローカルにおける時間を2秒単位で丸める。すべてのタイムスタンプは偶数秒を持つ。)の代わりにより詳細なフォーマットで作成、更新、アクセス時刻を格納するための拡張ヘッダが存在する。

拡張領域は、拡張ヘッダ

[HeaderID=2bytes:short LE][DataSize=2bytes:short LE][DataBody=(Datasize)bytes]

の連続である必要がある。zipを扱うアーカイバはこれらを(ヘッダIDを知らなくても)読み取れる必要がある。上述したタイムスタンプは0x5455がヘッダIDにあたる(リトルエンディアン指定のため実際のファイル中では5554というバイト列になる)。

さて、主要な拡張ヘッダのヘッダIDは例えばこの辺とかにいろいろ載っているが、この前あるzipファイルの拡張領域を見ていたらこんな拡張ヘッダを見つけた。

7A E5 04 00 B5 03 00 00

ヘッダIDは0xE57Aとのことだが、少なくとも検索してみたところでそれっぽいものは1つもヒットしない。 とりあえずデータサイズ0x0004=4bytesはデータ本体B5030000とも合致するしミスとかではなさそうである。

データ本体の見た目からint(LE)っぽいので、とりあえず10進にすると0x000003B5=949である。949という数字でピンと来る人もまあいるかもしれないけど、これはおそらくcp949の意なんだろう。cp949はeuc-krの拡張である。

軽い確認がてら高難易度BMS差分アップローダーのzipファイルについて調べてみる。ちょっと手を抜いている。

cpd={}
for zp in glob.glob('./*.zip'):
    try:z=zipfile.ZipFile(zp)
    except:continue
    cp=[
        struct.unpack('<I',zi.extra[zi.extra.find('z\xe5\x04\x00')+4:zi.extra.find('z\xe5\x04\x00')+8])[0]
        for zi in z.infolist() if zi.extra.find('z\xe5\x04\x00')!=-1
    ]
    cp=list(set(cp))
    for n in cp:
        cpd[n]=cpd.get(n,0)+1
print cpd
# ----------
{932: 519, 949: 179}

932->cp932が大量にあるし、まずコードポイントとみて間違いないだろう。

ところで、len(glob.glob('./*.zip'))==6308であったので、このヘッダを付加しているアーカイバは愚直に考えれば10%程度のシェアと考えるのが普通だろう。 どのアーカイバがこのヘッダを付加しているのかいくつか試してみた結果、ALZipでのみデフォルトで付加された(ほかに確認したのはWinRAR,7-Zip,explorer.exe)。 ALZipでは圧縮の際に言語(エンコード)を指定できるため、ここで選択した値が書き込まれるのだろう。 f:id:GNQG:20160911154505j:plain ちなみに言語にUNICODEを指定した場合にはこのヘッダは付加されず、代わりに汎用ビットフラグのUnicodeフラグ(%0000100000000000=2048)が設定されるようだ。

個人的にはとても便利だと思うけどさすがにこれから普及することはないだろうなあ。日本語版ALZipも公開終了してしまったみたいだし。