他人の空似自作物置場

touhouSE_th145BGMOnly.zip/touhouSE_src/dx_archive_v4.cpp

#include "stdafx.h"

namespace TouhouSE {

namespace DX_ARCHIVE_V4 {

namespace endian = boost::spirit::endian;

namespace {

void decrypt(const unsigned int size, const unsigned int addr, const std::array<unsigned char, 12> &key, unsigned char * const buf) {
  unsigned int table_index = addr % key.size();
  for (unsigned int i = 0; i < size; i++) {
    buf[i] ^= key[table_index];
    table_index = (table_index + 1) % key.size();
  }
}

bool decompress(const std::vector<unsigned char> &in, std::vector<unsigned char> &out) {
  if (in.size() < 9) {
    return false;
  }
  const unsigned int out_size = *reinterpret_cast<const unsigned int *>(&in[0]);
  const unsigned int in_size = *reinterpret_cast<const unsigned int *>(&in[4]);
  if (in_size != in.size()) {
    return false;
  }
  out.resize(out_size);
  const unsigned char compress_flag = in[8];
  for (unsigned int i = 9, out_index = 0; true;) {
    if (in.size() <= i) {
      if (i != in.size() || out_index != out.size()) {
        return false;
      }
      break;
    }
    if (in[i] != compress_flag) {
      if (out.size() <= out_index) {
        return false;
      }
      out[out_index] = in[i];
      i++;
      out_index++;
      continue;
    }
    if (in.size() <= i + 1) {
      return false;
    }
    if (in[i+1] == compress_flag) {
      if (out.size() <= out_index) {
        return false;
      }
      out[out_index] = in[i];
      i += 2;
      out_index++;
      continue;
    }
    if (in.size() <= i + 2) {
      return false;
    }
    const unsigned char temp = (in[i+1] > compress_flag ? in[i+1] - 1 : in[i+1]);
    const unsigned int index_size = (temp & 0x03) + 1;
    const bool is_ex_length = ((temp & 0x04) != 0);
    const unsigned int len = (is_ex_length ? in[i + 2] << 5 : 0) + (temp >> 3) + 4;
    if (is_ex_length) {
      i += 3;
    } else {
      i += 2;
    }
    const unsigned int index = 1 + (
      index_size == 1 ?
        in[i]
      :
        (index_size == 2 ?
          *reinterpret_cast<const unsigned short *>(&in[i])
        :
          *reinterpret_cast<const unsigned int *>(&in[i]) & 0x00FFFFFF
        )
      );
    i += index_size;
    if (out_index < index) {
      return false;
    }
    for (unsigned int j = 0; j < len; j++) {
      out[out_index] = out[out_index - index];
      out_index++;
    }
  }
  return true;
}

class Header : boost::noncopyable {
public:
  endian::ulittle8_t signature[2];
  endian::ulittle16_t version;
  endian::ulittle32_t footer_size;
  endian::ulittle32_t header_size;
  endian::ulittle32_t file_name_table_address;
  endian::ulittle32_t file_data_table_offset;
  endian::ulittle32_t directory_data_table_offset;
  endian::ulittle32_t unknown;

  bool isValid() const {
    const char expected_signature[] = {0x44, 0x58};
    return std::equal(expected_signature, &expected_signature[_countof(expected_signature)], signature)
      && version == 4 && header_size == sizeof(Header) && file_data_table_offset < footer_size
      && directory_data_table_offset < footer_size && directory_data_table_offset > file_data_table_offset;
  }
};

class DirectoryData : boost::noncopyable {
public:
  static const unsigned int directory_data_size = 16;
  endian::ulittle32_t directory_data_offset;
  endian::ulittle32_t parent_directory_offset;
  endian::ulittle32_t file_count;
  endian::ulittle32_t file_data_offset;
  std::vector<boost::shared_ptr<DirectoryData> > child;

