他人の空似自作物置場

touhouSE_th145BGMOnly.zip/touhouSE_src/nfa0_v2.cpp

#include "stdafx.h"

namespace NFA0_V2 {

namespace endian = boost::spirit::endian;

template<typename T>
class NFA0Base {
public:
  unsigned char rotate(unsigned char c, unsigned int rawShift) {
    const unsigned int shift = rawShift % 8;
    return (c >> shift) + (c << (8 - shift));
  }
  void Decrypt() {
    BOOST_FOREACH(char &c, std::make_pair(reinterpret_cast<char *>(this), &reinterpret_cast<char *>(this)[sizeof(T)])) {
      c ^= 0x08;
    }
  }
};

#pragma pack(push, 1)
class Header : /* VC2010だと1byte無用なデータがくっつくためコメントアウト boost::noncopyable, */ public NFA0Base<Header> {
public:
  char signature[4];
  endian::ulittle16_t version;
  endian::ulittle16_t fileCount;
  endian::ulittle32_t type;

  bool IsValid() const {
    return std::equal(&this->signature[0], &this->signature[_countof(this->signature)], "NFA0") &&
      this->version == 2;
  }
};
class RawFileRecord17 : public NFA0Base<RawFileRecord17> /* std::vectorに入れるためコメントアウト , boost::noncopyable*/ {
public:
  endian::ulittle32_t size;
  endian::ulittle32_t address;
  endian::ulittle32_t unknown;
  char name[128];

  bool IsValid() const {
    return std::find(&this->name[0], &this->name[_countof(this->name)], '\0') != &this->name[_countof(this->name)];
  }
  const char *GetName(const unsigned int, const boost::filesystem::path &) const {
    return name;
  }
  const unsigned int GetSize(const unsigned int) const {
    return size;
  }
  unsigned int GetOrigSize() const {
    return size;
  }
};
class RawFileRecord1 : public NFA0Base<RawFileRecord1> /* std::vectorに入れるためコメントアウト , boost::noncopyable*/ {
public:
  endian::ulittle32_t size;
  endian::ulittle32_t address;
  endian::ulittle32_t unknown;

  bool IsValid() const {
    return true;
  }
  const boost::filesystem::path GetName(const unsigned int index, const boost::filesystem::path &path) const {
    char temp[256];
    ::sprintf(temp, "%d", index);
    if (path.extension() == ".snd") {
      if (path.filename() == "jingle.snd" || path.filename() == "se.snd") {
        ::strcat(temp, ".wav");
      } else {
        ::strcat(temp, ".ogg");
      }
    } else if (path.extension() == ".spk") {
      ::strcat(temp, ".lua.lcd");
    } else if (path.wstring().find(L".mov") != std::wstring::npos) {
      ::strcat(temp, ".ogv");
    } else {
      ::strcat(temp, "");
    }
    return temp;
  }
  const unsigned int GetSize(const unsigned int) const {
    return size;
  }
  unsigned int GetOrigSize() const {
    return size;
  }
};
class RawFileRecord5 : public NFA0Base<RawFileRecord5> /* std::vectorに入れるためコメントアウト , boost::noncopyable*/ {
public:
  endian::ulittle32_t origSize;
  endian::ulittle32_t address;
  endian::ulittle32_t unknown;

  bool IsValid() const {
    return true;
  }
  const boost::filesystem::path GetName(const unsigned int index, const boost::filesystem::path &path) const {
    char temp[256];
    ::sprintf(temp, "%d", index);
    if (path.filename() == "font.cat") {
      ::strcat(temp, ".bmf");
    }
    return temp;
  }
  unsigned int GetSize(const unsigned int nextAddr) const {
    return nextAddr - address;
  }
  unsigned int GetOrigSize() const {
    return origSize;
  }
};
class FileRecord /* std::vectorに入れるためコメントアウト : boost::noncopyable*/ {
public:
  boost::filesystem::path path;
  unsigned int address;
  unsigned int size;
  bool crypt;
  bool compress;
  unsigned int origSize;
  unsigned int index;

