m0: binkw32 proxy shim, minhook, trace hook on Application::Initialize; build/deploy tooling

This commit is contained in:
alex 2026-09-07 17:22:15 -04:00
parent 4a15301db6
commit 5f5bc41fd5
27 changed files with 3886 additions and 0 deletions

42
CMakeLists.txt Normal file
View file

@ -0,0 +1,42 @@
cmake_minimum_required(VERSION 3.25)
project(sots-engine LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# Build id stamped into the shim log banner (tools/build-shim.sh passes git describe + UTC time).
set(SHIM_BUILD_ID "dev" CACHE STRING "Build identifier logged by the shim")
# Provenance of the binary facts: the generated header's own "Source:" line, forwarded verbatim
# so every shim.log records which RE snapshot the RVAs came from.
file(STRINGS include/generated/sots_addresses.h _addr_provenance REGEX "^// Source:")
list(GET _addr_provenance 0 _addr_provenance)
string(REGEX REPLACE "^// " "" SOTS_ADDR_PROVENANCE "${_addr_provenance}")
add_library(sots_addresses INTERFACE)
target_include_directories(sots_addresses INTERFACE ${CMAKE_SOURCE_DIR}/include)
if(WIN32)
# ---- shim: proxy binkw32.dll that the original game loads (Phase 2 frontend) ----
add_library(minhook STATIC
third_party/minhook/src/buffer.c
third_party/minhook/src/hook.c
third_party/minhook/src/trampoline.c
third_party/minhook/src/hde/hde32.c)
target_include_directories(minhook PUBLIC third_party/minhook/include)
add_library(binkw32 SHARED src/shim/main.cpp src/shim/binkw32.def)
target_link_libraries(binkw32 PRIVATE minhook sots_addresses)
target_compile_definitions(binkw32 PRIVATE
SHIM_BUILD_ID="${SHIM_BUILD_ID}"
SOTS_ADDR_PROVENANCE="${SOTS_ADDR_PROVENANCE}")
# Must be exactly binkw32.dll (no "lib" prefix); export names come solely from the .def.
set_target_properties(binkw32 PROPERTIES PREFIX "" OUTPUT_NAME "binkw32")
else()
# ---- host: nothing to build yet; keep the preset alive with a header smoke test ----
enable_testing()
add_executable(addr_smoke tests/addr_smoke.cpp)
target_link_libraries(addr_smoke PRIVATE sots_addresses)
add_test(NAME addr_smoke COMMAND addr_smoke)
endif()

27
CMakePresets.json Normal file
View file

@ -0,0 +1,27 @@
{
"version": 6,
"configurePresets": [
{
"name": "shim",
"displayName": "shim (i686 MinGW cross, binkw32.dll)",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build-shim",
"toolchainFile": "${sourceDir}/cmake/toolchain-mingw-i686.cmake",
"cacheVariables": { "CMAKE_BUILD_TYPE": "RelWithDebInfo" }
},
{
"name": "host",
"displayName": "host (Linux, tests)",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build-host",
"cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" }
}
],
"buildPresets": [
{ "name": "shim", "configurePreset": "shim" },
{ "name": "host", "configurePreset": "host" }
],
"testPresets": [
{ "name": "host", "configurePreset": "host", "output": { "outputOnFailure": true } }
]
}

View file

@ -0,0 +1,17 @@
# Cross toolchain: Linux host -> 32-bit Windows (the game is a 32-bit MSVC 2010 exe).
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR i686)
set(_triple i686-w64-mingw32)
set(CMAKE_C_COMPILER ${_triple}-gcc)
set(CMAKE_CXX_COMPILER ${_triple}-g++)
set(CMAKE_RC_COMPILER ${_triple}-windres)
set(CMAKE_FIND_ROOT_PATH /usr/${_triple})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# No libgcc/libstdc++ DLLs next to the game: link the runtimes in.
set(CMAKE_EXE_LINKER_FLAGS_INIT "-static-libgcc -static-libstdc++")
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-static-libgcc -static-libstdc++")

72
docs/M0.md Normal file
View file

@ -0,0 +1,72 @@
# M0 — shim bootstrap (proxy `binkw32.dll` + one hook)
**Result (2026-09-07): proven end to end on the real game.** The proxy DLL loads, forwards all
Bink calls, relocates the target RVA against the ASLR'd exe base, hooks `Mars::Application::Initialize`,
logs the call, and the game plays its intro and reaches the main menu.
## What was built
- `src/shim/binkw32.def` — 66 forwarders (`_BinkOpen@8 = binkw32_real._BinkOpen@8 @41` style),
names and ordinals mirrored from the real DLL. Note: the real DLL has **66** exports including
`_RADTimerRead@0`, which earlier lists missed. The exe imports 12 of them **by name** (no ordinals).
- `src/shim/main.cpp` — `DllMain` → `Shim_Init`: opens `<dll dir>\shim.log`, logs banner (build id,
exe path, exe base + ASLR delta, provenance line lifted from `sots_addresses.h`), reads
`shim.cfg` (`hooks=trace|off`), `MH_Initialize`, one MinHook hook on
`exeBase + sots::addr::Mars_Application_Initialize`, `MH_EnableHook`. Detach → `MH_Uninitialize`.
- `third_party/minhook/` — MinHook vendored (BSD-2, see `VENDOR.txt` for commit).
- `CMakeLists.txt`, `cmake/toolchain-mingw-i686.cmake`, `CMakePresets.json` (`shim` = i686 MinGW
cross, `host` = Linux placeholder test `tests/addr_smoke.cpp`).
- `tools/build-shim.sh` (build box), `tools/sync-build.sh` (dev box → build box), `tools/deploy.ps1`,
`tools/undeploy.ps1` (game VM).
## Build & deploy
```
# dev box: push the tree to spicy:/bulk-storage/re-lab/build/sots-engine (== CT111 /srv/re-lab/...)
# and run tools/build-shim.sh inside CT111 (needs g++-mingw-w64-i686 binutils-mingw-w64-i686 cmake ninja-build)
tools/sync-build.sh
# -> builds build-shim/binkw32.dll, diffs its export-name table against the real DLL (fails on mismatch),
# stages binkw32.dll + shim.cfg + deploy.ps1 + undeploy.ps1 + BUILD_ID in /srv/re-lab/shim/dist (= Z:\shim\dist)
# game VM (re@192.168.10.139): Z: is per-logon, so pass share creds (or set RELAB_USER/RELAB_PASS)
powershell -ExecutionPolicy Bypass -File deploy.ps1 -ShareUser re -SharePass <pw>
# stops the game, keeps the original as C:\SOTS\binkw32_real.dll (only if not already a proxy),
# copies the proxy, writes shim.cfg only if absent, relaunches via scheduled task SOTS
powershell -ExecutionPolicy Bypass -File undeploy.ps1 # put the original back
```
Logs: `C:\SOTS\shim.log` (append mode, one banner per run). Screenshots: `qm monitor 140` screendump.
## Evidence (`/bulk-storage/re-lab/shim/logs/`)
- `m0.log` — the shim log. Key lines from the passing run (build `4a15301-20260907T2118Z`):
```
==== sots-engine shim (binkw32 proxy) build 4a15301-20260907T2118Z ====
exe base=0x00e80000 (link-time image base 0x00400000, ASLR delta +11010048) pid=3808 shim=730c0000
addresses: Source: sots-re ghidra/addresses.json @ daea98f, generated 2026-09-07 by tools/gen_addresses.py
hook: Mars_Application_Initialize rva=0x004a0e50 -> va=01320e50
hook: MH_Initialize -> MH_OK / MH_CreateHook -> MH_OK (trampoline=00e50fe0) / MH_EnableHook -> MH_OK
Application::Initialize called (this=035c8128)
```
- `m0-main-menu.png` — main menu with the hook live (intro skipped with 3× Esc, ~35 s after launch).
- Export table: `build-shim/exports.txt`; `tools/build-shim.sh` printed
`exports: 66 names, identical to binkw32.dll`.
- `hooks=off` run (first banner in `m0.log`, pid 6396): forwarders alone play the Bink intro — proxy proven
separately from hooking.
- `tools/clean_room_check.sh` → OK.
## Gotchas
1. **Do not call the original from a C++ `thiscall` detour.** v1 did exactly that
(`void __thiscall Detour(void* self) { log; orig(self); }`) and the game died inside Initialize with
"Mars: Application error encountered" (`m0-crash-hookv1.{log,png}`). The prototype in
`sots_addresses.h` is `[unverified]`; if Initialize takes stack args, returns via EAX or relies on
EDX (fastcall-like), a calling wrapper corrupts the stack/registers on return. The shipped detour is
an asm stub: `pushfl/pushal`, call a C logger with ECX, `popal/popfl`, **`jmp` to the trampoline**.
It's correct for any convention but cannot log the return. Until a prototype is verified, use this
pattern for every trace hook; only replace-hooks (that never call the original) may be plain C++.
2. Hooking from `DllMain` worked (process is single-threaded at that point; MinHook only touches
kernel32). The fallback (install from the first forwarded Bink call) was not needed.
3. `--kill-at` / `--enable-stdcall-fixup` must NOT be passed to ld — they would strip the `@N`
suffixes the exe imports by name. With a plain `.def` MinGW exports the names verbatim.
4. `objdump -p` lists `+base[` rows in both the address table and the name table; compare the
`[Ordinal/Name Pointer] Table` section only (forwarder rows print `Forwarder RVA -- target`).
5. rsync `--exclude 'build-*'` also matches `build-shim.sh`; use `/build-*/`.
6. The CT is unprivileged: files written on the host must be `--chown=100000:100000` or the CT can't
write its build dir. `Z:` mappings are per logon on the VM; SSH sessions start without it.
7. `shim.log` is held open by the running game; move/rotate it only after `Stop-Process`.

71
src/shim/binkw32.def Normal file
View file

@ -0,0 +1,71 @@
; binkw32.dll proxy: every export of the real Bink DLL is forwarded to binkw32_real.dll
; (the original, renamed by tools/deploy.ps1). Names and ordinals mirror the real DLL exactly;
; the exe imports 12 of these by decorated name, so they must match byte-for-byte.
LIBRARY binkw32.dll
EXPORTS
_BinkBufferBlit@12 = binkw32_real._BinkBufferBlit@12 @1
_BinkBufferCheckWinPos@12 = binkw32_real._BinkBufferCheckWinPos@12 @2
_BinkBufferClear@8 = binkw32_real._BinkBufferClear@8 @3
_BinkBufferClose@4 = binkw32_real._BinkBufferClose@4 @4
_BinkBufferGetDescription@4 = binkw32_real._BinkBufferGetDescription@4 @5
_BinkBufferGetError@0 = binkw32_real._BinkBufferGetError@0 @6
_BinkBufferLock@4 = binkw32_real._BinkBufferLock@4 @7
_BinkBufferOpen@16 = binkw32_real._BinkBufferOpen@16 @8
_BinkBufferSetDirectDraw@8 = binkw32_real._BinkBufferSetDirectDraw@8 @9
_BinkBufferSetHWND@8 = binkw32_real._BinkBufferSetHWND@8 @10
_BinkBufferSetOffset@12 = binkw32_real._BinkBufferSetOffset@12 @11
_BinkBufferSetResolution@12 = binkw32_real._BinkBufferSetResolution@12 @12
_BinkBufferSetScale@12 = binkw32_real._BinkBufferSetScale@12 @13
_BinkBufferUnlock@4 = binkw32_real._BinkBufferUnlock@4 @14
_BinkCheckCursor@20 = binkw32_real._BinkCheckCursor@20 @15
_BinkClose@4 = binkw32_real._BinkClose@4 @16
_BinkCloseTrack@4 = binkw32_real._BinkCloseTrack@4 @17
_BinkControlBackgroundIO@8 = binkw32_real._BinkControlBackgroundIO@8 @18
_BinkControlPlatformFeatures@8 = binkw32_real._BinkControlPlatformFeatures@8 @19
_BinkCopyToBuffer@28 = binkw32_real._BinkCopyToBuffer@28 @20
_BinkCopyToBufferRect@44 = binkw32_real._BinkCopyToBufferRect@44 @21
_BinkDDSurfaceType@4 = binkw32_real._BinkDDSurfaceType@4 @22
_BinkDX8SurfaceType@4 = binkw32_real._BinkDX8SurfaceType@4 @23
_BinkDX9SurfaceType@4 = binkw32_real._BinkDX9SurfaceType@4 @24
_BinkDoFrame@4 = binkw32_real._BinkDoFrame@4 @25
_BinkGetError@0 = binkw32_real._BinkGetError@0 @26
_BinkGetFrameBuffersInfo@8 = binkw32_real._BinkGetFrameBuffersInfo@8 @27
_BinkGetKeyFrame@12 = binkw32_real._BinkGetKeyFrame@12 @28
_BinkGetPalette@4 = binkw32_real._BinkGetPalette@4 @29
_BinkGetRealtime@12 = binkw32_real._BinkGetRealtime@12 @30
_BinkGetRects@8 = binkw32_real._BinkGetRects@8 @31
_BinkGetSummary@8 = binkw32_real._BinkGetSummary@8 @32
_BinkGetTrackData@8 = binkw32_real._BinkGetTrackData@8 @33
_BinkGetTrackID@8 = binkw32_real._BinkGetTrackID@8 @34
_BinkGetTrackMaxSize@8 = binkw32_real._BinkGetTrackMaxSize@8 @35
_BinkGetTrackType@8 = binkw32_real._BinkGetTrackType@8 @36
_BinkGoto@12 = binkw32_real._BinkGoto@12 @37
_BinkIsSoftwareCursor@8 = binkw32_real._BinkIsSoftwareCursor@8 @38
_BinkLogoAddress@0 = binkw32_real._BinkLogoAddress@0 @39
_BinkNextFrame@4 = binkw32_real._BinkNextFrame@4 @40
_BinkOpen@8 = binkw32_real._BinkOpen@8 @41
_BinkOpenDirectSound@4 = binkw32_real._BinkOpenDirectSound@4 @42
_BinkOpenMiles@4 = binkw32_real._BinkOpenMiles@4 @43
_BinkOpenTrack@8 = binkw32_real._BinkOpenTrack@8 @44
_BinkOpenWaveOut@4 = binkw32_real._BinkOpenWaveOut@4 @45
_BinkPause@8 = binkw32_real._BinkPause@8 @46
_BinkRegisterFrameBuffers@8 = binkw32_real._BinkRegisterFrameBuffers@8 @47
_BinkRestoreCursor@4 = binkw32_real._BinkRestoreCursor@4 @48
_BinkService@4 = binkw32_real._BinkService@4 @49
_BinkSetError@4 = binkw32_real._BinkSetError@4 @50
_BinkSetFrameRate@8 = binkw32_real._BinkSetFrameRate@8 @51
_BinkSetIO@4 = binkw32_real._BinkSetIO@4 @52
_BinkSetIOSize@4 = binkw32_real._BinkSetIOSize@4 @53
_BinkSetMemory@8 = binkw32_real._BinkSetMemory@8 @54
_BinkSetMixBinVolumes@20 = binkw32_real._BinkSetMixBinVolumes@20 @55
_BinkSetMixBins@16 = binkw32_real._BinkSetMixBins@16 @56
_BinkSetPan@12 = binkw32_real._BinkSetPan@12 @57
_BinkSetSimulate@4 = binkw32_real._BinkSetSimulate@4 @58
_BinkSetSoundOnOff@8 = binkw32_real._BinkSetSoundOnOff@8 @59
_BinkSetSoundSystem@8 = binkw32_real._BinkSetSoundSystem@8 @60
_BinkSetSoundTrack@8 = binkw32_real._BinkSetSoundTrack@8 @61
_BinkSetVideoOnOff@8 = binkw32_real._BinkSetVideoOnOff@8 @62
_BinkSetVolume@12 = binkw32_real._BinkSetVolume@12 @63
_BinkShouldSkip@4 = binkw32_real._BinkShouldSkip@4 @64
_BinkWait@4 = binkw32_real._BinkWait@4 @65
_RADTimerRead@0 = binkw32_real._RADTimerRead@0 @66

