他人の空似自作物置場

thxxbgmTh155Patch.zip/src/main.cpp


#include "stdafx.h"

// 東方憑依華を有効にする
#define ENABLE_TH155

// 東方深秘録を有効にする
#define ENABLE_TH145

// 東方心綺楼を有効にする
#define ENABLE_TH135

namespace {

constexpr unsigned int SIGNATURE_MASK = 0xFFFF0000;
constexpr unsigned int ARG_MASK = 0x0000FFFF;

constexpr unsigned int SIGNATURE_FIND_FILE_W = 0x00010000;
constexpr unsigned int SIGNATURE_NATIVE_FILE = 0x00020000;
constexpr unsigned int SIGNATURE_VIRTUAL_FILE = 0x00030000;

struct CloseHandleProc {
  void operator()(const HANDLE handle) const {
    ::CloseHandle(handle);
  }
};

class VirtualFileBase {
public:
  enum SeekType {
    BEGIN, CURRENT, END
  };
  virtual ~VirtualFileBase() {};
  virtual unsigned int tell() const = 0;
  virtual bool seek(unsigned int dis, const SeekType seekType) = 0;
  virtual unsigned int read(unsigned char * const buf, const unsigned int size) = 0;
};

typedef std::unique_ptr<std::remove_pointer<HANDLE>::type, CloseHandleProc> NativeFileHandle;
typedef std::unique_ptr<VirtualFileBase> VirtualFileHandle;

std::vector<std::pair<unsigned int, std::vector<WIN32_FIND_DATAW> > > findFileAryW;
std::vector<NativeFileHandle> nativeFileAry;
std::vector<VirtualFileHandle> virtualFileAry;

unsigned int getArg(const unsigned int handle) {
  return handle & ARG_MASK;
}

unsigned int getSignature(const unsigned int handle) {
  return handle & SIGNATURE_MASK;
}

unsigned int isNativeFileHandle(const unsigned int handle) {
  return getSignature(handle) == SIGNATURE_NATIVE_FILE;
}

unsigned int isVirtualFileHandle(const unsigned int handle) {
  return getSignature(handle) == SIGNATURE_VIRTUAL_FILE;
}

bool isKnownHandle(const unsigned int handle) {
  const unsigned int arg = getArg(handle);
  switch (getSignature(handle)) {
  case SIGNATURE_FIND_FILE_W:
    return arg < findFileAryW.size();
  case SIGNATURE_NATIVE_FILE:
    return arg < nativeFileAry.size();
  case SIGNATURE_VIRTUAL_FILE:
    return arg < virtualFileAry.size();
  default:
    return false;
  }
}

unsigned int newHandle(const unsigned int signature) {
  switch (signature) {
  case SIGNATURE_FIND_FILE_W:
    findFileAryW.resize(findFileAryW.size() + 1);
    return SIGNATURE_FIND_FILE_W + (findFileAryW.size() - 1);
  case SIGNATURE_NATIVE_FILE:
    nativeFileAry.resize(nativeFileAry.size() + 1);
    return SIGNATURE_NATIVE_FILE + (nativeFileAry.size() - 1);
  case SIGNATURE_VIRTUAL_FILE:
    virtualFileAry.resize(virtualFileAry.size() + 1);
    return SIGNATURE_VIRTUAL_FILE + (virtualFileAry.size() - 1);
  default:
    assert(false);
    return reinterpret_cast<unsigned int>(INVALID_HANDLE_VALUE);
  }
}

unsigned int newFindFileWHandle() {
  return newHandle(SIGNATURE_FIND_FILE_W);
}

unsigned int newNativeFileHandle() {
  return newHandle(SIGNATURE_NATIVE_FILE);
}

unsigned int newVirtualFileHandle() {
  return newHandle(SIGNATURE_VIRTUAL_FILE);
}

template<typename CONTAINER, typename IS_EMPTY_PROC, typename CLEAR_PROC>
void closeAnyHandle(const unsigned int arg, CONTAINER &container, const IS_EMPTY_PROC &isEmptyProc, const CLEAR_PROC &clearProc) {
  const unsigned int curSize = container.size();
  if (arg < curSize - 1) {
    clearProc(container[arg]);
    return;
  }
  const std::reverse_iterator<CONTAINER::iterator> begin(container.end());
  const std::reverse_iterator<CONTAINER::iterator> end(container.begin());
  const std::reverse_iterator<CONTAINER::iterator> it = std::find_if(
    begin + 1,
    end,
    [isEmptyProc](const auto &item) {
      return !isEmptyProc(item);
    }
  );
  container.resize(container.size() - std::distance(begin, it));
}

void closeFindFileW(const unsigned int arg) {
  closeAnyHandle(
    arg,
    findFileAryW,
    [](const std::pair<unsigned int, std::vector<WIN32_FIND_DATAW> > &item) { return item.second.empty(); },
    [](std::pair<unsigned int, std::vector<WIN32_FIND_DATAW> > &item) { item.second.clear(); });
}

void closeNativeFileHandle(const unsigned int arg) {
  closeAnyHandle(arg, nativeFileAry, [](const NativeFileHandle &item) { return !item; }, [](NativeFileHandle &item) { item.reset(); });
}

void closeVirtualFileHandle(const unsigned int arg) {
  closeAnyHandle(arg, virtualFileAry, [](const VirtualFileHandle &item) { return !item; }, [](VirtualFileHandle &item) { item.reset(); });
}

void closeHandle(const unsigned int handle) {
  const unsigned int arg = getArg(handle);
  switch (getSignature(handle)) {
  case SIGNATURE_FIND_FILE_W:
    closeFindFileW(arg);
    return;
  case SIGNATURE_NATIVE_FILE:
    closeNativeFileHandle(arg);
    return;
  case SIGNATURE_VIRTUAL_FILE:
    closeVirtualFileHandle(arg);
    return;
  default:
    assert(false);
    return;
  }
}

std::pair<unsigned int, std::vector<WIN32_FIND_DATAW> > &getFindFileAryW(const unsigned int handle) {
  assert(getSignature(handle) == SIGNATURE_FIND_FILE_W);
  return findFileAryW[getArg(handle)];
}

void hookFindFirstFileW(const wchar_t * const lpFileName, std::vector<WIN32_FIND_DATAW> &data);

HANDLE __stdcall d_FindFirstFileW(const wchar_t * const lpFileName, WIN32_FIND_DATAW * const lpFindFileData) {
  const unsigned int handle = newFindFileWHandle();
  std::pair<unsigned int, std::vector<WIN32_FIND_DATAW> > &data = getFindFileAryW(handle);
  hookFindFirstFileW(lpFileName, data.second);
  WIN32_FIND_DATAW item = { 0 };
  HANDLE win32handle = INVALID_HANDLE_VALUE;
  for (win32handle = ::FindFirstFileW(lpFileName, &item); win32handle != INVALID_HANDLE_VALUE; ) {
    data.second.push_back(item);
    if (::FindNextFileW(win32handle, &item) == FALSE) {
      break;
    }
  }
  ::FindClose(win32handle);
  if (data.second.empty()) {
    closeHandle(handle);
    return INVALID_HANDLE_VALUE;
  }
  data.first = 1;
  *lpFindFileData = data.second[0];
  return reinterpret_cast<HANDLE>(handle);
}

BOOL __stdcall d_FindNextFileW(const HANDLE hFindFile, WIN32_FIND_DATAW * const lpFindFileData) {
  const unsigned int handle = reinterpret_cast<unsigned int>(hFindFile);
  std::pair<unsigned int, std::vector<WIN32_FIND_DATAW> > &data = getFindFileAryW(handle);
  if (data.first >= data.second.size()) {
    ::SetLastError(ERROR_NO_MORE_FILES);
    return FALSE;
  }
  *lpFindFileData = data.second[data.first];
  data.first++;
  return TRUE;
}

BOOL __stdcall d_FindClose(HANDLE hFindFile) {
  closeHandle(reinterpret_cast<unsigned int>(hFindFile));
  return TRUE;
}

class VirtualFileOnMemory : public VirtualFileBase {
private:
  const std::vector<unsigned char> data;
  unsigned int pos;

public:
  VirtualFileOnMemory(std::vector<unsigned char> data) : data(std::move(data)), pos(0) {
  }
  ~VirtualFileOnMemory() override {
  }

  unsigned int tell() const override {
    return pos;
  }

  bool seek(unsigned int dis, const SeekType seekType) override {
    unsigned int ptr = 0;
    switch (seekType) {
    case BEGIN:
      ptr = dis;
      break;
    case CURRENT:
      ptr = pos + dis;
      break;
    case END:
      ptr = data.size() + dis;
      break;
    default:
      return false;
    }
    if (ptr < 0) {
      ::SetLastError(ERROR_NEGATIVE_SEEK);
      return false;
    }
    if (ptr > data.size()) {
      ptr = data.size();
    }
    pos = ptr;
    return true;
  }