  void Set(const boost::filesystem::path &path, const unsigned int address, const unsigned int size, bool crypt) {
    this->path = path;
    this->address = address;
    this->size = size;
    this->crypt = crypt;
    this->compress = false;
    this->origSize = size;
  }
  void Set(const boost::filesystem::path &path, const unsigned int address, const unsigned int size, bool crypt, unsigned int origSize, unsigned int index) {
    this->path = path;
    this->address = address;
    this->size = size;
    this->crypt = crypt;
    this->compress = size != origSize;
    this->origSize = origSize;
    this->index = index;
  }
};
#pragma pack(pop)

class Owner : public ExtractorBase {
private:
  std::istream &in;
  const boost::shared_ptr<const std::vector<FileRecord> > fileList;

  Owner(std::istream &in, const boost::shared_ptr<const std::vector<FileRecord> > fileList) :
    in(in), fileList(fileList)
  {
  }

public:
  static boost::shared_ptr<Owner> Open(std::istream &in, const unsigned long long int fileSize) {
    boost::shared_ptr<Owner> result;
    if (!in.good()) {
      return result;
    }
    Header header;
    in.read(reinterpret_cast<char *>(&header), sizeof(header));
    if (!in.good() || !header.IsValid()) {
      return result;
    }

    const boost::shared_ptr<std::vector<FileRecord> > fileList(new std::vector<FileRecord>());
    if (!OpenImpl(in, 0, "", static_cast<unsigned int>(fileSize), false, *fileList)) {
      return result;
    }
    return boost::shared_ptr<Owner>(new Owner(in, fileList));
  }
  template<typename T>
  static bool AddFileList(std::istream &in, const unsigned int baseAddr, const Header &header, const boost::filesystem::path &path, const unsigned int fileSize, bool crypt, std::vector<FileRecord> &result) {
    if (fileSize < sizeof(header) + static_cast<unsigned long long int>(sizeof(T)) * header.fileCount) {
      return false;
    }

    std::vector<T> fileList(header.fileCount);
    in.read(reinterpret_cast<char *>(&fileList.front()), sizeof(T) * header.fileCount);
    if (!in.good()) {
      return false;
    }
    const boost::filesystem::path dir = boost::filesystem::path(path).replace_extension();
    for (unsigned int i = 0; i < fileList.size(); i++) {
      T &file = fileList[i];
      if (!crypt) {
        file.Decrypt();
      }
      const unsigned int size = file.GetSize(fileList.size() == i + 1 ? fileSize : fileList[i+1].address);
      if (!file.IsValid() || static_cast<unsigned long long int>(file.address) + size > fileSize) {
        return false;
      }
      const boost::filesystem::path name = file.GetName(i, path);
      if (size != file.GetOrigSize()) {
        if (!OpenCompressData(in, baseAddr + file.address, dir / name, size, !crypt, file.GetOrigSize(), result)) {
          return false;
        }
        continue;
      }
      if (!OpenImpl(in, baseAddr + file.address, dir / name, size, !crypt, result)) {
        return false;
      }
    }
    return true;
  }
  static bool OpenImpl(std::istream &in, const unsigned int baseAddr, const boost::filesystem::path &path, const unsigned int fileSize, bool crypt, std::vector<FileRecord> &result) {
    if (fileSize < sizeof(Header)) {
      result.resize(result.size() + 1);
      result.back().Set(path, baseAddr, fileSize, crypt);
      return true;
    }
    in.seekg(baseAddr, std::ios::beg);
    Header header;
    in.read(reinterpret_cast<char *>(&header), sizeof(header));
    if (!in.good()) {
      return false;
    }
    if (crypt) {
      header.Decrypt();
    }
    if (!header.IsValid()) {
      result.resize(result.size() + 1);
      result.back().Set(path, baseAddr, fileSize, crypt);
      return true;
    }
    if (header.fileCount == 0) {
      return true;
    }
    switch(header.type) {
    case 17:
      return AddFileList<RawFileRecord17>(in, baseAddr, header, path, fileSize, crypt, result);
    case 0:
      return AddFileList<RawFileRecord1>(in, baseAddr, header, path, fileSize, !crypt, result);
    case 1:
      return AddFileList<RawFileRecord1>(in, baseAddr, header, path, fileSize, crypt, result);
    case 5:
      return AddFileList<RawFileRecord5>(in, baseAddr, header, path, fileSize, crypt, result);
    default:
      return false;
    }
  }
  static bool OpenCompressData(std::istream &in, const unsigned int baseAddr, const boost::filesystem::path &path, const unsigned int fileSize, bool crypt, const unsigned int origSize, std::vector<FileRecord> &result) {
    if (fileSize == 0 || origSize == 0) {
      return false;
    }
    std::vector<unsigned char> compData(fileSize);
    in.read(reinterpret_cast<char *>(&compData.front()), compData.size());
    if (!in.good()) {
      return false;
    }
    if (crypt) {
      BOOST_FOREACH(unsigned char &c, compData) {
        c ^= 0x08;
      }
    }
    std::vector<unsigned char> origData(origSize);
    unsigned long origSizeResult = origSize;
    if (Z_OK != ::uncompress(&origData.front(), &origSizeResult, &compData.front(), compData.size()) || origSizeResult != origSize) {
      return false;
    }
    if (!reinterpret_cast<Header *>(&result.front())->IsValid()) {
      result.resize(result.size() + 1);
      result.back().Set(path, baseAddr, fileSize, crypt, origSize, 0);
      return true;
    }
    boost::iostreams::array_source source(reinterpret_cast<char *>(&compData.front()), compData.size());
    boost::iostreams::stream<boost::iostreams::array_source> stream(source);
    boost::shared_ptr<Owner> owner = Owner::Open(stream, compData.size());
    if (!owner) {
      return false;
    }
    const boost::filesystem::path dir = boost::filesystem::path(path).remove_filename();
    for (unsigned int i = 0; i < owner->GetSize(); i++) {
      result.resize(result.size() + 1);
      result.back().Set(dir / owner->GetFileName(i), baseAddr, fileSize, crypt, origSize, i);
    }
    return true;
  }
  bool Extract(const unsigned int index, std::vector<unsigned char> &result) {
    if (index >= GetSize() || !in.good()) {
      return false;
    }
    const FileRecord &file = fileList->at(index);
    if (file.size == 0) {
      result.clear();
      return true;
    }
    result.resize(file.size);
    in.seekg(file.address);
    in.read(reinterpret_cast<char *>(&result.front()), result.size());
    if (!in.good()) {
      return false;
    }
    if (file.crypt) {
      BOOST_FOREACH(unsigned char &c, result) {
        c ^= 0x08;
      }
    }
    if (!file.compress) {
      return true;
    }
    std::vector<unsigned char> temp(file.origSize);
    unsigned long origSize = file.origSize;
    if (Z_OK != ::uncompress(&temp.front(), &origSize, &result.front(), result.size())) {
      return false;
    }
    std::swap(result, temp);
    if (!reinterpret_cast<Header *>(&result.front())->IsValid()) {
      return true;
    }
    std::vector<unsigned char> sourceImpl;
    std::swap(sourceImpl, result);
    boost::iostreams::array_source source(reinterpret_cast<char *>(&sourceImpl.front()), sourceImpl.size());
    boost::iostreams::stream<boost::iostreams::array_source> stream(source);
    boost::shared_ptr<Owner> owner = Owner::Open(stream, sourceImpl.size());
    if (!owner) {
      return false;
    }
    if (owner->GetSize() <= file.index) {
      return false;
    }
    return owner->Extract(file.index, result);
  }
  unsigned int GetSize() const {
    return fileList->size();
  }
  std::wstring GetName() const {
    return L"不思議の幻想郷2";
  }
  boost::filesystem::path GetFileName(const unsigned int index) const {
    return this->fileList->at(index).path;
  }
};

ADD_DAT_EXTRACTOR(Owner);

} // NFA0_V2