184
src/shim/main.cpp Normal file
View file

@ -0,0 +1,184 @@
// sots-engine shim: a proxy binkw32.dll the original game loads.
//
// Every Bink export is forwarded to the real DLL by the linker (see binkw32.def); this file only
// exists to get code running inside the game process. M0 scope: log a banner, install one hook on
// Mars::Application::Initialize, and prove injection + ASLR relocation + hooking end to end.
//
// Binary facts (RVAs, calling conventions) come exclusively from the generated header.
#include <windows.h>
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include "MinHook.h"
#include "generated/sots_addresses.h"
namespace {
// ---- logging ------------------------------------------------------------------------------
FILE* g_log = nullptr;
char g_dir[MAX_PATH] = {}; // directory the shim DLL lives in (== game dir)
void Log(const char* fmt, ...) {
if (!g_log) return;
SYSTEMTIME st;
GetLocalTime(&st);
std::fprintf(g_log, "%02u:%02u:%02u.%03u [tid %5lu] ", st.wHour, st.wMinute, st.wSecond,
st.wMilliseconds, GetCurrentThreadId());
va_list ap;
va_start(ap, fmt);
std::vfprintf(g_log, fmt, ap);
va_end(ap);
std::fputc('\n', g_log);
std::fflush(g_log);
}
// ---- config (shim.cfg next to the DLL; "key=value" per line) ------------------------------
struct Config {
bool hooks = true; // hooks=trace (default) | off
};
Config ReadConfig() {
Config cfg;
char path[MAX_PATH];
std::snprintf(path, sizeof path, "%s\\shim.cfg", g_dir);
FILE* f = std::fopen(path, "r");
if (!f) {
Log("config: %s not found, using defaults", path);
return cfg;
}
char line[256];
while (std::fgets(line, sizeof line, f)) {
char* p = line;
while (*p == ' ' || *p == '\t') ++p;
if (*p == '#' || *p == ';' || *p == '\n' || *p == '\0') continue;
char* eq = std::strchr(p, '=');
if (!eq) continue;
*eq = '\0';
char* val = eq + 1;
val[std::strcspn(val, "\r\n")] = '\0';
if (std::strcmp(p, "hooks") == 0) {
cfg.hooks = std::strcmp(val, "off") != 0;
Log("config: hooks=%s", val);
} else {
Log("config: ignoring unknown key '%s'", p);
}
}
std::fclose(f);
return cfg;
}
// ---- the one M0 hook ----------------------------------------------------------------------
bool g_minhookUp = false;
} // namespace
// The detour is a register-transparent asm stub rather than a C++ thiscall function: the
// prototype in sots_addresses.h is [unverified], and a C++ detour that *calls* the original
// bakes in assumptions about stack args / EDX / return value. Calling out for the log with
// everything saved and then tail-jumping to the trampoline leaves the original's own `ret`
// in charge of cleanup, so the hook is correct for any calling convention. (v1 of this hook
// was a thiscall wrapper and crashed the game inside Initialize.)
extern "C" void* g_origInitialize; // trampoline, filled in by MH_CreateHook
void* g_origInitialize = nullptr;
extern "C" void InitializeDetour();
extern "C" void LogInitializeCalled(void* self) {
Log("Application::Initialize called (this=%p)", self);
}
asm(R"(
.text
.globl _InitializeDetour
_InitializeDetour:
pushfl
pushal
pushl %ecx
call _LogInitializeCalled
addl $4, %esp
popal
popfl
jmp *_g_origInitialize
)");
namespace {
void InstallHooks() {
const uintptr_t exeBase = reinterpret_cast<uintptr_t>(GetModuleHandleA(nullptr));
void* target = reinterpret_cast<void*>(exeBase + sots::addr::Mars_Application_Initialize);
Log("hook: Mars_Application_Initialize rva=0x%08x -> va=%p", sots::addr::Mars_Application_Initialize,
target);
MH_STATUS st = MH_Initialize();
Log("hook: MH_Initialize -> %s", MH_StatusToString(st));
if (st != MH_OK) return;
g_minhookUp = true;
st = MH_CreateHook(target, reinterpret_cast<void*>(&InitializeDetour), &g_origInitialize);
Log("hook: MH_CreateHook -> %s (trampoline=%p)", MH_StatusToString(st), g_origInitialize);
if (st != MH_OK) return;
st = MH_EnableHook(target);
Log("hook: MH_EnableHook -> %s", MH_StatusToString(st));
}
// ---- lifecycle -----------------------------------------------------------------------------
void Shim_Init(HMODULE self) {
GetModuleFileNameA(self, g_dir, sizeof g_dir);
if (char* slash = std::strrchr(g_dir, '\\')) *slash = '\0';
char logPath[MAX_PATH];
std::snprintf(logPath, sizeof logPath, "%s\\shim.log", g_dir);
g_log = std::fopen(logPath, "a");
if (!g_log) return; // nothing we can do; the forwarders still work without us
char exePath[MAX_PATH] = {};
GetModuleFileNameA(nullptr, exePath, sizeof exePath);
const uintptr_t exeBase = reinterpret_cast<uintptr_t>(GetModuleHandleA(nullptr));
Log("==== sots-engine shim (binkw32 proxy) build %s ====", SHIM_BUILD_ID);
Log("exe: %s", exePath);
Log("exe base=0x%08lx (link-time image base 0x%08lx, ASLR delta %+ld) pid=%lu shim=%p",
static_cast<unsigned long>(exeBase), static_cast<unsigned long>(sots::addr::IMAGE_BASE),
static_cast<long>(exeBase - sots::addr::IMAGE_BASE), GetCurrentProcessId(),
static_cast<void*>(self));
Log("addresses: %s", SOTS_ADDR_PROVENANCE);
const Config cfg = ReadConfig();
if (cfg.hooks) {
InstallHooks();
} else {
Log("hook: disabled by config");
}
}
void Shim_Shutdown() {
if (g_minhookUp) {
MH_STATUS st = MH_Uninitialize();
Log("shutdown: MH_Uninitialize -> %s", MH_StatusToString(st));
}
Log("==== shim detach ====");
if (g_log) std::fclose(g_log);
g_log = nullptr;
}
} // namespace
extern "C" BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, LPVOID) {
switch (reason) {
case DLL_PROCESS_ATTACH:
DisableThreadLibraryCalls(hinst);
Shim_Init(hinst);
break;
case DLL_PROCESS_DETACH:
Shim_Shutdown();
break;
}
return TRUE;
}

7
src/shim/shim.cfg Normal file
View file

@ -0,0 +1,7 @@
# sots-engine shim configuration. Read once when binkw32.dll (the proxy) loads.
# One key=value per line; '#' starts a comment.
#
# hooks = trace | off
# trace install the hooks and log them to shim.log (default)
# off forward Bink calls only; touch nothing in the exe
hooks=trace

11
tests/addr_smoke.cpp Normal file
View file

@ -0,0 +1,11 @@
// Host-side placeholder: the generated address header must compile and stay in-image.
#include "generated/sots_addresses.h"
#include <cstdio>
int main() {
static_assert(sots::addr::Mars_Application_Initialize > 0 &&
sots::addr::Mars_Application_Initialize < 0x01000000,
"RVA out of range");
std::printf("Mars_Application_Initialize rva=0x%08x\n", sots::addr::Mars_Application_Initialize);
return 0;
}

81
third_party/minhook/LICENSE.txt vendored Normal file
View file

@ -0,0 +1,81 @@
MinHook - The Minimalistic API Hooking Library for x64/x86
Copyright (C) 2009-2017 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.
================================================================================
Portions of this software are Copyright (c) 2008-2009, Vyacheslav Patkov.
================================================================================
Hacker Disassembler Engine 32 C
Copyright (c) 2008-2009, Vyacheslav Patkov.
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 REGENTS 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.
-------------------------------------------------------------------------------
Hacker Disassembler Engine 64 C
Copyright (c) 2008-2009, Vyacheslav Patkov.
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 REGENTS 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.

3
third_party/minhook/VENDOR.txt vendored Normal file
View file

@ -0,0 +1,3 @@
MinHook vendored from https://github.com/TsudaKageyu/minhook
commit 7586d1a64ad87864e336560327f6b4bd720a6917 (2026-09-07), copied src/ include/ LICENSE.txt unmodified.
License: BSD 2-Clause (see LICENSE.txt).

185
third_party/minhook/include/MinHook.h vendored Normal file
View file

@ -0,0 +1,185 @@
/*
* MinHook - The Minimalistic API Hooking Library for x64/x86
* Copyright (C) 2009-2017 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.
*/
#pragma once
#if !(defined _M_IX86) && !(defined _M_X64) && !(defined __i386__) && !(defined __x86_64__)
#error MinHook supports only x86 and x64 systems.
#endif
#include <windows.h>
// MinHook Error Codes.
typedef enum MH_STATUS
{
// Unknown error. Should not be returned.
MH_UNKNOWN = -1,
// Successful.
MH_OK = 0,
// MinHook is already initialized.
MH_ERROR_ALREADY_INITIALIZED,
// MinHook is not initialized yet, or already uninitialized.
MH_ERROR_NOT_INITIALIZED,
// The hook for the specified target function is already created.
MH_ERROR_ALREADY_CREATED,
// The hook for the specified target function is not created yet.
MH_ERROR_NOT_CREATED,
// The hook for the specified target function is already enabled.
MH_ERROR_ENABLED,
// The hook for the specified target function is not enabled yet, or already
// disabled.
MH_ERROR_DISABLED,
// The specified pointer is invalid. It points the address of non-allocated
// and/or non-executable region.
MH_ERROR_NOT_EXECUTABLE,
// The specified target function cannot be hooked.
MH_ERROR_UNSUPPORTED_FUNCTION,
// Failed to allocate memory.
MH_ERROR_MEMORY_ALLOC,
// Failed to change the memory protection.
MH_ERROR_MEMORY_PROTECT,
// The specified module is not loaded.
MH_ERROR_MODULE_NOT_FOUND,
// The specified function is not found.
MH_ERROR_FUNCTION_NOT_FOUND
}
MH_STATUS;
// Can be passed as a parameter to MH_EnableHook, MH_DisableHook,
// MH_QueueEnableHook or MH_QueueDisableHook.
#define MH_ALL_HOOKS NULL
#ifdef __cplusplus
extern "C" {
#endif
// Initialize the MinHook library. You must call this function EXACTLY ONCE
// at the beginning of your program.
MH_STATUS WINAPI MH_Initialize(VOID);
// Uninitialize the MinHook library. You must call this function EXACTLY
// ONCE at the end of your program.
MH_STATUS WINAPI MH_Uninitialize(VOID);
// Creates a hook for the specified target function, in disabled state.
// Parameters:
// pTarget [in] A pointer to the target function, which will be
// overridden by the detour function.
// pDetour [in] A pointer to the detour function, which will override
// the target function.
// ppOriginal [out] A pointer to the trampoline function, which will be
// used to call the original target function.
// This parameter can be NULL.
MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal);
// Creates a hook for the specified API function, in disabled state.
// Parameters:
// pszModule [in] A pointer to the loaded module name which contains the
// target function.
// pszProcName [in] A pointer to the target function name, which will be
// overridden by the detour function.
// pDetour [in] A pointer to the detour function, which will override
// the target function.
// ppOriginal [out] A pointer to the trampoline function, which will be
// used to call the original target function.
// This parameter can be NULL.
MH_STATUS WINAPI MH_CreateHookApi(
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal);
// Creates a hook for the specified API function, in disabled state.
// Parameters:
// pszModule [in] A pointer to the loaded module name which contains the
// target function.
// pszProcName [in] A pointer to the target function name, which will be
// overridden by the detour function.
// pDetour [in] A pointer to the detour function, which will override
// the target function.
// ppOriginal [out] A pointer to the trampoline function, which will be
// used to call the original target function.
// This parameter can be NULL.
// ppTarget [out] A pointer to the target function, which will be used
// with other functions.
// This parameter can be NULL.
MH_STATUS WINAPI MH_CreateHookApiEx(
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal, LPVOID *ppTarget);
// Removes an already created hook.
// Parameters:
// pTarget [in] A pointer to the target function.
MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget);
// Enables an already created hook.
// Parameters:
// pTarget [in] A pointer to the target function.
// If this parameter is MH_ALL_HOOKS, all created hooks are
// enabled in one go.
MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget);
// Disables an already created hook.
// Parameters:
// pTarget [in] A pointer to the target function.
// If this parameter is MH_ALL_HOOKS, all created hooks are
// disabled in one go.
MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget);
// Queues to enable an already created hook.
// Parameters:
// pTarget [in] A pointer to the target function.
// If this parameter is MH_ALL_HOOKS, all created hooks are
// queued to be enabled.
MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget);
// Queues to disable an already created hook.
// Parameters:
// pTarget [in] A pointer to the target function.
// If this parameter is MH_ALL_HOOKS, all created hooks are
// queued to be disabled.
MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget);
// Applies all queued changes in one go.
MH_STATUS WINAPI MH_ApplyQueued(VOID);
// Translates the MH_STATUS to its name as a string.
const char *WINAPI MH_StatusToString(MH_STATUS status);
#ifdef __cplusplus
}
#endif

