C++と色々

主にC++やプログラムに関する記事を投稿します。

最低限の機能のアーカイバ書いてみた

コード書くのに疲れたのでコードの説明は割愛。正直コメントや説明ないと読むの辛いと思います…

やってること自体はセガ本(ゲームプログラマになる前に覚えておきたい技術)の最もシンプルなアーカイバと同じです。

ディレクトリの再帰構造に対応していません。暗号化、圧縮対応していません。今後の課題です。エンコードするコードとデコードするコードです。

エンコード

#include <algorithm>
#include <cstdint>
#include <fstream>
#include <iostream>
#include <iterator>
#include <new>
#include <string>
#include <type_traits>
#include <vector>
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;

std::vector<fs::path> enumerate_files(std::string const& path)
{
  fs::path p{path};
  std::vector<fs::path> files;
  std::copy(fs::directory_iterator{p}, fs::directory_iterator{}, std::back_inserter(files));
  return files;
}

std::vector<boost::uintmax_t> enumerate_file_sizes(std::vector<fs::path> const& files)
{
  using size_type = std::vector<fs::path>::size_type;
  size_type const& size = files.size();
  std::vector<boost::uintmax_t> file_sizes(size);
  std::transform(files.begin(), files.end(), file_sizes.begin(), [](fs::path const& file) {
    return fs::file_size(file);
  });
  return file_sizes;
}

void create_body(std::vector<fs::path> const& files, std::vector<boost::uintmax_t> const& file_sizes, std::ofstream& archive)
{
  using size_type = std::vector<fs::path>::size_type;
  size_type const& size = files.size();
  for (auto i = 0U; i < size; ++i) {
    std::ifstream file{files[i].string(), std::ifstream::binary};
    std::vector<char> buffer(static_cast<size_type>(file_sizes[i]));
    file.read(buffer.data(), buffer.size());
    archive.write(buffer.data(), buffer.size());
  }
}

template <class T, std::size_t N = sizeof(T)>
std::enable_if_t<std::is_trivial<T>::value> write_trivial(std::ofstream& ofs, T value)
{
  unsigned char buffer[N];
  new (buffer) T(value);
  ofs.write(reinterpret_cast<char*>(buffer), N);
}

void create_header(std::vector<fs::path> const& files, std::vector<boost::uintmax_t> const& file_sizes, std::ofstream& archive)
{
  std::uint32_t const body_end = static_cast<std::uint32_t>(archive.tellp());
  write_trivial(archive, files.size());
  auto const size = files.size();
  std::int32_t pos = 0;
  for (auto i = 0U; i < size; ++i) {
    write_trivial(archive, pos);
    std::uint32_t s = static_cast<std::uint32_t>(file_sizes[i]);
    write_trivial(archive, s);
    s = static_cast<std::uint32_t>(files[i].string().size());
    write_trivial(archive, s);
    archive.write(files[i].string().c_str(), files[i].string().size());
    pos += static_cast<int>(file_sizes[i]);
  }
  write_trivial(archive, body_end);
}

void create_archive_file(std::vector<fs::path> const& files, std::string const& archive_name)
{
  using size_type = std::vector<fs::path>::size_type;
  auto const file_sizes = enumerate_file_sizes(files);
  std::ofstream archive{archive_name, std::ofstream::binary};
  create_body(files, file_sizes, archive);
  create_header(files, file_sizes, archive);
}

int main(int argc, char** argv)
{
  if (argc != 2) {
    std::cerr << "invalid argument(s). there is a argument that archived directory name.\n";
    return 1;
  }
  try {
    auto const& files = enumerate_files(argv[1]);
    std::copy(files.begin(), files.end(), std::ostream_iterator<fs::path>(std::cout, "\n"));
    std::string archive_name{argv[1]};
    archive_name += ".bin";
    create_archive_file(files, archive_name);
  } catch (fs::filesystem_error const& e) {
    std::cerr << e.what() << " code " << e.code() << std::endl;
  } catch (...) {
    std::cerr << "unknown error\n";
  }
}

デコート側。archive\a.txtになっているところはそれぞれ読みたいファイルの名前に置き換えて下さい。

#include <algorithm>
#include <cstdint>
#include <fstream>
#include <iostream>
#include <iterator>
#include <new>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <vector>
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;

struct entity
{
  std::int32_t position;
  std::uint32_t size;
};

std::unordered_map<std::string, entity> entities;

template <class T, std::size_t N = sizeof(T)>
std::enable_if_t<std::is_trivial<T>::value, T> read_trivial(std::ifstream& ifs)
{
  unsigned char buffer[N];
  ifs.read(reinterpret_cast<char*>(buffer), N);
  return *reinterpret_cast<T*>(buffer);
}

void decode_archive(std::ifstream& archive)
{
  archive.seekg(-4, std::ifstream::end);
  std::uint32_t const header_start = read_trivial<std::uint32_t>(archive);
  archive.seekg(header_start, std::ifstream::beg);
  std::uint32_t const file_number = read_trivial<std::uint32_t>(archive);
  for (auto i = 0U; i < file_number; ++i) {
    std::int32_t const pos = read_trivial<std::int32_t>(archive);
    std::uint32_t const size = read_trivial<std::uint32_t>(archive);
    std::uint32_t const length = read_trivial<std::uint32_t>(archive);
    std::vector<char> filename_buffer(length + 1);
    archive.read(filename_buffer.data(), length);
    filename_buffer[length] = '\0';
    std::string filename{filename_buffer.data()};
    entities.emplace(filename, entity{pos, size});
  }
}

int main(int argc, char** argv)
{
  if (argc != 2) {
    std::cerr << "invalid argument(s). there is a argument that archive file name.\n";
    return 1;
  }
  try {
    std::string const archive_name{argv[1]};
    std::ifstream archive{archive_name, std::ifstream::binary};
    decode_archive(archive);
    for (auto const& m : entities) {
      std::cout << m.first << " " << m.second.position << " " << m.second.size << std::endl;
    }
    auto it = entities.find("archive\\b.txt");
    if (it != entities.end()) {
      auto const& entity = it->second;
      std::vector<char> buffer(entity.size + 1);
      archive.seekg(entity.position, std::ifstream::beg);
      archive.read(buffer.data(), entity.size);
      buffer[entity.size] = '\0';
      std::string const str{buffer.data()};
      std::cout << str << std::endl;
    }
  } catch (fs::filesystem_error const& e) {
    std::cerr << e.what() << " code " << e.code() << std::endl;
  } catch (...) {
    std::cerr << "unknown error\n";
  }
}