C++でzip解凍 with boost

はいどうも、松山事務所の石丸です。
突然ですが、世のC++erはどうやってgzip解凍しているのでしょうか。
今回はboostライブラリのfiltering_streamとgzip_decompressorを使って解凍してみました。
環境はmacOS Sierra 10.12.2でclangがたぶん3.9.0でC++が11かと思います。

#include <fstream>
#include <iostream>
#include <iomanip>
#include <boost/iostreams/filter/gzip.hpp>
#include <boost/iostreams/filtering_stream.hpp>

int main(int argc, char *argv[])
{
    const auto path = "level.dat";
    std::ifstream input_file(path, std::ios_base::in | std::ios_base::binary);

    boost::iostreams::filtering_istream filtering_stream;
    filtering_stream.push(boost::iostreams::gzip_decompressor());
    filtering_stream.push(input_file);

    char buffer[1024];
    std::cout << std::hex;
    while (!filtering_stream.read(buffer, sizeof buffer).eof())
    {
        auto count = filtering_stream.gcount();
        for (int i = 0; i < count; i++)
        {
            std::cout << std::setw(2) << std::setfill('0') << (buffer[i] & 0xff) << " ";
        }
        std::cout << std::endl;
    }

    return 0;
}

filtering_streamのインスタンスを作って、gzip_decompressorと入力ファイルのstreamをpushしたら準備完了。
後は普通の入力ストリームと同じように読み出せば、gzip_decompressorによって解凍されたデータが読み取れます。

おまけ(本編)

上記コードでサンプルとして読み込んだlevel.datというのは、実はマインクラフトのワールドデータファイルでした。
このファイルは全体がgzip圧縮されてるので最初から最後までフィルターをかけて読んでいけばいいのですが、
マップデータが記録されているリージョンファイルは、ファイルヘッダが無圧縮で、以降に続くデータ部は、ブロックごとにgzip、またはzlib形式で圧縮されています。
フィルターを切り替えながら読み込むサンプルとしてmcaファイルをダンプしてみます。

#include <cstdint>
#include <fstream>
#include <iostream>
#include <iomanip>
#include <boost/iostreams/filter/zlib.hpp>
#include <boost/iostreams/filter/gzip.hpp>
#include <boost/iostreams/filtering_stream.hpp>

static std::uint32_t swap32(std::uint32_t value)
{
     return (value & 0x000000ff) << 24
          | (value & 0x0000ff00) <<  8
          | (value & 0xff000000) >> 24
          | (value & 0x00ff0000) >>  8;
}

int main(int argc, char *argv[])
{
    const auto path = "r.-1.-1.mca";
    std::ifstream input_file(path, std::ios_base::in | std::ios_base::binary);

    // load region header
    const int stored_chunk_count = 1024;
    std::uint32_t chunk_locations[stored_chunk_count];
    input_file.read(reinterpret_cast<char *>(chunk_locations), sizeof chunk_locations);

    // load chunk
    const int sector_size = 4096;
    for (int i = 0; i < stored_chunk_count; i++)
    {
        if (chunk_locations[i] == 0) continue;

        std::uint32_t location = swap32(chunk_locations[i]);
        std::uint32_t offset = location >> 8;
        std::uint32_t position = offset * sector_size;
        input_file.seekg(position, std::ios::beg);

        // load chunk header
        std::uint32_t length;
        input_file.read(reinterpret_cast<char *>(&length), sizeof length);
        length = swap32(length);

        std::int8_t compression_type;
        input_file.read(reinterpret_cast<char *>(&compression_type), sizeof compression_type);

        // setup filtering stream
        boost::iostreams::filtering_istream filtering_istream;
        switch (compression_type)
        {
            case 1: // gzip
                filtering_istream.push(boost::iostreams::gzip_decompressor());
                break;
            case 2: // zlib
                filtering_istream.push(boost::iostreams::zlib_decompressor());
                break;
            default:
                continue;
        }
        filtering_istream.push(input_file);

        // load chunk data
        char buffer[1024];
        std::cout << std::hex;
        for (int i = 0; i < length; i += filtering_istream.gcount())
        {
            filtering_istream.read(buffer, sizeof buffer);
            auto count = filtering_istream.gcount();
            for (int i = 0; i < count; i++)
            {
                std::cout << std::setw(2) << std::setfill('0') << (buffer[i] & 0xff) << " ";
            }
            std::cout << std::endl;
        }
    }

    return 0;
}

フィルターを切り替えるサンプルと言いながら、そのコードがしっかりと埋もれてしまいましたね。
リージョンファイルのフォーマット詳細については次々回ぐらいで書いてみたいと思います。
では。

TOP
TOP