311
third_party/minhook/src/buffer.c vendored Normal file
View file

@ -0,0 +1,311 @@
/*
* MinHook - The Minimalistic API Hooking Library for x64/x86
* Copyright (C) 2009-2017 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.
*/
#include <windows.h>
#include "buffer.h"
// Size of each memory block. (= page size of VirtualAlloc)
#define MEMORY_BLOCK_SIZE 0x1000
// Max range for seeking a memory block. (= 1024MB)
#define MAX_MEMORY_RANGE 0x40000000
// Memory protection flags to check the executable address.
#define PAGE_EXECUTE_FLAGS \
(PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)
// Memory slot.
typedef struct _MEMORY_SLOT
{
union
{
struct _MEMORY_SLOT *pNext;
UINT8 buffer[MEMORY_SLOT_SIZE];
};
} MEMORY_SLOT, *PMEMORY_SLOT;
// Memory block info. Placed at the head of each block.
typedef struct _MEMORY_BLOCK
{
struct _MEMORY_BLOCK *pNext;
PMEMORY_SLOT pFree; // First element of the free slot list.
UINT usedCount;
} MEMORY_BLOCK, *PMEMORY_BLOCK;
//-------------------------------------------------------------------------
// Global Variables:
//-------------------------------------------------------------------------
// First element of the memory block list.
static PMEMORY_BLOCK g_pMemoryBlocks;
//-------------------------------------------------------------------------
VOID InitializeBuffer(VOID)
{
// Nothing to do for now.
}
//-------------------------------------------------------------------------
VOID UninitializeBuffer(VOID)
{
PMEMORY_BLOCK pBlock = g_pMemoryBlocks;
g_pMemoryBlocks = NULL;
while (pBlock)
{
PMEMORY_BLOCK pNext = pBlock->pNext;
VirtualFree(pBlock, 0, MEM_RELEASE);
pBlock = pNext;
}
}
//-------------------------------------------------------------------------
#if defined(_M_X64) || defined(__x86_64__)
static LPVOID FindPrevFreeRegion(LPVOID pAddress, LPVOID pMinAddr, DWORD dwAllocationGranularity)
{
ULONG_PTR tryAddr = (ULONG_PTR)pAddress;
// Round down to the allocation granularity.
tryAddr -= tryAddr % dwAllocationGranularity;
// Start from the previous allocation granularity multiply.
tryAddr -= dwAllocationGranularity;
while (tryAddr >= (ULONG_PTR)pMinAddr)
{
MEMORY_BASIC_INFORMATION mbi;
if (VirtualQuery((LPVOID)tryAddr, &mbi, sizeof(mbi)) == 0)
break;
if (mbi.State == MEM_FREE)
return (LPVOID)tryAddr;
if ((ULONG_PTR)mbi.AllocationBase < dwAllocationGranularity)
break;
tryAddr = (ULONG_PTR)mbi.AllocationBase - dwAllocationGranularity;
}
return NULL;
}
#endif
//-------------------------------------------------------------------------
#if defined(_M_X64) || defined(__x86_64__)
static LPVOID FindNextFreeRegion(LPVOID pAddress, LPVOID pMaxAddr, DWORD dwAllocationGranularity)
{
ULONG_PTR tryAddr = (ULONG_PTR)pAddress;
// Round down to the allocation granularity.
tryAddr -= tryAddr % dwAllocationGranularity;
// Start from the next allocation granularity multiply.
tryAddr += dwAllocationGranularity;
while (tryAddr <= (ULONG_PTR)pMaxAddr)
{
MEMORY_BASIC_INFORMATION mbi;
if (VirtualQuery((LPVOID)tryAddr, &mbi, sizeof(mbi)) == 0)
break;
if (mbi.State == MEM_FREE)
return (LPVOID)tryAddr;
tryAddr = (ULONG_PTR)mbi.BaseAddress + mbi.RegionSize;
// Round up to the next allocation granularity.
tryAddr += dwAllocationGranularity - 1;
tryAddr -= tryAddr % dwAllocationGranularity;
}
return NULL;
}
#endif
//-------------------------------------------------------------------------
static PMEMORY_BLOCK GetMemoryBlock(LPVOID pOrigin)
{
PMEMORY_BLOCK pBlock;
#if defined(_M_X64) || defined(__x86_64__)
ULONG_PTR minAddr;
ULONG_PTR maxAddr;
SYSTEM_INFO si;
GetSystemInfo(&si);
minAddr = (ULONG_PTR)si.lpMinimumApplicationAddress;
maxAddr = (ULONG_PTR)si.lpMaximumApplicationAddress;
// pOrigin ± 512MB
if ((ULONG_PTR)pOrigin > MAX_MEMORY_RANGE && minAddr < (ULONG_PTR)pOrigin - MAX_MEMORY_RANGE)
minAddr = (ULONG_PTR)pOrigin - MAX_MEMORY_RANGE;
if (maxAddr > (ULONG_PTR)pOrigin + MAX_MEMORY_RANGE)
maxAddr = (ULONG_PTR)pOrigin + MAX_MEMORY_RANGE;
// Make room for MEMORY_BLOCK_SIZE bytes.
maxAddr -= MEMORY_BLOCK_SIZE - 1;
#endif
// Look the registered blocks for a reachable one.
for (pBlock = g_pMemoryBlocks; pBlock != NULL; pBlock = pBlock->pNext)
{
#if defined(_M_X64) || defined(__x86_64__)
// Ignore the blocks too far.
if ((ULONG_PTR)pBlock < minAddr || (ULONG_PTR)pBlock >= maxAddr)
continue;
#endif
// The block has at least one unused slot.
if (pBlock->pFree != NULL)
return pBlock;
}
#if defined(_M_X64) || defined(__x86_64__)
// Alloc a new block above if not found.
{
LPVOID pAlloc = pOrigin;
while ((ULONG_PTR)pAlloc >= minAddr)
{
pAlloc = FindPrevFreeRegion(pAlloc, (LPVOID)minAddr, si.dwAllocationGranularity);
if (pAlloc == NULL)
break;
pBlock = (PMEMORY_BLOCK)VirtualAlloc(
pAlloc, MEMORY_BLOCK_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (pBlock != NULL)
break;
}
}
// Alloc a new block below if not found.
if (pBlock == NULL)
{
LPVOID pAlloc = pOrigin;
while ((ULONG_PTR)pAlloc <= maxAddr)
{
pAlloc = FindNextFreeRegion(pAlloc, (LPVOID)maxAddr, si.dwAllocationGranularity);
if (pAlloc == NULL)
break;
pBlock = (PMEMORY_BLOCK)VirtualAlloc(
pAlloc, MEMORY_BLOCK_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (pBlock != NULL)
break;
}
}
#else
// In x86 mode, a memory block can be placed anywhere.
pBlock = (PMEMORY_BLOCK)VirtualAlloc(
NULL, MEMORY_BLOCK_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
#endif
if (pBlock != NULL)
{
// Build a linked list of all the slots.
PMEMORY_SLOT pSlot = (PMEMORY_SLOT)pBlock + 1;
pBlock->pFree = NULL;
pBlock->usedCount = 0;
do
{
pSlot->pNext = pBlock->pFree;
pBlock->pFree = pSlot;
pSlot++;
} while ((ULONG_PTR)pSlot - (ULONG_PTR)pBlock <= MEMORY_BLOCK_SIZE - MEMORY_SLOT_SIZE);
pBlock->pNext = g_pMemoryBlocks;
g_pMemoryBlocks = pBlock;
}
return pBlock;
}
//-------------------------------------------------------------------------
LPVOID AllocateBuffer(LPVOID pOrigin)
{
PMEMORY_SLOT pSlot;
PMEMORY_BLOCK pBlock = GetMemoryBlock(pOrigin);
if (pBlock == NULL)
return NULL;
// Remove an unused slot from the list.
pSlot = pBlock->pFree;
pBlock->pFree = pSlot->pNext;
pBlock->usedCount++;
#ifdef _DEBUG
// Fill the slot with INT3 for debugging.
memset(pSlot, 0xCC, sizeof(MEMORY_SLOT));
#endif
return pSlot;
}
//-------------------------------------------------------------------------
VOID FreeBuffer(LPVOID pBuffer)
{
PMEMORY_BLOCK pBlock = g_pMemoryBlocks;
PMEMORY_BLOCK pPrev = NULL;
ULONG_PTR pTargetBlock = ((ULONG_PTR)pBuffer / MEMORY_BLOCK_SIZE) * MEMORY_BLOCK_SIZE;
while (pBlock != NULL)
{
if ((ULONG_PTR)pBlock == pTargetBlock)
{
PMEMORY_SLOT pSlot = (PMEMORY_SLOT)pBuffer;
#ifdef _DEBUG
// Clear the released slot for debugging.
memset(pSlot, 0x00, sizeof(MEMORY_SLOT));
#endif
// Restore the released slot to the list.
pSlot->pNext = pBlock->pFree;
pBlock->pFree = pSlot;
pBlock->usedCount--;
// Free if unused.
if (pBlock->usedCount == 0)
{
if (pPrev)
pPrev->pNext = pBlock->pNext;
else
g_pMemoryBlocks = pBlock->pNext;
VirtualFree(pBlock, 0, MEM_RELEASE);
}
break;
}
pPrev = pBlock;
pBlock = pBlock->pNext;
}
}
//-------------------------------------------------------------------------
BOOL IsExecutableAddress(LPVOID pAddress)
{
MEMORY_BASIC_INFORMATION mi;
return VirtualQuery(pAddress, &mi, sizeof(mi)) && mi.State == MEM_COMMIT &&
(mi.Protect & PAGE_EXECUTE_FLAGS);
}

42
third_party/minhook/src/buffer.h vendored Normal file
View file

@ -0,0 +1,42 @@
/*
* MinHook - The Minimalistic API Hooking Library for x64/x86
* Copyright (C) 2009-2017 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.
*/
#pragma once
// Size of each memory slot.
#if defined(_M_X64) || defined(__x86_64__)
#define MEMORY_SLOT_SIZE 64
#else
#define MEMORY_SLOT_SIZE 32
#endif
VOID InitializeBuffer(VOID);
VOID UninitializeBuffer(VOID);
LPVOID AllocateBuffer(LPVOID pOrigin);
VOID FreeBuffer(LPVOID pBuffer);
BOOL IsExecutableAddress(LPVOID pAddress);

462
third_party/minhook/src/hde/hde32.c vendored Normal file
View file

@ -0,0 +1,462 @@
/*
* Hacker Disassembler Engine 32 C
* Copyright (c) 2008-2009, Vyacheslav Patkov.
* All rights reserved.
*
*/
#if defined(_M_IX86) || defined(__i386__)
#include <string.h>
#include "hde32.h"
#include "table32.h"
/* An instruction is at most 15 bytes long, so the bytes past that may not be
* mapped: a decode that would run over the limit stops instead of reading. */
#define NEED(n) do { if (limit - p < (n)) goto error_length; } while (0)
unsigned int hde32_disasm(const void *code, hde32s *hs)
{
uint8_t x, c, *p = (uint8_t *)code, cflags, opcode, pref = 0;
uint8_t *ht = hde32_table, m_mod, m_reg, m_rm, disp_size = 0;
uint8_t ext = 0;
uint8_t *limit = (uint8_t *)code + 15;
memset(hs, 0, sizeof(hde32s));
for (x = 15; x; x--)
switch (c = *p++) {
case 0xf3:
hs->p_rep = c;
pref |= PRE_F3;
break;
case 0xf2:
hs->p_rep = c;
pref |= PRE_F2;
break;
case 0xf0:
hs->p_lock = c;
pref |= PRE_LOCK;
break;
case 0x26: case 0x2e: case 0x36:
case 0x3e: case 0x64: case 0x65:
hs->p_seg = c;
pref |= PRE_SEG;
break;
case 0x66:
hs->p_66 = c;
pref |= PRE_66;
break;
case 0x67:
hs->p_67 = c;
pref |= PRE_67;
break;
default:
goto pref_done;
}
goto error_length; /* 15 prefixes leave no room for an opcode */
pref_done:
hs->flags = (uint32_t)pref << 23;
if (!pref)
pref |= PRE_NONE;
/* VEX, EVEX and XOP. C4, C5 and 62 keep their legacy meaning unless the
* byte after them has mod = 11, which les, lds and bound cannot encode;
* 8F is an escape only from map 8 upwards, which leaves 8F /0 as pop. The
* header names the opcode map, the map decides the immediate, and every
* instruction reached this way has a ModR/M byte. */
if (p < limit &&
(((c == 0xc4 || c == 0xc5 || c == 0x62) && (*p & 0xc0) == 0xc0) ||
(c == 0x8f && (*p & 0x1f) >= 8))) {
uint8_t map;
hs->opcode = c;
if (c == 0xc5) {
NEED(3);
map = 1;
p++;
} else if (c == 0x62) {
NEED(5);
map = *p & 0x0f;
p += 3;
} else {
NEED(4);
map = *p & 0x1f;
p += 2;
}
hs->opcode2 = opcode = *p++;
/* The escape carries the operand size itself, so a 66, F2, F3 or LOCK
* in front of it is invalid. A segment or 67 prefix still applies. */
if (pref & (PRE_66 | PRE_F2 | PRE_F3 | PRE_LOCK))
hs->flags |= F_ERROR | F_ERROR_OPCODE;
pref &= ~PRE_66;
cflags = C_MODRM;
if (c == 0x8f) {
switch (map) {
case 8: /* every XOP8 opcode takes an imm8 */
cflags |= C_IMM8;
break;
case 9: /* no XOP9 opcode takes one */
break;
case 10: /* bextr and the lwp forms: imm32 */
cflags |= C_IMM_P66;
break;
default:
hs->flags |= F_ERROR | F_ERROR_OPCODE;
break;
}
} else switch (map) {
case 1: /* 0F map */
if (opcode == 0x70 || (opcode >= 0x71 && opcode <= 0x73) ||
opcode == 0xc2 || (opcode >= 0xc4 && opcode <= 0xc6))
cflags |= C_IMM8;
else if (c != 0x62 && opcode == 0x77)
cflags = C_NONE; /* vzeroupper and vzeroall: no ModR/M */
break;
case 2: /* 0F 38 map: never an immediate */
break;
case 3: /* 0F 3A map: always an imm8 */
cflags |= C_IMM8;
break;
case 5: case 6: /* half-precision maps, EVEX only */
if (c != 0x62)
hs->flags |= F_ERROR | F_ERROR_OPCODE;
else if (map == 5 && (opcode == 0x08 || opcode == 0x0a ||
opcode == 0x26 || opcode == 0x27 ||
opcode == 0x56 || opcode == 0x57 ||
opcode == 0x66 || opcode == 0x67 ||
opcode == 0xc2))
cflags |= C_IMM8; /* the 0F 3A forms of that map */
break;
default:
hs->flags |= F_ERROR | F_ERROR_OPCODE;
break;
}
ext = 1;
x = 0;
goto modrm;
}
if ((hs->opcode = c) == 0x0f) {
NEED(1);
hs->opcode2 = c = *p++;
if (c == 0x38 || c == 0x3a) {
/* Three-byte maps: the opcode follows and always has a ModR/M
* byte. Every 0F 3A opcode takes an imm8, no 0F 38 one does, and
* none of them is lockable. */
NEED(2);
opcode = *p++;
cflags = (c == 0x3a) ? (C_MODRM | C_IMM8) : C_MODRM;
if (pref & PRE_LOCK)
hs->flags |= F_ERROR | F_ERROR_LOCK;
ext = 1;
x = 0;
goto modrm;
}
ht += DELTA_OPCODES;
} else if (c >= 0xa0 && c <= 0xa3) {
if (pref & PRE_67)
pref |= PRE_66;
else
pref &= ~PRE_66;
}
opcode = c;
cflags = ht[ht[opcode / 4] + (opcode % 4)];
if (cflags == C_ERROR) {
hs->flags |= F_ERROR | F_ERROR_OPCODE;
/* An unknown opcode is still measured when its shape is known: 0F 24
* and 0F 26 (test registers), 0F A6 and 0F A7 (PadLock), 0F B9 (ud1)
* and 0F FF (ud0) all take a ModR/M byte. */
cflags = 0;
if ((opcode & -3) == 0x24 || opcode == 0xa6 || opcode == 0xa7 ||
opcode == 0xb9 || opcode == 0xff)
cflags = C_MODRM;
}
x = 0;
if (cflags & C_GROUP) {
uint16_t t;
t = *(uint16_t *)(ht + (cflags & 0x7f));
cflags = (uint8_t)t;
x = (uint8_t)(t >> 8);
}
if (hs->opcode2) {
ht = hde32_table + DELTA_PREFIXES;
if (ht[ht[opcode / 4] + (opcode % 4)] & pref)
hs->flags |= F_ERROR | F_ERROR_OPCODE;
/* vmread, vmwrite and popcnt take a ModR/M byte that the table cannot
* give them: their entries share a row with opcodes that take none. */
if (opcode == 0x78 || opcode == 0x79 || opcode == 0xb8)
cflags |= C_MODRM;
}
modrm:
if (cflags & C_MODRM) {
hs->flags |= F_MODRM;
NEED(1);
hs->modrm = c = *p++;
hs->modrm_mod = m_mod = c >> 6;
hs->modrm_rm = m_rm = c & 7;
hs->modrm_reg = m_reg = (c & 0x3f) >> 3;
if (x && ((x << m_reg) & 0x80))
hs->flags |= F_ERROR | F_ERROR_OPCODE;
if (ext)
goto no_error_operand; /* no legacy operand rule applies */
if (!hs->opcode2 && opcode >= 0xd9 && opcode <= 0xdf) {
uint8_t t = opcode - 0xd9;
if (m_mod == 3) {
ht = hde32_table + DELTA_FPU_MODRM + t*8;
t = ht[m_reg] << m_rm;
} else {
ht = hde32_table + DELTA_FPU_REG;
t = ht[t] << m_reg;
}
if (t & 0x80)
hs->flags |= F_ERROR | F_ERROR_OPCODE;
}
if (pref & PRE_LOCK) {
if (m_mod == 3) {
hs->flags |= F_ERROR | F_ERROR_LOCK;
} else {
uint8_t *table_end, op = opcode;
if (hs->opcode2) {
ht = hde32_table + DELTA_OP2_LOCK_OK;
table_end = ht + DELTA_OP_ONLY_MEM - DELTA_OP2_LOCK_OK;
} else {
ht = hde32_table + DELTA_OP_LOCK_OK;
table_end = ht + DELTA_OP2_LOCK_OK - DELTA_OP_LOCK_OK;
op &= -2;
}
for (; ht != table_end; ht++)
if (*ht++ == op) {
if (!((*ht << m_reg) & 0x80))
goto no_lock_error;
else
break;
}
hs->flags |= F_ERROR | F_ERROR_LOCK;
no_lock_error:
;
}
}
if (hs->opcode2) {
switch (opcode) {
case 0x20: case 0x22:
m_mod = 3;
if (m_reg > 4 || m_reg == 1)
goto error_operand;
else
goto no_error_operand;
case 0x21: case 0x23:
m_mod = 3;
if (m_reg == 4 || m_reg == 5)
goto error_operand;
else
goto no_error_operand;
}
} else {
switch (opcode) {
case 0x8c:
if (m_reg > 5)
goto error_operand;
else
goto no_error_operand;
case 0x8e:
if (m_reg == 1 || m_reg > 5)
goto error_operand;
else
goto no_error_operand;
}
}
if (m_mod == 3) {
uint8_t *table_end;
if (hs->opcode2) {
ht = hde32_table + DELTA_OP2_ONLY_MEM;
table_end = ht + sizeof(hde32_table) - DELTA_OP2_ONLY_MEM;
} else {
ht = hde32_table + DELTA_OP_ONLY_MEM;
table_end = ht + DELTA_OP2_ONLY_MEM - DELTA_OP_ONLY_MEM;
}
for (; ht != table_end; ht += 2)
if (*ht++ == opcode) {
if ((*ht++ & pref) && !((*ht << m_reg) & 0x80))
goto error_operand;
else
break;
}
goto no_error_operand;
} else if (hs->opcode2) {
switch (opcode) {
case 0x50: case 0xd7: case 0xf7:
if (pref & (PRE_NONE | PRE_66))
goto error_operand;
break;
case 0xd6:
if (pref & (PRE_F2 | PRE_F3))
goto error_operand;
break;
case 0xc5:
goto error_operand;
}
goto no_error_operand;
} else
goto no_error_operand;
error_operand:
hs->flags |= F_ERROR | F_ERROR_OPERAND;
no_error_operand:
/* Group 3: F6 /0 and /1 take an imm8, F7 /0 and /1 an imm32 or imm16.
* The rule belongs to the one-byte map; 0F F6 and 0F F7 are psadbw
* and maskmovq, which take neither. */
if (!ext && !hs->opcode2 && m_reg <= 1) {
if (opcode == 0xf6)
cflags |= C_IMM8;
else if (opcode == 0xf7)
cflags |= C_IMM_P66;
}
/* C7 /7 is xbegin, whose immediate is a displacement. */
if (!ext && !hs->opcode2 && opcode == 0xc7 && m_reg == 7)
hs->flags |= F_RELATIVE;
/* extrq and insertq take two imm8 operands where vmread takes none. */
if (!ext && hs->opcode2 == 0x78 && (pref & (PRE_66 | PRE_F2)))
cflags |= C_IMM16;
switch (m_mod) {
case 0:
if (pref & PRE_67) {
if (m_rm == 6)
disp_size = 2;
} else
if (m_rm == 5)
disp_size = 4;
break;
case 1:
disp_size = 1;
break;
case 2:
disp_size = 2;
if (!(pref & PRE_67))
disp_size <<= 1;
break;
}
if (m_mod != 3 && m_rm == 4 && !(pref & PRE_67)) {
hs->flags |= F_SIB;
NEED(1);
hs->sib = c = *p++;
hs->sib_scale = c >> 6;
hs->sib_index = (c & 0x3f) >> 3;
if ((hs->sib_base = c & 7) == 5 && !(m_mod & 1))
disp_size = 4;
}
if (disp_size) {
NEED(disp_size);
switch (disp_size) {
case 1:
hs->flags |= F_DISP8;
hs->disp.disp8 = *p;
break;
case 2:
hs->flags |= F_DISP16;
hs->disp.disp16 = *(uint16_t *)p;
break;
case 4:
hs->flags |= F_DISP32;
hs->disp.disp32 = *(uint32_t *)p;
break;
}
p += disp_size;
}
} else if (pref & PRE_LOCK)
hs->flags |= F_ERROR | F_ERROR_LOCK;
if (cflags & C_IMM_P66) {
if (cflags & C_REL32) {
if (pref & PRE_66) {
NEED(2);
hs->flags |= F_IMM16 | F_RELATIVE;
hs->imm.imm16 = *(uint16_t *)p;
p += 2;
goto disasm_done;
}
goto rel32_ok;
}
if (pref & PRE_66) {
NEED(2);
hs->flags |= F_IMM16;
hs->imm.imm16 = *(uint16_t *)p;
p += 2;
} else {
NEED(4);
hs->flags |= F_IMM32;
hs->imm.imm32 = *(uint32_t *)p;
p += 4;
}
}
if (cflags & C_IMM16) {
NEED(2);
if (hs->flags & F_IMM32) {
hs->flags |= F_IMM16;
hs->disp.disp16 = *(uint16_t *)p;
} else if (hs->flags & F_IMM16) {
hs->flags |= F_2IMM16;
hs->disp.disp16 = *(uint16_t *)p;
} else {
hs->flags |= F_IMM16;
hs->imm.imm16 = *(uint16_t *)p;
}
p += 2;
}
if (cflags & C_IMM8) {
NEED(1);
hs->flags |= F_IMM8;
if (hs->flags & F_IMM16)
hs->disp.disp8 = *p++; /* enter: the union holds the imm16 */
else
hs->imm.imm8 = *p++;
}
if (cflags & C_REL32) {
rel32_ok:
NEED(4);
hs->flags |= F_IMM32 | F_RELATIVE;
hs->imm.imm32 = *(uint32_t *)p;
p += 4;
} else if (cflags & C_REL8) {
NEED(1);
hs->flags |= F_IMM8 | F_RELATIVE;
hs->imm.imm8 = *p++;
}
disasm_done:
hs->len = (uint8_t)(p - (uint8_t *)code);
return (unsigned int)hs->len;
error_length:
hs->flags |= F_ERROR | F_ERROR_LENGTH;
hs->len = 15;
return 15;
}
#undef NEED
#endif // defined(_M_IX86) || defined(__i386__)

105
third_party/minhook/src/hde/hde32.h vendored Normal file
View file

@ -0,0 +1,105 @@
/*
* Hacker Disassembler Engine 32
* Copyright (c) 2006-2009, Vyacheslav Patkov.
* All rights reserved.
*
* hde32.h: C/C++ header file
*
*/
#ifndef _HDE32_H_
#define _HDE32_H_
/* stdint.h - C99 standard header
* http://en.wikipedia.org/wiki/stdint.h
*
* if your compiler doesn't contain "stdint.h" header (for
* example, Microsoft Visual C++), you can download file:
* http://www.azillionmonkeys.com/qed/pstdint.h
* and change next line to:
* #include "pstdint.h"
*/
#include "pstdint.h"
#define F_MODRM 0x00000001
#define F_SIB 0x00000002
#define F_IMM8 0x00000004
#define F_IMM16 0x00000008
#define F_IMM32 0x00000010
#define F_DISP8 0x00000020
#define F_DISP16 0x00000040
#define F_DISP32 0x00000080
#define F_RELATIVE 0x00000100
#define F_2IMM16 0x00000800
#define F_ERROR 0x00001000
#define F_ERROR_OPCODE 0x00002000
#define F_ERROR_LENGTH 0x00004000
#define F_ERROR_LOCK 0x00008000
#define F_ERROR_OPERAND 0x00010000
#define F_PREFIX_REPNZ 0x01000000
#define F_PREFIX_REPX 0x02000000
#define F_PREFIX_REP 0x03000000
#define F_PREFIX_66 0x04000000
#define F_PREFIX_67 0x08000000
#define F_PREFIX_LOCK 0x10000000
#define F_PREFIX_SEG 0x20000000
#define F_PREFIX_ANY 0x3f000000
#define PREFIX_SEGMENT_CS 0x2e
#define PREFIX_SEGMENT_SS 0x36
#define PREFIX_SEGMENT_DS 0x3e
#define PREFIX_SEGMENT_ES 0x26
#define PREFIX_SEGMENT_FS 0x64
#define PREFIX_SEGMENT_GS 0x65
#define PREFIX_LOCK 0xf0
#define PREFIX_REPNZ 0xf2
#define PREFIX_REPX 0xf3
#define PREFIX_OPERAND_SIZE 0x66
#define PREFIX_ADDRESS_SIZE 0x67
#pragma pack(push,1)
typedef struct {
uint8_t len;
uint8_t p_rep;
uint8_t p_lock;
uint8_t p_seg;
uint8_t p_66;
uint8_t p_67;
uint8_t opcode;
uint8_t opcode2;
uint8_t modrm;
uint8_t modrm_mod;
uint8_t modrm_reg;
uint8_t modrm_rm;
uint8_t sib;
uint8_t sib_scale;
uint8_t sib_index;
uint8_t sib_base;
union {
uint8_t imm8;
uint16_t imm16;
uint32_t imm32;
} imm;
union {
uint8_t disp8;
uint16_t disp16;
uint32_t disp32;
} disp;
uint32_t flags;
} hde32s;
#pragma pack(pop)
#ifdef __cplusplus
extern "C" {
#endif
/* __cdecl */
unsigned int hde32_disasm(const void *code, hde32s *hs);
#ifdef __cplusplus
}
#endif
#endif /* _HDE32_H_ */

470
third_party/minhook/src/hde/hde64.c vendored Normal file
View file

@ -0,0 +1,470 @@
/*
* Hacker Disassembler Engine 64 C
* Copyright (c) 2008-2009, Vyacheslav Patkov.
* All rights reserved.
*
*/
#if defined(_M_X64) || defined(__x86_64__)
#include <string.h>
#include "hde64.h"
#include "table64.h"
/* An instruction is at most 15 bytes long, so the bytes past that may not be
* mapped: a decode that would run over the limit stops instead of reading. */
#define NEED(n) do { if (limit - p < (n)) goto error_length; } while (0)
unsigned int hde64_disasm(const void *code, hde64s *hs)
{
uint8_t x, c, *p = (uint8_t *)code, cflags, opcode, pref = 0;
uint8_t *ht = hde64_table, m_mod, m_reg, m_rm, disp_size = 0;
uint8_t op64 = 0, rex = 0, ext = 0;
uint8_t *limit = (uint8_t *)code + 15;
memset(hs, 0, sizeof(hde64s));
for (x = 15; x; x--)
switch (c = *p++) {
case 0xf3:
hs->p_rep = c;
pref |= PRE_F3;
rex = 0;
break;
case 0xf2:
hs->p_rep = c;
pref |= PRE_F2;
rex = 0;
break;
case 0xf0:
hs->p_lock = c;
pref |= PRE_LOCK;
rex = 0;
break;
case 0x26: case 0x2e: case 0x36:
case 0x3e: case 0x64: case 0x65:
hs->p_seg = c;
pref |= PRE_SEG;
rex = 0;
break;
case 0x66:
hs->p_66 = c;
pref |= PRE_66;
rex = 0;
break;
case 0x67:
hs->p_67 = c;
pref |= PRE_67;
rex = 0;
break;
default:
if ((c & 0xf0) != 0x40)
goto pref_done;
/* REX counts only as the last prefix before the opcode: a
* legacy prefix after it, or a further REX, discards it. */
rex = c;
break;
}
goto error_length; /* 15 prefixes leave no room for an opcode */
pref_done:
hs->flags = (uint32_t)pref << 23;
if (!pref)
pref |= PRE_NONE;
if (rex) {
hs->flags |= F_PREFIX_REX;
hs->rex = rex;
hs->rex_w = (rex & 0xf) >> 3;
hs->rex_r = (rex & 7) >> 2;
hs->rex_x = (rex & 3) >> 1;
hs->rex_b = rex & 1;
if (hs->rex_w && (c & 0xf8) == 0xb8)
op64++;
}
/* VEX, EVEX and XOP. C4, C5 and 62 are always escapes in long mode; 8F is
* one only from map 8 upwards, which leaves 8F /0 as POP. The header names
* the opcode map, the map decides the immediate, and every instruction
* reached this way has a ModR/M byte. */
if (c == 0xc4 || c == 0xc5 || c == 0x62 ||
(c == 0x8f && p < limit && (*p & 0x1f) >= 8)) {
uint8_t map;
hs->opcode = c;
if (c == 0xc5) {
NEED(3);
map = 1;
p++;
} else if (c == 0x62) {
NEED(5);
map = *p & 0x07; /* the bit above the map is APX's B4 */
p += 3;
} else {
NEED(4);
map = *p & 0x1f;
p += 2;
}
hs->opcode2 = opcode = *p++;
/* The escape carries the operand size and the register extensions
* itself, so a 66, F2, F3, LOCK or REX in front of it is invalid. A
* segment or 67 prefix still applies. */
if (rex || (pref & (PRE_66 | PRE_F2 | PRE_F3 | PRE_LOCK)))
hs->flags |= F_ERROR | F_ERROR_OPCODE;
pref &= ~PRE_66;
cflags = C_MODRM;
if (c == 0x8f) {
switch (map) {
case 8: /* every XOP8 opcode takes an imm8 */
cflags |= C_IMM8;
break;
case 9: /* no XOP9 opcode takes one */
break;
case 10: /* bextr and the lwp forms: imm32 */
cflags |= C_IMM_P66;
break;
default:
hs->flags |= F_ERROR | F_ERROR_OPCODE;
break;
}
} else switch (map) {
case 1: /* 0F map */
if (opcode == 0x70 || (opcode >= 0x71 && opcode <= 0x73) ||
opcode == 0xc2 || (opcode >= 0xc4 && opcode <= 0xc6))
cflags |= C_IMM8;
else if (c != 0x62 && opcode == 0x77)
cflags = C_NONE; /* vzeroupper and vzeroall: no ModR/M */
else if (c != 0x62 && (opcode == 0x84 || opcode == 0x85))
cflags = C_REL32; /* jkzd and jknzd: rel32, no ModR/M */
break;
case 2: /* 0F 38 map: never an immediate */
break;
case 3: /* 0F 3A map: always an imm8 */
cflags |= C_IMM8;
break;
case 5: case 6: /* half-precision maps, EVEX only */
if (c != 0x62)
hs->flags |= F_ERROR | F_ERROR_OPCODE;
else if (map == 5 && (opcode == 0x08 || opcode == 0x0a ||
opcode == 0x26 || opcode == 0x27 ||
opcode == 0x56 || opcode == 0x57 ||
opcode == 0x66 || opcode == 0x67 ||
opcode == 0xc2))
cflags |= C_IMM8; /* the 0F 3A forms of that map */
break;
default:
hs->flags |= F_ERROR | F_ERROR_OPCODE;
break;
}
ext = 1;
x = 0;
goto modrm;
}
if ((hs->opcode = c) == 0x0f) {
NEED(1);
hs->opcode2 = c = *p++;
if (c == 0x38 || c == 0x3a) {
/* Three-byte maps: the opcode follows and always has a ModR/M
* byte. Every 0F 3A opcode takes an imm8, no 0F 38 one does, and
* none of them is lockable. */
NEED(2);
opcode = *p++;
cflags = (c == 0x3a) ? (C_MODRM | C_IMM8) : C_MODRM;
if (pref & PRE_LOCK)
hs->flags |= F_ERROR | F_ERROR_LOCK;
ext = 1;
x = 0;
goto modrm;
}
ht += DELTA_OPCODES;
} else if (c >= 0xa0 && c <= 0xa3) {
/* moffs: the absolute address is as wide as the address size, so
* eight bytes unless 67 selects 32-bit addressing. */
if (!(pref & PRE_67))
op64++;
pref &= ~PRE_66;
}
opcode = c;
cflags = ht[ht[opcode / 4] + (opcode % 4)];
if (cflags == C_ERROR) {
hs->flags |= F_ERROR | F_ERROR_OPCODE;
/* An unknown opcode is still measured when its shape is known: 0F 24
* and 0F 26 (test registers), 0F A6 and 0F A7 (PadLock), 0F B9 (ud1)
* and 0F FF (ud0) all take a ModR/M byte. */
cflags = 0;
if ((opcode & -3) == 0x24 || opcode == 0xa6 || opcode == 0xa7 ||
opcode == 0xb9 || opcode == 0xff)
cflags = C_MODRM;
}
x = 0;
if (cflags & C_GROUP) {
uint16_t t;
t = *(uint16_t *)(ht + (cflags & 0x7f));
cflags = (uint8_t)t;
x = (uint8_t)(t >> 8);
}
if (hs->opcode2) {
ht = hde64_table + DELTA_PREFIXES;
if (ht[ht[opcode / 4] + (opcode % 4)] & pref)
hs->flags |= F_ERROR | F_ERROR_OPCODE;
/* vmread, vmwrite and popcnt take a ModR/M byte that the table cannot
* give them: their entries share a row with opcodes that take none. */
if (opcode == 0x78 || opcode == 0x79 || opcode == 0xb8)
cflags |= C_MODRM;
}
modrm:
if (cflags & C_MODRM) {
hs->flags |= F_MODRM;
NEED(1);
hs->modrm = c = *p++;
hs->modrm_mod = m_mod = c >> 6;
hs->modrm_rm = m_rm = c & 7;
hs->modrm_reg = m_reg = (c & 0x3f) >> 3;
if (x && ((x << m_reg) & 0x80))
hs->flags |= F_ERROR | F_ERROR_OPCODE;
if (ext)
goto no_error_operand; /* no legacy operand rule applies */
if (!hs->opcode2 && opcode >= 0xd9 && opcode <= 0xdf) {
uint8_t t = opcode - 0xd9;
if (m_mod == 3) {
ht = hde64_table + DELTA_FPU_MODRM + t*8;
t = ht[m_reg] << m_rm;
} else {
ht = hde64_table + DELTA_FPU_REG;
t = ht[t] << m_reg;
}
if (t & 0x80)
hs->flags |= F_ERROR | F_ERROR_OPCODE;
}
if (pref & PRE_LOCK) {
if (m_mod == 3) {
hs->flags |= F_ERROR | F_ERROR_LOCK;
} else {
uint8_t *table_end, op = opcode;
if (hs->opcode2) {
ht = hde64_table + DELTA_OP2_LOCK_OK;
table_end = ht + DELTA_OP_ONLY_MEM - DELTA_OP2_LOCK_OK;
} else {
ht = hde64_table + DELTA_OP_LOCK_OK;
table_end = ht + DELTA_OP2_LOCK_OK - DELTA_OP_LOCK_OK;
op &= -2;
}
for (; ht != table_end; ht++)
if (*ht++ == op) {
if (!((*ht << m_reg) & 0x80))
goto no_lock_error;
else
break;
}
hs->flags |= F_ERROR | F_ERROR_LOCK;
no_lock_error:
;
}
}
if (hs->opcode2) {
switch (opcode) {
case 0x20: case 0x22:
m_mod = 3;
if (m_reg > 4 || m_reg == 1)
goto error_operand;
else
goto no_error_operand;
case 0x21: case 0x23:
m_mod = 3;
if (m_reg == 4 || m_reg == 5)
goto error_operand;
else
goto no_error_operand;
}
} else {
switch (opcode) {
case 0x8c:
if (m_reg > 5)
goto error_operand;
else
goto no_error_operand;
case 0x8e:
if (m_reg == 1 || m_reg > 5)
goto error_operand;
else
goto no_error_operand;
}
}
if (m_mod == 3) {
uint8_t *table_end;
if (hs->opcode2) {
ht = hde64_table + DELTA_OP2_ONLY_MEM;
table_end = ht + sizeof(hde64_table) - DELTA_OP2_ONLY_MEM;
} else {
ht = hde64_table + DELTA_OP_ONLY_MEM;
table_end = ht + DELTA_OP2_ONLY_MEM - DELTA_OP_ONLY_MEM;
}
for (; ht != table_end; ht += 2)
if (*ht++ == opcode) {
if ((*ht++ & pref) && !((*ht << m_reg) & 0x80))
goto error_operand;
else
break;
}
goto no_error_operand;
} else if (hs->opcode2) {
switch (opcode) {
case 0x50: case 0xd7: case 0xf7:
if (pref & (PRE_NONE | PRE_66))
goto error_operand;
break;
case 0xd6:
if (pref & (PRE_F2 | PRE_F3))
goto error_operand;
break;
case 0xc5:
goto error_operand;
}
goto no_error_operand;
} else
goto no_error_operand;
error_operand:
hs->flags |= F_ERROR | F_ERROR_OPERAND;
no_error_operand:
/* Group 3: F6 /0 and /1 take an imm8, F7 /0 and /1 an imm32 or imm16.
* The rule belongs to the one-byte map; 0F F6 and 0F F7 are psadbw
* and maskmovq, which take neither. */
if (!ext && !hs->opcode2 && m_reg <= 1) {
if (opcode == 0xf6)
cflags |= C_IMM8;
else if (opcode == 0xf7)
cflags |= C_IMM_P66;
}
/* C7 /7 is xbegin, whose immediate is a displacement. */
if (!ext && !hs->opcode2 && opcode == 0xc7 && m_reg == 7)
hs->flags |= F_RELATIVE;
/* extrq and insertq take two imm8 operands where vmread takes none. */
if (!ext && hs->opcode2 == 0x78 && (pref & (PRE_66 | PRE_F2)))
cflags |= C_IMM16;
switch (m_mod) {
case 0:
if (m_rm == 5)
disp_size = 4;
break;
case 1:
disp_size = 1;
break;
case 2:
disp_size = 4;
break;
}
if (m_mod != 3 && m_rm == 4) {
hs->flags |= F_SIB;
NEED(1);
hs->sib = c = *p++;
hs->sib_scale = c >> 6;
hs->sib_index = (c & 0x3f) >> 3;
if ((hs->sib_base = c & 7) == 5 && !(m_mod & 1))
disp_size = 4;
}
if (disp_size) {
NEED(disp_size);
if (disp_size == 1) {
hs->flags |= F_DISP8;
hs->disp.disp8 = *p;
} else {
hs->flags |= F_DISP32;
hs->disp.disp32 = *(uint32_t *)p;
}
p += disp_size;
}
} else if (pref & PRE_LOCK)
hs->flags |= F_ERROR | F_ERROR_LOCK;
/* An operand-size dependent immediate is 32 bits wide, 16 under a 66
* prefix, and 32 again when a REX.W overrides that prefix. */
if (cflags & C_IMM_P66) {
if (cflags & C_REL32) {
if ((pref & PRE_66) && !hs->rex_w) {
NEED(2);
hs->flags |= F_IMM16 | F_RELATIVE;
hs->imm.imm16 = *(uint16_t *)p;
p += 2;
goto disasm_done;
}
goto rel32_ok;
}
if (op64) {
NEED(8);
hs->flags |= F_IMM64;
hs->imm.imm64 = *(uint64_t *)p;
p += 8;
} else if (!(pref & PRE_66) || hs->rex_w) {
NEED(4);
hs->flags |= F_IMM32;
hs->imm.imm32 = *(uint32_t *)p;
p += 4;
} else
goto imm16_ok;
}
if (cflags & C_IMM16) {
imm16_ok:
NEED(2);
hs->flags |= F_IMM16;
hs->imm.imm16 = *(uint16_t *)p;
p += 2;
}
if (cflags & C_IMM8) {
NEED(1);
hs->flags |= F_IMM8;
if (hs->flags & F_IMM16)
hs->disp.disp8 = *p++; /* enter: the union holds the imm16 */
else
hs->imm.imm8 = *p++;
}
if (cflags & C_REL32) {
rel32_ok:
NEED(4);
hs->flags |= F_IMM32 | F_RELATIVE;
hs->imm.imm32 = *(uint32_t *)p;
p += 4;
} else if (cflags & C_REL8) {
NEED(1);
hs->flags |= F_IMM8 | F_RELATIVE;
hs->imm.imm8 = *p++;
}
disasm_done:
hs->len = (uint8_t)(p - (uint8_t *)code);
return (unsigned int)hs->len;
error_length:
hs->flags |= F_ERROR | F_ERROR_LENGTH;
hs->len = 15;
return 15;
}
#undef NEED
#endif // defined(_M_X64) || defined(__x86_64__)

112
third_party/minhook/src/hde/hde64.h vendored Normal file
View file

@ -0,0 +1,112 @@
/*
* Hacker Disassembler Engine 64
* Copyright (c) 2008-2009, Vyacheslav Patkov.
* All rights reserved.
*
* hde64.h: C/C++ header file
*
*/
#ifndef _HDE64_H_
#define _HDE64_H_
/* stdint.h - C99 standard header
* http://en.wikipedia.org/wiki/stdint.h
*
* if your compiler doesn't contain "stdint.h" header (for
* example, Microsoft Visual C++), you can download file:
* http://www.azillionmonkeys.com/qed/pstdint.h
* and change next line to:
* #include "pstdint.h"
*/
#include "pstdint.h"
#define F_MODRM 0x00000001
#define F_SIB 0x00000002
#define F_IMM8 0x00000004
#define F_IMM16 0x00000008
#define F_IMM32 0x00000010
#define F_IMM64 0x00000020
#define F_DISP8 0x00000040
#define F_DISP16 0x00000080
#define F_DISP32 0x00000100
#define F_RELATIVE 0x00000200
#define F_ERROR 0x00001000
#define F_ERROR_OPCODE 0x00002000
#define F_ERROR_LENGTH 0x00004000
#define F_ERROR_LOCK 0x00008000
#define F_ERROR_OPERAND 0x00010000
#define F_PREFIX_REPNZ 0x01000000
#define F_PREFIX_REPX 0x02000000
#define F_PREFIX_REP 0x03000000
#define F_PREFIX_66 0x04000000
#define F_PREFIX_67 0x08000000
#define F_PREFIX_LOCK 0x10000000
#define F_PREFIX_SEG 0x20000000
#define F_PREFIX_REX 0x40000000
#define F_PREFIX_ANY 0x7f000000
#define PREFIX_SEGMENT_CS 0x2e
#define PREFIX_SEGMENT_SS 0x36
#define PREFIX_SEGMENT_DS 0x3e
#define PREFIX_SEGMENT_ES 0x26
#define PREFIX_SEGMENT_FS 0x64
#define PREFIX_SEGMENT_GS 0x65
#define PREFIX_LOCK 0xf0
#define PREFIX_REPNZ 0xf2
#define PREFIX_REPX 0xf3
#define PREFIX_OPERAND_SIZE 0x66
#define PREFIX_ADDRESS_SIZE 0x67
#pragma pack(push,1)
typedef struct {
uint8_t len;
uint8_t p_rep;
uint8_t p_lock;
uint8_t p_seg;
uint8_t p_66;
uint8_t p_67;
uint8_t rex;
uint8_t rex_w;
uint8_t rex_r;
uint8_t rex_x;
uint8_t rex_b;
uint8_t opcode;
uint8_t opcode2;
uint8_t modrm;
uint8_t modrm_mod;
uint8_t modrm_reg;
uint8_t modrm_rm;
uint8_t sib;
uint8_t sib_scale;
uint8_t sib_index;
uint8_t sib_base;
union {
uint8_t imm8;
uint16_t imm16;
uint32_t imm32;
uint64_t imm64;
} imm;
union {
uint8_t disp8;
uint16_t disp16;
uint32_t disp32;
} disp;
uint32_t flags;
} hde64s;
#pragma pack(pop)
#ifdef __cplusplus
extern "C" {
#endif
/* __cdecl */
unsigned int hde64_disasm(const void *code, hde64s *hs);
#ifdef __cplusplus
}
#endif
#endif /* _HDE64_H_ */

39
third_party/minhook/src/hde/pstdint.h vendored Normal file
View file

@ -0,0 +1,39 @@
/*
* MinHook - The Minimalistic API Hooking Library for x64/x86
* Copyright (C) 2009-2017 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 AUTHOR "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 AUTHOR 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.
*/
#pragma once
#include <windows.h>
// Integer types for HDE.
typedef INT8 int8_t;
typedef INT16 int16_t;
typedef INT32 int32_t;
typedef INT64 int64_t;
typedef UINT8 uint8_t;
typedef UINT16 uint16_t;
typedef UINT32 uint32_t;
typedef UINT64 uint64_t;

73
third_party/minhook/src/hde/table32.h vendored Normal file
View file

@ -0,0 +1,73 @@
/*
* Hacker Disassembler Engine 32 C
* Copyright (c) 2008-2009, Vyacheslav Patkov.
* All rights reserved.
*
*/
#define C_NONE 0x00
#define C_MODRM 0x01
#define C_IMM8 0x02
#define C_IMM16 0x04
#define C_IMM_P66 0x10
#define C_REL8 0x20
#define C_REL32 0x40
#define C_GROUP 0x80
#define C_ERROR 0xff
#define PRE_ANY 0x00
#define PRE_NONE 0x01
#define PRE_F2 0x02
#define PRE_F3 0x04
#define PRE_66 0x08
#define PRE_67 0x10
#define PRE_LOCK 0x20
#define PRE_SEG 0x40
#define PRE_ALL 0xff
#define DELTA_OPCODES 0x4a
#define DELTA_FPU_REG 0xf1
#define DELTA_FPU_MODRM 0xf8
#define DELTA_PREFIXES 0x130
#define DELTA_OP_LOCK_OK 0x1a1
#define DELTA_OP2_LOCK_OK 0x1b9
#define DELTA_OP_ONLY_MEM 0x1cb
#define DELTA_OP2_ONLY_MEM 0x1da
unsigned char hde32_table[] = {
0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,
0xa8,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xac,0xaa,0xb2,0xaa,0x9f,0x9f,
0x9f,0x9f,0xb5,0xa3,0xa3,0xa4,0xaa,0xaa,0xba,0xaa,0x96,0xaa,0xa8,0xaa,0xc3,
0xc3,0x96,0x96,0xb7,0xae,0xd6,0xbd,0xa3,0xc5,0xa3,0xa3,0x9f,0xc3,0x9c,0xaa,
0xaa,0xac,0xaa,0xbf,0x03,0x7f,0x11,0x7f,0x01,0x7f,0x01,0x3f,0x01,0x01,0x90,
0x82,0x7d,0x97,0x59,0x59,0x59,0x59,0x59,0x7f,0x59,0x59,0x60,0x7d,0x7f,0x7f,
0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x9a,0x88,0x7d,
0x59,0x50,0x50,0x50,0x50,0x59,0x59,0x59,0x59,0x61,0x94,0x61,0x9e,0x59,0x59,
0x85,0x59,0x92,0xa3,0x60,0x60,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,
0x59,0x59,0x9f,0x01,0x03,0x01,0x04,0x03,0xd5,0x03,0xcc,0x01,0xbc,0x03,0xf0,
0x10,0x10,0x10,0x10,0x50,0x50,0x50,0x50,0x14,0x20,0x20,0x20,0x20,0x01,0x01,
0x01,0x01,0xc4,0x02,0x10,0x00,0x00,0x00,0x00,0x01,0x01,0xc0,0xc2,0x10,0x11,
0x02,0x03,0x11,0x03,0x03,0x04,0x00,0x00,0x14,0x00,0x02,0x00,0x00,0xc6,0xc8,
0x02,0x02,0x02,0x02,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0xff,0xca,
0x01,0x01,0x01,0x00,0x06,0x00,0x04,0x00,0xc0,0xc2,0x01,0x01,0x03,0x01,0xff,
0xff,0x01,0x00,0x03,0xc4,0xc4,0xc6,0x03,0x01,0x01,0x01,0xff,0x03,0x03,0x03,
0xc8,0x40,0x00,0x0a,0x00,0x04,0x00,0x00,0x00,0x00,0x7f,0x00,0x33,0x01,0x00,
0x00,0x00,0x00,0x00,0x00,0xff,0xbf,0xff,0xff,0x00,0x00,0x00,0x00,0x07,0x00,
0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0xff,0xff,0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x7f,0x00,0x00,0xff,0x4a,0x4a,0x4a,0x4a,0x4b,0x52,0x4a,0x4a,0x4a,0x4a,0x4f,
0x4c,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x55,0x45,0x40,0x4a,0x4a,0x4a,
0x45,0x59,0x4d,0x46,0x4a,0x5d,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,
0x4a,0x4a,0x4a,0x4a,0x4a,0x61,0x63,0x67,0x4e,0x4a,0x4a,0x6b,0x6d,0x4a,0x4a,
0x45,0x6d,0x4a,0x4a,0x44,0x45,0x4a,0x4a,0x00,0x00,0x00,0x02,0x0d,0x06,0x06,
0x06,0x06,0x0e,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x00,0x06,0x06,0x02,0x06,
0x00,0x0a,0x0a,0x07,0x07,0x06,0x02,0x05,0x05,0x02,0x02,0x00,0x00,0x04,0x04,
0x04,0x04,0x00,0x00,0x00,0x0e,0x05,0x06,0x06,0x06,0x01,0x06,0x00,0x00,0x08,
0x00,0x10,0x00,0x18,0x00,0x20,0x00,0x28,0x00,0x30,0x00,0x80,0x01,0x82,0x01,
0x86,0x00,0xf6,0xcf,0xfe,0x3f,0xab,0x00,0xb0,0x00,0xb1,0x00,0xb3,0x00,0xba,
0xf8,0xbb,0x00,0xc0,0x00,0xc1,0x00,0xc7,0xbf,0x62,0xff,0x00,0x8d,0xff,0x00,
0xc4,0xff,0x00,0xc5,0xff,0x00,0xff,0xff,0xeb,0x01,0xff,0x0e,0x12,0x08,0x00,
0x13,0x09,0x00,0x16,0x08,0x00,0x17,0x09,0x00,0x2b,0x09,0x00,0xae,0xff,0x07,
0xb2,0xff,0x00,0xb4,0xff,0x00,0xb5,0xff,0x00,0xc3,0x01,0x00,0xc7,0xff,0xbf,
0xe7,0x08,0x00,0xf0,0x02,0x00
};

74
third_party/minhook/src/hde/table64.h vendored Normal file
View file

@ -0,0 +1,74 @@
/*
* Hacker Disassembler Engine 64 C
* Copyright (c) 2008-2009, Vyacheslav Patkov.
* All rights reserved.
*
*/
#define C_NONE 0x00
#define C_MODRM 0x01
#define C_IMM8 0x02
#define C_IMM16 0x04
#define C_IMM_P66 0x10
#define C_REL8 0x20
#define C_REL32 0x40
#define C_GROUP 0x80
#define C_ERROR 0xff
#define PRE_ANY 0x00
#define PRE_NONE 0x01
#define PRE_F2 0x02
#define PRE_F3 0x04
#define PRE_66 0x08
#define PRE_67 0x10
#define PRE_LOCK 0x20
#define PRE_SEG 0x40
#define PRE_ALL 0xff
#define DELTA_OPCODES 0x4a
#define DELTA_FPU_REG 0xfd
#define DELTA_FPU_MODRM 0x104
#define DELTA_PREFIXES 0x13c
#define DELTA_OP_LOCK_OK 0x1ae
#define DELTA_OP2_LOCK_OK 0x1c6
#define DELTA_OP_ONLY_MEM 0x1d8
#define DELTA_OP2_ONLY_MEM 0x1e7
unsigned char hde64_table[] = {
0xa5,0xaa,0xa5,0xb8,0xa5,0xaa,0xa5,0xaa,0xa5,0xb8,0xa5,0xb8,0xa5,0xb8,0xa5,
0xb8,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xac,0xc0,0xcc,0xc0,0xa1,0xa1,
0xa1,0xa1,0xb1,0xa5,0xa5,0xa6,0xc0,0xc0,0xd7,0xda,0xe0,0xc0,0xe4,0xc0,0xea,
0xea,0xe0,0xe0,0x98,0xc8,0xee,0xf1,0xa5,0xd3,0xa5,0xa5,0xa1,0xea,0x9e,0xc0,
0xc0,0xc2,0xc0,0xe6,0x03,0x7f,0x11,0x7f,0x01,0x7f,0x01,0x3f,0x01,0x01,0xab,
0x8b,0x90,0x64,0x5b,0x5b,0x5b,0x5b,0x5b,0x92,0x5b,0x5b,0x76,0x90,0x92,0x92,
0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x6a,0x73,0x90,
0x5b,0x52,0x52,0x52,0x52,0x5b,0x5b,0x5b,0x5b,0x77,0x7c,0x77,0x85,0x5b,0x5b,
0x70,0x5b,0x7a,0xaf,0x76,0x76,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,
0x5b,0x5b,0x86,0x01,0x03,0x01,0x04,0x03,0xd5,0x03,0xd5,0x03,0xcc,0x01,0xbc,
0x03,0xf0,0x03,0x03,0x04,0x00,0x50,0x50,0x50,0x50,0xff,0x20,0x20,0x20,0x20,
0x01,0x01,0x01,0x01,0xc4,0x02,0x10,0xff,0xff,0xff,0x01,0x00,0x03,0x11,0xff,
0x03,0xc4,0xc6,0xc8,0x02,0x10,0x00,0xff,0xcc,0x01,0x01,0x01,0x00,0x00,0x00,
0x00,0x01,0x01,0x03,0x01,0xff,0xff,0xc0,0xc2,0x10,0x11,0x02,0x03,0x01,0x01,
0x01,0xff,0xff,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x10,
0x10,0x10,0x10,0x02,0x10,0x00,0x00,0xc6,0xc8,0x02,0x02,0x02,0x02,0x06,0x00,
0x04,0x00,0x02,0xff,0x00,0xc0,0xc2,0x01,0x01,0x03,0x03,0x03,0xca,0x40,0x00,
0x0a,0x00,0x04,0x00,0x00,0x00,0x00,0x7f,0x00,0x33,0x01,0x00,0x00,0x00,0x00,
0x00,0x00,0xff,0xbf,0xff,0xff,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0xff,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,
0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0x00,0x00,
0xff,0x40,0x40,0x40,0x40,0x41,0x49,0x40,0x40,0x40,0x40,0x4c,0x42,0x40,0x40,
0x40,0x40,0x40,0x40,0x40,0x40,0x4f,0x44,0x53,0x40,0x40,0x40,0x44,0x57,0x43,
0x5c,0x40,0x60,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,
0x40,0x40,0x64,0x66,0x6e,0x6b,0x40,0x40,0x6a,0x46,0x40,0x40,0x44,0x46,0x40,
0x40,0x5b,0x44,0x40,0x40,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x06,0x01,0x06,
0x06,0x02,0x06,0x06,0x00,0x06,0x00,0x0a,0x0a,0x00,0x00,0x00,0x02,0x07,0x07,
0x06,0x02,0x0d,0x06,0x06,0x06,0x0e,0x05,0x05,0x02,0x02,0x00,0x00,0x04,0x04,
0x04,0x04,0x05,0x06,0x06,0x06,0x00,0x00,0x00,0x0e,0x00,0x00,0x08,0x00,0x10,
0x00,0x18,0x00,0x20,0x00,0x28,0x00,0x30,0x00,0x80,0x01,0x82,0x01,0x86,0x00,
0xf6,0xcf,0xfe,0x3f,0xab,0x00,0xb0,0x00,0xb1,0x00,0xb3,0x00,0xba,0xf8,0xbb,
0x00,0xc0,0x00,0xc1,0x00,0xc7,0xbf,0x62,0xff,0x00,0x8d,0xff,0x00,0xc4,0xff,
0x00,0xc5,0xff,0x00,0xff,0xff,0xeb,0x01,0xff,0x0e,0x12,0x08,0x00,0x13,0x09,
0x00,0x16,0x08,0x00,0x17,0x09,0x00,0x2b,0x09,0x00,0xae,0xff,0x07,0xb2,0xff,
0x00,0xb4,0xff,0x00,0xb5,0xff,0x00,0xc3,0x01,0x00,0xc7,0xff,0xbf,0xe7,0x08,
0x00,0xf0,0x02,0x00
};

939
third_party/minhook/src/hook.c vendored Normal file
View file

@ -0,0 +1,939 @@
/*
* MinHook - The Minimalistic API Hooking Library for x64/x86
* Copyright (C) 2009-2017 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.
*/
#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.
UINT8 patchAbove : 1; // Uses the hot patch area.
UINT8 isEnabled : 1; // Enabled.
UINT8 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().
static volatile LONG g_isLocked = FALSE;
// Private heap handle. If not NULL, this library is initialized.
static HANDLE g_hHeap = NULL;
// Hook entries.
static 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];
}
#if defined(_M_X64) || defined(__x86_64__)
// 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;
#if defined(_M_X64) || defined(__x86_64__)
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;
default: // 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 BOOL EnumerateThreads(PFROZEN_THREADS pThreads)
{
BOOL succeeded = FALSE;
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hSnapshot != INVALID_HANDLE_VALUE)
{
THREADENTRY32 te;
te.dwSize = sizeof(THREADENTRY32);
if (Thread32First(hSnapshot, &te))
{
succeeded = TRUE;
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)
{
succeeded = FALSE;
break;
}
}
else if (pThreads->size >= pThreads->capacity)
{
LPDWORD p;
pThreads->capacity *= 2;
p = (LPDWORD)HeapReAlloc(
g_hHeap, 0, pThreads->pItems, pThreads->capacity * sizeof(DWORD));
if (p == NULL)
{
succeeded = FALSE;
break;
}
pThreads->pItems = p;
}
pThreads->pItems[pThreads->size++] = te.th32ThreadID;
}
te.dwSize = sizeof(THREADENTRY32);
} while (Thread32Next(hSnapshot, &te));
if (succeeded && GetLastError() != ERROR_NO_MORE_FILES)
succeeded = FALSE;
if (!succeeded && pThreads->pItems != NULL)
{
HeapFree(g_hHeap, 0, pThreads->pItems);
pThreads->pItems = NULL;
}
}
CloseHandle(hSnapshot);
}
return succeeded;
}
//-------------------------------------------------------------------------
static MH_STATUS Freeze(PFROZEN_THREADS pThreads, UINT pos, UINT action)
{
MH_STATUS status = MH_OK;
pThreads->pItems = NULL;
pThreads->capacity = 0;
pThreads->size = 0;
if (!EnumerateThreads(pThreads))
{
status = MH_ERROR_MEMORY_ALLOC;
}
else if (pThreads->pItems != NULL)
{
UINT i;
for (i = 0; i < pThreads->size; ++i)
{
HANDLE hThread = OpenThread(THREAD_ACCESS, FALSE, pThreads->pItems[i]);
BOOL suspended = FALSE;
if (hThread != NULL)
{
DWORD result = SuspendThread(hThread);
if (result != 0xFFFFFFFF)
{
suspended = TRUE;
ProcessThreadIPs(hThread, pos, action);
}
CloseHandle(hThread);
}
if (!suspended)
{
// Mark thread as not suspended, so it's not resumed later on.
pThreads->pItems[i] = 0;
}
}
}
return status;
}
//-------------------------------------------------------------------------
static VOID Unfreeze(PFROZEN_THREADS pThreads)
{
if (pThreads->pItems != NULL)
{
UINT i;
for (i = 0; i < pThreads->size; ++i)
{
DWORD threadId = pThreads->pItems[i];
if (threadId != 0)
{
HANDLE hThread = OpenThread(THREAD_ACCESS, FALSE, threadId);
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 = (INT32)((LPBYTE)pHook->pDetour - (pPatchTarget + sizeof(JMP_REL)));
if (pHook->patchAbove)
{
PJMP_REL_SHORT pShortJmp = (PJMP_REL_SHORT)pHook->pTarget;
pShortJmp->opcode = 0xEB;
pShortJmp->operand = (INT8)(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;
status = Freeze(&threads, ALL_HOOKS_POS, enable ? ACTION_ENABLE : ACTION_DISABLE);
if (status == MH_OK)
{
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;
#if defined(_M_X64) || defined(__x86_64__)
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_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;
status = Freeze(&threads, pos, ACTION_DISABLE);
if (status == MH_OK)
{
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;
}
//-------------------------------------------------------------------------
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
{
UINT pos = FindHookEntry(pTarget);
if (pos != INVALID_HOOK_POS)
{
if (g_hooks.pItems[pos].isEnabled != enable)
{
FROZEN_THREADS threads;
status = Freeze(&threads, pos, ACTION_ENABLE);
if (status == MH_OK)
{
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_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;
status = Freeze(&threads, ALL_HOOKS_POS, ACTION_APPLY_QUEUED);
if (status == MH_OK)
{
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_CreateHookApiEx(
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour,
LPVOID *ppOriginal, LPVOID *ppTarget)
{
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;
if (ppTarget != NULL)
*ppTarget = pTarget;
return MH_CreateHook(pTarget, pDetour, ppOriginal);
}
//-------------------------------------------------------------------------
MH_STATUS WINAPI MH_CreateHookApi(
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal)
{
return MH_CreateHookApiEx(pszModule, pszProcName, pDetour, ppOriginal, NULL);
}
//-------------------------------------------------------------------------
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)";
}

320
third_party/minhook/src/trampoline.c vendored Normal file
View file

@ -0,0 +1,320 @@
/*
* MinHook - The Minimalistic API Hooking Library for x64/x86
* Copyright (C) 2009-2017 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.
*/
#include <windows.h>
#if defined(_MSC_VER) && !defined(MINHOOK_DISABLE_INTRINSICS)
#define ALLOW_INTRINSICS
#include <intrin.h>
#endif
#ifndef ARRAYSIZE
#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0]))
#endif
#if defined(_M_X64) || defined(__x86_64__)
#include "./hde/hde64.h"
typedef hde64s HDE;
#define HDE_DISASM(code, hs) hde64_disasm(code, hs)
#else
#include "./hde/hde32.h"
typedef hde32s HDE;
#define HDE_DISASM(code, hs) hde32_disasm(code, hs)
#endif
#include "trampoline.h"
#include "buffer.h"
// Maximum size of a trampoline function.
#if defined(_M_X64) || defined(__x86_64__)
#define TRAMPOLINE_MAX_SIZE (MEMORY_SLOT_SIZE - sizeof(JMP_ABS))
#else
#define TRAMPOLINE_MAX_SIZE MEMORY_SLOT_SIZE
#endif
//-------------------------------------------------------------------------
static BOOL IsCodePadding(LPBYTE pInst, UINT size)
{
UINT i;
if (pInst[0] != 0x00 && pInst[0] != 0x90 && pInst[0] != 0xCC)
return FALSE;
for (i = 1; i < size; ++i)
{
if (pInst[i] != pInst[0])
return FALSE;
}
return TRUE;
}
//-------------------------------------------------------------------------
BOOL CreateTrampolineFunction(PTRAMPOLINE ct)
{
#if defined(_M_X64) || defined(__x86_64__)
CALL_ABS call = {
0xFF, 0x15, 0x00000002, // FF15 00000002: CALL [RIP+8]
0xEB, 0x08, // EB 08: JMP +10
0x0000000000000000ULL // Absolute destination address
};
JMP_ABS jmp = {
0xFF, 0x25, 0x00000000, // FF25 00000000: JMP [RIP+6]
0x0000000000000000ULL // Absolute destination address
};
JCC_ABS jcc = {
0x70, 0x0E, // 7* 0E: J** +16
0xFF, 0x25, 0x00000000, // FF25 00000000: JMP [RIP+6]
0x0000000000000000ULL // Absolute destination address
};
#else
CALL_REL call = {
0xE8, // E8 xxxxxxxx: CALL +5+xxxxxxxx
0x00000000 // Relative destination address
};
JMP_REL jmp = {
0xE9, // E9 xxxxxxxx: JMP +5+xxxxxxxx
0x00000000 // Relative destination address
};
JCC_REL jcc = {
0x0F, 0x80, // 0F8* xxxxxxxx: J** +6+xxxxxxxx
0x00000000 // Relative destination address
};
#endif
UINT8 oldPos = 0;
UINT8 newPos = 0;
ULONG_PTR jmpDest = 0; // Destination address of an internal jump.
BOOL finished = FALSE; // Is the function completed?
#if defined(_M_X64) || defined(__x86_64__)
UINT8 instBuf[16];
#endif
ct->patchAbove = FALSE;
ct->nIP = 0;
do
{
HDE hs;
UINT copySize;
LPVOID pCopySrc;
ULONG_PTR pOldInst = (ULONG_PTR)ct->pTarget + oldPos;
ULONG_PTR pNewInst = (ULONG_PTR)ct->pTrampoline + newPos;
copySize = HDE_DISASM((LPVOID)pOldInst, &hs);
if (hs.flags & F_ERROR)
return FALSE;
pCopySrc = (LPVOID)pOldInst;
if (oldPos >= sizeof(JMP_REL))
{
// The trampoline function is long enough.
// Complete the function with the jump to the target function.
#if defined(_M_X64) || defined(__x86_64__)
jmp.address = pOldInst;
#else
jmp.operand = (INT32)(pOldInst - (pNewInst + sizeof(jmp)));
#endif
pCopySrc = &jmp;
copySize = sizeof(jmp);
finished = TRUE;
}
#if defined(_M_X64) || defined(__x86_64__)
else if ((hs.modrm & 0xC7) == 0x05)
{
// Instructions using RIP relative addressing. (ModR/M = 00???101B)
// Modify the RIP relative address.
PUINT32 pRelAddr;
// Avoid using memcpy to reduce the footprint.
#ifndef ALLOW_INTRINSICS
memcpy(instBuf, (LPBYTE)pOldInst, copySize);
#else
__movsb(instBuf, (LPBYTE)pOldInst, copySize);
#endif
pCopySrc = instBuf;
// Relative address is stored at (instruction length - immediate value length - 4).
pRelAddr = (PUINT32)(instBuf + hs.len - ((hs.flags & 0x3C) >> 2) - 4);
*pRelAddr
= (UINT32)((pOldInst + hs.len + (INT32)hs.disp.disp32) - (pNewInst + hs.len));
// Complete the function if JMP (FF /4).
if (hs.opcode == 0xFF && hs.modrm_reg == 4)
finished = TRUE;
}
#endif
else if (hs.opcode == 0xE8)
{
// Direct relative CALL
ULONG_PTR dest = pOldInst + hs.len + (INT32)hs.imm.imm32;
#if defined(_M_X64) || defined(__x86_64__)
call.address = dest;
#else
call.operand = (INT32)(dest - (pNewInst + sizeof(call)));
#endif
pCopySrc = &call;
copySize = sizeof(call);
}
else if ((hs.opcode & 0xFD) == 0xE9)
{
// Direct relative JMP (EB or E9)
ULONG_PTR dest = pOldInst + hs.len;
if (hs.opcode == 0xEB) // isShort jmp
dest += (INT8)hs.imm.imm8;
else
dest += (INT32)hs.imm.imm32;
// Simply copy an internal jump.
if ((ULONG_PTR)ct->pTarget <= dest
&& dest < ((ULONG_PTR)ct->pTarget + sizeof(JMP_REL)))
{
if (jmpDest < dest)
jmpDest = dest;
}
else
{
#if defined(_M_X64) || defined(__x86_64__)
jmp.address = dest;
#else
jmp.operand = (INT32)(dest - (pNewInst + sizeof(jmp)));
#endif
pCopySrc = &jmp;
copySize = sizeof(jmp);
// Exit the function if it is not in the branch.
finished = (pOldInst >= jmpDest);
}
}
else if ((hs.opcode & 0xF0) == 0x70
|| (hs.opcode & 0xFC) == 0xE0
|| (hs.opcode2 & 0xF0) == 0x80)
{
// Direct relative Jcc
ULONG_PTR dest = pOldInst + hs.len;
if ((hs.opcode & 0xF0) == 0x70 // Jcc
|| (hs.opcode & 0xFC) == 0xE0) // LOOPNZ/LOOPZ/LOOP/JECXZ
dest += (INT8)hs.imm.imm8;
else
dest += (INT32)hs.imm.imm32;
// Simply copy an internal jump.
if ((ULONG_PTR)ct->pTarget <= dest
&& dest < ((ULONG_PTR)ct->pTarget + sizeof(JMP_REL)))
{
if (jmpDest < dest)
jmpDest = dest;
}
else if ((hs.opcode & 0xFC) == 0xE0)
{
// LOOPNZ/LOOPZ/LOOP/JCXZ/JECXZ to the outside are not supported.
return FALSE;
}
else
{
UINT8 cond = ((hs.opcode != 0x0F ? hs.opcode : hs.opcode2) & 0x0F);
#if defined(_M_X64) || defined(__x86_64__)
// Invert the condition in x64 mode to simplify the conditional jump logic.
jcc.opcode = 0x71 ^ cond;
jcc.address = dest;
#else
jcc.opcode1 = 0x80 | cond;
jcc.operand = (INT32)(dest - (pNewInst + sizeof(jcc)));
#endif
pCopySrc = &jcc;
copySize = sizeof(jcc);
}
}
else if ((hs.opcode & 0xFE) == 0xC2)
{
// RET (C2 or C3)
// Complete the function if not in a branch.
finished = (pOldInst >= jmpDest);
}
// Can't alter the instruction length in a branch.
if (pOldInst < jmpDest && copySize != hs.len)
return FALSE;
// Trampoline function is too large.
if ((newPos + copySize) > TRAMPOLINE_MAX_SIZE)
return FALSE;
// Trampoline function has too many instructions.
if (ct->nIP >= ARRAYSIZE(ct->oldIPs))
return FALSE;
ct->oldIPs[ct->nIP] = oldPos;
ct->newIPs[ct->nIP] = newPos;
ct->nIP++;
// Avoid using memcpy to reduce the footprint.
#ifndef ALLOW_INTRINSICS
memcpy((LPBYTE)ct->pTrampoline + newPos, pCopySrc, copySize);
#else
__movsb((LPBYTE)ct->pTrampoline + newPos, (LPBYTE)pCopySrc, copySize);
#endif
newPos += copySize;
oldPos += hs.len;
} while (!finished);
// Is there enough place for a long jump?
if (oldPos < sizeof(JMP_REL)
&& !IsCodePadding((LPBYTE)ct->pTarget + oldPos, sizeof(JMP_REL) - oldPos))
{
// Is there enough place for a short jump?
if (oldPos < sizeof(JMP_REL_SHORT)
&& !IsCodePadding((LPBYTE)ct->pTarget + oldPos, sizeof(JMP_REL_SHORT) - oldPos))
{
return FALSE;
}
// Can we place the long jump above the function?
if (!IsExecutableAddress((LPBYTE)ct->pTarget - sizeof(JMP_REL)))
return FALSE;
if (!IsCodePadding((LPBYTE)ct->pTarget - sizeof(JMP_REL), sizeof(JMP_REL)))
return FALSE;
ct->patchAbove = TRUE;
}
#if defined(_M_X64) || defined(__x86_64__)
// Create a relay function.
jmp.address = (ULONG_PTR)ct->pDetour;
ct->pRelay = (LPBYTE)ct->pTrampoline + newPos;
memcpy(ct->pRelay, &jmp, sizeof(jmp));
#endif
return TRUE;
}

105
third_party/minhook/src/trampoline.h vendored Normal file
View file

@ -0,0 +1,105 @@
/*
* MinHook - The Minimalistic API Hooking Library for x64/x86
* Copyright (C) 2009-2017 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.
*/
#pragma once
#pragma pack(push, 1)
// Structs for writing x86/x64 instructions.
// 8-bit relative jump.
typedef struct _JMP_REL_SHORT
{
UINT8 opcode; // EB xx: JMP +2+xx
INT8 operand; // Relative destination address
} JMP_REL_SHORT, *PJMP_REL_SHORT;
// 32-bit direct relative jump/call.
typedef struct _JMP_REL
{
UINT8 opcode; // E9/E8 xxxxxxxx: JMP/CALL +5+xxxxxxxx
INT32 operand; // Relative destination address
} JMP_REL, *PJMP_REL, CALL_REL;
// 64-bit indirect absolute jump.
typedef struct _JMP_ABS
{
UINT8 opcode0; // FF25 00000000: JMP [+6]
UINT8 opcode1;
UINT32 dummy;
UINT64 address; // Absolute destination address
} JMP_ABS, *PJMP_ABS;
// 64-bit indirect absolute call.
typedef struct _CALL_ABS
{
UINT8 opcode0; // FF15 00000002: CALL [+6]
UINT8 opcode1;
UINT32 dummy0;
UINT8 dummy1; // EB 08: JMP +10
UINT8 dummy2;
UINT64 address; // Absolute destination address
} CALL_ABS;
// 32-bit direct relative conditional jumps.
typedef struct _JCC_REL
{
UINT8 opcode0; // 0F8* xxxxxxxx: J** +6+xxxxxxxx
UINT8 opcode1;
INT32 operand; // Relative destination address
} JCC_REL;
// 64bit indirect absolute conditional jumps that x64 lacks.
typedef struct _JCC_ABS
{
UINT8 opcode; // 7* 0E: J** +16
UINT8 dummy0;
UINT8 dummy1; // FF25 00000000: JMP [+6]
UINT8 dummy2;
UINT32 dummy3;
UINT64 address; // Absolute destination address
} JCC_ABS;
#pragma pack(pop)
typedef struct _TRAMPOLINE
{
LPVOID pTarget; // [In] Address of the target function.
LPVOID pDetour; // [In] Address of the detour function.
LPVOID pTrampoline; // [In] Buffer address for the trampoline and relay function.
#if defined(_M_X64) || defined(__x86_64__)
LPVOID pRelay; // [Out] Address of the relay function.
#endif
BOOL patchAbove; // [Out] Should use the hot patch area?
UINT nIP; // [Out] Number of the instruction boundaries.
UINT8 oldIPs[8]; // [Out] Instruction boundaries of the target function.
UINT8 newIPs[8]; // [Out] Instruction boundaries of the trampoline function.
} TRAMPOLINE, *PTRAMPOLINE;
BOOL CreateTrampolineFunction(PTRAMPOLINE ct);

38
tools/build-shim.sh Executable file
View file

@ -0,0 +1,38 @@
#!/usr/bin/env bash
# Build the shim (binkw32.dll) with the MinGW i686 cross toolchain and stage a deployable set.
# Runs on the build box (CT111). Env:
# BUILD_ID stamp for the log banner (default: git describe or "nogit", plus UTC time)
# DIST staging dir (default /srv/re-lab/shim/dist, == Z:\shim\dist on the game VM)
# REAL_DLL original binkw32.dll to compare export tables against (skipped if absent)
set -euo pipefail
cd "$(dirname "$0")/.."
BUILD_ID=${BUILD_ID:-$(git describe --always --dirty 2>/dev/null || echo nogit)-$(date -u +%Y%m%dT%H%MZ)}
DIST=${DIST:-/srv/re-lab/shim/dist}
REAL_DLL=${REAL_DLL:-/srv/re-lab/sots-game/binkw32.dll}
OBJDUMP=${OBJDUMP:-i686-w64-mingw32-objdump}
cmake --preset shim -DSHIM_BUILD_ID="$BUILD_ID"
cmake --build --preset shim
dll=build-shim/binkw32.dll
"$OBJDUMP" -p "$dll" > build-shim/exports.txt
# Export-table check: names must be byte-identical to the real DLL's (the exe imports by name).
names() { # the "[Ordinal/Name Pointer] Table" section only (the address table also lists +base[ rows)
"$OBJDUMP" -p "$1" | sed -n '/Ordinal\/Name Pointer\] Table/,/^$/p' | awk '/\+base\[/ { print $6 }' | sort
}
if [ -f "$REAL_DLL" ]; then
if diff <(names "$REAL_DLL") <(names "$dll") > build-shim/exports.diff; then
echo "exports: $(names "$dll" | wc -l) names, identical to $(basename "$REAL_DLL")"
else
echo "exports: MISMATCH vs $REAL_DLL:"; cat build-shim/exports.diff; exit 1
fi
else
echo "exports: $(names "$dll" | wc -l) names (no REAL_DLL to compare against)"
fi
mkdir -p "$DIST"
cp "$dll" src/shim/shim.cfg tools/deploy.ps1 tools/undeploy.ps1 "$DIST/"
echo "$BUILD_ID" > "$DIST/BUILD_ID"
echo "staged $BUILD_ID in $DIST"

52
tools/deploy.ps1 Normal file
View file

@ -0,0 +1,52 @@
# Deploy the shim onto the game VM: stop the game, keep the original binkw32.dll as
# binkw32_real.dll, drop in the proxy + config, relaunch.
# powershell -ExecutionPolicy Bypass -File deploy.ps1 [-Dist Z:\shim\dist] [-NoLaunch]
# If -Dist is on an unmapped drive, the share is mapped using -ShareUser/-SharePass
# (or env RELAB_USER / RELAB_PASS). Credentials are never stored here.
param(
[string]$Dist = 'Z:\shim\dist',
[string]$GameDir = 'C:\SOTS',
[string]$Share = '\\192.168.10.138\re-lab',
[string]$ShareUser = $env:RELAB_USER,
[string]$SharePass = $env:RELAB_PASS,
[string]$Task = 'SOTS',
[switch]$NoLaunch
)
$ProgressPreference = 'SilentlyContinue'
$ErrorActionPreference = 'Stop'
if (-not (Test-Path $Dist)) {
$drive = $Dist.Substring(0, 2)
if ($SharePass) { net use $drive $Share /user:$ShareUser $SharePass | Out-Null }
if (-not (Test-Path $Dist)) { throw "cannot reach $Dist (share not mapped; pass -ShareUser/-SharePass)" }
}
$src = Join-Path $Dist 'binkw32.dll'
if (-not (Test-Path $src)) { throw "no binkw32.dll in $Dist" }
$exe = 'Sword of the Stars'
$cur = Join-Path $GameDir 'binkw32.dll'
$real = Join-Path $GameDir 'binkw32_real.dll'
$p = Get-Process -Name $exe -ErrorAction SilentlyContinue
if ($p) { Write-Output "stopping $exe (pid $($p.Id))"; $p | Stop-Process -Force; Start-Sleep -Seconds 3 }
# First deploy: the file in place is the original. A proxy is recognisable by its forwarder strings.
$isProxy = (Get-Content -Path $cur -Raw) -match 'binkw32_real\.'
if (-not (Test-Path $real)) {
if ($isProxy) { throw "$cur is already a proxy and $real is missing - restore the original first" }
Copy-Item $cur $real
Write-Output "backed up original -> $real"
}
Copy-Item $src $cur -Force
$cfg = Join-Path $GameDir 'shim.cfg'
if (-not (Test-Path $cfg)) { Copy-Item (Join-Path $Dist 'shim.cfg') $cfg } # keep operator edits
$id = Join-Path $Dist 'BUILD_ID'
if (Test-Path $id) { Write-Output "deployed build $(Get-Content $id)" }
if (-not $NoLaunch) {
schtasks /Run /TN $Task | Out-Null
Start-Sleep -Seconds 5
$p = Get-Process -Name $exe -ErrorAction SilentlyContinue
if ($p) { Write-Output "launched $exe (pid $($p.Id))" } else { Write-Warning "$exe not running 5 s after launch" }
}

17
tools/sync-build.sh Executable file
View file

@ -0,0 +1,17 @@
#!/usr/bin/env bash
# From the dev box: push this tree to the build box's share and run tools/build-shim.sh there.
# The share (/bulk-storage/re-lab on host "spicy") is /srv/re-lab inside CT111.
set -euo pipefail
cd "$(dirname "$0")/.."
HOST=${HOST:-spicy}
CT=${CT:-111}
REMOTE_TREE=${REMOTE_TREE:-/bulk-storage/re-lab/build/sots-engine} # host path
CT_TREE=${CT_TREE:-/srv/re-lab/build/sots-engine} # same dir, seen from the CT
BUILD_ID=$(git describe --always --dirty 2>/dev/null || echo nogit)-$(date -u +%Y%m%dT%H%MZ)
# unprivileged CT: uid 0 in the container is 100000 on the host
rsync -a --delete --chown=100000:100000 \
--exclude .git --exclude '/build-*/' --exclude '/build/' \
./ "$HOST:$REMOTE_TREE/"
ssh "$HOST" pct exec "$CT" -- env BUILD_ID="$BUILD_ID" bash "$CT_TREE/tools/build-shim.sh"

27
tools/undeploy.ps1 Normal file
View file

@ -0,0 +1,27 @@
# Put the original binkw32.dll back and relaunch the game.
# powershell -ExecutionPolicy Bypass -File undeploy.ps1 [-NoLaunch]
param(
[string]$GameDir = 'C:\SOTS',
[string]$Task = 'SOTS',
[switch]$NoLaunch
)
$ProgressPreference = 'SilentlyContinue'
$ErrorActionPreference = 'Stop'
$exe = 'Sword of the Stars'
$cur = Join-Path $GameDir 'binkw32.dll'
$real = Join-Path $GameDir 'binkw32_real.dll'
if (-not (Test-Path $real)) { throw "$real not found - nothing to restore" }
$p = Get-Process -Name $exe -ErrorAction SilentlyContinue
if ($p) { Write-Output "stopping $exe (pid $($p.Id))"; $p | Stop-Process -Force; Start-Sleep -Seconds 3 }
Move-Item $real $cur -Force
Write-Output "restored original binkw32.dll"
if (-not $NoLaunch) {
schtasks /Run /TN $Task | Out-Null
Start-Sleep -Seconds 5
$p = Get-Process -Name $exe -ErrorAction SilentlyContinue
if ($p) { Write-Output "launched $exe (pid $($p.Id))" } else { Write-Warning "$exe not running 5 s after launch" }
}