他人の空似自作物置場

touhouSE_th145BGMOnly.zip/touhouSE_src/th145.cpp

#include "stdafx.h"

namespace TouhouSE {

namespace TH145 {

extern const std::array<std::wstring, 40000> FILE_NAME_LIST;

namespace {

const unsigned char PUBLIC_EXPONENT[3] = {
  0x01, 0x00, 0x01,
};

const unsigned int MAX_DECRYPT_SIZE = 32;
const unsigned int BUF_SIZE = 64;

struct closeAlg {
  static void close(const BCRYPT_ALG_HANDLE handle) {
    ::BCryptCloseAlgorithmProvider(handle, 0);
  }
};

class DecryptEngine : public boost::noncopyable {
private:
  const std::unique_ptr<std::remove_pointer<BCRYPT_ALG_HANDLE>::type, decltype(&closeAlg::close)> alg;
  const std::unique_ptr<std::remove_pointer<BCRYPT_KEY_HANDLE>::type, decltype(&::BCryptDestroyKey)> key;

public:
  DecryptEngine(std::unique_ptr<std::remove_pointer<BCRYPT_ALG_HANDLE>::type, decltype(&closeAlg::close)> alg,
    std::unique_ptr<std::remove_pointer<BCRYPT_KEY_HANDLE>::type, decltype(&::BCryptDestroyKey)> key)
    : alg(std::move(alg)), key(std::move(key))
  {
  }

  static std::unique_ptr<DecryptEngine> create(const std::array<unsigned char, 64> &keySrc) {
    BCRYPT_ALG_HANDLE algRaw = NULL;
    if (!NT_SUCCESS(::BCryptOpenAlgorithmProvider(&algRaw, BCRYPT_RSA_ALGORITHM, NULL, 0))) {
      return{};
    }
    std::unique_ptr<std::remove_pointer<BCRYPT_ALG_HANDLE>::type, decltype(&closeAlg::close)> alg(algRaw, &closeAlg::close);
    auto key = importRsaPublicKey(alg.get(), keySrc);
    if (!key) {
      return{};
    }
    return std::make_unique<DecryptEngine>(std::move(alg), std::move(key));
  }

  static std::unique_ptr<std::remove_pointer<BCRYPT_KEY_HANDLE>::type, decltype(&::BCryptDestroyKey)> importRsaPublicKey(BCRYPT_ALG_HANDLE alg, const std::array<unsigned char, 64> &key) {
    static std::vector<unsigned char> keyData(sizeof(BCRYPT_RSAKEY_BLOB) + sizeof(PUBLIC_EXPONENT) + key.size());
    BCRYPT_RSAKEY_BLOB &keyInfo = *reinterpret_cast<BCRYPT_RSAKEY_BLOB *>(&keyData.front());
    keyInfo.Magic = BCRYPT_RSAPUBLIC_MAGIC;
    keyInfo.BitLength = key.size() * 8;
    keyInfo.cbPublicExp = sizeof(PUBLIC_EXPONENT);
    keyInfo.cbModulus = key.size();
    keyInfo.cbPrime1 = 0;
    keyInfo.cbPrime2 = 0;
    unsigned char *ptr = &keyData[sizeof(BCRYPT_RSAKEY_BLOB)];
    for (unsigned int i = 0; i < sizeof(PUBLIC_EXPONENT); i++) {
      ptr[i] = PUBLIC_EXPONENT[i];
    }
    ptr = &keyData[sizeof(BCRYPT_RSAKEY_BLOB) + sizeof(PUBLIC_EXPONENT)];
    for (unsigned int i = 0; i < key.size(); i++) {
      ptr[i] = key[i];
    }
    BCRYPT_KEY_HANDLE keyRaw = NULL;
    if (!NT_SUCCESS(::BCryptImportKeyPair(alg, NULL, BCRYPT_RSAPUBLIC_BLOB, &keyRaw, &keyData.front(), keyData.size(), 0))) {
      return{ NULL, ::BCryptDestroyKey };
    }
    return{ keyRaw, ::BCryptDestroyKey };
  }

