はいどうも、松山事務所の石丸です。
突然ですが、世の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; }
フィルターを切り替えるサンプルと言いながら、そのコードがしっかりと埋もれてしまいましたね。
リージョンファイルのフォーマット詳細については次々回ぐらいで書いてみたいと思います。
では。