他人の空似自作物置場

th17patch.zip/src/main.cpp


#include <cstdio>

#include <string>
#include <vector>
#include <memory>
#include <fstream>
#include <filesystem>

#include <Windows.h>

#include "main.h"
#include "dat_utility.h"
#include "th_crypt.h"
#include "th_base.h"

/* デバッグ用
#include <boost/filesystem.hpp>
#include <boost/utility.hpp>
#include <org/click3/dll_hack_lib.h>
*/

class Th17 : public ThOwnerBase<Th17, 0x1B, 0x37, 0x075BCD15, 0x3ADE68B1, 0x08180754, 0x3E, 0x9B, 0x80> {
public:

   Th17(std::istream& in, const unsigned long long int file_size, const std::shared_ptr<const Header> header, const std::shared_ptr<const std::vector<FileRecord> > file_list) :
      ThOwnerBase(in, file_size, header, file_list)
   {
   }

   const unsigned int* GetConvMap(const unsigned int index) {
      static const unsigned int conv_map[8][4] = {
       {0x1B,	0x73,	0x0100,	0x3800},
       {0x12,	0x43,	0x0200,	0x3E00},
         {0x35,	0x79,	0x0400,	0x3C00},
         {0x03,	0x91,	0x0080,	0x6400},
         {0xab,	0xDC,	0x0080,	0x7000},
         {0x51,	0x9E,	0x0100,	0x4000},
         {0xC1,	0x15,	0x0400,	0x2C00},
         {0x99,	0x7D,	0x0080,	0x4400}
      };
      return conv_map[index];
   }

   const unsigned int* GetKey(const FileRecord &record) {
      if (record.name.empty()) {
         return nullptr;
      }
      return GetConvMap(DatUtility::CalcKeyIndex(&record.name.front(), record.name.length()));
   }
};


unsigned int GetPageSize(void) {
   SYSTEM_INFO info;
   ::GetSystemInfo(&info);
   return info.dwPageSize;
}

void* GetBaseAddr(const void* addr) {
   MEMORY_BASIC_INFORMATION mbi;
   ::VirtualQuery(addr, &mbi, sizeof(mbi));
   return mbi.BaseAddress;
}

bool ChangeProtect(unsigned int* old_protect, const void* addr, unsigned int new_protect) {
   void* const base_addr = GetBaseAddr(addr);
   const unsigned int page_size = GetPageSize();
   if (0 == ::VirtualProtect(base_addr, page_size, new_protect, reinterpret_cast<DWORD*>(old_protect))) {
      return false;
   }
   return true;
}

bool ChangeCode(unsigned int addr, const unsigned char* old_code, const unsigned char* new_code, unsigned int size) {
   unsigned char* write_ptr = reinterpret_cast<unsigned char*>(addr);
   unsigned int old_protect;
   if (!ChangeProtect(&old_protect, write_ptr, PAGE_EXECUTE_READWRITE)) {
      return false;
   }
   if (::memcmp(write_ptr, old_code, size) != 0) {
      ChangeProtect(&old_protect, write_ptr, old_protect);
      return false;
   }
   ::memcpy(write_ptr, new_code, size);
   unsigned int protect;
   if (!ChangeProtect(&protect, write_ptr, old_protect) || protect != PAGE_EXECUTE_READWRITE) {
      return false;
   }
   return true;
}


HANDLE th17dat = 0;
std::shared_ptr<std::ifstream> ifs;
std::shared_ptr<Th17> th17;
const FileRecord *prevRecord = nullptr;