  static boost::shared_ptr<DirectoryData> read(std::istream &in, const unsigned long long int file_size, const Header &header, const std::array<unsigned char, 12> &key) {
    boost::shared_ptr<DirectoryData> result;
    const unsigned int directory_data_footer_size = static_cast<unsigned int>(file_size) - header.file_name_table_address - header.directory_data_table_offset;
    if (directory_data_footer_size == 0 || (directory_data_footer_size % directory_data_size) != 0) {
      return result;
    }
    const unsigned int directory_count = directory_data_footer_size / directory_data_size;
    const unsigned int base_addr = header.file_name_table_address + header.directory_data_table_offset;
    in.seekg(base_addr, std::ios::beg);
    if (!in.good()) {
      return result;
    }
    std::map<unsigned int, boost::shared_ptr<DirectoryData> > data_list;
    unsigned int read_size = 0;
    for (unsigned int i = 0; i < directory_count; i++) {
      const boost::shared_ptr<DirectoryData> data = boost::make_shared<DirectoryData>();
      in.read(reinterpret_cast<char *>(data.get()), directory_data_size);
      if (!in.good()) {
        return result;
      }
      const unsigned int addr = base_addr + read_size;
      decrypt(directory_data_size, addr, key, reinterpret_cast<unsigned char *>(data.get()));
      data_list.insert(std::make_pair(addr, data));
      read_size += directory_data_size;
    }
    typedef std::pair<unsigned int, boost::shared_ptr<DirectoryData> > DirectoryDataPair;
    BOOST_FOREACH(const DirectoryDataPair &data, data_list) {
      if (data.second->parent_directory_offset == 0xFFFFFFFF) {
        continue;
      }
      const std::map<unsigned int, boost::shared_ptr<DirectoryData> >::iterator it = data_list.find(
        header.file_name_table_address
        + header.directory_data_table_offset
        + data.second->parent_directory_offset);
      if (it == data_list.end()) {
        return result;
      }
      it->second->child.push_back(data.second);
    }
    result = data_list.begin()->second;
    return result;
  }
};

class FileData : boost::noncopyable {
public:
  endian::ulittle32_t name_offset;
  endian::ulittle32_t flag;
  endian::ulittle64_t create_time;
  endian::ulittle64_t last_access_time;
  endian::ulittle64_t last_update_time;
  endian::ulittle32_t body_offset;
  endian::ulittle32_t body_size;
  endian::ulittle32_t body_compress_size;

  static boost::shared_ptr<std::map<unsigned int, boost::shared_ptr<const FileData> > > read(
    std::istream &in,
    const unsigned long long int file_size,
    const Header &header,
    const std::array<unsigned char, 12> &key)
  {
    boost::shared_ptr<std::map<unsigned int, boost::shared_ptr<const FileData> > > result;
    const unsigned int file_data_footer_size = header.directory_data_table_offset - header.file_data_table_offset;
    if (file_data_footer_size == 0 || (file_data_footer_size % sizeof(FileData)) != 0) {
      return result;
    }
    const unsigned int file_data_count = file_data_footer_size / sizeof(FileData);
    const unsigned int base_addr = header.file_name_table_address + header.file_data_table_offset;
    in.seekg(base_addr, std::ios::beg);
    if (!in.good()) {
      return result;
    }
    const boost::shared_ptr<std::map<unsigned int, boost::shared_ptr<const FileData> > > data_list = boost::make_shared<std::map<unsigned int, boost::shared_ptr<const FileData> > >();
    unsigned int read_size = 0;
    for (unsigned int i = 0; i < file_data_count; i++) {
      boost::shared_ptr<FileData> data = boost::make_shared<FileData>();
      in.read(reinterpret_cast<char *>(data.get()), sizeof(FileData));
      if (!in.good()) {
        return result;
      }
      const unsigned int addr = base_addr + read_size;
      decrypt(sizeof(FileData), addr, key, reinterpret_cast<unsigned char *>(data.get()));
      data_list->insert(std::make_pair(addr, data));
      read_size += sizeof(FileData);
    }
    result = data_list;
    return result;
  }

  bool isDir() const {
    return (flag & 16) != 0;
  }
};

class Filename : boost::noncopyable {
private:

public:
  const std::string name;

  Filename() {
  }
  Filename(const char * const name) : name(name) {
  }

