text: ﻿/*
   *  MinHook - The Minimalistic API Hooking Library for x64/x86
   *  Copyright (C) 2009-2015 Tsuda Kageyu.
   *  All rights reserved.
   *
   *  Redistribution and use in source and binary forms, with or without
   *  modification, are permitted provided that the following conditions
   *  are met:
   *
   *   1. Redistributions of source code must retain the above copyright
   *      notice, this list of conditions and the following disclaimer.
   *   2. Redistributions in binary form must reproduce the above copyright
   *      notice, this list of conditions and the following disclaimer in the
   *      documentation and/or other materials provided with the distribution.
   *
   *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
   *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
   *  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
   *  OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
   *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
   *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  #define STRICT
  #define _WIN32_WINNT 0x0501
  #include <windows.h>
  #include <tlhelp32.h>
  #include <limits.h>
  
  #include "../include/MinHook.h"
  #include "buffer.h"
  #include "trampoline.h"
  
  #ifndef ARRAYSIZE
      #define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0]))
  #endif
  
  // Initial capacity of the HOOK_ENTRY buffer.
  #define INITIAL_HOOK_CAPACITY   32
  
  // Initial capacity of the thread IDs buffer.
  #define INITIAL_THREAD_CAPACITY 128
  
  // Special hook position values.
  #define INVALID_HOOK_POS UINT_MAX
  #define ALL_HOOKS_POS    UINT_MAX
  
  // Freeze() action argument defines.
  #define ACTION_DISABLE      0
  #define ACTION_ENABLE       1
  #define ACTION_APPLY_QUEUED 2
  
  // Thread access rights for suspending/resuming threads.
  #define THREAD_ACCESS \
      (THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION | THREAD_SET_CONTEXT)
  
  // Hook information.
  typedef struct _HOOK_ENTRY
  {
      LPVOID pTarget;             // Address of the target function.
      LPVOID pDetour;             // Address of the detour or relay function.
      LPVOID pTrampoline;         // Address of the trampoline function.
      UINT8  backup[8];           // Original prologue of the target function.
  
      BOOL   patchAbove  : 1;     // Uses the hot patch area.
      BOOL   isEnabled   : 1;     // Enabled.
      BOOL   queueEnable : 1;     // Queued for enabling/disabling when != isEnabled.
  
      UINT   nIP : 4;             // Count of the instruction boundaries.
      UINT8  oldIPs[8];           // Instruction boundaries of the target function.
      UINT8  newIPs[8];           // Instruction boundaries of the trampoline function.
  } HOOK_ENTRY, *PHOOK_ENTRY;
  
  // Suspended threads for Freeze()/Unfreeze().
  typedef struct _FROZEN_THREADS
  {
      LPDWORD pItems;         // Data heap
      UINT    capacity;       // Size of allocated data heap, items
      UINT    size;           // Actual number of data items
  } FROZEN_THREADS, *PFROZEN_THREADS;
  
  //-------------------------------------------------------------------------
  // Global Variables:
  //-------------------------------------------------------------------------
  
  // Spin lock flag for EnterSpinLock()/LeaveSpinLock().
  volatile LONG g_isLocked = FALSE;
  
  // Private heap handle. If not NULL, this library is initialized.
  HANDLE g_hHeap = NULL;
  
  // Hook entries.
  struct
  {
      PHOOK_ENTRY pItems;     // Data heap
      UINT        capacity;   // Size of allocated data heap, items
      UINT        size;       // Actual number of data items
  } g_hooks;
  
  //-------------------------------------------------------------------------
  // Returns INVALID_HOOK_POS if not found.
  static UINT FindHookEntry(LPVOID pTarget)
  {
      UINT i;
      for (i = 0; i < g_hooks.size; ++i)
      {
          if ((ULONG_PTR)pTarget == (ULONG_PTR)g_hooks.pItems[i].pTarget)
              return i;
      }
  
      return INVALID_HOOK_POS;
  }
  
  //-------------------------------------------------------------------------
  static PHOOK_ENTRY AddHookEntry()
  {
      if (g_hooks.pItems == NULL)
      {
          g_hooks.capacity = INITIAL_HOOK_CAPACITY;
          g_hooks.pItems = (PHOOK_ENTRY)HeapAlloc(
              g_hHeap, 0, g_hooks.capacity * sizeof(HOOK_ENTRY));
          if (g_hooks.pItems == NULL)
              return NULL;
      }
      else if (g_hooks.size >= g_hooks.capacity)
      {
          PHOOK_ENTRY p = (PHOOK_ENTRY)HeapReAlloc(
              g_hHeap, 0, g_hooks.pItems, (g_hooks.capacity * 2) * sizeof(HOOK_ENTRY));
          if (p == NULL)
              return NULL;
  
          g_hooks.capacity *= 2;
          g_hooks.pItems = p;
      }
  
      return &g_hooks.pItems[g_hooks.size++];
  }
  
  //-------------------------------------------------------------------------
  static void DeleteHookEntry(UINT pos)
  {
      if (pos < g_hooks.size - 1)
          g_hooks.pItems[pos] = g_hooks.pItems[g_hooks.size - 1];
  
      g_hooks.size--;
  
      if (g_hooks.capacity / 2 >= INITIAL_HOOK_CAPACITY && g_hooks.capacity / 2 >= g_hooks.size)
      {
          PHOOK_ENTRY p = (PHOOK_ENTRY)HeapReAlloc(
              g_hHeap, 0, g_hooks.pItems, (g_hooks.capacity / 2) * sizeof(HOOK_ENTRY));
          if (p == NULL)
              return;
  
          g_hooks.capacity /= 2;
          g_hooks.pItems = p;
      }
  }
  
  //-------------------------------------------------------------------------
  static DWORD_PTR FindOldIP(PHOOK_ENTRY pHook, DWORD_PTR ip)
  {
      UINT i;
  
      if (pHook->patchAbove && ip == ((DWORD_PTR)pHook->pTarget - sizeof(JMP_REL)))
          return (DWORD_PTR)pHook->pTarget;
  
      for (i = 0; i < pHook->nIP; ++i)
      {
          if (ip == ((DWORD_PTR)pHook->pTrampoline + pHook->newIPs[i]))
              return (DWORD_PTR)pHook->pTarget + pHook->oldIPs[i];
      }
  
  #ifdef _M_X64
      // Check relay function.
      if (ip == (DWORD_PTR)pHook->pDetour)
          return (DWORD_PTR)pHook->pTarget;
  #endif
  
      return 0;
  }
  
  //-------------------------------------------------------------------------
  static DWORD_PTR FindNewIP(PHOOK_ENTRY pHook, DWORD_PTR ip)
  {
      UINT i;
      for (i = 0; i < pHook->nIP; ++i)
      {
          if (ip == ((DWORD_PTR)pHook->pTarget + pHook->oldIPs[i]))
              return (DWORD_PTR)pHook->pTrampoline + pHook->newIPs[i];
      }
  
      return 0;
  }
  
  //-------------------------------------------------------------------------
  static void ProcessThreadIPs(HANDLE hThread, UINT pos, UINT action)
  {
      // If the thread suspended in the overwritten area,
      // move IP to the proper address.
  
      CONTEXT c;
  #ifdef _M_X64
      DWORD64 *pIP = &c.Rip;
  #else
      DWORD   *pIP = &c.Eip;
  #endif
      UINT count;
  
      c.ContextFlags = CONTEXT_CONTROL;
      if (!GetThreadContext(hThread, &c))
          return;
  
      if (pos == ALL_HOOKS_POS)
      {
          pos = 0;
          count = g_hooks.size;
      }
      else
      {
          count = pos + 1;
      }
  
      for (; pos < count; ++pos)
      {
          PHOOK_ENTRY pHook = &g_hooks.pItems[pos];
          BOOL        enable;
          DWORD_PTR   ip;
  
          switch (action)
          {
          case ACTION_DISABLE:
              enable = FALSE;
              break;
  
          case ACTION_ENABLE:
              enable = TRUE;
              break;
  
          case ACTION_APPLY_QUEUED:
              enable = pHook->queueEnable;
              break;
          }
          if (pHook->isEnabled == enable)
              continue;
  
          if (enable)
              ip = FindNewIP(pHook, *pIP);
          else
              ip = FindOldIP(pHook, *pIP);
  
          if (ip != 0)
          {
              *pIP = ip;
              SetThreadContext(hThread, &c);
          }
      }
  }
  
  //-------------------------------------------------------------------------
  static VOID EnumerateThreads(PFROZEN_THREADS pThreads)
  {
      HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
      if (hSnapshot != INVALID_HANDLE_VALUE)
      {
          THREADENTRY32 te;
          te.dwSize = sizeof(THREADENTRY32);
          if (Thread32First(hSnapshot, &te))
          {
              do
              {
                  if (te.dwSize >= (FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(DWORD))
                      && te.th32OwnerProcessID == GetCurrentProcessId()
                      && te.th32ThreadID != GetCurrentThreadId())
                  {
                      if (pThreads->pItems == NULL)
                      {
                          pThreads->capacity = INITIAL_THREAD_CAPACITY;
                          pThreads->pItems
                              = (LPDWORD)HeapAlloc(g_hHeap, 0, pThreads->capacity * sizeof(DWORD));
                          if (pThreads->pItems == NULL)
                              break;
                      }
                      else if (pThreads->size >= pThreads->capacity)
                      {
                          LPDWORD p = (LPDWORD)HeapReAlloc(
                              g_hHeap, 0, pThreads->pItems, (pThreads->capacity * 2) * sizeof(DWORD));
                          if (p == NULL)
                              break;
  
                          pThreads->capacity *= 2;
                          pThreads->pItems = p;
                      }
                      pThreads->pItems[pThreads->size++] = te.th32ThreadID;
                  }
  
                  te.dwSize = sizeof(THREADENTRY32);
              } while (Thread32Next(hSnapshot, &te));
          }
          CloseHandle(hSnapshot);
      }
  }
  
  //-------------------------------------------------------------------------
  static VOID Freeze(PFROZEN_THREADS pThreads, UINT pos, UINT action)
  {
      pThreads->pItems   = NULL;
      pThreads->capacity = 0;
      pThreads->size     = 0;
      EnumerateThreads(pThreads);
  
      if (pThreads->pItems != NULL)
      {
          UINT i;
          for (i = 0; i < pThreads->size; ++i)
          {
              HANDLE hThread = OpenThread(THREAD_ACCESS, FALSE, pThreads->pItems[i]);
              if (hThread != NULL)
              {
                  SuspendThread(hThread);
                  ProcessThreadIPs(hThread, pos, action);
                  CloseHandle(hThread);
              }
          }
      }
  }
  
  //-------------------------------------------------------------------------
  static VOID Unfreeze(PFROZEN_THREADS pThreads)
  {
      if (pThreads->pItems != NULL)
      {
          UINT i;
          for (i = 0; i < pThreads->size; ++i)
          {
              HANDLE hThread = OpenThread(THREAD_ACCESS, FALSE, pThreads->pItems[i]);
              if (hThread != NULL)
              {
                  ResumeThread(hThread);
                  CloseHandle(hThread);
              }
          }
  
          HeapFree(g_hHeap, 0, pThreads->pItems);
      }
  }
  
  //-------------------------------------------------------------------------
  static MH_STATUS EnableHookLL(UINT pos, BOOL enable)
  {
      PHOOK_ENTRY pHook = &g_hooks.pItems[pos];
      DWORD  oldProtect;
      SIZE_T patchSize    = sizeof(JMP_REL);
      LPBYTE pPatchTarget = (LPBYTE)pHook->pTarget;
  
      if (pHook->patchAbove)
      {
          pPatchTarget -= sizeof(JMP_REL);
          patchSize    += sizeof(JMP_REL_SHORT);
      }
  
      if (!VirtualProtect(pPatchTarget, patchSize, PAGE_EXECUTE_READWRITE, &oldProtect))
          return MH_ERROR_MEMORY_PROTECT;
  
      if (enable)
      {
          PJMP_REL pJmp = (PJMP_REL)pPatchTarget;
          pJmp->opcode = 0xE9;
          pJmp->operand = (UINT32)((LPBYTE)pHook->pDetour - (pPatchTarget + sizeof(JMP_REL)));
  
          if (pHook->patchAbove)
          {
              PJMP_REL_SHORT pShortJmp = (PJMP_REL_SHORT)pHook->pTarget;
              pShortJmp->opcode = 0xEB;
              pShortJmp->operand = (UINT8)(0 - (sizeof(JMP_REL_SHORT) + sizeof(JMP_REL)));
          }
      }
      else
      {
          if (pHook->patchAbove)
              memcpy(pPatchTarget, pHook->backup, sizeof(JMP_REL) + sizeof(JMP_REL_SHORT));
          else
              memcpy(pPatchTarget, pHook->backup, sizeof(JMP_REL));
      }
  
      VirtualProtect(pPatchTarget, patchSize, oldProtect, &oldProtect);
  
      // Just-in-case measure.
      FlushInstructionCache(GetCurrentProcess(), pPatchTarget, patchSize);
  
      pHook->isEnabled   = enable;
      pHook->queueEnable = enable;
  
      return MH_OK;
  }
  
  //-------------------------------------------------------------------------
  static MH_STATUS EnableAllHooksLL(BOOL enable)
  {
      MH_STATUS status = MH_OK;
      UINT i, first = INVALID_HOOK_POS;
  
      for (i = 0; i < g_hooks.size; ++i)
      {
          if (g_hooks.pItems[i].isEnabled != enable)
          {
              first = i;
              break;
          }
      }
  
      if (first != INVALID_HOOK_POS)
      {
          FROZEN_THREADS threads;
          Freeze(&threads, ALL_HOOKS_POS, enable ? ACTION_ENABLE : ACTION_DISABLE);
  
          for (i = first; i < g_hooks.size; ++i)
          {
              if (g_hooks.pItems[i].isEnabled != enable)
              {
                  status = EnableHookLL(i, enable);
                  if (status != MH_OK)
                      break;
              }
          }
  
          Unfreeze(&threads);
      }
  
      return status;
  }
  
  //-------------------------------------------------------------------------
  static VOID EnterSpinLock(VOID)
  {
      SIZE_T spinCount = 0;
  
      // Wait until the flag is FALSE.
      while (InterlockedCompareExchange(&g_isLocked, TRUE, FALSE) != FALSE)
      {
          // No need to generate a memory barrier here, since InterlockedCompareExchange()
          // generates a full memory barrier itself.
  
          // Prevent the loop from being too busy.
          if (spinCount < 32)
              Sleep(0);
          else
              Sleep(1);
  
          spinCount++;
      }
  }
  
  //-------------------------------------------------------------------------
  static VOID LeaveSpinLock(VOID)
  {
      // No need to generate a memory barrier here, since InterlockedExchange()
      // generates a full memory barrier itself.
  
      InterlockedExchange(&g_isLocked, FALSE);
  }
  
  //-------------------------------------------------------------------------
  MH_STATUS WINAPI MH_Initialize(VOID)
  {
      MH_STATUS status = MH_OK;
  
      EnterSpinLock();
  
      if (g_hHeap == NULL)
      {
          g_hHeap = HeapCreate(0, 0, 0);
          if (g_hHeap != NULL)
          {
              // Initialize the internal function buffer.
              InitializeBuffer();
          }
          else
          {
              status = MH_ERROR_MEMORY_ALLOC;
          }
      }
      else
      {
          status = MH_ERROR_ALREADY_INITIALIZED;
      }
  
      LeaveSpinLock();
  
      return status;
  }
  
  //-------------------------------------------------------------------------
  MH_STATUS WINAPI MH_Uninitialize(VOID)
  {
      MH_STATUS status = MH_OK;
  
      EnterSpinLock();
  
      if (g_hHeap != NULL)
      {
          status = EnableAllHooksLL(FALSE);
          if (status == MH_OK)
          {
              // Free the internal function buffer.
  
              // HeapFree is actually not required, but some tools detect a false
              // memory leak without HeapFree.
  
              UninitializeBuffer();
  
              HeapFree(g_hHeap, 0, g_hooks.pItems);
              HeapDestroy(g_hHeap);
  
              g_hHeap = NULL;
  
              g_hooks.pItems   = NULL;
              g_hooks.capacity = 0;
              g_hooks.size     = 0;
          }
      }
      else
      {
          status = MH_ERROR_NOT_INITIALIZED;
      }
  
      LeaveSpinLock();
  
      return status;
  }
  
  //-------------------------------------------------------------------------
  MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal)
  {
      MH_STATUS status = MH_OK;
  
      EnterSpinLock();
  
      if (g_hHeap != NULL)
      {
          if (IsExecutableAddress(pTarget) && IsExecutableAddress(pDetour))
          {
              UINT pos = FindHookEntry(pTarget);
              if (pos == INVALID_HOOK_POS)
              {
                  LPVOID pBuffer = AllocateBuffer(pTarget);
                  if (pBuffer != NULL)
                  {
                      TRAMPOLINE ct;
  
                      ct.pTarget = pTarget;
                      ct.pDetour = pDetour;
                      ct.pTrampoline = pBuffer;
                      if (CreateTrampolineFunction(&ct))
                      {
                          PHOOK_ENTRY pHook = AddHookEntry();
                          if (pHook != NULL)
                          {
                              pHook->pTarget     = ct.pTarget;
  #ifdef _M_X64
                              pHook->pDetour     = ct.pRelay;
  #else
                              pHook->pDetour     = ct.pDetour;
  #endif
                              pHook->pTrampoline = ct.pTrampoline;
                              pHook->patchAbove  = ct.patchAbove;
                              pHook->isEnabled   = FALSE;
                              pHook->queueEnable = FALSE;
                              pHook->nIP         = ct.nIP;
                              memcpy(pHook->oldIPs, ct.oldIPs, ARRAYSIZE(ct.oldIPs));
                              memcpy(pHook->newIPs, ct.newIPs, ARRAYSIZE(ct.newIPs));
  
                              // Back up the target function.
  
                              if (ct.patchAbove)
                              {
                                  memcpy(
                                      pHook->backup,
                                      (LPBYTE)pTarget - sizeof(JMP_REL),
                                      sizeof(JMP_REL) + sizeof(JMP_REL_SHORT));
                              }
                              else
                              {
                                  memcpy(pHook->backup, pTarget, sizeof(JMP_REL));
                              }
  
                              if (ppOriginal != NULL)
                                  *ppOriginal = pHook->pTrampoline;
                          }
                          else
                          {
                              status = MH_ERROR_MEMORY_ALLOC;
                          }
                      }
                      else
                      {
                          status = MH_ERROR_UNSUPPORTED_FUNCTION;
                      }
  
                      if (status != MH_OK)
                      {
                          FreeBuffer(pBuffer);
                      }
                  }
                  else
                  {
                      status = MH_ERROR_MEMORY_ALLOC;
                  }
              }
              else
              {
                  status = MH_ERROR_ALREADY_CREATED;
              }
          }
          else
          {
              status = MH_ERROR_NOT_EXECUTABLE;
          }
      }
      else
      {
          status = MH_ERROR_NOT_INITIALIZED;
      }
  
      LeaveSpinLock();
  
      return status;
  }
  
  //-------------------------------------------------------------------------
  MH_STATUS WINAPI MH_Orbit_CreateHookPrologEpilog(LPVOID pTarget, LPVOID pPrologCallback, LPVOID pEpilogCallback, LPVOID pReturnAddressCallback)
  {
      MH_STATUS status = MH_OK;
  
      EnterSpinLock();
  
      if (g_hHeap != NULL)
      {
          if (IsExecutableAddress(pTarget) /*&& IsExecutableAddress(pDetour)*/)
          {
              UINT pos = FindHookEntry(pTarget);
              if (pos == INVALID_HOOK_POS)
              {
                  LPVOID pBuffer = AllocateBuffer(pTarget);
                  if (pBuffer != NULL)
                  {
                      TRAMPOLINE ct;
  
                      ct.pTarget = pTarget;
                      ct.pDetour = NULL;
                      ct.pTrampoline = pBuffer;
                      ct.pPrologCallback = pPrologCallback;
                      ct.pEpilogCallback = pEpilogCallback;
                      ct.pReturnAddressCb = pReturnAddressCallback;
                      if (CreatePrologFunction(&ct))
                      {
                          PHOOK_ENTRY pHook = AddHookEntry();
                          if (pHook != NULL)
                          {
                              pHook->pTarget = ct.pTarget;
  #ifdef _M_X64
                              pHook->pDetour = ct.pRelay;
  #else
                              pHook->pDetour = ct.pDetour;
  #endif
                              pHook->pTrampoline = ct.pTrampoline;
                              pHook->patchAbove = ct.patchAbove;
                              pHook->isEnabled = FALSE;
                              pHook->queueEnable = FALSE;
                              pHook->nIP = ct.nIP;
                              memcpy(pHook->oldIPs, ct.oldIPs, ARRAYSIZE(ct.oldIPs));
                              memcpy(pHook->newIPs, ct.newIPs, ARRAYSIZE(ct.newIPs));
  
                              // Back up the target function.
  
                              if (ct.patchAbove)
                              {
                                  memcpy(
                                      pHook->backup,
                                      (LPBYTE)pTarget - sizeof(JMP_REL),
                                      sizeof(JMP_REL) + sizeof(JMP_REL_SHORT));
                              }
                              else
                              {
                                  memcpy(pHook->backup, pTarget, sizeof(JMP_REL));
                              }
                          }
                          else
                          {
                              status = MH_ERROR_MEMORY_ALLOC;
                          }
                      }
                      else
                      {
                          status = MH_ERROR_UNSUPPORTED_FUNCTION;
                      }
  
                      if (status != MH_OK)
                      {
                          FreeBuffer(pBuffer);
                      }
                  }
                  else
                  {
                      status = MH_ERROR_MEMORY_ALLOC;
                  }
              }
              else
              {
                  status = MH_ERROR_ALREADY_CREATED;
              }
          }
          else
          {
              status = MH_ERROR_NOT_EXECUTABLE;
          }
      }
      else
      {
          status = MH_ERROR_NOT_INITIALIZED;
      }
  
      LeaveSpinLock();
  
      return status;
  }
  
  //-------------------------------------------------------------------------
  MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget)
  {
      MH_STATUS status = MH_OK;
  
      EnterSpinLock();
  
      if (g_hHeap != NULL)
      {
          UINT pos = FindHookEntry(pTarget);
          if (pos != INVALID_HOOK_POS)
          {
              if (g_hooks.pItems[pos].isEnabled)
              {
                  FROZEN_THREADS threads;
                  Freeze(&threads, pos, ACTION_DISABLE);
  
                  status = EnableHookLL(pos, FALSE);
  
                  Unfreeze(&threads);
              }
  
              if (status == MH_OK)
              {
                  FreeBuffer(g_hooks.pItems[pos].pTrampoline);
                  DeleteHookEntry(pos);
              }
          }
          else
          {
              status = MH_ERROR_NOT_CREATED;
          }
      }
      else
      {
          status = MH_ERROR_NOT_INITIALIZED;
      }
  
      LeaveSpinLock();
  
      return status;
  }
  
  //-------------------------------------------------------------------------
  MH_STATUS MH_EnableHooks( DWORD64* pTargets, UINT numTargets, BOOL enable )
  {
      MH_STATUS status = MH_OK;
      if( numTargets == 0 )
      {
          return status;
      }
  
      EnterSpinLock();
  
      if( g_hHeap != NULL )
      {
          FROZEN_THREADS threads;
          Freeze( &threads, ALL_HOOKS_POS, enable ? ACTION_ENABLE : ACTION_DISABLE );
  
          for( UINT i = 0; i < numTargets; ++i )
          {
              UINT pos = FindHookEntry( (LPVOID)pTargets[i] );
              if( pos != INVALID_HOOK_POS )
              {
                  if( g_hooks.pItems[pos].isEnabled != enable )
                  {
                      status = EnableHookLL( pos, enable );
                  }
                  else
                  {
                      status = enable ? MH_ERROR_ENABLED : MH_ERROR_DISABLED;
                  }
              }
              else
              {
                  status = MH_ERROR_NOT_CREATED;
              }
          }
  
          Unfreeze( &threads );
      }
      else
      {
          status = MH_ERROR_NOT_INITIALIZED;
      }
  
      LeaveSpinLock();
  
      return status;
  }
  
  //-------------------------------------------------------------------------
  static MH_STATUS EnableHook(LPVOID pTarget, BOOL enable)
  {
      MH_STATUS status = MH_OK;
  
      EnterSpinLock();
  
      if (g_hHeap != NULL)
      {
          if (pTarget == MH_ALL_HOOKS)
          {
              status = EnableAllHooksLL(enable);
          }
          else
          {
              FROZEN_THREADS threads;
              UINT pos = FindHookEntry(pTarget);
              if (pos != INVALID_HOOK_POS)
              {
                  if (g_hooks.pItems[pos].isEnabled != enable)
                  {
                      Freeze(&threads, pos, ACTION_ENABLE);
  
                      status = EnableHookLL(pos, enable);
  
                      Unfreeze(&threads);
                  }
                  else
                  {
                      status = enable ? MH_ERROR_ENABLED : MH_ERROR_DISABLED;
                  }
              }
              else
              {
                  status = MH_ERROR_NOT_CREATED;
              }
          }
      }
      else
      {
          status = MH_ERROR_NOT_INITIALIZED;
      }
  
      LeaveSpinLock();
  
      return status;
  }
  
  //-------------------------------------------------------------------------
  MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget)
  {
      return EnableHook(pTarget, TRUE);
  }
  
  //-------------------------------------------------------------------------
  MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget)
  {
      return EnableHook(pTarget, FALSE);
  }
  
  //-------------------------------------------------------------------------
  static MH_STATUS QueueHook(LPVOID pTarget, BOOL queueEnable)
  {
      MH_STATUS status = MH_OK;
  
      EnterSpinLock();
  
      if (g_hHeap != NULL)
      {
          if (pTarget == MH_ALL_HOOKS)
          {
              UINT i;
              for (i = 0; i < g_hooks.size; ++i)
                  g_hooks.pItems[i].queueEnable = queueEnable;
          }
          else
          {
              UINT pos = FindHookEntry(pTarget);
              if (pos != INVALID_HOOK_POS)
              {
                  g_hooks.pItems[pos].queueEnable = queueEnable;
              }
              else
              {
                  status = MH_ERROR_NOT_CREATED;
              }
          }
      }
      else
      {
          status = MH_ERROR_NOT_INITIALIZED;
      }
  
      LeaveSpinLock();
  
      return status;
  }
  
  //-------------------------------------------------------------------------
  MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget)
  {
      return QueueHook(pTarget, TRUE);
  }
  
  //-------------------------------------------------------------------------
  MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget)
  {
      return QueueHook(pTarget, FALSE);
  }
  
  //-------------------------------------------------------------------------
  MH_STATUS WINAPI MH_DisableAllHooks(VOID)
  {
      UINT i;
      for (i = 0; i < g_hooks.size; ++i)
      {
          MH_QueueDisableHook( g_hooks.pItems[i].pTarget );
      }
  
      return MH_ApplyQueued();
  }
  
  //-------------------------------------------------------------------------
  MH_STATUS WINAPI MH_ApplyQueued(VOID)
  {
      MH_STATUS status = MH_OK;
      UINT i, first = INVALID_HOOK_POS;
  
      EnterSpinLock();
  
      if (g_hHeap != NULL)
      {
          for (i = 0; i < g_hooks.size; ++i)
          {
              if (g_hooks.pItems[i].isEnabled != g_hooks.pItems[i].queueEnable)
              {
                  first = i;
                  break;
              }
          }
  
          if (first != INVALID_HOOK_POS)
          {
              FROZEN_THREADS threads;
              Freeze(&threads, ALL_HOOKS_POS, ACTION_APPLY_QUEUED);
  
              for (i = first; i < g_hooks.size; ++i)
              {
                  PHOOK_ENTRY pHook = &g_hooks.pItems[i];
                  if (pHook->isEnabled != pHook->queueEnable)
                  {
                      status = EnableHookLL(i, pHook->queueEnable);
                      if (status != MH_OK)
                          break;
                  }
              }
  
              Unfreeze(&threads);
          }
      }
      else
      {
          status = MH_ERROR_NOT_INITIALIZED;
      }
  
      LeaveSpinLock();
  
      return status;
  }
  
  //-------------------------------------------------------------------------
  MH_STATUS WINAPI MH_CreateHookApi(
      LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal)
  {
      HMODULE hModule;
      LPVOID  pTarget;
  
      hModule = GetModuleHandleW(pszModule);
      if (hModule == NULL)
          return MH_ERROR_MODULE_NOT_FOUND;
  
      pTarget = (LPVOID)GetProcAddress(hModule, pszProcName);
      if (pTarget == NULL)
          return MH_ERROR_FUNCTION_NOT_FOUND;
  
      return MH_CreateHook(pTarget, pDetour, ppOriginal);
  }
  
  //-------------------------------------------------------------------------
  const char * WINAPI MH_StatusToString(MH_STATUS status)
  {
  #define MH_ST2STR(x)    \
      case x:             \
          return #x;
  
      switch (status) {
          MH_ST2STR(MH_UNKNOWN)
          MH_ST2STR(MH_OK)
          MH_ST2STR(MH_ERROR_ALREADY_INITIALIZED)
          MH_ST2STR(MH_ERROR_NOT_INITIALIZED)
          MH_ST2STR(MH_ERROR_ALREADY_CREATED)
          MH_ST2STR(MH_ERROR_NOT_CREATED)
          MH_ST2STR(MH_ERROR_ENABLED)
          MH_ST2STR(MH_ERROR_DISABLED)
          MH_ST2STR(MH_ERROR_NOT_EXECUTABLE)
          MH_ST2STR(MH_ERROR_UNSUPPORTED_FUNCTION)
          MH_ST2STR(MH_ERROR_MEMORY_ALLOC)
          MH_ST2STR(MH_ERROR_MEMORY_PROTECT)
          MH_ST2STR(MH_ERROR_MODULE_NOT_FOUND)
          MH_ST2STR(MH_ERROR_FUNCTION_NOT_FOUND)
      }
  
  #undef MH_ST2STR
  
      return "(unknown)";
  }
  
  extern void OrbitPrologAsm();
  extern void OrbitEpilogAsm();
  
  //-------------------------------------------------------------------------
  void WINAPI MH_Test()
  {
  #if defined(_M_X64) || defined(__x86_64__)
      OrbitPrologAsm();
      OrbitEpilogAsm();
  #endif
  }