HANDLE _stdcall d_CreateFileA(
   LPCSTR lpFileName,
   DWORD dwDesiredAccess,
   DWORD dwShareMode,
   LPSECURITY_ATTRIBUTES lpSecurityAttributes,
   DWORD dwCreationDisposition,
   DWORD dwFlagsAndAttributes,
   HANDLE hTemplateFile)
{
   const HANDLE result = ::CreateFileA(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
   if (result == INVALID_HANDLE_VALUE) {
      return INVALID_HANDLE_VALUE;
   }
   const std::filesystem::path path = lpFileName;
   if (path.filename() == "th17.dat") {
      th17dat = result;
      ifs = std::make_shared<std::ifstream>(path, std::ios::binary);
      th17 = Th17::Open(*ifs, std::filesystem::file_size(path));
   }
   return result;
}

const FileRecord& findRecord(const unsigned int addr, const unsigned int compressSize) {
   for (const FileRecord& item : *th17->file_list) {
      if (item.addr == addr) {
         if (item.compress_size != compressSize) {
            ::printf("Invalid: ");
         }
         return item;
      }
   }
   static const FileRecord dummy;
   return dummy;
}

BOOL _stdcall d_ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped) {
   const unsigned int current = ::SetFilePointer(hFile, 0, nullptr, FILE_CURRENT);
   const BOOL result = ::ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, lpOverlapped);
   if (result == 0) {
      return 0;
   }
   if (hFile == th17dat) {
      const FileRecord &record = findRecord(current, *lpNumberOfBytesRead);
      prevRecord = &record;
      //::printf("%s\n", record.name.c_str());
   }
   return result;
}

bool ChangeIAT(HMODULE module) {
   static const struct {
      const std::string dllName;
      const std::string procName;
      const unsigned int procAddress;
   } targetList[] = {
     { "KERNEL32.dll", "CreateFileA", reinterpret_cast<unsigned int>(d_CreateFileA) },
     { "KERNEL32.dll", "ReadFile", reinterpret_cast<unsigned int>(d_ReadFile) },
   };
   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;
            }
            unsigned int oldProtect;
            ChangeProtect(&oldProtect, out, PAGE_READWRITE);
            *out = target.procAddress;
            ChangeProtect(&oldProtect, out, oldProtect);
         }
      }
   }
   return true;
}

void dumpSht(unsigned char * const data) {
   float& unknown = *reinterpret_cast<float*>(&data[0x04]);
   float& unknown2 = *reinterpret_cast<float*>(&data[0x08]);
   float& unknown3 = *reinterpret_cast<float*>(&data[0x0C]);
   ::printf("????: %f %f %f\n", unknown, unknown2, unknown3);
   float& speed = *reinterpret_cast<float*>(&data[0x10]);
   float& speed2 = *reinterpret_cast<float*>(&data[0x14]);
   float& speed3 = *reinterpret_cast<float*>(&data[0x18]);
   float& speed4 = *reinterpret_cast<float*>(&data[0x1C]);
   ::printf("速度: %f %f %f %f\n", speed, speed2, speed3, speed4);
   unsigned int& powerMax = *reinterpret_cast<unsigned int*>(&data[0x20]); // signed?
   ::printf("Power上限: %d\n", powerMax);
   unsigned int& unknown4 = *reinterpret_cast<unsigned int*>(&data[0x24]);
   ::printf("????: %d\n", unknown4);
   signed int& damageLimit = *reinterpret_cast<signed int*>(&data[0x28]);
   ::printf("ダメージキャップ: %d\n", damageLimit);
   float* const unknown5 = reinterpret_cast<float*>(&data[0x40]);// float[40];
   ::printf("????:\n");
   for (unsigned int i = 0; i < 40; i++) {
      ::printf("%5.2f ", unknown5[i]);
      unknown5[i] = -1.0;
      if (i % 4 == 3) {
         ::printf("\n");
      }
   }
}

// カワウソ妖夢1面の誤字「早詰み」を「早積み」に
void patchHayatsumi(unsigned char* const data) {
   if (prevRecord->name != "st01h.msg") {
      return;
   }
   if (data[1336] != 0x60 || data[1337] != 0xDE) {
      return;
   }
   data[1336] = 0x7B;
   data[1337] = 0x7D;
}

// 実績画面の誤字「4面」を「5面」に
void patch4men(unsigned char* const data) {
   if (prevRecord->name != "trophy.txt") {
      return;
   }
   if (data[11210] != 0x53) {
      return;
   }
   data[11210] = 0x54;
}