  bool decrypt(const unsigned char * const in, const unsigned int inSize, unsigned char * const out, const unsigned int outSize) {
    const unsigned int bufSize = BUF_SIZE;
    if (in == nullptr || out == nullptr || inSize != bufSize || outSize > MAX_DECRYPT_SIZE) {
      return false;
    }
    std::vector<unsigned char> buf(bufSize);
    ULONG size = 0;
    if (!NT_SUCCESS(::BCryptEncrypt(key.get(), const_cast<unsigned char *>(in), bufSize, NULL, NULL, 0, &buf.front(), buf.size(), &size, BCRYPT_PAD_NONE))) {
      return false;
    }
    unsigned char * const signatureEndIt = &buf[bufSize - outSize];
    if (buf[0] != 0x00 || buf[1] != 0x01 || signatureEndIt[-1] != 0x00 || std::find_if(&buf[2], signatureEndIt - 1, [](const unsigned char a) {return a != 0xFF; }) != signatureEndIt - 1) {
      return false;
    }
    const std::vector<unsigned char>::const_iterator start = buf.end() - outSize;
    const std::vector<unsigned char>::const_iterator end = buf.end();
    std::copy(start, end, out);
    return true;
  }
};

class FolderInfo {
public:
  unsigned int hash;
  unsigned int fileCount;
  std::vector<std::string> fileNameList;

  FolderInfo(const unsigned int hash, const unsigned int fileCount) : hash(hash), fileCount(fileCount) {
  }

  static std::shared_ptr<std::vector<std::shared_ptr<FolderInfo> > > Read(std::istream &in, DecryptEngine &engine) {
    auto result = std::make_shared<std::vector<std::shared_ptr<FolderInfo> > >();
    std::vector<std::shared_ptr<FolderInfo> > &folderList = *result;
    std::array<unsigned char, BUF_SIZE> buf;
    in.read(reinterpret_cast<char *>(&buf[0]), buf.size());
    if (!in.good()) {
      return{};
    }
    unsigned int folderCount;
    if (!engine.decrypt(&buf[0], buf.size(), reinterpret_cast<unsigned char *>(&folderCount), sizeof(folderCount))) {
      return{};
    }
    if (folderCount == 0) {
      return result;
    }

    for (const unsigned int i : boost::irange(0U, folderCount)) {
      in.read(reinterpret_cast<char *>(&buf[0]), buf.size());
      if (!in.good()) {
        return{};
      }
      unsigned int info[2];
      if (!engine.decrypt(&buf[0], buf.size(), reinterpret_cast<unsigned char *>(&info[0]), sizeof(info))) {
        return{};
      }
      folderList.push_back(std::make_shared<FolderInfo>(info[0], info[1]));
    }
    const unsigned int fileCount = std::accumulate(folderList.begin(), folderList.end(), 0U, [](const unsigned int result, const std::shared_ptr<const FolderInfo> folder) {
      return result + folder->fileCount;
    });
    if (fileCount == 0) {
      return result;
    }

    struct {
      unsigned int compressSize;
      unsigned int size;
      unsigned int blockCount;
    } fileNameListInfo;
    in.read(reinterpret_cast<char *>(&buf[0]), buf.size());
    if (!in.good()) {
      return{};
    }
    if (!engine.decrypt(&buf[0], buf.size(), reinterpret_cast<unsigned char *>(&fileNameListInfo), sizeof(fileNameListInfo))) {
      return{};
    }
    std::vector<unsigned char> compressData(fileNameListInfo.compressSize);
    for (const unsigned int i : boost::irange(0U, fileNameListInfo.blockCount)) {
      in.read(reinterpret_cast<char *>(&buf[0]), buf.size());
      if (!in.good()) {
        return{};
      }
      std::array<unsigned char, 32> temp;
      if (!engine.decrypt(&buf[0], buf.size(), reinterpret_cast<unsigned char *>(&temp[0]), temp.size())) {
        return{};
      }
      const unsigned int copySize = std::min(temp.size(), compressData.size() - i * temp.size());
      std::copy(temp.begin(), temp.begin() + copySize, compressData.begin() + i * temp.size());
    }
    std::vector<unsigned char> outData(fileNameListInfo.size);
    uLongf outSize = outData.size();
    ::uncompress(&outData[0], &outSize, &compressData[0], compressData.size());

    std::vector<std::string> fileNameList;
    for (std::vector<unsigned char>::const_iterator it = outData.begin();;) {
      const std::vector<unsigned char>::const_iterator findIt = std::find(it, const_cast<const std::vector<unsigned char> *>(&outData)->end(), '\0');
      if (findIt == outData.end()) {
        break;
      }
      fileNameList.push_back(std::string(it, findIt));
      it = findIt + 1;
    }
    if (fileNameList.size() != fileCount) {
      return{};
    }

    std::vector<std::string>::const_iterator it = fileNameList.begin();
    for (const std::shared_ptr<FolderInfo> folder : folderList) {
      for (const unsigned int i : boost::irange(0U, folder->fileCount)) {
        folder->fileNameList.push_back(*it);
        it++;
      }
    }
    return result;
  }
};

class FileInfo : boost::noncopyable {
public:
  unsigned int address;
  unsigned int size;
  unsigned int hash;
  unsigned int unknown;
  std::array<unsigned char, 16> key;

