他人の空似自作物置場

thxxbgmTh155Patch.zip/src/th135.h


namespace TouhouSE {

namespace TH135 {

extern const unsigned char KEY[64];
extern const unsigned char PUBLIC_EXPONENT[3];
extern const unsigned char SIGNATURE_DATA[32];
extern const unsigned int MAX_DECRYPT_SIZE;

extern const unsigned int BLOCK_SIZE;

namespace {

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() {
    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());
    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) {
    static std::vector<unsigned char> keyData(sizeof(BCRYPT_RSAKEY_BLOB) + sizeof(PUBLIC_EXPONENT) + sizeof(KEY));
    BCRYPT_RSAKEY_BLOB &keyInfo = *reinterpret_cast<BCRYPT_RSAKEY_BLOB *>(&keyData.front());
    keyInfo.Magic = BCRYPT_RSAPUBLIC_MAGIC;
    keyInfo.BitLength = sizeof(KEY) * 8;
    keyInfo.cbPublicExp = sizeof(PUBLIC_EXPONENT);
    keyInfo.cbModulus = sizeof(KEY);
    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 < sizeof(KEY); 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) {
    if (in == nullptr || out == nullptr || inSize != _countof(KEY) || outSize > MAX_DECRYPT_SIZE) {
      return false;
    }
    std::vector<unsigned char> buf(_countof(KEY));
    ULONG size = 0;
    if (!NT_SUCCESS(::BCryptEncrypt(key.get(), const_cast<unsigned char *>(in), inSize, NULL, NULL, 0, &buf.front(), buf.size(), &size, BCRYPT_PAD_NONE))) {
      return false;
    }
    if (!std::equal(&SIGNATURE_DATA[0], &SIGNATURE_DATA[_countof(SIGNATURE_DATA)], buf.begin())) {
      return false;
    }
    const std::vector<unsigned char>::const_iterator start = buf.begin() + _countof(SIGNATURE_DATA);
    const std::vector<unsigned char>::const_iterator end = start + outSize;
    std::copy(start, end, stdext::checked_array_iterator<unsigned char *>(out, outSize));
    return true;
  }
};

class FileNameList : public boost::noncopyable {
public:
  static bool read(std::istream &in, DecryptEngine &engine) {
    unsigned char buf[_countof(KEY)];
    in.read(reinterpret_cast<char *>(buf), sizeof(buf));
    if (!in.good()) {
      return false;
    }
    struct {
      unsigned int compSize;
      unsigned int origSize;
      unsigned int blockCount;
    } info;
    if (!engine.decrypt(buf, _countof(buf), reinterpret_cast<unsigned char *>(&info), sizeof(info))) {
      return{};
    }
    if (info.compSize == 0 && info.origSize == 0 && info.blockCount == 0) {
      return true;
    }
    if ((info.compSize + (MAX_DECRYPT_SIZE - 1)) / MAX_DECRYPT_SIZE != info.blockCount) {
      return false;
    }
    in.seekg(_countof(KEY) * info.blockCount, std::ios::cur);
    return true;
  }
};

class FileInfo : public boost::noncopyable {
public:
  const unsigned int size;
  const unsigned int address;
  const unsigned int hash;
  const std::array<unsigned char, 16> key;
  FileInfo(const unsigned int size, const unsigned int address, const unsigned int hash, const std::array<unsigned char, 16> &key) :
    size(size), address(address), hash(hash), key(key)
  {
  }
  static std::shared_ptr<FileInfo> read(std::istream &in, DecryptEngine &engine) {
    unsigned char buf[_countof(KEY)];
    in.read(reinterpret_cast<char *>(buf), sizeof(buf));
    if (!in.good()) {
      return{};
    }
    struct {
      unsigned int size;
      unsigned int address;
    } info;
    if (!engine.decrypt(buf, _countof(buf), reinterpret_cast<unsigned char *>(&info), sizeof(info))) {
      return{};
    }
    in.read(reinterpret_cast<char *>(buf), sizeof(buf));
    if (!in.good()) {
      return{};
    }
    unsigned int hash;
    if (!engine.decrypt(buf, _countof(buf), reinterpret_cast<unsigned char *>(&hash), sizeof(hash))) {
      return{};
    }
    in.read(reinterpret_cast<char *>(buf), sizeof(buf));
    if (!in.good()) {
      return{};
    }
    std::array<unsigned char, 16> key;
    if (!engine.decrypt(buf, _countof(buf), reinterpret_cast<unsigned char *>(&key.front()), key.size())) {
      return{};
    }
    return std::make_shared<FileInfo>(info.size, info.address, hash, key);
  }
  static std::unique_ptr<std::vector<std::shared_ptr<FileInfo> > > readAll(std::istream &in, DecryptEngine &engine) {
    unsigned char buf[_countof(KEY)];
    in.read(reinterpret_cast<char *>(buf), sizeof(buf));
    if (!in.good()) {
      return{};
    }
    unsigned int count;
    if (!engine.decrypt(buf, _countof(buf), reinterpret_cast<unsigned char *>(&count), sizeof(count))) {
      return{};
    }
    std::unique_ptr<std::vector<std::shared_ptr<FileInfo> > > result = std::make_unique<std::vector<std::shared_ptr<FileInfo> > >();
    for (unsigned int i = 0; i < count; i++) {
      const std::shared_ptr<FileInfo> item = read(in, engine);
      if (!item) {
        return{};
      }
      result->push_back(item);
    }
    return std::move(result);
  }
};

} // anonymous