// オオワシ妖夢の与ダメージ上限を60から160に
void patchPlayer02C(unsigned char* const data) {
   if (prevRecord->name != "pl02c.sht") {
      return;
   }
   if (data[40] != 60) {
      return;
   }
   data[40] = 160;
}

// オオワシ魔理沙の爆風ダメージをカワウソ魔理沙と同値の16/13/11/10(power4/3/2/1)に変更
void patchPlayer01C(unsigned char* const data) {
   if (prevRecord->name != "pl01c.sht") {
      return;
   }
   if (data[0x00000BFA] != 10 || data[0x00000C52] != 10 || data[0x00000CAA] != 10
      || data[0x00000DB6] != 11 || data[0x00000E0E] != 11 || data[0x00000E66] != 11 || data[0x00000EBE] != 11)
   {
      return;
   }
   const unsigned int power3 = 11;
   const unsigned int power4 = 10;
   data[0x00000BFA] = power3;
   data[0x00000C52] = power3;
   data[0x00000CAA] = power3;
   data[0x00000DB6] = power4;
   data[0x00000E0E] = power4;
   data[0x00000E66] = power4;
   data[0x00000EBE] = power4;
}

void _stdcall patchFile(unsigned char* const data) {
   if (prevRecord == nullptr) {
      return;
   }
   patchPlayer02C(data);
   // patchPlayer01C(data); // 些細な違いすぎてリプレイ互換性壊すだけになりがちなのでdisable
   patchHayatsumi(data);
   patch4men(data);
}

bool patchFileDecode() {
   // 余白に自作コードをねじ込む
   {
      unsigned char newCode[] = {
         0x57,                         // PUSH EDI
         0x68, 0x00, 0x00, 0x00, 0x00, // PUSH 0x00000000(PUSH &patchFile)
         0x58,                         // POP EAX
         0xFF, 0xD0,                   // CALL EAX
         // ここから上書きで消した元のコード
         0x6A, 0x02,                   // PUSH 2
         0xE8, 0x50, 0xAF, 0xF6, 0xFF, // CALL 0x00404E20
         // 元のコードに戻す
         0xE9, 0xCB, 0x82, 0xF6, 0xFF, // JMP 0x004021A0
      };
      *reinterpret_cast<unsigned int *>(&newCode[2]) = reinterpret_cast<unsigned int>(&patchFile);
      unsigned char oldCode[_countof(newCode)] = {
         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      };
      if (!ChangeCode(0x00499EC0, oldCode, newCode, sizeof(newCode))) {
         return false;
      }
   }
   // ファイルデコード処理直後から自作部分へ飛ばす
   {
      const unsigned char newCode[] = {
         0xE9, 0x22, 0x7D, 0x09, 0x00, // JMP 0x499EC0
         0x90,                         // NOP
         0x90,                         // NOP
      };
      unsigned char oldCode[_countof(newCode)] = {
         0x6A, 0x02,                   // PUSH 2
         0xE8, 0x80, 0x2C, 0x00, 0x00, // CALL 0x00404E20
      };
      if (!ChangeCode(0x402199, oldCode, newCode, sizeof(newCode))) {
         return false;
      }
   }
   return true;
}

// ED呼び出しでmsgファイルとの対応を変更する
void _stdcall patchAchievementMain() {
   unsigned int &index = *reinterpret_cast<unsigned int*>(0x004B23E0);
   char * const filename = reinterpret_cast<char*>(0x52A810);
   const char* const map[] = {
      "e01.msg",
      "e02.msg",
      "e03.msg",
      "e04.msg",
      "e05.msg",
      "e06.msg",
      "e07.msg",
      "e08.msg",
      "e09.msg",
      "e10.msg",
      "e11.msg",
      "e12.msg",
   };
   if (_countof(map) > index) {
      ::strcpy_s(filename, 256, map[index]);
   }
}