  unsigned int read(unsigned char * const buf, const unsigned int size) override {
    unsigned int readSize = size;
    if (data.size() < pos + size) {
      readSize = data.size() - pos;
      ::SetLastError(ERROR_HANDLE_EOF);
    }
    std::copy(
      data.begin() + pos,
      data.begin() + pos + readSize,
      stdext::checked_array_iterator<unsigned char *>(buf, readSize));
    pos += readSize;
    return readSize;
  }

};

NativeFileHandle &getNativeFileHandle(const unsigned int handle) {
  assert(getSignature(handle) == SIGNATURE_NATIVE_FILE);
  return nativeFileAry[getArg(handle)];
}

VirtualFileHandle &getVirtualFileHandle(const unsigned int handle) {
  assert(getSignature(handle) == SIGNATURE_VIRTUAL_FILE);
  return virtualFileAry[getArg(handle)];
}

unsigned int hookCreateFileW(const wchar_t * const lpFileName, const DWORD dwDesiredAccess, const DWORD dwCreationDisposition);

HANDLE __stdcall d_CreateFileW(
  const wchar_t * const lpFileName,
  const DWORD dwDesiredAccess,
  const DWORD dwShareMode,
  SECURITY_ATTRIBUTES * const lpSecurityAttributes,
  const DWORD dwCreationDisposition,
  const DWORD dwFlagsAndAttributes,
  const HANDLE hTemplateFile)
{
  const unsigned int virtualHandle = hookCreateFileW(lpFileName, dwDesiredAccess, dwCreationDisposition);
  if (reinterpret_cast<HANDLE>(virtualHandle) != INVALID_HANDLE_VALUE) {
    return reinterpret_cast<HANDLE>(virtualHandle);
  }
  const HANDLE rawHandle = ::CreateFileW(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
  if (rawHandle == INVALID_HANDLE_VALUE) {
    return INVALID_HANDLE_VALUE;
  }
  const unsigned int result = newNativeFileHandle();
  NativeFileHandle &file = getNativeFileHandle(result);
  file.reset(rawHandle);
  return reinterpret_cast<HANDLE>(result);
}

BOOL __stdcall d_CloseHandle(const HANDLE rawHandle) {
  const unsigned int handle = reinterpret_cast<unsigned int>(rawHandle);
  if (isKnownHandle(handle)) {
    closeHandle(handle);
    return TRUE;
  }
  return ::CloseHandle(rawHandle);
}

BOOL __stdcall d_ReadFile(const HANDLE hFile, void * const lpBuffer, const DWORD nNumberOfBytesToRead, DWORD * const lpNumberOfBytesRead, OVERLAPPED * const lpOverlapped) {
  const unsigned int handle = reinterpret_cast<unsigned int>(hFile);
  if (isNativeFileHandle(handle)) {
    return ::ReadFile(getNativeFileHandle(handle).get(), lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, lpOverlapped);
  }
  assert(isVirtualFileHandle(handle));
  VirtualFileHandle &file = getVirtualFileHandle(handle);
  if (lpBuffer == nullptr) {
    return FALSE;
  }
  const unsigned int readSize = file->read(reinterpret_cast<unsigned char *>(lpBuffer), nNumberOfBytesToRead);
  if (lpNumberOfBytesRead != nullptr) {
    *lpNumberOfBytesRead = readSize;
  }
  return TRUE;
}

DWORD __stdcall d_SetFilePointer(const HANDLE hFile, const LONG lDistanceToMove, LONG * const lpDistanceToMoveHigh, const DWORD dwMoveMethod) {
  const unsigned int handle = reinterpret_cast<unsigned int>(hFile);
  if (isNativeFileHandle(handle)) {
    return ::SetFilePointer(getNativeFileHandle(handle).get(), lDistanceToMove, lpDistanceToMoveHigh, dwMoveMethod);
  }
  assert(isVirtualFileHandle(handle));
  VirtualFileHandle &file = getVirtualFileHandle(handle);
  long long int distance = 0;
  if (lpDistanceToMoveHigh == nullptr) {
    distance = lDistanceToMove;
  } else {
    distance = static_cast<unsigned int>(lDistanceToMove) | (static_cast<long long int>(*lpDistanceToMoveHigh) << 32);
  }
  unsigned long long int ptr = 0;
  VirtualFileBase::SeekType type;
  switch (dwMoveMethod) {
  case FILE_BEGIN:
    type = VirtualFileBase::BEGIN;
    break;
  case FILE_CURRENT:
    type = VirtualFileBase::CURRENT;
    break;
  case FILE_END:
    type = VirtualFileBase::END;
    break;
  default:
    return FALSE;
  }
  return file->seek(static_cast<unsigned int>(distance), type) ? TRUE : FALSE;
}

DWORD __stdcall d_GetFileType(HANDLE hFile) {
  const unsigned int handle = reinterpret_cast<unsigned int>(hFile);
  if (isNativeFileHandle(handle)) {
    return ::GetFileType(getNativeFileHandle(handle).get());
  }
  if (isVirtualFileHandle(handle)) {
    return FILE_TYPE_DISK;
  }
  return ::GetFileType(hFile);
}

BOOL __stdcall d_WriteFile(
  const HANDLE hFile,
  const void * const lpBuffer,
  const DWORD nNumberOfBytesToWrite,
  DWORD * const lpNumberOfBytesWritten,
  OVERLAPPED * const lpOverlapped)
{
  const unsigned int handle = reinterpret_cast<unsigned int>(hFile);
  if (isNativeFileHandle(handle)) {
    return ::WriteFile(getNativeFileHandle(handle).get(), lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
  }
  assert(isVirtualFileHandle(handle));
  assert(false);
  return FALSE;
}

bool hookPathFileExistsW(const wchar_t * const pszPath);

BOOL __stdcall d_PathFileExistsW(const wchar_t * const pszPath) {
  return ::PathFileExistsW(pszPath) || hookPathFileExistsW(pszPath);
}


// ダミー実装ここまで
// 独自機能ここから


const wchar_t LIST_DIR_NAME[] = L"list";

const wchar_t TH135_LIST_FILENAME[] = L"135.txt";
const wchar_t TH145_LIST_FILENAME[] = L"145.txt";
const wchar_t TH155_LIST_FILENAME[] = L"155.txt";

struct MusicInfo {
  const char * const path;
  const unsigned int headerSize;
  const unsigned int introSize;
  const unsigned int bodySize;
  const char * const name;
};

const MusicInfo th135MusicList[] = {
  { "data/bgm/reimu1.ogg", 0x00000000, 0x0004927c * 4, 0x00461595 * 4, "春色小径 〜 Colorful Path" },
  { "data/bgm/marisa1.ogg", 0x00000000, 0x0003ca1d * 4, 0x0050bfe0 * 4, "メイガスナイト" },
  { "data/bgm/ichirin1.ogg", 0x00000000, 0x000159cc * 4, 0x0046e14c * 4, "時代親父とハイカラ少女" },
  { "data/bgm/hijiri1.ogg", 0x00000000, 0x00098cee * 4, 0x00546b7c * 4, "感情の摩天楼 〜 Cosmic Mind" },
  { "data/bgm/futo1.ogg", 0x00000000, 0x00031a05 * 4, 0x005237bf * 4, "大神神話伝" },
  { "data/bgm/miko1.ogg", 0x00000000, 0x001999a3 * 4, 0x004fcf7e * 4, "聖徳伝説 〜 True Administrator" },
  { "data/bgm/nitori1.ogg", 0x00000000, 0x00074443 * 4, 0x004ae670 * 4, "芥川龍之介の河童 〜 Candid Friend" },
  { "data/bgm/koishi1.ogg", 0x00000000, 0x0003e4d4 * 4, 0x004f5719 * 4, "ハルトマンの妖怪少女" },
  { "data/bgm/mamizou1.ogg", 0x00000000, 0x000367dc * 4, 0x0050c0a8 * 4, "佐渡のニッ岩" },
  { "data/bgm/chuboss1.ogg", 0x00000000, 0x00097c77 * 4, 0x004c6c8c * 4, "幻想郷の二ッ岩" },
  { "data/bgm/kokoro1.ogg", 0x00000000, 0x0007b749 * 4, 0x00a8b328 * 4, "亡失のエモーション" },
  { "data/bgm/op.ogg", 0x00000000, 0x00000000 * 4, 0x001fd6b2 * 4, "塵界不変のペシミズム" },
  { "data/bgm/title.ogg", 0x00000000, 0x00030600 * 4, 0x00409982 * 4, "心綺楼囃子" },
  { "data/bgm/ed.ogg", 0x00000000, 0x00025519 * 4, 0x00307320 * 4, "暁雲" },
  { "data/bgm/staffroll.ogg", 0x00000000, 0x00000000 * 4, 0x003053f2 * 4, "官板黄昏新聞" },
  { "data/bgm/win.ogg", 0x00000000, 0x0006bcae * 4, 0x000e5af3 * 4, "本日の一面記事" },
  //{ "data/bgm/staffroll2.ogg", 0x00000000, 0x00000000 * 4, 0x0036162b * 4, "" },
  { "data/bgm/kaiwa1.ogg", 0x00000000, 0x0001d86e * 4, 0x002b1101 * 4, "人気のある場所" },
  { "data/bgm/kaiwa3.ogg", 0x00000000, 0x0001e847 * 4, 0x002b10fe * 4, "人気のない場所" },
  //{ "data/bgm/kaiwa4.ogg", 0x00000000, 0x00037aa4 * 4, 0x000e5b13 * 4, "" },
  { "data/bgm/kaiwa5.ogg", 0x00000000, 0x0006219a * 4, 0x002241e1 * 4, "丑三つ時の里" },
  { "data/bgm/charasel.ogg", 0x00000000, 0x0005027f * 4, 0x003126e4 * 4, "演者選択" },
  { "data/bgm/reimu2.ogg", 0x00000000, 0x00000000 * 4, 0x000e7742 * 4, "人気爆発/博麗霊夢" },
  { "data/bgm/marisa2.ogg", 0x00000000, 0x00000000 * 4, 0x000e7780 * 4, "人気爆発/霧雨魔理沙" },
  { "data/bgm/ichirin2.ogg", 0x00000000, 0x00000000 * 4, 0x000e779b * 4, "人気爆発/雲居一輪&雲山" },
  { "data/bgm/hijiri2.ogg", 0x00000000, 0x00000000 * 4, 0x000e778d * 4, "人気爆発/聖白蓮" },
  { "data/bgm/futo2.ogg", 0x00000000, 0x00000000 * 4, 0x000e778c * 4, "人気爆発/物部布都" },
  { "data/bgm/miko2.ogg", 0x00000000, 0x00000000 * 4, 0x000e778a * 4, "人気爆発/豊聡耳神子" },
  { "data/bgm/nitori2.ogg", 0x00000000, 0x00000000 * 4, 0x000e7797 * 4, "人気爆発/河城にとり" },
  { "data/bgm/koishi2.ogg", 0x00000000, 0x00000000 * 4, 0x000e7769 * 4, "人気爆発/古明地こいし" },
  { "data/bgm/mamizou2.ogg", 0x00000000, 0x00000000 * 4, 0x000e777b * 4, "人気爆発/二ッ岩マミゾウ" },
  { "data/bgm/kokoro2.ogg", 0x00000000, 0x00000000 * 4, 0x000e777f * 4, "人気爆発/秦こころ" },
  { "data/bgm/maxwaza.ogg", 0x00000000, 0x00029679 * 4, 0x001c2b1d * 4, "ラストワード発動" },
};

const MusicInfo th145MusicList[] = {
  // オープニング曲
  { "data/bgm/title.ogg", 0x00000000, 0x000395FD * 4, 0x003C8FE8 * 4, "心揺さぶる都市伝説" },
  { "data/bgm/select.ogg", 0x00000000, 0x0003F3C4 * 4, 0x001E6D6B * 4, "幻想郷ふしぎ発見" },

  // 各キャラテーマ曲
  { "data/bgm/reimu1.ogg", 0x00000000, 0x0003992E * 4, 0x0059B8C0 * 4, "二色蓮花蝶 〜 Red and White" },
  { "data/bgm/marisa1.ogg", 0x00000000, 0x000938DA * 4, 0x005EFFDB * 4, "恋色マスタースパーク" },
  { "data/bgm/kasen1.ogg", 0x00000000, 0x0003CCF5 * 4, 0x0062CCA8 * 4, "華狭間のバトルフィールド" },
  { "data/bgm/ichirin1.ogg", 0x00000000, 0x00026C37 * 4, 0x0051270B * 4, "時代親父とハイカラ少女" },
  { "data/bgm/hijiri1.ogg", 0x00000000, 0x000C92B6 * 4, 0x0057ED40 * 4, "感情の摩天楼 〜 Cosmic Mind" },
  { "data/bgm/futo1.ogg", 0x00000000, 0x00034F05 * 4, 0x005D2C0A * 4, "大神神話伝" },
  { "data/bgm/miko1.ogg", 0x00000000, 0x00103560 * 4, 0x005CB65F * 4, "聖徳伝説 〜 True Administrator" },
  { "data/bgm/nitori1.ogg", 0x00000000, 0x00013B8A * 4, 0x0058A479 * 4, "芥川龍之介の河童  〜 Candid Friend" },
  { "data/bgm/koishi1.ogg", 0x00000000, 0x00018BD3 * 4, 0x004FECEF * 4, "ハルトマンの妖怪少女" },
  { "data/bgm/mamizou1.ogg", 0x00000000, 0x00019D1E * 4, 0x004AD74C * 4, "幻想郷の二ッ岩" },
  { "data/bgm/kokoro1.ogg", 0x00000000, 0x00094025 * 4, 0x00975901 * 4, "亡失のエモーション" },
  { "data/bgm/mokou1.ogg", 0x00000000, 0x00071113 * 4, 0x006EBE00 * 4, "月まで届け、不死の煙" },
  { "data/bgm/sinmyoumaru1.ogg", 0x00000000, 0x000B5D99 * 4, 0x00595E72 * 4, "輝く針の小人族 〜 Little Princess" },
  { "data/bgm/usami1.ogg", 0x00000000, 0x000B88A5 * 4, 0x009A4D6F * 4, "ラストオカルティズム 〜 現し世の秘術師" },

  // 通常戦闘曲
  { "data/bgm/st01_1.ogg", 0x00000000, 0x0004CA93 * 4, 0x004B30D4 * 4, "七玉蒐集ショウダウン" },
  { "data/bgm/st02_1.ogg", 0x00000000, 0x00059209 * 4, 0x00435E3E * 4, "オカルトアラカルト" },
  { "data/bgm/st03_1.ogg", 0x00000000, 0x0007FEFF * 4, 0x003CCFE3 * 4, "公正なる奪い合い" },
  { "data/bgm/st04_1.ogg", 0x00000000, 0x0009CFD4 * 4, 0x00405D9A * 4, "対蹠地の鐘" },
  { "data/bgm/st05_1.ogg", 0x00000000, 0x0006BC35 * 4, 0x0040997C * 4, "竹林インフレイム" },

  // 各キャラテーマ曲、キャラセレクト時の20秒版
  /*
  { "data/bgm/reimu2.ogg", 0x00000000, 0x00000000 * 4, 0x000E5E9C * 4, "二色蓮花蝶 〜 Red and White" },
  { "data/bgm/marisa2.ogg", 0x00000000, 0x00000000 * 4, 0x000EAD74 * 4, "恋色マスタースパーク" },
  { "data/bgm/kasen1.ogg", 0x00000000, 0x0003CCF5 * 4, 0x0062CCA8 * 4, "華狭間のバトルフィールド" },
  { "data/bgm/ichirin2.ogg", 0x00000000, 0x00000000 * 4, 0x000D929A * 4, "時代親父とハイカラ少女" },
  { "data/bgm/hijiri2.ogg", 0x00000000, 0x00000000 * 4, 0x000DAFB6 * 4, "感情の摩天楼 〜 Cosmic Mind" },
  { "data/bgm/futo2.ogg", 0x00000000, 0x00000000 * 4, 0x000DF950 * 4, "大神神話伝" },
  { "data/bgm/miko2.ogg", 0x00000000, 0x00000000 * 4, 0x000FF2F5 * 4, "聖徳伝説 〜 True Administrator" },
  { "data/bgm/nitori2.ogg", 0x00000000, 0x00000000 * 4, 0x000EB0E3 * 4, "芥川龍之介の河童  〜 Candid Friend" },
  { "data/bgm/koishi2.ogg", 0x00000000, 0x00000000 * 4, 0x000F1C1B * 4, "ハルトマンの妖怪少女" },
  { "data/bgm/mamizou2.ogg", 0x00000000, 0x00000000 * 4, 0x000FC368 * 4, "幻想郷の二ッ岩" },
  { "data/bgm/kokoro2.ogg", 0x00000000, 0x00000000 * 4, 0x000EC32A * 4, "亡失のエモーション" },
  { "data/bgm/mokou2.ogg", 0x00000000, 0x00000000 * 4, 0x000DD890 * 4, "月まで届け、不死の煙" },
  { "data/bgm/sinmyoumaru2.ogg", 0x00000000, 0x00000000 * 4, 0x000E8470 * 4, "輝く針の小人族 〜 Little Princess" },
  { "data/bgm/usami2.ogg", 0x00000000, 0x00000000 * 4, 0x000F33E8 * 4, "ラストオカルティズム 〜 現し世の秘術師" },
  */

  // 通常戦闘曲、キャラセレクト時の20秒版
  /*
  { "data/bgm/st01_2.ogg", 0x00000000, 0x00000000 * 4, 0x000F826D * 4, "七玉蒐集ショウダウン" },
  { "data/bgm/st02_2.ogg", 0x00000000, 0x00000000 * 4, 0x000EC74A * 4, "オカルトアラカルト" },
  { "data/bgm/st03_2.ogg", 0x00000000, 0x00000000 * 4, 0x000DF578 * 4, "公正なる奪い合い" },
  { "data/bgm/st04_2.ogg", 0x00000000, 0x00000000 * 4, 0x000E098E * 4, "対蹠地の鐘" },
  { "data/bgm/st05_2.ogg", 0x00000000, 0x00000000 * 4, 0x000EAFF2 * 4, "竹林インフレイム" },
  */

  // ストーリー曲
  { "data/bgm/ta01.ogg", 0x00000000, 0x000564C4 * 4, 0x002B1100 * 4, "顕現した伝承の形" },
  { "data/bgm/ta02.ogg", 0x00000000, 0x0008D313 * 4, 0x00233C76 * 4, "ボールのある日常" },
  { "data/bgm/ta03.ogg", 0x00000000, 0x00016C4A * 4, 0x0026C280 * 4, "価値がわからない" },
  { "data/bgm/ta04.ogg", 0x00000000, 0x00033697 * 4, 0x00204CB6 * 4, "時代の風の訪れ" },
  { "data/bgm/ta05.ogg", 0x00000000, 0x00084278 * 4, 0x00278D01 * 4, "真実を知る者" },
  { "data/bgm/ta06.ogg", 0x00000000, 0x0004FE5C * 4, 0x002D6BF0 * 4, "可能性を信じて" },
  { "data/bgm/ta07.ogg", 0x00000000, 0x00025445 * 4, 0x0027389D * 4, "外界フォークロア" },

  // 特殊演出用曲
  /*
  { "data/bgm/ta13.ogg", 0x00000000, 0x00000000 * 4, 0x001274FF * 4, "SE" },
  { "data/bgm/ta14.ogg", 0x00000000, 0x0001EFE6 * 4, 0x00081330 * 4, "SE" },
  */

  // エンディング曲
  { "data/bgm/ed.ogg", 0x00000000, 0x0004994A * 4, 0x00260E57 * 4, "各々の結末" },
  { "data/bgm/staffroll.ogg", 0x00000000, 0x00000000 * 4, 0x00397B22 * 4, "明かされる深秘" },
};

const MusicInfo th155MusicList[] = {

  // 並び順はMusic Room準拠

  // 各キャラテーマ曲
  { "data/bgm/155reimu1.ogg", 0x00000000, 0x0003992E * 4, 0x0059B8C0 * 4, "二色蓮花蝶 〜 Red and White" },
  { "data/bgm/155marisa1.ogg", 0x00000000, 0x000938DA * 4, 0x005EFFDB * 4, "恋色マスタースパーク" },
  { "data/bgm/155ichirin1.ogg", 0x00000000, 0x00026C37 * 4, 0x0051270B * 4, "時代親父とハイカラ少女" },
  { "data/bgm/155hijiri1.ogg", 0x00000000, 0x000C92B6 * 4, 0x0057ED40 * 4, "感情の摩天楼 〜 Cosmic Mind" },
  { "data/bgm/155futo1.ogg", 0x00000000, 0x00034F05 * 4, 0x005D2C0A * 4, "大神神話伝" },
  { "data/bgm/155miko1.ogg", 0x00000000, 0x00103560 * 4, 0x005CB65F * 4, "聖徳伝説 〜 True Administrator" },
  { "data/bgm/155nitori1.ogg", 0x00000000, 0x00013B8A * 4, 0x0058A479 * 4, "芥川龍之介の河童  〜 Candid Friend" },
  { "data/bgm/155koishi1.ogg", 0x00000000, 0x00018BD3 * 4, 0x004FECEF * 4, "ハルトマンの妖怪少女" },
  { "data/bgm/155mamizou1.ogg", 0x00000000, 0x00019D1E * 4, 0x004AD74C * 4, "幻想郷の二ッ岩" },
  { "data/bgm/155kokoro1.ogg", 0x00000000, 0x00093D07 * 4, 0x009758BC * 4, "亡失のエモーション" },
  { "data/bgm/155mokou1.ogg", 0x00000000, 0x00071113 * 4, 0x006EBE00 * 4, "月まで届け、不死の煙" },
  { "data/bgm/155sinmyoumaru1.ogg", 0x00000000, 0x0003A481 * 4, 0x005B13F5 * 4, "輝く針の小人族 〜 Little Princess" },
  { "data/bgm/155kasen1.ogg", 0x00000000, 0x0008D9C1 * 4, 0x00565DEB * 4, "華狭間のバトルフィールド" },
  { "data/bgm/155usami1.ogg", 0x00000000, 0x0007A6A4 * 4, 0x00534CAD * 4, "ラストオカルティズム 〜 現し世の秘術師" },
  { "data/bgm/155udonge1.ogg", 0x00000000, 0x001758D1 * 4, 0x0050C003 * 4, "狂気の瞳 〜 Invisible Full Moon" },
  { "data/bgm/155doremy1.ogg", 0x00000000, 0x000D7575 * 4, 0x006DBC8C * 4, "永遠の春夢" },
  { "data/bgm/155tenshi1.ogg", 0x00000000, 0x0002866D * 4, 0x006D0497 * 4, "有頂天変 〜 Wonderful Heaven" },
  { "data/bgm/155yukari1.ogg", 0x00000000, 0x0003AA3E * 4, 0x00552FE9 * 4, "夜が降りてくる 〜 Evening Star" },

  // ストーリー戦闘曲
  { "data/bgm/bgst01.ogg", 0x00000000, 0x0005A9DA * 4, 0x003A51B5 * 4, "地の色は黄色 〜 Primrose" },
  { "data/bgm/bgst02.ogg", 0x00000000, 0x0006A2B3 * 4, 0x00412367 * 4, "マッシュルーム・ワルツ" },
  { "data/bgm/bgst03.ogg", 0x00000000, 0x00060EBE * 4, 0x0042321F * 4, "聖輦船空を往く" },
  { "data/bgm/bgst04.ogg", 0x00000000, 0x00044DED * 4, 0x003E65CA * 4, "法力の下の平等" },
  { "data/bgm/bgst05.ogg", 0x00000000, 0x0008325D * 4, 0x003C8FE4 * 4, "恒常不変の参廟祀" },
  { "data/bgm/bgst06.ogg", 0x00000000, 0x0007BAEF * 4, 0x00384FEA * 4, "光輝く天球儀" },
  { "data/bgm/bgst07.ogg", 0x00000000, 0x0002DC73 * 4, 0x003CDAD1 * 4, "沢の河童の技術力" },
  { "data/bgm/bgst08.ogg", 0x00000000, 0x00053C13 * 4, 0x003D369E * 4, "地底に咲く薔薇" },
  { "data/bgm/bgst09.ogg", 0x00000000, 0x000532A4 * 4, 0x003E9C35 * 4, "深緑の狸森にて" },
  { "data/bgm/bgst10.ogg", 0x00000000, 0x00026682 * 4, 0x003C4197 * 4, "心綺楼演舞" },
  { "data/bgm/bgst11.ogg", 0x00000000, 0x000A7C29 * 4, 0x0040997C * 4, "不滅のレッドソウル" },
  { "data/bgm/bgst12.ogg", 0x00000000, 0x00027AD2 * 4, 0x003D62C2 * 4, "落日に映える逆さ城" },
  { "data/bgm/bgst13.ogg", 0x00000000, 0x001181DD * 4, 0x003D1B91 * 4, "千の試練を超えて" },
  { "data/bgm/bgst14.ogg", 0x00000000, 0x00074896 * 4, 0x0043A7AA * 4, "夢世界フォークロア" },
  { "data/bgm/bgst15.ogg", 0x00000000, 0x000BC905 * 4, 0x003C8FE8 * 4, "永遠に続く回廊" },
  { "data/bgm/bgst16.ogg", 0x00000000, 0x00012E1F * 4, 0x003BAF3A * 4, "スリープシープ・パレード" },
  { "data/bgm/bgst17.ogg", 0x00000000, 0x0008C6D5 * 4, 0x003CDAD1 * 4, "至る有頂天" },
  // 中ボス曲
  { "data/bgm/midboss.ogg", 0x00000000, 0x0007E036 * 4, 0x005C036D * 4, "憑坐は夢と現の間に 〜 Necro-Fantasia" },
  // ラスボス曲
  { "data/bgm/lastboss.ogg", 0x00000000, 0x000B7CB3 * 4, 0x009D1732 * 4, "今宵は飄逸なエゴイスト(Live ver) 〜 Egoistic Flowers." },
  // 深秘録から続投
  { "data/bgm/145st01.ogg", 0x00000000, 0x0003B84F * 4, 0x00435E4E * 4, "オカルトアトラクト" },
  { "data/bgm/145st02.ogg", 0x00000000, 0x0001C71B * 4, 0x003DD2E9 * 4, "ネオ竹林インフレイム" },
  { "data/bgm/145st03.ogg", 0x00000000, 0x0003B262 * 4, 0x00434A8A * 4, "億万劫の鐘" },
  { "data/bgm/145st04.ogg", 0x00000000, 0x001D5D95 * 4, 0x0067B098 * 4, "アンノウンX 〜 Occultly Madness" },

  // ストーリー会話パート曲
  { "data/bgm/story01.ogg", 0x00000000, 0x00032C96 * 4, 0x00292A99 * 4, "憑依投合" },
  { "data/bgm/story02.ogg", 0x00000000, 0x0005D9A3 * 4, 0x002AE5EF * 4, "連帯責人" },
  { "data/bgm/story03.ogg", 0x00000000, 0x00020585 * 4, 0x0029FD60 * 4, "合縁奇縁" },
  { "data/bgm/story04.ogg", 0x00000000, 0x000144C4 * 4, 0x002AE5EF * 4, "異心同体" },
  { "data/bgm/story05.ogg", 0x00000000, 0x00026EAA * 4, 0x0029FD5F * 4, "壮言大語" },
  { "data/bgm/story06.ogg", 0x00000000, 0x0002EDA5 * 4, 0x002A64BA * 4, "知略縦横" },
  { "data/bgm/story07.ogg", 0x00000000, 0x0002EC31 * 4, 0x002DC20D * 4, "意気揚々" },
  { "data/bgm/story08.ogg", 0x00000000, 0x0004F4BC * 4, 0x00299922 * 4, "開演間近" },
  { "data/bgm/story09.ogg", 0x00000000, 0x00071759 * 4, 0x002B10FC * 4, "天衣無縫 〜 Yellow Lily" },
  // エンディングでも使われてる曲
  { "data/bgm/ending.ogg", 0x00000000, 0x000397EB * 4, 0x002F0D42 * 4, "行雲流水" },

  // オープニング/エンディング曲
  { "data/bgm/th155op1.ogg", 0x00000000, 0x0008149C * 4, 0x003277EC * 4, "異変の種子" },
  { "data/bgm/th155op2.ogg", 0x00000000, 0x000814CE * 4, 0x003277ED * 4, "疑惑の芽生え" },
  { "data/bgm/staffroll0.ogg", 0x00000000, 0x00000000 * 4, 0x003A308B * 4, "未だ蕾む憑依華" },
  { "data/bgm/th155op3.ogg", 0x00000000, 0x000814C7 * 4, 0x003277EC * 4, "真相へ繋がる枝葉" },
  { "data/bgm/staffroll1.ogg", 0x00000000, 0x00000000 * 4, 0x00444AC8 * 4, "咲き誇る憑依華" },
  { "data/bgm/th155op4.ogg", 0x00000000, 0x000814D8 * 4, 0x003277EC * 4, "舞い散る憑依華吹雪" },
  { "data/bgm/staffroll2.ogg", 0x00000000, 0x00000000 * 4, 0x0041E685 * 4, "悠久の蒸気機関" },

  // 20秒版
  /*
  { "data/bgm/155reimu1_20.ogg", 0x00000000, 0x00000000 * 4, 0x000E5E9C * 4, "二色蓮花蝶 〜 Red and White" },
  { "data/bgm/155marisa1_20.ogg", 0x00000000, 0x00000000 * 4, 0x000EAD74 * 4, "恋色マスタースパーク" },
  { "data/bgm/155ichirin1_20.ogg", 0x00000000, 0x00000000 * 4, 0x000D929A * 4, "時代親父とハイカラ少女" },
  { "data/bgm/155hijiri1_20.ogg", 0x00000000, 0x00000000 * 4, 0x000DAFB6 * 4, "感情の摩天楼 〜 Cosmic Mind" },
  { "data/bgm/155futo1_20.ogg", 0x00000000, 0x00000000 * 4, 0x000DF950 * 4, "大神神話伝" },
  { "data/bgm/155miko1_20.ogg", 0x00000000, 0x00000000 * 4, 0x000FF2F5 * 4, "聖徳伝説 〜 True Administrator" },
  { "data/bgm/155nitori1_20.ogg", 0x00000000, 0x00000000 * 4, 0x000EB0E3 * 4, "芥川龍之介の河童  〜 Candid Friend" },
  { "data/bgm/155koishi1_20.ogg", 0x00000000, 0x00000000 * 4, 0x000F1C1B * 4, "ハルトマンの妖怪少女" },
  { "data/bgm/155mamizou1_20.ogg", 0x00000000, 0x00000000 * 4, 0x000FC368 * 4, "幻想郷の二ッ岩" },
  { "data/bgm/155kokoro1_20.ogg", 0x00000000, 0x00000000 * 4, 0x000EC5CF * 4, "亡失のエモーション" },
  { "data/bgm/155mokou1_20.ogg", 0x00000000, 0x00000000 * 4, 0x000DD890 * 4, "月まで届け、不死の煙" },
  { "data/bgm/155sinmyoumaru1_20.ogg", 0x00000000, 0x00000000 * 4, 0x000D7550 * 4, "輝く針の小人族 〜 Little Princess" },
  { "data/bgm/155kasen1_20.ogg", 0x00000000, 0x00000000 * 4, 0x000D7551 * 4, "華狭間のバトルフィールド" },
  { "data/bgm/155usami1_20.ogg", 0x00000000, 0x00000000 * 4, 0x000D7550 * 4, "ラストオカルティズム 〜 現し世の秘術師" },
  { "data/bgm/155udonge1_20.ogg", 0x00000000, 0x00000000 * 4, 0x000D5166 * 4, "狂気の瞳 〜 Invisible Full Moon" },
  { "data/bgm/155doremy1_20.ogg", 0x00000000, 0x00000000 * 4, 0x000D7550 * 4, "永遠の春夢" },
  { "data/bgm/155tenshi1_20.ogg", 0x00000000, 0x00000000 * 4, 0x000F7A1C * 4, "有頂天変 〜 Wonderful Heaven" },
  { "data/bgm/155yukari1_20.ogg", 0x00000000, 0x00000000 * 4, 0x000D7550 * 4, "夜が降りてくる 〜 Evening Star" },
  { "data/bgm/bgst01_20.ogg", 0x00000000, 0x00000000 * 4, 0x000D7550 * 4, "地の色は黄色 〜 Primrose" },
  { "data/bgm/bgst02_20.ogg", 0x00000000, 0x00000000 * 4, 0x000D7550 * 4, "マッシュルーム・ワルツ" },
  { "data/bgm/bgst03_20.ogg", 0x00000000, 0x00000000 * 4, 0x000D7550 * 4, "聖輦船空を往く" },
  { "data/bgm/bgst04_20.ogg", 0x00000000, 0x00000000 * 4, 0x000D7550 * 4, "法力の下の平等" },
  { "data/bgm/bgst05_20.ogg", 0x00000000, 0x00000000 * 4, 0x000EB93A * 4, "恒常不変の参廟祀" },
  { "data/bgm/bgst06_20.ogg", 0x00000000, 0x00000000 * 4, 0x000D7550 * 4, "光輝く天球儀" },
  { "data/bgm/bgst07_20.ogg", 0x00000000, 0x00000000 * 4, 0x000D7550 * 4, "沢の河童の技術力" },
  { "data/bgm/bgst08_20.ogg", 0x00000000, 0x00000000 * 4, 0x000D7550 * 4, "地底に咲く薔薇" },
  { "data/bgm/bgst09_20.ogg", 0x00000000, 0x00000000 * 4, 0x000E547A * 4, "深緑の狸森にて" },
  { "data/bgm/bgst10_20.ogg", 0x00000000, 0x00000000 * 4, 0x000D7550 * 4, "心綺楼演舞" },
  { "data/bgm/bgst11_20.ogg", 0x00000000, 0x00000000 * 4, 0x000FB54C * 4, "不滅のレッドソウル" },
  { "data/bgm/bgst12_20.ogg", 0x00000000, 0x00000000 * 4, 0x000E5920 * 4, "落日に映える逆さ城" },
  { "data/bgm/bgst13_20.ogg", 0x00000000, 0x00000000 * 4, 0x000D7550 * 4, "千の試練を超えて" },
  { "data/bgm/bgst14_20.ogg", 0x00000000, 0x00000000 * 4, 0x000D7550 * 4, "夢世界フォークロア" },
  { "data/bgm/bgst15_20.ogg", 0x00000000, 0x00000000 * 4, 0x000D7550 * 4, "永遠に続く回廊" },
  { "data/bgm/bgst16_20.ogg", 0x00000000, 0x00000000 * 4, 0x000E08B9 * 4, "スリープシープ・パレード" },
  { "data/bgm/bgst17_20.ogg", 0x00000000, 0x00000000 * 4, 0x000D7550 * 4, "至る有頂天" },
  { "data/bgm/midboss_20.ogg", 0x00000000, 0x00000000 * 4, 0x000ECDD8 * 4, "憑坐は夢と現の間に 〜 Necro-Fantasia" },
  { "data/bgm/lastboss_20.ogg", 0x00000000, 0x00000000 * 4, 0x000D7550 * 4, "今宵は飄逸なエゴイスト(Live ver) 〜 Egoistic Flowers." },
  { "data/bgm/145st01_20.ogg", 0x00000000, 0x00000000 * 4, 0x000ED752 * 4, "オカルトアトラクト" },
  { "data/bgm/145st02_20.ogg", 0x00000000, 0x00000000 * 4, 0x000DD182 * 4, "ネオ竹林インフレイム" },
  { "data/bgm/145st03_20.ogg", 0x00000000, 0x00000000 * 4, 0x000EAE08 * 4, "億万劫の鐘" },
  { "data/bgm/145st04_20.ogg", 0x00000000, 0x00000000 * 4, 0x000EACEA * 4, "アンノウンX 〜 Occultly Madness" },
  */
};

std::string createMusicInfoString(const MusicInfo * const info, const unsigned int count) {
  return count == 0 ? "" : (
    (boost::format("\n%%%s,%08x,%08x,%08x,%s") % info[0].path % info[0].headerSize % info[0].introSize % info[0].bodySize % info[0].name).str()
    + createMusicInfoString(&info[1], count - 1)
  );
}

std::string createTh135List() {
  return std::string("@,東方心綺楼") + createMusicInfoString(th135MusicList, _countof(th135MusicList));
}

std::string createTh145List() {
  return std::string("@,東方深秘録") + createMusicInfoString(th145MusicList, _countof(th145MusicList));
}

std::string createTh155List() {
  return std::string("@,東方憑依華") + createMusicInfoString(th155MusicList, _countof(th155MusicList));
}

const std::string th135ListTxt = createTh135List();
const std::string th145ListTxt = createTh145List();
const std::string th155ListTxt = createTh155List();

boost::filesystem::path getAppDir() {
  wchar_t buf[4096];
  ::GetModuleFileNameW(NULL, buf, _countof(buf));
  const boost::filesystem::path exePath = buf;
  boost::filesystem::path exeDir = buf;
  exeDir.remove_filename();
  return exeDir;
}

boost::filesystem::path getListDir() {
  return getAppDir() / LIST_DIR_NAME;
}

boost::filesystem::path getTh135ListPath() {
  return getListDir() / TH135_LIST_FILENAME;
}

boost::filesystem::path getTh145ListPath() {
  return getListDir() / TH145_LIST_FILENAME;
}

boost::filesystem::path getTh155ListPath() {
  return getListDir() / TH155_LIST_FILENAME;
}

void hookFindFirstFileW(const wchar_t * const lpFileName, std::vector<WIN32_FIND_DATAW> &data) {
  const boost::filesystem::path targetPath = getListDir() / L"*.txt";
  if (targetPath != lpFileName) {
    return;
  }
  struct {
    const wchar_t * const fileName;
    const unsigned int fileSize;
  } list[] = {
#ifdef ENABLE_TH135
    { TH135_LIST_FILENAME, th135ListTxt.size() },
#endif
#ifdef ENABLE_TH145
    { TH145_LIST_FILENAME, th145ListTxt.size() },
#endif
#ifdef ENABLE_TH155
    { TH155_LIST_FILENAME, th155ListTxt.size() },
#endif
  };
  data.resize(data.size() + _countof(list));
  auto it = data.end() - _countof(list);
  SYSTEMTIME st;
  ::GetSystemTime(&st);
  for (const auto &item : list) {
    WIN32_FIND_DATAW &fd = *it;
    fd.dwFileAttributes = FILE_ATTRIBUTE_READONLY;
    ::SystemTimeToFileTime(&st, &fd.ftCreationTime);
    ::SystemTimeToFileTime(&st, &fd.ftLastAccessTime);
    ::SystemTimeToFileTime(&st, &fd.ftLastWriteTime);
    fd.nFileSizeLow = item.fileSize;
    ::wcscpy_s(fd.cFileName, _countof(fd.cFileName), item.fileName);
    it++;
  }
}

unsigned int hookCreateFileWTh135List(const wchar_t * const lpFileName, const DWORD dwDesiredAccess, const DWORD dwCreationDisposition) {
  const boost::filesystem::path listPath = getTh135ListPath();
  if (listPath != lpFileName) {
    return reinterpret_cast<unsigned int>(INVALID_HANDLE_VALUE);
  }
  const unsigned int handle = newVirtualFileHandle();
  VirtualFileHandle &file = getVirtualFileHandle(handle);
  file = std::make_unique<VirtualFileOnMemory>(
    std::vector<unsigned char>(th135ListTxt.begin(), th135ListTxt.end())
  );
  return handle;
}

unsigned int hookCreateFileWTh145List(const wchar_t * const lpFileName, const DWORD dwDesiredAccess, const DWORD dwCreationDisposition) {
  const boost::filesystem::path listPath = getTh145ListPath();
  if (listPath != lpFileName) {
    return reinterpret_cast<unsigned int>(INVALID_HANDLE_VALUE);
  }
  const unsigned int handle = newVirtualFileHandle();
  VirtualFileHandle &file = getVirtualFileHandle(handle);
  file = std::make_unique<VirtualFileOnMemory>(
    std::vector<unsigned char>(th145ListTxt.begin(), th145ListTxt.end())
    );
  return handle;
}

unsigned int hookCreateFileWTh155List(const wchar_t * const lpFileName, const DWORD dwDesiredAccess, const DWORD dwCreationDisposition) {
  const boost::filesystem::path listPath = getTh155ListPath();
  if (listPath != lpFileName) {
    return reinterpret_cast<unsigned int>(INVALID_HANDLE_VALUE);
  }
  const unsigned int handle = newVirtualFileHandle();
  VirtualFileHandle &file = getVirtualFileHandle(handle);
  file = std::make_unique<VirtualFileOnMemory>(
    std::vector<unsigned char>(th155ListTxt.begin(), th155ListTxt.end())
    );
  return handle;
}

class VirtualWavFile : public VirtualFileBase {
private:
  OggVorbis_File ovf;
  const unsigned int blockSize;
  std::vector<unsigned char> temp;

public:
  VirtualWavFile(OggVorbis_File ovf) : ovf(ovf), blockSize(4) {
  }
  ~VirtualWavFile() override {
    ::ov_clear(&ovf);
  }

  unsigned int size() const {
    return static_cast<unsigned int>(::ov_pcm_total(const_cast<OggVorbis_File *>(&ovf), -1)) * blockSize;
  }

  unsigned int tell() const override {
    return static_cast<unsigned int>(::ov_pcm_tell(const_cast<OggVorbis_File *>(&ovf))) * blockSize - temp.size();
  }

  bool seek(unsigned int dis, const SeekType seekType) override {
    assert((dis % blockSize) == 0);
    const unsigned int pos = tell();
    const unsigned int totalSize = size();
    unsigned int ptr = 0;
    switch (seekType) {
    case BEGIN:
      ptr = dis;
      break;
    case CURRENT:
      ptr = pos + dis;
      break;
    case END:
      ptr = totalSize + dis;
      break;
    default:
      return false;
    }
    if (ptr > totalSize) {
      ptr = totalSize;
    }
    ::ov_pcm_seek(&ovf, ptr / blockSize);
    temp.clear();
    return true;
  }

  unsigned int read(unsigned char * const buf, const unsigned int bufSize) override {
    const unsigned int tempSize = temp.size();
    assert((tempSize % 4) == 0);
    if (!temp.empty() && bufSize < tempSize) {
      std::copy(temp.begin(), temp.begin() + bufSize, stdext::checked_array_iterator<unsigned char *>(buf, bufSize));
      std::copy(temp.begin() + bufSize, temp.end(), temp.begin());
      temp.resize(tempSize - bufSize);
      return bufSize;
    }
    const unsigned int pos = static_cast<unsigned int>(::ov_pcm_tell(const_cast<OggVorbis_File *>(&ovf))) * blockSize;
    const unsigned int totalSize = size();
    unsigned int readSize = bufSize - tempSize;
    if (pos + readSize > totalSize) {
      readSize = totalSize - pos - tempSize;
    }
    temp.resize(tempSize + (std::max)(bufSize * 2, 4096u));
    int bitStream = 0;
    unsigned int readSizeResult = 0;
    unsigned int tempIndex = tempSize;
    while (true) {
      const unsigned int curReadSize = ::ov_read(&ovf, reinterpret_cast<char *>(&temp[tempIndex]), temp.size() - tempIndex, 0, 2, 1, &bitStream);
      if (curReadSize == 0) {
        break;
      }
      readSizeResult += curReadSize;
      tempIndex += curReadSize;
      if (tempIndex == temp.size()) {
        break;
      }
    }
    temp.resize(tempSize + readSizeResult);
    if (readSizeResult < readSize) {
      readSize = readSizeResult;
    }
    std::copy(temp.begin(), temp.begin() + tempSize + readSize, stdext::checked_array_iterator<unsigned char *>(buf, bufSize));
    std::copy(temp.begin() + tempSize + readSize, temp.end(), temp.begin());
    temp.resize(temp.size() - tempSize - readSize);
    if (readSize == 0 && tempSize == 0) {
      ::SetLastError(ERROR_HANDLE_EOF);
    }
    return tempSize + readSize;
  }
};

unsigned int hookCreateFileWNativeOgg(const wchar_t * const lpFileName, const DWORD dwDesiredAccess, const DWORD dwCreationDisposition) {
  const boost::filesystem::path path = lpFileName;
  if (path.extension() != ".ogg" || !boost::filesystem::is_regular_file(path)) {
    return reinterpret_cast<unsigned int>(INVALID_HANDLE_VALUE);
  }
  OggVorbis_File ovf;
  if (::ov_fopen(path.string().c_str(), &ovf) != 0) {
    return reinterpret_cast<unsigned int>(INVALID_HANDLE_VALUE);
  }
  const unsigned int handle = newVirtualFileHandle();
  VirtualFileHandle &file = getVirtualFileHandle(handle);
  file = std::make_unique<VirtualWavFile>(ovf);
  return handle;
}

template<typename T>
class ReaderBase : public boost::noncopyable {
private:
  T &pak;
  const TouhouSE::TH135::FileInfo &info;
  unsigned int ptr;

public:
  ReaderBase(T &pak, const TouhouSE::TH135::FileInfo &info) : pak(pak), info(info), ptr(0) {
    seek(0, SEEK_SET);
  }

  unsigned int size() const {
    return info.size;
  }

  unsigned int tell() const {
    return ptr;
  }

  bool seek(unsigned int dis, const int origin) {
    const unsigned int pos = tell();
    const unsigned int totalSize = size();
    unsigned int ptrPos = 0;
    switch (origin) {
    case SEEK_SET:
      ptrPos = dis;
      break;
    case SEEK_CUR:
      ptrPos = pos + dis;
      break;
    case SEEK_END:
      ptrPos = totalSize + dis;
      break;
    default:
      return false;
    }
    if (ptrPos > totalSize) {
      ptrPos = totalSize;
    }
    this->ptr = ptrPos;
    pak.in->seekg(pak.headerSize + info.address + this->ptr);
    return true;
  }

  unsigned int read(unsigned char * const buf, const unsigned int bufSize) {
    const unsigned int pos = tell();
    const unsigned int totalSize = size();
    unsigned int readSize = bufSize;
    if (pos + readSize > totalSize) {
      readSize = totalSize - pos;
    }
    if (readSize == 0) {
      return 0;
    }
    pak.in->read(reinterpret_cast<char *>(buf), readSize);
    unsigned char *it = buf;
    const unsigned char * const end = &buf[readSize];
    for (unsigned int i = (ptr % TouhouSE::TH135::BLOCK_SIZE); i != 0 && buf != end && i < TouhouSE::TH135::BLOCK_SIZE; i++) {
      *it ^= info.key[i];
      it++;
    }
    const unsigned int loopCount = (end - it) / TouhouSE::TH135::BLOCK_SIZE;
    for (unsigned int i = 0; i < loopCount; i++) {
      *reinterpret_cast<unsigned long long int *>(&it[0]) ^= *reinterpret_cast<const unsigned long long int *>(&info.key[0]);
      *reinterpret_cast<unsigned long long int *>(&it[8]) ^= *reinterpret_cast<const unsigned long long int *>(&info.key[8]);
      it += TouhouSE::TH135::BLOCK_SIZE;
    }
    for (unsigned int i = 0; it != end; i++) {
      *it ^= info.key[i];
      it++;
    }
    assert(it == end);
    ptr += readSize;
    return readSize;
  }
};

class Th135Reader : public ReaderBase<TouhouSE::TH135::Th135Extracter> {
public:
  Th135Reader(TouhouSE::TH135::Th135Extracter &pak, const TouhouSE::TH135::FileInfo &info) : ReaderBase<TouhouSE::TH135::Th135Extracter>(pak, info) {
  }
};

class Th145Reader : public boost::noncopyable {
private:
  TouhouSE::TH145Pak::Th145PakExtracter &pak;
  const TouhouSE::TH145Pak::FileInfo &info;
  unsigned int ptr;
  unsigned int key2;

public:
  Th145Reader(TouhouSE::TH145Pak::Th145PakExtracter &pak, const TouhouSE::TH145Pak::FileInfo &info) : pak(pak), info(info), ptr(0) {
    seek(0, SEEK_SET);
  }

  unsigned int size() const {
    return info.size;
  }

  unsigned int tell() const {
    return ptr;
  }

  bool seek(unsigned int dis, const int origin) {
    const unsigned int pos = tell();
    const unsigned int totalSize = size();
    unsigned int ptrPos = 0;
    switch (origin) {
    case SEEK_SET:
      ptrPos = dis;
      break;
    case SEEK_CUR:
      ptrPos = pos + dis;
      break;
    case SEEK_END:
      ptrPos = totalSize + dis;
      break;
    default:
      return false;
    }
    if (ptrPos > totalSize) {
      ptrPos = totalSize;
    }
    this->ptr = ptrPos;
    if (this->ptr == 0) {
      pak.in->seekg(info.address);
      key2 = *reinterpret_cast<const unsigned int *>(&info.key[0]);
    } else {
      const unsigned int curPtr = this->ptr - 4;
      pak.in->seekg(info.address + curPtr);
      std::array<unsigned char, 4> temp;
      pak.in->read(reinterpret_cast<char *>(&temp[0]), 4);
      switch (curPtr % 4) {
      case 0:
        key2 = *reinterpret_cast<unsigned int *>(&temp[0]);
        break;
      case 1:
        key2 = (*reinterpret_cast<unsigned int *>(&temp[0]) >> 24) + ((*reinterpret_cast<unsigned int *>(&temp[0]) & 0xFFFFFF) << 8);
        break;
      case 2:
        key2 = (*reinterpret_cast<unsigned int *>(&temp[0]) >> 16) + ((*reinterpret_cast<unsigned int *>(&temp[0]) & 0xFFFF) << 16);
        break;
      case 3:
        key2 = (*reinterpret_cast<unsigned int *>(&temp[0]) >> 8) + ((*reinterpret_cast<unsigned int *>(&temp[0]) & 0xFF) << 24);
        break;
      }
    }
    return true;
  }

  unsigned int read(unsigned char * const buf, const unsigned int bufSize) {
    const unsigned int pos = tell();
    const unsigned int totalSize = size();
    unsigned int readSize = bufSize;
    if (pos + readSize > totalSize) {
      readSize = totalSize - pos;
    }
    if (readSize == 0) {
      return 0;
    }
    pak.in->read(reinterpret_cast<char *>(buf), readSize);
    const unsigned int pre = (ptr + 3) / 4 * 4 - ptr;
    const unsigned int post = (ptr + bufSize) / 4 * 4 - ptr;
    for (const unsigned int i : boost::irange(0U, pre)) {
      const unsigned int j = ptr + i;
      const unsigned char temp = buf[i];
      buf[i] = (buf[i] ^ info.key[j % 16]) ^ reinterpret_cast<const unsigned char *>(&key2)[j % 4];
      reinterpret_cast<unsigned char *>(&key2)[j % 4] = temp;
    }
    for (const unsigned int i : boost::irange(pre, post, 4)) {
      const unsigned int temp = *reinterpret_cast<const unsigned int *>(&buf[i]);
      *reinterpret_cast<unsigned int *>(&buf[i]) = (*reinterpret_cast<const unsigned int *>(&buf[i]) ^ *reinterpret_cast<const unsigned int *>(&info.key[(ptr + i) % 16])) ^ key2;
      key2 = temp;
    }
    for (const unsigned int i : boost::irange(post, bufSize)) {
      const unsigned int j = ptr + i;
      const unsigned char temp = buf[i];
      buf[i] = (buf[i] ^ info.key[j % 16]) ^ reinterpret_cast<const unsigned char *>(&key2)[j % 4];
      reinterpret_cast<unsigned char *>(&key2)[j % 4] = temp;
    }
    ptr += readSize;
    return readSize;
  }
};

template<typename T>
bool oggOpen(OggVorbis_File &ovf, T * const readerPtr) {
  const int result = ::ov_open_callbacks(readerPtr, &ovf, NULL, 0, {
    [](void * const buf, const size_t size, const size_t n, void * const ptr) -> size_t {
      T &reader = *reinterpret_cast<T *>(ptr);
      return reader.read(reinterpret_cast<unsigned char *>(buf), size * n) / size;
    },
      [](void * const ptr, const ogg_int64_t dis, const int origin) -> int {
      T &reader = *reinterpret_cast<T *>(ptr);
      return reader.seek(static_cast<unsigned int>(dis), origin) ? 0 : 1;
    },
      [](void * const ptr) -> int {
      T * const reader = reinterpret_cast<T *>(ptr);
      delete ptr;
      return 0;
    },
      [](void * const ptr) -> long {
      const T &reader = *reinterpret_cast<T *>(ptr);
      return reader.tell();
    }
  });
  return result == 0;
}

unsigned int getRelativeRootPos(const wchar_t * const lpFileName) {
  boost::filesystem::path tempPath = lpFileName;
  while (true) {
    if (tempPath.empty()) {
      return{};
    }
    if (tempPath.filename() == L"data") {
      tempPath = tempPath.parent_path();
      break;
    }
    tempPath = tempPath.parent_path();
  }
  return tempPath.wstring().size() + 1;
}

boost::filesystem::path getRelativeRootPath(const wchar_t * const lpFileName) {
  return boost::filesystem::path(&lpFileName[0], &lpFileName[getRelativeRootPos(lpFileName)]);
}

boost::filesystem::path getRelativePath(const wchar_t * const lpFileName) {
  return boost::filesystem::path(&lpFileName[getRelativeRootPos(lpFileName)], &lpFileName[wcslen(lpFileName)]);
}

boost::shared_ptr<TouhouSE::TH135::Th135Extracter> th135pak = nullptr;
boost::shared_ptr<TouhouSE::TH135::Th135Extracter> th135pakB = nullptr;

unsigned int hookCreateFileWTh135Ogg(const wchar_t * const lpFileName, const DWORD dwDesiredAccess, const DWORD dwCreationDisposition) {
  if (!th135pak || !th135pakB || !boost::filesystem::is_regular_file(getRelativeRootPath(lpFileName) / L"th135.pak")) {
    return reinterpret_cast<unsigned int>(INVALID_HANDLE_VALUE);
  }
  const boost::filesystem::path path = getRelativePath(lpFileName);
  const TouhouSE::TH135::FileInfo * const info = th135pak->Find(path);
  const TouhouSE::TH135::FileInfo * const infoB = th135pakB->Find(path);
  if (info == nullptr && infoB == nullptr) {
    return reinterpret_cast<unsigned int>(INVALID_HANDLE_VALUE);
  }
  OggVorbis_File ovf;
  Th135Reader * const readerPtr = new Th135Reader(*(infoB ? th135pakB : th135pak), *(infoB ? infoB : info));
  if (!oggOpen(ovf, readerPtr)) {
    return reinterpret_cast<unsigned int>(INVALID_HANDLE_VALUE);
  }
  const unsigned int handle = newVirtualFileHandle();
  getVirtualFileHandle(handle) = std::make_unique<VirtualWavFile>(ovf);
  return handle;
}

boost::shared_ptr<TouhouSE::TH145Pak::Th145PakExtracter> th145pak = nullptr;
boost::shared_ptr<TouhouSE::TH145Pak::Th145PakExtracter> th145pakB = nullptr;

unsigned int hookCreateFileWTh145Ogg(const wchar_t * const lpFileName, const DWORD dwDesiredAccess, const DWORD dwCreationDisposition) {
  if (!th145pak || !boost::filesystem::is_regular_file(getRelativeRootPath(lpFileName) / L"th145.pak")) {
    return reinterpret_cast<unsigned int>(INVALID_HANDLE_VALUE);
  }
  const boost::filesystem::path path = getRelativePath(lpFileName);
  const auto * const info = th145pak->Find(path);
  const auto * const infoB = (!th145pakB ? nullptr : th145pakB->Find(path));
  if (info == nullptr && infoB == nullptr) {
    return reinterpret_cast<unsigned int>(INVALID_HANDLE_VALUE);
  }
  OggVorbis_File ovf;
  Th145Reader * const readerPtr = new Th145Reader(*(infoB ? th145pakB : th145pak), *(infoB ? infoB : info));
  if (!oggOpen(ovf, readerPtr)) {
    return reinterpret_cast<unsigned int>(INVALID_HANDLE_VALUE);
  }
  const unsigned int handle = newVirtualFileHandle();
  getVirtualFileHandle(handle) = std::make_unique<VirtualWavFile>(ovf);
  return handle;
}

boost::shared_ptr<TouhouSE::TH145Pak::Th145PakExtracter> th155pak = nullptr;
boost::shared_ptr<TouhouSE::TH145Pak::Th145PakExtracter> th155pakB = nullptr;

unsigned int hookCreateFileWTh155Ogg(const wchar_t * const lpFileName, const DWORD dwDesiredAccess, const DWORD dwCreationDisposition) {
  if (!th155pak || !boost::filesystem::is_regular_file(getRelativeRootPath(lpFileName) / L"th155.pak")) {
    return reinterpret_cast<unsigned int>(INVALID_HANDLE_VALUE);
  }
  const boost::filesystem::path path = getRelativePath(lpFileName);
  const auto * const info = th155pak->Find(path);
  const auto * const infoB = (!th155pakB ? nullptr : th155pakB->Find(path));
  if (info == nullptr && infoB == nullptr) {
    return reinterpret_cast<unsigned int>(INVALID_HANDLE_VALUE);
  }
  OggVorbis_File ovf;
  Th145Reader * const readerPtr = new Th145Reader(*(infoB ? th155pakB : th155pak), *(infoB ? infoB : info));
  if (!oggOpen(ovf, readerPtr)) {
    return reinterpret_cast<unsigned int>(INVALID_HANDLE_VALUE);
  }
  const unsigned int handle = newVirtualFileHandle();
  getVirtualFileHandle(handle) = std::make_unique<VirtualWavFile>(ovf);
  return handle;
}

unsigned int hookCreateFileW(const wchar_t * const lpFileName, const DWORD dwDesiredAccess, const DWORD dwCreationDisposition) {
  const unsigned int th135ListResult = hookCreateFileWTh135List(lpFileName, dwDesiredAccess, dwCreationDisposition);
  if (reinterpret_cast<HANDLE>(th135ListResult) != INVALID_HANDLE_VALUE) {
    return th135ListResult;
  }
  const unsigned int th145ListResult = hookCreateFileWTh145List(lpFileName, dwDesiredAccess, dwCreationDisposition);
  if (reinterpret_cast<HANDLE>(th145ListResult) != INVALID_HANDLE_VALUE) {
    return th145ListResult;
  }
  const unsigned int th155ListResult = hookCreateFileWTh155List(lpFileName, dwDesiredAccess, dwCreationDisposition);
  if (reinterpret_cast<HANDLE>(th155ListResult) != INVALID_HANDLE_VALUE) {
    return th155ListResult;
  }
  const unsigned int nativeOggResult = hookCreateFileWNativeOgg(lpFileName, dwDesiredAccess, dwCreationDisposition);
  if (reinterpret_cast<HANDLE>(nativeOggResult) != INVALID_HANDLE_VALUE) {
    return nativeOggResult;
  }
  const unsigned int th135OggResult = hookCreateFileWTh135Ogg(lpFileName, dwDesiredAccess, dwCreationDisposition);
  if (reinterpret_cast<HANDLE>(th135OggResult) != INVALID_HANDLE_VALUE) {
    return th135OggResult;
  }
  const unsigned int th145OggResult = hookCreateFileWTh145Ogg(lpFileName, dwDesiredAccess, dwCreationDisposition);
  if (reinterpret_cast<HANDLE>(th145OggResult) != INVALID_HANDLE_VALUE) {
    return th145OggResult;
  }
  const unsigned int th155OggResult = hookCreateFileWTh155Ogg(lpFileName, dwDesiredAccess, dwCreationDisposition);
  if (reinterpret_cast<HANDLE>(th155OggResult) != INVALID_HANDLE_VALUE) {
    return th155OggResult;
  }
  return reinterpret_cast<unsigned int>(INVALID_HANDLE_VALUE);
}

bool hookOpenArchiveTh135() {
  const boost::filesystem::path th135PakPath = L"th135.pak";
  const boost::filesystem::path th135PakBPath = L"th135b.pak";
  if (!boost::filesystem::is_regular_file(th135PakPath) || !boost::filesystem::is_regular_file(th135PakBPath)) {
    return false;
  }
  if (th135pak && th135pakB) {
    return true;
  }
  std::unique_ptr<boost::filesystem::ifstream> ifs = std::make_unique<boost::filesystem::ifstream>(th135PakPath, std::ios::binary);
  th135pak = TouhouSE::TH135::Th135Extracter::Open(std::move(ifs), boost::filesystem::file_size(th135PakPath));
  if (!th135pak) {
    return false;
  }
  ifs = std::make_unique<boost::filesystem::ifstream>(th135PakBPath, std::ios::binary);
  th135pakB = TouhouSE::TH135::Th135Extracter::Open(std::move(ifs), boost::filesystem::file_size(th135PakBPath));
  return !!th135pakB;
}

bool hookOpenArchiveTh145() {
  const boost::filesystem::path th145PakPath = L"th145.pak";
  if (!boost::filesystem::is_regular_file(th145PakPath)) {
    return false;
  }
  if (th145pak) {
    return true;
  }
  std::unique_ptr<boost::filesystem::ifstream> ifs = std::make_unique<boost::filesystem::ifstream>(th145PakPath, std::ios::binary);
  th145pak = TouhouSE::TH145::openTh145Extracter(std::move(ifs), boost::filesystem::file_size(th145PakPath));
  if (!th145pak) {
    return false;
  }
  const boost::filesystem::path th145PakBPath = L"th145b.pak";
  if (!boost::filesystem::is_regular_file(th145PakBPath)) {
    return true;
  }
  ifs = std::make_unique<boost::filesystem::ifstream>(th145PakBPath, std::ios::binary);
  th145pakB = TouhouSE::TH145::openTh145Extracter(std::move(ifs), boost::filesystem::file_size(th145PakBPath));
  return true;
}

bool hookOpenArchiveTh155() {
  const boost::filesystem::path th155PakPath = L"th155.pak";
  if (!boost::filesystem::is_regular_file(th155PakPath)) {
    return false;
  }
  if (th155pak) {
    return true;
  }
  std::unique_ptr<boost::filesystem::ifstream> ifs = std::make_unique<boost::filesystem::ifstream>(th155PakPath, std::ios::binary);
  th155pak = TouhouSE::TH155::openTh155Extracter(std::move(ifs), boost::filesystem::file_size(th155PakPath));
  if (!th155pak) {
    return false;
  }
  const boost::filesystem::path th155PakBPath = L"th155b.pak";
  if (!boost::filesystem::is_regular_file(th155PakBPath)) {
    return true;
  }
  ifs = std::make_unique<boost::filesystem::ifstream>(th155PakBPath, std::ios::binary);
  th155pakB = TouhouSE::TH155::openTh155Extracter(std::move(ifs), boost::filesystem::file_size(th155PakBPath));
  return true;
}

template<typename LIST, typename EXTRACTER>
bool isTargetFile(const wchar_t * const pszPath, const LIST &list, bool (*openProc)(), EXTRACTER &pakA, EXTRACTER &pakB) {
  const boost::filesystem::path path = pszPath;
  const auto endIt = &list[_countof(list)];
  const auto it = std::find_if(&list[0], endIt, [path](const auto &item) {
    return path == item.path;
  });
  if (it != endIt) {
    if (openProc()) {
      if (pakA->Find(path) != nullptr || (pakB && pakB->Find(path) != nullptr)) {
        return true;
      }
    }
  }
  return false;
}

bool isTh135File(const wchar_t * const pszPath) {
  return isTargetFile(pszPath, th135MusicList, hookOpenArchiveTh135, th135pak, th135pakB);
}

bool isTh145File(const wchar_t * const pszPath) {
  return isTargetFile(pszPath, th145MusicList, hookOpenArchiveTh145, th145pak, th145pakB);
}

bool isTh155File(const wchar_t * const pszPath) {
  return isTargetFile(pszPath, th155MusicList, hookOpenArchiveTh155, th155pak, th155pakB);
}

bool hookPathFileExistsW(const wchar_t * const pszPath) {
  return isTh135File(pszPath) || isTh145File(pszPath) || isTh155File(pszPath);
}


// 独自機能ここまで


bool ChangeIAT(HMODULE module) {
  static const struct {
    const std::string dllName;
    const std::string procName;
    const unsigned int procAddress;
  } targetList[] = {
    { "KERNEL32.dll", "FindFirstFileW", reinterpret_cast<unsigned int>(d_FindFirstFileW) },
    { "KERNEL32.dll", "FindNextFileW", reinterpret_cast<unsigned int>(d_FindNextFileW) },
    { "KERNEL32.dll", "FindClose", reinterpret_cast<unsigned int>(d_FindClose) },
    { "KERNEL32.dll", "CreateFileW", reinterpret_cast<unsigned int>(d_CreateFileW) },
    { "KERNEL32.dll", "CloseHandle", reinterpret_cast<unsigned int>(d_CloseHandle) },
    { "KERNEL32.dll", "ReadFile", reinterpret_cast<unsigned int>(d_ReadFile) },
    { "KERNEL32.dll", "SetFilePointer", reinterpret_cast<unsigned int>(d_SetFilePointer) },
    { "KERNEL32.dll", "WriteFile", reinterpret_cast<unsigned int>(d_WriteFile) },
    { "KERNEL32.dll", "GetFileType", reinterpret_cast<unsigned int>(d_GetFileType) },
    { "SHLWAPI.dll", "PathFileExistsW", reinterpret_cast<unsigned int>(d_PathFileExistsW) },
  };
  unsigned char * const baseAddr = reinterpret_cast<unsigned char *>(module == NULL ? ::GetModuleHandleW(NULL) : module);
  const IMAGE_DOS_HEADER &mz = *reinterpret_cast<const IMAGE_DOS_HEADER *>(baseAddr);
  const IMAGE_NT_HEADERS32 &pe = *reinterpret_cast<const IMAGE_NT_HEADERS32 *>(baseAddr + mz.e_lfanew);
  const IMAGE_IMPORT_DESCRIPTOR *desc = reinterpret_cast<IMAGE_IMPORT_DESCRIPTOR *>(baseAddr + pe.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
  for (; desc->OriginalFirstThunk != 0; desc = ++desc) {
    const char * const dllName = reinterpret_cast<const char *>(baseAddr + desc->Name);
    const unsigned int *in = reinterpret_cast<unsigned int *>(baseAddr + desc->OriginalFirstThunk);
    unsigned int *out = reinterpret_cast<unsigned int *>(baseAddr + desc->FirstThunk);
    for (; *in != 0; ++in, ++out) {
      if ((*in & 0x80000000) != 0) {
        continue;
      }
      const char * const procName = reinterpret_cast<const char *>(baseAddr + *in + 2);
      for (const auto &target : targetList) {
        if (::_stricmp(target.dllName.c_str(), dllName) != 0 || target.procName != procName) {
          continue;
        }
        DWORD oldProtect;
        ::VirtualProtect(out, 4, PAGE_READWRITE, &oldProtect);
        *out = target.procAddress;
        ::VirtualProtect(out, 4, oldProtect, &oldProtect);
      }
    }
  }
  return true;
}

} // anonymous

void main() {
  // debug用
  //org::click3::DllHackLib::SetupConsole();
  const bool changeResult = ChangeIAT(NULL);
}