  static boost::shared_ptr<std::map<unsigned int, boost::shared_ptr<const Filename> > > read(
    std::istream &in,
    const unsigned long long int file_size,
    const Header &header,
    const std::array<unsigned char, 12> &key)
  {
    boost::shared_ptr<std::map<unsigned int, boost::shared_ptr<const Filename> > > result;
    in.seekg(header.file_name_table_address, std::ios::beg);
    if (!in.good()) {
      return result;
    }
    const boost::shared_ptr<std::map<unsigned int, boost::shared_ptr<const Filename> > > data_list = boost::make_shared<std::map<unsigned int, boost::shared_ptr<const Filename> > >();
    unsigned int read_size = 0;
    while (read_size < header.file_data_table_offset) {
      unsigned int length = 0;
      in.read(reinterpret_cast<char *>(&length), 2);
      if (!in.good()) {
        return result;
      }
      const unsigned int addr = header.file_name_table_address + read_size;
      decrypt(2, addr, key, reinterpret_cast<unsigned char *>(&length));
      length = length * 4;
      in.seekg(2, std::ios::cur);
      read_size += 4;
      if (length == 0) {
        data_list->insert(std::make_pair(addr, boost::make_shared<const Filename>()));
        continue;
      }
      std::vector<char> name(length);
      in.seekg(name.size(), std::ios::cur);
      read_size += name.size();
      in.read(&name.front(), name.size());
      decrypt(name.size(), header.file_name_table_address + read_size, key, reinterpret_cast<unsigned char *>(&name.front()));
      read_size += name.size();
      data_list->insert(std::make_pair(addr, boost::make_shared<const Filename>(&name.front())));
    }
    result = data_list;
    return result;
  }
};

class FileRecord : boost::noncopyable {
private:
  static bool createListImpl(
    const Header &header,
    const DirectoryData &directory_data,
    const std::map<unsigned int, boost::shared_ptr<const FileData> > &file_data_list,
    const std::map<unsigned int, boost::shared_ptr<const Filename> > &filename_list,
    std::vector<boost::shared_ptr<const FileRecord> > &out,
    const boost::filesystem::path &cur = L"data")
  {
    for (unsigned int i = 0; i < directory_data.file_count; i++) {
      const unsigned int file_data_addr =
        header.file_name_table_address
        + header.file_data_table_offset
        + directory_data.file_data_offset
        + i * sizeof(FileData);
      const std::map<unsigned int, boost::shared_ptr<const FileData> >::const_iterator file_data_it = file_data_list.find(file_data_addr);
      if (file_data_it == file_data_list.end()) {
        return false;
      }
      const FileData &file_data = *file_data_it->second;
      if (file_data.isDir()) {
        continue;
      }
      const unsigned int filename_addr = header.file_name_table_address + file_data.name_offset;
      const std::map<unsigned int, boost::shared_ptr<const Filename> >::const_iterator filename_it = filename_list.find(filename_addr);
      if (filename_it == filename_list.end()) {
        return false;
      }
      const Filename &filename = *filename_it->second;
      out.push_back(boost::make_shared<const FileRecord>(
        sizeof(Header) + file_data.body_offset,
        cur / filename.name,
        file_data.body_size,
        file_data.body_compress_size));
    }
    BOOST_FOREACH (const boost::shared_ptr<const DirectoryData> childImpl, directory_data.child) {
      const DirectoryData &child = *childImpl;
      const unsigned int file_data_addr = header.file_name_table_address + header.file_data_table_offset + child.directory_data_offset;
      const std::map<unsigned int, boost::shared_ptr<const FileData> >::const_iterator file_data_it = file_data_list.find(file_data_addr);
      if (file_data_it == file_data_list.end()) {
        return false;
      }
      const FileData &file_data = *file_data_it->second;
      const unsigned int filename_addr = header.file_name_table_address + file_data.name_offset;
      const std::map<unsigned int, boost::shared_ptr<const Filename> >::const_iterator filename_it = filename_list.find(filename_addr);
      if (filename_it == filename_list.end()) {
        return false;
      }
      const Filename &filename = *filename_it->second;
      if (!createListImpl(header, child, file_data_list, filename_list, out, cur / filename.name)) {
        return false;
      }
    }
    return true;
  }

public:
  const unsigned int addr;
  const boost::filesystem::path path;
  const unsigned int file_size;
  const unsigned int file_compress_size;

  FileRecord(const unsigned int addr, const boost::filesystem::path &path, const unsigned int file_size, const unsigned int file_compress_size) :
    addr(addr), path(path), file_size(file_size), file_compress_size(file_compress_size)
  {
  }

  static boost::shared_ptr<std::vector<boost::shared_ptr<const FileRecord> > > createList(
    const Header &header,
    const DirectoryData &directory_data,
    const std::map<unsigned int, boost::shared_ptr<const FileData> > &file_data,
    const std::map<unsigned int, boost::shared_ptr<const Filename> > &filename_list)
  {
    boost::shared_ptr<std::vector<boost::shared_ptr<const FileRecord> > > result;
    const boost::shared_ptr<std::vector<boost::shared_ptr<const FileRecord> > > list = boost::make_shared<std::vector<boost::shared_ptr<const FileRecord> > >();
    if (!createListImpl(header, directory_data, file_data, filename_list, *list)) {
      return result;
    }
    result = list;
    return result;
  }
};

} // anonymous

class Owner : public ExtractorBase {
private:
  std::istream &in;
  const unsigned long long int file_size;
  const std::array<unsigned char, 12> key;
  const boost::shared_ptr<const Header> header;
  const boost::shared_ptr<std::vector<boost::shared_ptr<const FileRecord> > > file_record_list;