// Extra会話呼び出しで自機タイプの対応を変更する
void _stdcall patchAchievementMain2(const unsigned int tropyId) {
   unsigned int &playerId = *reinterpret_cast<unsigned int *>(0x004B59F4);
   unsigned int &typeId = *reinterpret_cast<unsigned int*>(0x004B59F8);
   const unsigned int map[][2] = {
      // 0〜11は来ないはずだが念のためオオカミ霊夢を入れておく
      {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},
      {0, 0}, {0, 0},
      // 霊夢
      {0, 0}, {0, 1}, {0, 2},
      // 魔理沙
      {1, 0}, {1, 1}, {1, 2},
      // 妖夢
      {2, 0}, {2, 1}, {2, 2},
   };
   if (tropyId >= _countof(map)) {
      return;
   }
   playerId = map[tropyId][0];
   typeId = map[tropyId][1];
}

bool patchAchievement() {
   // 余白に自作コードをねじ込む
   {
      unsigned char newCode[] = {
         0x52,                         // PUSH EDX
         0x68, 0x00, 0x00, 0x00, 0x00, // PUSH 0x00000000(PUSH &patchAchievementMain)
         0x58,                         // POP EAX
         0xFF, 0xD0,                   // CALL EAX
         0x5A,                         // POP EDX
         // ここから上書きで消した元のコード
         0x6A, 0x00,                   // PUSH 0
         0xB9, 0x10, 0xA8, 0x52, 0x00, // MOV ECX, 0x0052A810
         // 元のコードに戻す
         0xE9, 0x9E, 0x21, 0xF8, 0xFF, // JMP 0x0041C094
      };
      *reinterpret_cast<unsigned int*>(&newCode[2]) = reinterpret_cast<unsigned int>(&patchAchievementMain);
      unsigned char oldCode[_countof(newCode)] = {
         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      };
      if (!ChangeCode(0x00499EE0, oldCode, newCode, sizeof(newCode))) {
         return false;
      }
   }
   // .msg読み込み直前に自作部分へ飛ばす
   {
      const unsigned char newCode[] = {
         0xE9, 0x4E, 0xDE, 0x07, 0x00, // JMP 0x499EE0
         0x90,                         // NOP
         0x90,                         // NOP
      };
      unsigned char oldCode[_countof(newCode)] = {
         0x6A, 0x00,                   // PUSH 0
         0xB9, 0x10, 0xA8, 0x52, 0x00, // MOV ECX, 0x0052A810
      };
      if (!ChangeCode(0x0041C08D, oldCode, newCode, sizeof(newCode))) {
         return false;
      }
   }
   // グローバル変数のクリア処理を取り除く
   {
      const unsigned char newCode[] = {
         0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // NOP
      };
      unsigned char oldCode[_countof(newCode)] = {
         0xC7, 0x05, 0xE0, 0x23, 0x4B, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, // MOV DWORD PTR DS:[0x4B23E0], -1
      };
      if (!ChangeCode(0x0041BFD1, oldCode, newCode, sizeof(newCode))) {
         return false;
      }
   }
   // 実績12番までED呼び出しルートに分岐させる
   {
      const unsigned char newCode[] = {
         0x83, 0xF9, 0x0B, // CMP 0x0A
      };
      unsigned char oldCode[_countof(newCode)] = {
         0x83, 0xF9, 0x08, // CMP 0x08
      };
      if (!ChangeCode(0x0045CB89, oldCode, newCode, sizeof(newCode))) {
         return false;
      }
   }
   // 余白に自作コードをねじ込む2
   {
      unsigned char newCode[] = {
         0x52,                         // PUSH EDX
         0xFF, 0x73, 0x24,             // PUSH DWORD PTR DS:[EBX+24]
         0x68, 0x00, 0x00, 0x00, 0x00, // PUSH 0x00000000(PUSH &patchAchievementMain2)
         0x58,                         // POP EAX
         0xFF, 0xD0,                   // CALL EAX
         0x5A,                         // POP EDX
         // 元のコードに戻す
         0xE9, 0xEC, 0x30, 0xFC, 0xFF, // JMP 0x0045CFFE
      };
      *reinterpret_cast<unsigned int*>(&newCode[5]) = reinterpret_cast<unsigned int>(&patchAchievementMain2);
      unsigned char oldCode[_countof(newCode)] = {
         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      };
      if (!ChangeCode(0x00499F00, oldCode, newCode, sizeof(newCode))) {
         return false;
      }
   }
   // Extra会話呼び出し直前で自作部分へ飛ばす
   {
      const unsigned char newCode[] = {
         0xE9, 0x08, 0xCF, 0x03, 0x00, // JMP 0x499F00
         0x90,                         // NOP
         0x90,                         // NOP
         0x90,                         // NOP
         0x90,                         // NOP
         0x90,                         // NOP
         0x90,                         // NOP
      };
      unsigned char oldCode[_countof(newCode)] = {
         0x8B, 0x4B, 0x24,             // MOV ECX, DWORD PTR DS:[EBX*24]
         0xB8, 0x56, 0x55, 0x55, 0x55, // MOV EAX, 0x55555556
         0x83, 0xE9, 0x0C,             // SUB ECX, 0C
      };
      if (!ChangeCode(0x0045CFF3, oldCode, newCode, sizeof(newCode))) {
         return false;
      }
   }
   // 既定の自機設定を取り除く
   {
      const unsigned char newCode[] = {
         0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // NOP
      };
      unsigned char oldCode[_countof(newCode)] = {
         0x89, 0x0D, 0xF4, 0x59, 0x4B, 0x00, // MOV DWORD PTR DS:[0x004B59F4], ECX
      };
      if (!ChangeCode(0x0045D02A, oldCode, newCode, sizeof(newCode))) {
         return false;
      }
   }
   // 既定の自機設定を取り除くその2
   {
      const unsigned char newCode[] = {
         0x90, 0x90, 0x90, 0x90, 0x90, // NOP
      };
      unsigned char oldCode[_countof(newCode)] = {
         0xA3, 0xF8, 0x59, 0x4B, 0x00, // MOV DWORD PTR DS:[0x004B59F8], EAX
      };
      if (!ChangeCode(0x0045D061, oldCode, newCode, sizeof(newCode))) {
         return false;
      }
   }
   return true;
}