  FileInfo(const unsigned int address, const unsigned int size, const unsigned int hash, const unsigned int unknown, const std::array<unsigned char, 16> key)
    : address(address), size(size), hash(hash), unknown(unknown), key(key)
  {
  }
};

class FileInfoAndName : public boost::noncopyable {
public:
  std::shared_ptr<const FileInfo> info;
  std::wstring name;

  FileInfoAndName(const std::shared_ptr<const FileInfo> info, const std::wstring name) : info(info), name(name) {
  }
};

class Th145Impl : public ExtractorBase {
private:
  std::istream &in;
  const unsigned long long int fileSize;
  const std::vector<std::shared_ptr<FileInfoAndName> > fileList;

  Th145Impl(std::istream &in, const unsigned long long int fileSize, std::vector<std::shared_ptr<FileInfoAndName> > fileList) :
    in(in), fileSize(fileSize), fileList(std::move(fileList))
  {
  }

public:
  static boost::shared_ptr<Th145Impl> Open(std::istream &in, const unsigned long long int fileSize, const std::array<unsigned char, 64> &rsaKey) {
    unsigned char signature[4];
    in.read(reinterpret_cast<char *>(signature), sizeof(signature));
    if (!in.good() || !std::equal(&signature[0], &signature[_countof(signature)], "TFPK")) {
      return{};
    }
    unsigned char version;
    in.read(reinterpret_cast<char *>(&version), sizeof(version));
    if (!in.good() || version != 1) {
      return{};
    }
    const std::unique_ptr<DecryptEngine> engine = DecryptEngine::create(rsaKey);
    if (!engine) {
      return{};
    }
    const std::shared_ptr<std::vector<std::shared_ptr<FolderInfo> > > folderList = FolderInfo::Read(in, *engine);
    if (!folderList) {
      return{};
    }
    std::array<unsigned char, BUF_SIZE> buf;
    in.read(reinterpret_cast<char *>(&buf[0]), buf.size());
    if (!in.good()) {
      return{};
    }
    unsigned int fileCount;
    if (!engine->decrypt(&buf[0], buf.size(), reinterpret_cast<unsigned char *>(&fileCount), sizeof(fileCount))) {
      return{};
    }
    const unsigned int baseAddress = static_cast<unsigned int>(in.tellg()) + fileCount * (3 * BUF_SIZE);
    std::vector<std::shared_ptr<const FileInfo> > fileInfoList(fileCount);
    for (std::shared_ptr<const FileInfo> &ptr : fileInfoList) {
      in.read(reinterpret_cast<char *>(&buf[0]), buf.size());
      if (!in.good()) {
        return{};
      }
      unsigned int sizeAndAddress[2];
      if (!engine->decrypt(&buf[0], buf.size(), reinterpret_cast<unsigned char *>(&sizeAndAddress), sizeof(sizeAndAddress))) {
        return{};
      }
      in.read(reinterpret_cast<char *>(&buf[0]), buf.size());
      if (!in.good()) {
        return{};
      }
      unsigned int unknownList[2];
      if (!engine->decrypt(&buf[0], buf.size(), reinterpret_cast<unsigned char *>(&unknownList), sizeof(unknownList))) {
        return{};
      }
      in.read(reinterpret_cast<char *>(&buf[0]), buf.size());
      if (!in.good()) {
        return{};
      }
      std::array<unsigned char, 16> key;
      if (!engine->decrypt(&buf[0], buf.size(), &key[0], key.size())) {
        return{};
      }
      const unsigned int size = sizeAndAddress[0] ^ *reinterpret_cast<const unsigned int *>(&key[0]);
      const unsigned int address = baseAddress + (sizeAndAddress[1] ^ *reinterpret_cast<const unsigned int *>(&key[4]));
      const unsigned int hash = (~(unknownList[0] ^ *reinterpret_cast<const unsigned int *>(&key[8]))) + 1;
      const unsigned int unknown3 = unknownList[1] ^ *reinterpret_cast<const unsigned int *>(&key[12]);
      *reinterpret_cast<unsigned int *>(&key[0]) = (~*reinterpret_cast<const unsigned int *>(&key[0])) + 1;
      *reinterpret_cast<unsigned int *>(&key[4]) = (~*reinterpret_cast<const unsigned int *>(&key[4])) + 1;
      *reinterpret_cast<unsigned int *>(&key[8]) = (~*reinterpret_cast<const unsigned int *>(&key[8])) + 1;
      *reinterpret_cast<unsigned int *>(&key[12]) = (~*reinterpret_cast<const unsigned int *>(&key[12])) + 1;
      ptr.reset(new FileInfo(address, size, hash, unknown3, key));
    }
    std::map<unsigned int, std::wstring> fileNameHash;
    for (const std::wstring &fileName : FILE_NAME_LIST) {
      if (fileName.empty()) {
        continue;
      }
      const unsigned int hash = TH135::calcFNV1aHash2(fileName);
      if (fileNameHash.find(hash) != fileNameHash.end()) {
        continue;
      }
      fileNameHash[hash] = fileName;
    }
    // 追加のファイルリストをファイルから読み込むデバッグ機能
    if (false) {
      boost::filesystem::ifstream ifs(L"C:/Users/sweetie/Desktop/temp2.txt");
      while (true) {
        char lineBuf[256];
        ifs.getline(lineBuf, _countof(lineBuf));
        if (!ifs.good()) {
          break;
        }
        const std::wstring fileName = Utility::strToWStr(std::string(lineBuf));
        if (fileName.empty()) {
          continue;
        }
        const unsigned int hash = TH135::calcFNV1aHash2(fileName);
        if (fileNameHash.find(hash) != fileNameHash.end()) {
          continue;
        }
        fileNameHash[hash] = fileName;
      }
      ifs.close();
    }
    std::vector<std::shared_ptr<FileInfoAndName> > fileList(fileInfoList.size());
    for (const unsigned int i : boost::irange(0U, fileList.size())) {
      std::shared_ptr<FileInfoAndName> &out = fileList[i];
      const std::shared_ptr<const FileInfo> &info = fileInfoList[i];
      const auto it = fileNameHash.find(info->hash);
      std::wstring fileName;
      if (it == fileNameHash.end()) {
        fileName = (boost::wformat(L"data/hash_%08X") % info->hash).str();
      } else {
        fileName = it->second;
      }
      out.reset(new FileInfoAndName(info, fileName));
    }
    // 出力ディレクトリを判明している範囲で考慮する
    if (true) {
      boost::filesystem::path prevPath = L"data/";
      std::vector<std::shared_ptr<FileInfoAndName> > targetList;
      for (const auto file : fileList) {
        const auto it = fileNameHash.find(file->info->hash);
        if (it == fileNameHash.end()) {
          targetList.push_back(file);
          continue;
        }
        if (!targetList.empty()) {
          boost::filesystem::path p = prevPath;
          boost::filesystem::path n = file->name;
          boost::filesystem::path outDir;
          while (true) {
            if (p == n) {
              outDir = p;
              break;
            }
            if (p.wstring().size() > n.wstring().size()) {
              p = p.parent_path();
            } else {
              n = n.parent_path();
            }
          }
          for (const auto target : targetList) {
            const boost::filesystem::path path = outDir / (boost::wformat(L"hash_%08X") % target->info->hash).str();
            target->name = path.wstring();
          }
          targetList.clear();
        }
        prevPath = file->name;
      }
    }
    // 全ファイル名をhashと共に一覧表示
    if (false) {
      for (const std::shared_ptr<const FileInfoAndName> &file : fileList) {
        std::wcout << boost::wformat(L"%08X\t%s\n") % file->info->hash % file->name;
      }
      std::wcin.get();
    }
    // searchFNV1Hash用情報出力
    if (false) {
      std::wstring prevPath = L"data/";
      std::vector<unsigned int> targetList;
      std::vector<std::wstring> resultList;
      std::vector<unsigned int> countList;
      for (const auto file : fileList) {
        const auto it = fileNameHash.find(file->info->hash);
        if (it == fileNameHash.end()) {
          targetList.push_back(file->info->hash);
          continue;
        }
        if (!targetList.empty()) {
          targetList.erase(std::unique(targetList.begin(), targetList.end()), targetList.end());
          std::vector<std::wstring> targetListW(targetList.size());
          std::transform(targetList.begin(), targetList.end(), targetListW.begin(), [](const unsigned int hash) {return (boost::wformat(L"0x%08x") % hash).str(); });
          const std::wstring target = boost::algorithm::join(targetListW, L", ");
          resultList.push_back((boost::wformat(L"{ L\"%s\", L\"%s\", {%s} },\n") % prevPath % file->name % target).str());
          countList.push_back(targetList.size());
          targetList.clear();
        }
        prevPath = file->name;
      }
      std::sort(countList.begin(), countList.end());
      for (const auto &item : countList) {
        std::wcout << item << std::endl;
      }
      std::sort(resultList.begin(), resultList.end(), [](const auto &a, const auto &b) {
        return a.size() > b.size();
      });
      for (const auto &item : resultList) {
        std::wcout << item;
      }
      std::cin.get();
    }
    // ファイル名が不明な物は出力しない
    if (true) {
      for (unsigned int i = 0; i < fileList.size(); i++) {
        const auto file = fileList[i];
        const auto it = fileNameHash.find(file->info->hash);
        if (it == fileNameHash.end()) {
          fileList.erase(fileList.begin() + i);
          i--;
          continue;
        }
      }
    }
    return boost::shared_ptr<Th145Impl>(new Th145Impl(in, fileSize, std::move(fileList)));
  }