  Owner(
    std::istream &in,
    const unsigned long long int file_size,
    const std::array<unsigned char, 12> key,
    const boost::shared_ptr<const Header> header,
    const boost::shared_ptr<std::vector<boost::shared_ptr<const FileRecord> > > file_record_list)
    :
    in(in), file_size(file_size), key(key), header(header), file_record_list(file_record_list) {
  }

  static bool readHeaderAndKey(
    std::istream &in,
    const unsigned long long int file_size,
    boost::shared_ptr<Header> &header,
    std::array<unsigned char, 12> &key)
  {
    if (file_size < sizeof(Header)) {
      return false;
    }
    header.reset(new Header());
    in.read(reinterpret_cast<char *>(header.get()), sizeof(Header));
    if (!in.good()) {
      return false;
    }
    *reinterpret_cast<unsigned int *>(&key[0]) = *reinterpret_cast<const unsigned int *>(&reinterpret_cast<const unsigned char *>(header.get())[0]) ^ 0x00045844;
    *reinterpret_cast<unsigned int *>(&key[8]) = *reinterpret_cast<const unsigned int *>(&reinterpret_cast<const unsigned char *>(header.get())[8]) ^ 0x0000001C;
    const unsigned int file_name_table_address = header->file_name_table_address ^ *reinterpret_cast<const unsigned int *>(&key[0]);
    if (file_name_table_address > file_size) {
      return false;
    }
    const unsigned int footer_size = static_cast<unsigned int>(file_size)-file_name_table_address;
    *reinterpret_cast<unsigned int *>(&key[4]) = *reinterpret_cast<const unsigned int *>(&reinterpret_cast<const unsigned char *>(header.get())[4]) ^ footer_size;

    decrypt(sizeof(Header), 0, key, reinterpret_cast<unsigned char *>(header.get()));
    if (!header->isValid()) {
      return false;
    }
    return header->file_name_table_address < file_size;
  }

public:
  static boost::shared_ptr<Owner> Open(std::istream &in, const unsigned long long int file_size) {
    boost::shared_ptr<Owner> result;
    std::array<unsigned char, 12> key;
    boost::shared_ptr<Header> headerImpl;
    if (!readHeaderAndKey(in, file_size, headerImpl, key)) {
      return result;
    }
    boost::shared_ptr<const Header> header = headerImpl;
    const boost::shared_ptr<DirectoryData> directory_data = DirectoryData::read(in, file_size, *header, key);
    if (!directory_data) {
      return result;
    }
    const boost::shared_ptr<std::map<unsigned int, boost::shared_ptr<const FileData> > > file_data = FileData::read(in, file_size, *header, key);
    if (!file_data) {
      return result;
    }
    const boost::shared_ptr<std::map<unsigned int, boost::shared_ptr<const Filename> > > filename_list = Filename::read(in, file_size, *header, key);
    if (!filename_list) {
      return result;
    }
    const boost::shared_ptr<std::vector<boost::shared_ptr<const FileRecord> > > file_record_list = FileRecord::createList(
      *header,
      *directory_data,
      *file_data,
      *filename_list);
    if (!file_record_list) {
      return result;
    }
    result.reset(new Owner(in, file_size, key, header, file_record_list));
    return result;
  }

  bool Extract(const unsigned int index, std::vector<unsigned char> &result) {
    const FileRecord &file = *(*file_record_list)[index];
    in.seekg(file.addr, std::ios::beg);
    if (!in.good()) {
      return false;
    }
    bool isCompress = (file.file_compress_size != 0xFFFFFFFF);
    const unsigned int read_size = (isCompress ? file.file_compress_size : file.file_size);
    result.resize(read_size);
    if (result.size() == 0) {
      return true;
    }
    in.read(reinterpret_cast<char *>(&result.front()), read_size);
    if (!in.good()) {
      return false;
    }
    decrypt(result.size(), file.addr, key, reinterpret_cast<unsigned char *>(&result.front()));
    if (isCompress) {
      std::vector<unsigned char> temp;
      if (!decompress(result, temp)) {
        return false;
      }
      result.swap(temp);
    }
    return true;
  }

  unsigned int GetSize() const {
    return file_record_list->size();
  }

  std::wstring GetName() const {
    return L"DxArchiveV4";
  }

  boost::filesystem::path GetFileName(unsigned int index) const {
    return (*file_record_list)[index]->path;
  }
};

ADD_DAT_EXTRACTOR(Owner);

} // DX_ARCHIVE_V4

} // ToudouSE