// replayフォルダ内にsnapshotフォルダが作成されるのを阻止
void _stdcall patchSnapshotMain() {
   ::SetCurrentDirectoryW(L"..\\");
}

bool patchSnapshot() {
   // 余白に自作コードをねじ込む
   {
      unsigned char newCode[] = {
         // 上書きした処理の代替処理
         0x5F,                         // POP EDI
         0x85, 0xC0,                   // TEST EAX
         0x74, 0x05,                   // JE SHORT 0x00499F2A
         0xE9, 0xA3, 0x7C, 0xFC, 0xFF, // JMP 0x00461BCD
         // 自作コードを実行
         0x57,                         // PUSH EDI
         0x68, 0x00, 0x00, 0x00, 0x00, // PUSH 0x00000000(PUSH &patchSnapshotMain)
         0x58,                         // POP EAX
         0xFF, 0xD0,                   // CALL EAX
         0x5F,                         // POP EDI
         // 元のコードに戻す
         0xE9, 0xA9, 0x7C, 0xFC, 0xFF, // JMP 0x00461BE2
      };
      *reinterpret_cast<unsigned int*>(&newCode[12]) = reinterpret_cast<unsigned int>(&patchSnapshotMain);
      unsigned char oldCode[_countof(newCode)] = {
         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      };
      if (!ChangeCode(0x00499F20, oldCode, newCode, sizeof(newCode))) {
         return false;
      }
   }
   // セーブデータ初期化処理中に自作部分へ飛ばす
   {
      const unsigned char newCode[] = {
         0xE9, 0x53, 0x83, 0x03, 0x00, // JMP 0x00499F20
      };
      unsigned char oldCode[_countof(newCode)] = {
         0x5F,       // POP EDI
         0x85, 0xC0, // TEST EAX,EAX
         0x74, 0x15, // JE SHORT 0x00461BE2
      };
      if (!ChangeCode(0x00461BC8, oldCode, newCode, sizeof(newCode))) {
         return false;
      }
   }
   return true;
}