  void Decrypt(std::vector<unsigned char> &data, const FileInfo &info) {
    const unsigned int dataSize = data.size();
    data.resize((data.size() + 3) / 4 * 4);
    unsigned int key2 = *reinterpret_cast<const unsigned int *>(&info.key[0]);
    for (unsigned int i = 0; i < data.size(); i += 4) {
      const unsigned int temp = *reinterpret_cast<unsigned int *>(&data[i]);
      *reinterpret_cast<unsigned int *>(&data[i]) = (*reinterpret_cast<const unsigned int *>(&data[i]) ^ *reinterpret_cast<const unsigned int *>(&info.key[i & 0x0F])) ^ key2;
      key2 = temp;
    }
    data.resize(dataSize);
  }

  bool Extract(const unsigned int index, std::vector<unsigned char> &result) {
    const FileInfo &item = *fileList[index]->info;
    result.resize(item.size);
    if (result.empty()) {
      return true;
    }
    in.seekg(item.address);
    in.read(reinterpret_cast<char *>(&result.front()), result.size());
    if (!in.good()) {
      return false;
    }
    Decrypt(result, item);
    return true;
  }

  unsigned int GetSize() const {
    return fileList.size();
  }

  std::wstring GetName() const {
    return L"東方深秘録";
  }

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

// 拡張子に.matをつけるためだけの存在
class Th145MatConvertor : public ConverterBase {
public:
  static bool Convert(
    const boost::filesystem::path &path,
    const std::vector<unsigned char> &data,
    boost::filesystem::path &resultPath,
    std::vector<unsigned char> &result,
    ExtractorBase &extractor)
  {
    if (data.size() < 20) {
      return false;
    }
    const std::array<unsigned char, 16> header = {
      0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
    };
    if (!boost::equal(boost::make_iterator_range(data.begin(), data.begin() + 16), header)) {
      BOOST_ASSERT(path.extension() != L".mat");
      return false;
    }
    const unsigned int count = *reinterpret_cast<const unsigned int *>(&data[16]);
    if (count == 0 || data.size() < 16 + 4 + count) {
      return false;
    }
    const std::string fileName(data.begin() + 16 + 4, data.begin() + 16 + 4 + count);
    // デバッグ出力
    if (false) {
      std::cout << boost::format("%s: %s\n") % path.string() % fileName;
    }
    if (path.extension() == L".mat") {
      return false;
    }
    result = data;
    resultPath = path;
    resultPath.replace_extension(L".mat");
    return true;
  }
};

ADD_FILE_CONVERTER(Th145MatConvertor);

class Owner : public ExtractorBase {
private:
  boost::shared_ptr<ExtractorBase> ptr;

