C#でzipファイルをバイナリデータとして読み込んでみる。

C# コンピュータ
C#

C#ではzipファイルを扱うライブラリがありますが、今回はzipファイルをバイナリデータとして読み込んんで中身をのぞいいてみようと思います。

wikipediaでZIPファイルのページを眺めていたところZIPファイルの構造の情報がありました。その情報を元に実際zipファイルを読み込んでみたいと思います。
ZIP (ファイルフォーマット) - Wikipedia

テストデータの作成

    static void MakeTestZIP(string zipfilename)
    {
        // 追加するファイル名
        const string entryFileName = @"dummy.txt";
        // 書き込む文字列
        const string writingStrings = "0123456789ABCDEF";
        // ZIPファイルを新規作成
        using var zip = ZipFile.Open(zipfilename,
            ZipArchiveMode.Create);
        // エントリーを無圧縮で作成
        var newEntry = zip.CreateEntry(entryFileName,
            CompressionLevel.NoCompression);
        // エントリーを開く
        using var stream = newEntry.Open();
        // エントリーからテキストストリームを開く
        using var sw = new StreamWriter(stream);
        // 文字列を書き込み
        sw.Write(writingStrings);
    }

実行するとzipファイルが作成されました。
作成されたzipファイルをエクスプローラーから開いてみたところdummy.txtファイルが作成され中身も指定した文字列がセットされていました。

ZIPファイルを読み込む

テストデータが出来たのでZIPファイルを読み込んでみます。

    static void ReadZIPFile(string zipfilename)
    {
        // ファイルを開く
        using FileStream fs = new(
            zipfilename, FileMode.Open, FileAccess.Read);
        // ローカルファイルヘッダ用バッファ
        int localFileHeaderBufferSzie = 30;
        byte[] buffer = new byte[localFileHeaderBufferSzie];
        // ローカルファイルヘッダを読み込み
        fs.Read(buffer, 0, (int)localFileHeaderBufferSzie);

        // 先頭の4バイトを取得
        var hm =  buffer.Take(4).ToArray();
        Console.WriteLine($"{(char)hm[0]}_{(char)hm[1]}_{hm[2]}_{hm[3]}");
        // P_K_3_4
    }

ファイルの先頭からの4バイトを読みだして出力したところ「P_K_3_4」となりました。
これはローカルファイルヘッダを表すシグネチャのようです。
wikipediaの情報通りなので読み進めます。

    static void ReadZIPFile(string zipfilename, int offset=0)
    {
        // ファイルを開く
        using FileStream fs = new(
            zipfilename, FileMode.Open, FileAccess.Read);
        // ローカルファイルヘッダ用バッファ
        int localFileHeaderBufferSzie = 30;
        byte[] buffer = new byte[localFileHeaderBufferSzie];
        // ローカルファイルヘッダを読み込み
        fs.Read(buffer, offset, (int)localFileHeaderBufferSzie);

        // 先頭の4バイトを取得
        var hm =  buffer.Take(4).ToArray();
        Console.WriteLine($"{(char)hm[0]}_{(char)hm[1]}_{hm[2]}_{hm[3]}");
        // P_K_3_4

        // ファイル名の長さ
        byte[] tmp2 = buffer.Skip(8).Take(2).ToArray();
        var compressMethod = BitConverter.ToInt16(tmp2);
        //Console.WriteLine($"{tmp2[0]}_{tmp2[1]}");
        Console.WriteLine($"圧縮メソッド:{compressMethod}");
        // 圧縮メソッド:0(無圧縮)

        // 圧縮後のファイルサイズ
        byte[] tmp4 = buffer.Skip(18).Take(4).ToArray();
        var compressSize = BitConverter.ToInt32(tmp4);
        //Console.WriteLine($"{tmp4[0]}_{tmp4[1]}_{tmp4[2]}_{tmp4[3]}");
        Console.WriteLine($"圧縮したファイルサイズ:{compressSize}");
        // 圧縮したファイルサイズ:16
        
        // 圧縮前のファイルサイズ
        tmp4 = buffer.Skip(22).Take(4).ToArray();
        var originalSize = BitConverter.ToInt32(tmp4);
        //Console.WriteLine($"{tmp4[0]}_{tmp4[1]}_{tmp4[2]}_{tmp4[3]}");
        Console.WriteLine($"圧縮前のファイルサイズ:{originalSize}");
        // 圧縮前のファイルサイズ:16

        // ファイル名の長さ
        tmp2 = buffer.Skip(26).Take(2).ToArray();
        var n = BitConverter.ToInt16(tmp2);
        //Console.WriteLine($"{tmp2[0]}_{tmp2[1]}");
        Console.WriteLine($"ファイル名の長さ:{n}");
        // ファイル名の長さ:9

        // 拡張フィールドの長さ
        tmp2 = buffer.Skip(28).Take(2).ToArray();
        var exFieldSize = BitConverter.ToInt16(tmp2);
        //Console.WriteLine($"{tmp2[0]}_{tmp2[1]}");
        Console.WriteLine($"拡張フィールドの長さ:{exFieldSize}");
        // 拡張フィールドの長さ:0


        // ファイル名を読み込む
        byte[] filenameBuffer = new byte[n];
        fs.Read(filenameBuffer, 0, n);
        var filename = System.Text.Encoding.UTF8.GetString(filenameBuffer);
        Console.WriteLine($"ファイル名:{filename}");
        
        // 拡張フィールド読み飛ばし
        if (exFieldSize > 0)
        {
            fs.Seek(exFieldSize, SeekOrigin.Current);
        }

        // ファイルの内容を読み込み
        byte[] fileDataBuffer = new byte[compressSize];
        fs.Read(fileDataBuffer, 0, compressSize);
        var fileData = System.Text.Encoding.UTF8.GetString(fileDataBuffer);
        Console.WriteLine($"{fileData}");

        Console.WriteLine($"ファイルの読み込み位置:{fs.Position}");
        // ファイルの読み込み位置:55
    }

結果

圧縮メソッド:0
圧縮したファイルサイズ:16
圧縮前のファイルサイズ:16
ファイル名の長さ:9
拡張フィールドの長さ:0
ファイル名:dummy.txt
0123456789ABCDEF
ファイルの読み込み位置:55

ファイルを読み出すことが出来ました。無圧縮でしたので、書き込んだデータをそのまま読み出すことが出来ました。
圧縮が施されている場合、展開処理をする必要がありますが、展開にはDeflateStreamやGZipStreamなどが使えそうです。(未確認)

エディアンについてはよくわからないのですが、ZIPファイルはリトルエンディアンらしくそれを読み込んで並びを変えずにそのままBitConverter.ToInt32()で数値に変換することが出来たので、実行環境もリトルディアンだと思われます。

コメント