// 実績を獲得する
void getTrophy(const unsigned int trophyId) {
   const auto proc = reinterpret_cast<void cdecl(*)(const unsigned int trophyId)>(0x00499F50);
   __asm {
      mov ecx, trophyId;
      call proc;
   }
}

// リプレイ中ならtrueを返す
bool isReplay() {
   const unsigned int baseAddr = *reinterpret_cast<const unsigned int*>(0x004B76B0);
   const unsigned int flag = *reinterpret_cast<const unsigned int*>(baseAddr + 0xA8);
   return flag != 0;
}

// replayフォルダ内にsnapshotフォルダが作成されるのを阻止
void _stdcall patchReplayAchievementMain(const unsigned int trophyId) {
   if (isReplay()) {
      return;
   }
   getTrophy(trophyId);
}

bool patchReplayAchievement() {
   // 余白に自作コードをねじ込む
   {
      unsigned char newCode[] = {
         // 自作コードを実行
         0x51,                         // PUSH ECX
         0x68, 0x00, 0x00, 0x00, 0x00, // PUSH 0x00000000(PUSH &patchSnapshotMain)
         0x58,                         // POP EAX
         0xFF, 0xD0,                   // CALL EAX
         // 元のコードに戻す
         0xC3,                         // RETN
      };
      *reinterpret_cast<unsigned int*>(&newCode[2]) = reinterpret_cast<unsigned int>(&patchReplayAchievementMain);
      unsigned char oldCode[_countof(newCode)] = {
         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      };
      if (!ChangeCode(0x00499F40, oldCode, newCode, sizeof(newCode))) {
         return false;
      }
   }
   // 実績開放関数先頭から自作部分へ飛ばす
   {
      const unsigned char newCode[] = {
         0xE9, 0x9B, 0xC6, 0x03, 0x00, // JMP 0x00499F40
         0x90, 0x90, 0x90, 0x90,       // NOP
      };
      unsigned char oldCode[_countof(newCode)] = {
         0x55,                         // PUSH EBP
         0x8B, 0xEC,                   // MOV EBP, ESP
         0x51,                         // PUSH ECX
         0xA1, 0xDC, 0x77, 0x4B, 0x00, // MOV EAX, DWORD PTR DS:[0x004B77DC]
      };
      if (!ChangeCode(0x0045D8A0, oldCode, newCode, sizeof(newCode))) {
         return false;
      }
   }
   // 余白に本来の実績解放関数先頭相当のコードをねじ込む
   {
      const unsigned char newCode[] = {
         0x55,                         // PUSH EBP
         0x8B, 0xEC,                   // MOV EBP, ESP
         0x51,                         // PUSH ECX
         0xA1, 0xDC, 0x77, 0x4B, 0x00, // MOV EAX, DWORD PTR DS:[0x004B77DC]
         0xE9, 0x4B, 0x39, 0xFC, 0xFF, // JMP 0x0045D8A9
      };
      const unsigned char oldCode[_countof(newCode)] = {
         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      };
      if (!ChangeCode(0x00499F50, oldCode, newCode, sizeof(newCode))) {
         return false;
      }
   }
   return true;
}

void main() {
   // デバッグ用に黒窓を生やす
   //org::click3::DllHackLib::SetupConsole();

   ::printf("ChangeIAT: ");
   if (ChangeIAT(::GetModuleHandleW(nullptr))) {
      ::printf("Success\n");
   } else {
      ::printf("Failure\n");
   }

   ::printf("patchFileDecode: ");
   if (patchFileDecode()) {
      ::printf("Success\n");
   } else {
      ::printf("Failure\n");
   }

   ::printf("patchAchievement: ");
   if (patchAchievement()) {
      ::printf("Success\n");
   } else {
      ::printf("Failure\n");
   }

   ::printf("patchSnapshot: ");
   if (patchSnapshot()) {
      ::printf("Success\n");
   }
   else {
      ::printf("Failure\n");
   }

   ::printf("patchReplayAchievement: ");
   if (patchReplayAchievement()) {
      ::printf("Success\n");
   }
   else {
      ::printf("Failure\n");
   }
}