template<typename T>
class Th135ArchiveExtractorBase {
public:
  const std::unique_ptr<std::istream> in;
  const std::map<unsigned int, std::shared_ptr<FileInfo> > hashToFileInfoMap;
  const unsigned int headerSize;

public:
  Th135ArchiveExtractorBase(
    std::unique_ptr<std::istream> in,
    std::map<unsigned int, std::shared_ptr<FileInfo> > hashToFileInfoMap,
    const unsigned int headerSize)
    :
    in(std::move(in)), hashToFileInfoMap(std::move(hashToFileInfoMap)), headerSize(headerSize)
  {
  }

  template<unsigned int KEY_SIZE>
  bool blockDecrypt(std::vector<unsigned char> &data, const std::array<unsigned char, KEY_SIZE> &key) {
    for (unsigned int i = 0; i < data.size(); i++) {
      data[i] = data[i] ^ key[i % KEY_SIZE];
    }
    return true;
  }

  static std::wstring strToWStr(const std::string &in) {
    if (in.empty()) {
      return{};
    }
    const unsigned int size = ::MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, &in.front(), in.size(), NULL, 0);
    if (size == 0) {
      return{};
    }
    std::wstring temp;
    temp.resize(size);
    ::MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, &in.front(), in.size(), &temp.front(), temp.size());
    return temp;
  }

  static std::string wStrToStr(const std::wstring &in) {
    if (in.empty()) {
      return{};
    }
    const unsigned int size = ::WideCharToMultiByte(CP_ACP, 0, &in.front(), in.size(), NULL, 0, NULL, NULL);
    if (size == 0) {
      return{};
    }
    std::string temp;
    temp.resize(size);
    ::WideCharToMultiByte(CP_ACP, 0, &in.front(), in.size(), &temp.front(), temp.size(), NULL, NULL);
    return temp;
  }

  static std::wstring normalize(const std::wstring &in) {
    std::wstring temp;
    temp.resize(in.size());
    std::locale loc;
    std::transform(in.begin(), in.end(), temp.begin(), [&loc](const wchar_t c) {
      if (c == L'/') {
        return L'\\';
      }
      // ascii範囲外文字ならそのまま返す
      if (c > 0xFF) {
        return c;
      }
      return std::tolower(c, loc);
    });
    return temp;
  }

  static boost::shared_ptr<T> Open(std::unique_ptr<std::istream> inPtr, const unsigned long long int file_size) {
    std::istream &in = *inPtr;
    if (!in.good()) {
      return{};
    }
    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 != 0) {
      return{};
    }
    unsigned char buf[_countof(KEY)];
    in.read(reinterpret_cast<char *>(buf), sizeof(buf));
    if (!in.good()) {
      return{};
    }
    const std::unique_ptr<DecryptEngine> engine = DecryptEngine::create();
    if (!engine) {
      return{};
    }
    unsigned int dirCount;
    if (!engine->decrypt(buf, _countof(buf), reinterpret_cast<unsigned char *>(&dirCount), sizeof(dirCount))) {
      return{};
    }
    in.seekg(_countof(KEY) * dirCount, std::ios::cur);
    if (!FileNameList::read(in, *engine)) {
      return{};
    }
    std::unique_ptr<std::vector<std::shared_ptr<FileInfo> > > fileInfoList = FileInfo::readAll(in, *engine);
    if (!fileInfoList) {
      return{};
    }
    const std::map<unsigned int, std::shared_ptr<FileInfo> > hashToFileInfoMap = buildPathList(*fileInfoList);
    if (hashToFileInfoMap.empty()) {
      return{};
    }
    return boost::make_shared<T>(std::move(inPtr), std::move(hashToFileInfoMap), static_cast<unsigned int>(in.tellg()));
  }
  static std::map<unsigned int, std::shared_ptr<FileInfo> > buildPathList(const std::vector<std::shared_ptr<FileInfo> > &fileInfoList) {
    std::map<unsigned int, std::shared_ptr<FileInfo> > hashToFileInfoMap;
    for (const std::shared_ptr<FileInfo> &item : fileInfoList) {
      hashToFileInfoMap.insert({ item->hash, item });
    }
    return std::move(hashToFileInfoMap);
  }

  bool Extract(const unsigned int index, std::vector<unsigned char> &result) {
    const FileInfo &info = *fileInfoList->at(index);
    result.resize(info.size);
    if (result.empty()) {
      return true;
    }
    in->seekg(headerSize + info.address);
    in->read(reinterpret_cast<char *>(&result.front()), result.size());
    if (!in->good()) {
      return false;
    }
    if (!blockDecrypt(result, info.key)) {
      return false;
    }
    return true;
  }

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

  const FileInfo *Find(const boost::filesystem::path &path) const {
    const auto it = hashToFileInfoMap.find(T::calcHash(path.wstring()));
    if (it == hashToFileInfoMap.end()) {
      return nullptr;
    }
    return &*it->second;
  }

  unsigned int FindIndex(const boost::filesystem::path &path) const {
    const auto it = hashToFileInfoMap.find(T::calcHash(path.wstring()));
    if (it == hashToFileInfoMap.end()) {
      return UINT_MAX;
    }
    return std::distance(fileInfoList->begin(), std::find(fileInfoList->begin(), fileInfoList->end(), it->second));
  }
};

class Th135Extracter : public Th135ArchiveExtractorBase<Th135Extracter> {
public:
  Th135Extracter(
    std::unique_ptr<std::istream> in,
    std::map<unsigned int, std::shared_ptr<FileInfo> > hashToFileInfoMap,
    const unsigned int headerSize)
    :
    Th135ArchiveExtractorBase<Th135Extracter>(std::move(in), std::move(hashToFileInfoMap), headerSize)
  {}

  static unsigned int calcHash(const std::wstring &in, const unsigned int init = 0x811C9DC5) {
    const std::string temp = wStrToStr(normalize(in));
    // FNV1
    return std::accumulate(temp.begin(), temp.end(), init, [](const unsigned int cur, const char c) {
      return (cur * 0x1000193) ^ c;
    });
  }

  static unsigned int calcHash(const std::string &in, const unsigned int init = 0x811C9DC5) {
    return calcHash(strToWStr(in), init);
  }
};

} // TH135

} // TouhouSE