mumble/overlay/lib.cpp
Mikkel Krautz 557eacd1e7 overlay: remove ability for overlay to decline being attached to a process.
This was originally implemented in
2f07778a0e.

This change was a mistake. We use the WH_CBT hook to inject our DLL into
potential targets. That means that every mouse/keyboard event will trigger
an attempt to load our overlay DLL, if it isn't already loaded.

That, combined with the new ability for the overlay to decline being
injected caused problems for processes that are blacklisted by Mumble:
Every mouse/keyboard event would attempt to load the DLL, go through the
exclusion checks (query the process tree, query the registry, find out
that the process is blacklisted, unload the DLL...).

We can't have that, so let's just revert this.
2017-04-22 10:08:18 +02:00

743 lines
19 KiB
C++

// Copyright 2005-2017 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#include "lib.h"
#include "excludecheck.h"
#include "overlay_exe/overlay_exe.h"
#undef max // for std::numeric_limits<T>::max()
static HANDLE hMapObject = NULL;
static HANDLE hHookMutex = NULL;
static HHOOK hhookWnd = 0;
BOOL bIsWin8 = FALSE;
static BOOL bMumble = FALSE;
static BOOL bDebug = FALSE;
static BOOL bEnableOverlay = TRUE;
static HardHook hhLoad;
static HardHook hhLoadW;
static SharedData *sd = NULL;
CRITICAL_SECTION Mutex::cs;
void Mutex::init() {
InitializeCriticalSection(&cs);
}
Mutex::Mutex() {
if (! TryEnterCriticalSection(&cs)) {
ods("Lib: Mutex: CritFail - blocking until able to enter critical section");
EnterCriticalSection(&cs);
}
}
Mutex::~Mutex() {
LeaveCriticalSection(&cs);
}
void __cdecl ods(const char *format, ...) {
#ifndef DEBUG
if (!bDebug)
return;
#endif
va_list args;
va_start(args, format);
_ods_out(format, &args);
va_end(args);
}
void __cdecl checkForWPF() {
if (bEnableOverlay && (GetModuleHandleW(L"wpfgfx_v0300.dll") || GetModuleHandleW(L"wpfgfx_v0400.dll"))) {
ods("Lib: Blacklisted for loading WPF library");
bEnableOverlay = FALSE;
}
}
Pipe::Pipe() {
hSocket = INVALID_HANDLE_VALUE;
hMemory = NULL;
a_ucTexture = NULL;
omMsg.omh.iLength = -1;
dwAlreadyRead = 0;
uiWidth = uiHeight = 0;
uiLeft = uiRight = uiTop = uiBottom = 0;
}
Pipe::~Pipe() {
disconnect();
}
void Pipe::release() {
if (hMemory) {
CloseHandle(hMemory);
hMemory = NULL;
if (a_ucTexture) {
UnmapViewOfFile(a_ucTexture);
a_ucTexture = NULL;
}
uiLeft = uiRight = uiTop = uiBottom = 0;
}
}
void Pipe::disconnect() {
release();
if (hSocket != INVALID_HANDLE_VALUE) {
ods("Pipe: Disconnect");
CloseHandle(hSocket);
hSocket = INVALID_HANDLE_VALUE;
}
uiWidth = 0;
uiHeight = 0;
omMsg.omh.iLength = -1;
}
bool Pipe::sendMessage(const OverlayMsg &om) {
DWORD dwBytesToWrite = sizeof(OverlayMsgHeader) + om.omh.iLength;
DWORD dwBytesWritten = dwBytesToWrite;
if (WriteFile(hSocket, om.headerbuffer, sizeof(OverlayMsgHeader) + om.omh.iLength, &dwBytesWritten, NULL))
if (dwBytesToWrite == dwBytesWritten)
return true;
ods("Pipe: Short write");
disconnect();
return false;
}
void Pipe::checkMessage(unsigned int width, unsigned int height) {
if (!width || ! height)
return;
if (hSocket == INVALID_HANDLE_VALUE) {
hSocket = CreateFileW(L"\\\\.\\pipe\\MumbleOverlayPipe", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hSocket == INVALID_HANDLE_VALUE) {
ods("Pipe: Connection failed");
return;
}
ods("Pipe: Connected");
uiWidth = 0;
uiHeight = 0;
// initially, instantiate and send an OverlayMessage with the current process id
OverlayMsg om;
om.omh.uiMagic = OVERLAY_MAGIC_NUMBER;
om.omh.uiType = OVERLAY_MSGTYPE_PID;
om.omh.iLength = sizeof(OverlayMsgPid);
om.omp.pid = GetCurrentProcessId();
if (!sendMessage(om))
return;
ods("Pipe: Process ID sent");
}
// if the passed width and height do not match the current overlays uiWidth and uiHeight, re-initialize
if ((uiWidth != width) || (uiHeight != height)) {
release();
uiWidth = width;
uiHeight = height;
// instantiate and send an initialization-OverlayMessage
OverlayMsg om;
om.omh.uiMagic = OVERLAY_MAGIC_NUMBER;
om.omh.uiType = OVERLAY_MSGTYPE_INIT;
om.omh.iLength = sizeof(OverlayMsgInit);
om.omi.uiWidth = uiWidth;
om.omi.uiHeight = uiHeight;
if (!sendMessage(om))
return;
ods("Pipe: SentInitMsg with w h %d %d", uiWidth, uiHeight);
}
std::vector<RECT> blits;
while (1) {
DWORD dwBytesLeft;
DWORD dwBytesRead;
if (! PeekNamedPipe(hSocket, NULL, 0, NULL, &dwBytesLeft, NULL)) {
ods("Pipe: Could not peek");
disconnect();
return;
}
if (! dwBytesLeft)
break;
if (omMsg.omh.iLength == -1) {
if (! ReadFile(hSocket, reinterpret_cast<unsigned char *>(omMsg.headerbuffer) + dwAlreadyRead, sizeof(OverlayMsgHeader) - dwAlreadyRead, &dwBytesRead, NULL)) {
ods("Pipe: Read header fail");
disconnect();
return;
}
dwBytesLeft -= dwBytesRead;
dwAlreadyRead += dwBytesRead;
if (dwAlreadyRead != sizeof(OverlayMsgHeader)) {
break;
}
dwAlreadyRead = 0;
if (omMsg.omh.uiMagic != OVERLAY_MAGIC_NUMBER) {
ods("Pipe: Invalid magic number %x", omMsg.omh.uiMagic);
disconnect();
return;
}
if (static_cast<int>(dwBytesLeft) < omMsg.omh.iLength)
continue;
}
if (! ReadFile(hSocket, reinterpret_cast<unsigned char *>(omMsg.msgbuffer) + dwAlreadyRead, omMsg.omh.iLength - dwAlreadyRead, &dwBytesRead, NULL)) {
ods("Pipe: Read data fail");
disconnect();
return;
}
dwAlreadyRead += dwBytesRead;
if (static_cast<int>(dwBytesLeft) < omMsg.omh.iLength)
continue;
dwAlreadyRead = 0;
switch (omMsg.omh.uiType) {
case OVERLAY_MSGTYPE_SHMEM: {
wchar_t memname[2048];
memname[0] = 0;
MultiByteToWideChar(CP_UTF8, 0, omMsg.oms.a_cName, omMsg.omh.iLength, memname, 2048);
release();
hMemory = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, uiWidth * uiHeight * 4, memname);
if (GetLastError() != ERROR_ALREADY_EXISTS) {
ods("Pipe: Memory %s(%d) => %ls doesn't exist", omMsg.oms.a_cName, omMsg.omh.iLength, memname);
if (hMemory) {
CloseHandle(hMemory);
hMemory = NULL;
break;
}
}
if (! hMemory) {
ods("Pipe: CreateFileMapping failed");
break;
}
a_ucTexture = reinterpret_cast<unsigned char *>(MapViewOfFile(hMemory, FILE_MAP_ALL_ACCESS, 0, 0, 0));
if (a_ucTexture == NULL) {
ods("Pipe: Failed to map memory");
CloseHandle(hMemory);
hMemory = NULL;
break;
}
MEMORY_BASIC_INFORMATION mbi;
memset(&mbi, 0, sizeof(mbi));
if ((VirtualQuery(a_ucTexture, &mbi, sizeof(mbi)) == 0) || (mbi.RegionSize < (uiHeight * uiWidth * 4))) {
ods("Pipe: Memory too small");
UnmapViewOfFile(a_ucTexture);
CloseHandle(hMemory);
a_ucTexture = NULL;
hMemory = NULL;
break;
}
OverlayMsg om;
om.omh.uiMagic = OVERLAY_MAGIC_NUMBER;
om.omh.uiType = OVERLAY_MSGTYPE_SHMEM;
om.omh.iLength = 0;
if (!sendMessage(om))
return;
newTexture(uiWidth, uiHeight);
}
break;
case OVERLAY_MSGTYPE_BLIT: {
RECT r = {
static_cast<LONG>(omMsg.omb.x),
static_cast<LONG>(omMsg.omb.y),
static_cast<LONG>(omMsg.omb.x + omMsg.omb.w),
static_cast<LONG>(omMsg.omb.y + omMsg.omb.h)
};
std::vector<RECT>::iterator i = blits.begin();
while (i != blits.end()) {
RECT is;
if (::IntersectRect(&is, &r, & *i)) {
::UnionRect(&is, &r, & *i);
r = is;
blits.erase(i);
i = blits.begin();
} else {
++i;
}
}
blits.push_back(r);
}
break;
case OVERLAY_MSGTYPE_ACTIVE: {
uiLeft = omMsg.oma.x;
uiTop = omMsg.oma.y;
uiRight = omMsg.oma.x + omMsg.oma.w;
uiBottom = omMsg.oma.y + omMsg.oma.h;
if (a_ucTexture) {
setRect();
blit(0, 0, uiWidth, uiHeight);
}
}
break;
default:
break;
}
omMsg.omh.iLength = -1;
}
if (!a_ucTexture)
return;
for (std::vector<RECT>::iterator i = blits.begin(); i != blits.end(); ++i)
blit((*i).left, (*i).top, (*i).right - (*i).left, (*i).bottom - (*i).top);
}
static void checkHooks(bool preonly) {
checkD3D9Hook(preonly);
checkDXGIHook(preonly);
checkDXGI10Hook(preonly);
checkDXGI11Hook(preonly);
checkOpenGLHook();
}
typedef HMODULE(__stdcall *LoadLibraryAType)(const char *);
static HMODULE WINAPI MyLoadLibrary(const char *lpFileName) {
//TODO: Move logic to HardHook.
// Call base without active hook in case of no trampoline.
LoadLibraryAType oLoadLibrary = (LoadLibraryAType) hhLoad.call;
hhLoad.restore();
HMODULE h = oLoadLibrary(lpFileName);
hhLoad.inject();
ods("Lib: Library %s loaded to %p", lpFileName, h);
if (bEnableOverlay) {
checkHooks();
}
return h;
}
typedef HMODULE(__stdcall *LoadLibraryWType)(const wchar_t *);
static HMODULE WINAPI MyLoadLibraryW(const wchar_t *lpFileName) {
//TODO: Move logic to HardHook.
// Call base without active hook in case of no trampoline.
LoadLibraryWType oLoadLibrary = (LoadLibraryWType) hhLoadW.call;
hhLoadW.restore();
HMODULE h = oLoadLibrary(lpFileName);
hhLoadW.inject();
ods("Lib: Library %ls wloaded to %p", lpFileName, h);
checkForWPF();
if (bEnableOverlay) {
checkHooks();
}
return h;
}
static LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam) {
return CallNextHookEx(hhookWnd, nCode, wParam, lParam);
}
extern "C" __declspec(dllexport) void __cdecl RemoveHooks() {
DWORD dwWaitResult = WaitForSingleObject(hHookMutex, 1000L);
if (dwWaitResult == WAIT_OBJECT_0) {
if (sd != NULL && sd->bHooked) {
if (hhookWnd) {
UnhookWindowsHookEx(hhookWnd);
hhookWnd = NULL;
}
sd->bHooked = false;
}
ReleaseMutex(hHookMutex);
}
}
extern "C" __declspec(dllexport) void __cdecl InstallHooks() {
DWORD dwWaitResult = WaitForSingleObject(hHookMutex, 1000L);
if (dwWaitResult == WAIT_OBJECT_0) {
if (sd != NULL && ! sd->bHooked) {
HMODULE hSelf = NULL;
GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (char *) &InstallHooks, &hSelf);
if (hSelf == NULL) {
ods("Lib: Failed to find myself");
} else {
hhookWnd = SetWindowsHookEx(WH_CBT, CallWndProc, hSelf, 0);
if (hhookWnd == NULL)
ods("Lib: Failed to insert WNDProc hook");
}
sd->bHooked = true;
}
ReleaseMutex(hHookMutex);
}
}
extern "C" __declspec(dllexport) unsigned int __cdecl GetOverlayMagicVersion() {
return OVERLAY_MAGIC_NUMBER;
}
// Via d3d9.cpp
extern "C" __declspec(dllexport) void __cdecl PrepareD3D9();
// Via dxgi.cpp
extern "C" __declspec(dllexport) void __cdecl PrepareDXGI();
void __stdcall OverlayHelperProcessParentDeathThread(void *udata) {
HANDLE parent = reinterpret_cast<HANDLE>(udata);
DWORD status = WaitForSingleObject(parent, INFINITE);
if (status != WAIT_OBJECT_0) {
ExitProcess(OVERLAY_HELPER_ERROR_DLL_PDEATH_WAIT_FAIL);
}
ExitProcess(0);
}
extern "C" __declspec(dllexport) int __cdecl OverlayHelperProcessMain(unsigned int magic, HANDLE parent) {
int retval = 0;
if (GetOverlayMagicVersion() != magic) {
return OVERLAY_HELPER_ERROR_DLL_MAGIC_MISMATCH;
}
HANDLE pcheckHandle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) OverlayHelperProcessParentDeathThread,
reinterpret_cast<void *>(parent), 0, NULL);
if (pcheckHandle == 0) {
return OVERLAY_HELPER_ERROR_DLL_PDEATH_THREAD_ERROR;
}
PrepareD3D9();
PrepareDXGI();
InstallHooks();
while (1) {
MSG msg;
BOOL ret;
ret = GetMessage(&msg, NULL, 0, 0);
// The ret variable is set to 0 on WM_QUIT,
// and -1 on error.
if (ret == 0) {
retval = 0;
break;
} else if (ret == -1) {
retval = -1001;
break;
}
if (msg.message == WM_CLOSE) {
retval = 0;
break;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
RemoveHooks();
return retval;
}
static bool createSharedDataMap();
// Check for the presence of a "nooverlay" file in the directory of the
// program we're injected into. If such a file exists, it's a hint that
// we shouldn't inject into it.
static void checkNoOverlayFile(const std::string &dir) {
std::string nooverlay = dir + "\\nooverlay";
HANDLE h = CreateFile(nooverlay.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (h != INVALID_HANDLE_VALUE) {
CloseHandle(h);
ods("Lib: Overlay disable %s found", dir.c_str());
bEnableOverlay = FALSE;
return;
}
}
// Check for the presence of a "debugoverlay" file in the directory of
// the program we're injected into. If such a file exists, it's a hint
// from the user that they want verbose debugging output from the overlay.
static void checkDebugOverlayFile(const std::string &dir) {
// check for "debugoverlay" file, which would enable overlay debugging
std::string debugoverlay = dir + "\\debugoverlay";
HANDLE h = CreateFile(debugoverlay.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (h != INVALID_HANDLE_VALUE) {
CloseHandle(h);
ods("Lib: Overlay debug %s found", debugoverlay.c_str());
bDebug = TRUE;
}
}
// Given the absolute path to the current process's executable via |procname|,
// return the absolute path to the executable in |absExeName|, the directory
// that the executable lives in in |dir| and the basename of the executable in
// |exeName|.
//
// Returns true on success and fills out |absExeName|, |dir| and |exeName|.
// Returns false on failure, and does not touch |absExeName|, |dir| and |exeName|.
static bool parseProcName(char *procname, std::string &absExeName, std::string &dir, std::string &exeName) {
if (procname == NULL) {
return false;
}
char *p = strrchr(procname, '\\');
if (p == NULL) {
return false;
}
absExeName = std::string(procname);
dir = std::string(procname, p - procname);
exeName = std::string(p + 1);
return true;
}
static bool dllmainProcAttach(char *procname) {
Mutex::init();
std::string absExeName;
std::string dir;
std::string exeName;
bool ok = parseProcName(procname, absExeName, dir, exeName);
if (!ok) {
// No blacklisting if the file has no path
} else if (GetProcAddress(NULL, "mumbleSelfDetection") != NULL) {
ods("Lib: Attached to overlay helper or Mumble process. Blacklisted - no overlay injection.");
bEnableOverlay = FALSE;
bMumble = TRUE;
} else {
checkNoOverlayFile(dir);
checkDebugOverlayFile(dir);
if (bEnableOverlay) {
bEnableOverlay = ExcludeCheckIsOverlayEnabled(absExeName, exeName);
}
if (!bEnableOverlay) {
return false;
}
}
OSVERSIONINFOEX ovi;
memset(&ovi, 0, sizeof(ovi));
ovi.dwOSVersionInfoSize = sizeof(ovi);
GetVersionEx(reinterpret_cast<OSVERSIONINFO *>(&ovi));
bIsWin8 = (ovi.dwMajorVersion >= 7) || ((ovi.dwMajorVersion == 6) && (ovi.dwBuildNumber >= 9200));
ods("Lib: bIsWin8: %i", bIsWin8);
hHookMutex = CreateMutex(NULL, false, "MumbleHookMutex");
if (hHookMutex == NULL) {
ods("Lib: CreateMutex failed");
return false;
}
if(!createSharedDataMap())
return false;
if (! bMumble) {
// Hook our own LoadLibrary functions so we notice when a new library (like the d3d ones) is loaded.
hhLoad.setup(reinterpret_cast<voidFunc>(LoadLibraryA), reinterpret_cast<voidFunc>(MyLoadLibrary));
hhLoadW.setup(reinterpret_cast<voidFunc>(LoadLibraryW), reinterpret_cast<voidFunc>(MyLoadLibraryW));
checkHooks(true);
ods("Lib: Injected into %s", procname);
}
return true;
}
static bool createSharedDataMap() {
DWORD dwSharedSize = sizeof(SharedData) + sizeof(Direct3D9Data) + sizeof(DXGIData) + sizeof(D3D10Data) + sizeof(D3D11Data);
#if defined(_M_IX86)
const char *name = "MumbleOverlayPrivate-x86";
#elif defined(_M_X64)
const char *name = "MumbleOverlayPrivate-x64";
#else
# error Unsupported architecture
#endif
hMapObject = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, dwSharedSize, name);
if (hMapObject == NULL) {
ods("Lib: CreateFileMapping failed");
return false;
}
//Note: If the mapping exists dwSharedSize value will be ignored and existing handle returned
bool bInit = (GetLastError() != ERROR_ALREADY_EXISTS);
unsigned char *rawSharedPointer = static_cast<unsigned char *>(
MapViewOfFile(hMapObject, FILE_MAP_ALL_ACCESS, 0, 0, dwSharedSize));
if (rawSharedPointer == NULL) {
ods("Lib: MapViewOfFile failed");
return false;
}
if (bInit)
memset(rawSharedPointer, 0, dwSharedSize);
sd = reinterpret_cast<SharedData *>(rawSharedPointer);
rawSharedPointer += sizeof(SharedData);
d3dd = reinterpret_cast<Direct3D9Data *>(rawSharedPointer);
rawSharedPointer += sizeof(Direct3D9Data);
dxgi = reinterpret_cast<DXGIData *>(rawSharedPointer);
rawSharedPointer += sizeof(DXGIData);
d3d10 = reinterpret_cast<D3D10Data *>(rawSharedPointer);
rawSharedPointer += sizeof(D3D10Data);
d3d11 = reinterpret_cast<D3D11Data *>(rawSharedPointer);
rawSharedPointer += sizeof(D3D11Data);
return true;
}
static void dllmainProcDetach() {
hhLoad.restore(true);
hhLoad.reset();
hhLoadW.restore(true);
hhLoadW.reset();
if (sd)
UnmapViewOfFile(sd);
if (hMapObject)
CloseHandle(hMapObject);
if (hHookMutex)
CloseHandle(hHookMutex);
}
static void dllmainThreadAttach() {
static bool bTriedHook = false;
if (bEnableOverlay && sd && ! bTriedHook) {
bTriedHook = true;
checkForWPF();
if (bEnableOverlay) {
ods("Lib: Checking for hooks, potentially injecting");
checkHooks();
}
}
}
extern "C" BOOL WINAPI DllMain(HINSTANCE, DWORD fdwReason, LPVOID) {
char procname[PROCNAMEFILEPATH_EXTENDED_BUFFER_BUFLEN];
GetModuleFileNameA(NULL, procname, ARRAY_NUM_ELEMENTS(procname));
// Fix for windows XP; on length nSize does not include null-termination
// @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms683197%28v=vs.85%29.aspx
procname[ARRAY_NUM_ELEMENTS(procname) - 1] = '\0';
switch (fdwReason) {
case DLL_PROCESS_ATTACH: {
ods("Lib: ProcAttach: %s", procname);
dllmainProcAttach(procname);
break;
}
case DLL_PROCESS_DETACH:
ods("Lib: ProcDetach: %s", procname);
dllmainProcDetach();
break;
case DLL_THREAD_ATTACH:
ods("Lib: ThreadAttach: %s", procname);
dllmainThreadAttach();
break;
default:
break;
}
return TRUE;
}
bool IsFnInModule(voidFunc fnptr, wchar_t *refmodulepath, const std::string &logPrefix, const std::string &fnName) {
HMODULE hModule = NULL;
BOOL success = GetModuleHandleEx(
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
reinterpret_cast<LPCSTR>(fnptr), &hModule);
if (!success) {
ods((logPrefix + ": Failed to get module for " + fnName).c_str());
} else {
wchar_t modulename[MODULEFILEPATH_BUFLEN];
GetModuleFileNameW(hModule, modulename, ARRAY_NUM_ELEMENTS(modulename));
return _wcsicmp(modulename, refmodulepath) == 0;
}
return false;
}
int GetFnOffsetInModule(voidFunc fnptr, wchar_t *refmodulepath, unsigned int refmodulepathLen, const std::string &logPrefix, const std::string &fnName) {
HMODULE hModule = NULL;
if (! GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (char *) fnptr, &hModule)) {
ods((logPrefix + ": Failed to get module for " + fnName).c_str());
return -1;
}
const bool bInit = refmodulepath[0] == '\0';
if (bInit) {
GetModuleFileNameW(hModule, refmodulepath, refmodulepathLen);
} else {
wchar_t modulename[MODULEFILEPATH_BUFLEN];
GetModuleFileNameW(hModule, modulename, ARRAY_NUM_ELEMENTS(modulename));
if (_wcsicmp(modulename, refmodulepath) != 0) {
ods((logPrefix + ": " + fnName + " functions module path does not match previously found. Now: '%ls', Previously: '%ls'").c_str(), modulename, refmodulepath);
return -2;
}
}
unsigned char *fn = reinterpret_cast<unsigned char *>(fnptr);
unsigned char *base = reinterpret_cast<unsigned char *>(hModule);
unsigned long off = static_cast<unsigned long>(fn - base);
// XXX: convert this function to use something other than int.
// Issue mumble-voip/mumble#1924.
if (off > static_cast<unsigned long>(std::numeric_limits<int>::max())) {
ods("Internal overlay error: GetFnOffsetInModule() offset greater than return type can hold.");
return -1;
}
return static_cast<int>(off);
}