  Owner(boost::shared_ptr<ExtractorBase> ptr) : ptr(ptr) {
  }

public:
  static boost::shared_ptr<Owner> Open(std::istream &in, const unsigned long long int fileSize) {
    const std::array<unsigned char, 64> rsaKey = {
      0xC6, 0x43, 0xE0, 0x9D, 0x35, 0x5E, 0x98, 0x1D, 0xBE, 0x63, 0x6D, 0x3A, 0x5F, 0x84, 0x0F, 0x49,
      0xB8, 0xE8, 0x53, 0xF5, 0x42, 0x06, 0x37, 0x3B, 0x36, 0x25, 0xCB, 0x65, 0xCE, 0xDD, 0x68, 0x8C,
      0xF7, 0x5D, 0x72, 0x0A, 0xC0, 0x47, 0xBD, 0xFA, 0x3B, 0x10, 0x4C, 0xD2, 0x2C, 0xFE, 0x72, 0x03,
      0x10, 0x4D, 0xD8, 0x85, 0x15, 0x35, 0x55, 0xA3, 0x5A, 0xAF, 0xC3, 0x4A, 0x3B, 0xF3, 0xE2, 0x37,
    };
    boost::shared_ptr<ExtractorBase> ptr = OpenTh145(in, fileSize, rsaKey);
    if (!ptr) {
      return{};
    }
    return boost::shared_ptr<Owner>(new Owner(ptr));
  }

  bool Extract(const unsigned int index, std::vector<unsigned char> &result) override {
    return ptr->Extract(index, result);
  }

  unsigned int GetSize() const override {
    return ptr->GetSize();
  }

  std::wstring GetName() const override {
    return L"東方深秘録";
  }

  boost::filesystem::path GetFileName(unsigned int index) const override {
    return ptr->GetFileName(index);
  }
};

} // anonymouse

boost::shared_ptr<ExtractorBase> OpenTh145(std::istream &in, const unsigned long long int fileSize, const std::array<unsigned char, 64> &rsaKey) {
  return Th145Impl::Open(in, fileSize, rsaKey);
}

ADD_DAT_EXTRACTOR(Owner);

} // TH145

} // TouhouSE