603 lines
17 KiB
C++
603 lines
17 KiB
C++
/********************************************************************************
|
|
Copyright (C) 2012 Hugh Bailey <obs.jim@gmail.com>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
|
|
********************************************************************************/
|
|
|
|
|
|
#include "GraphicsCaptureHook.h"
|
|
|
|
#include <time.h>
|
|
#include <psapi.h>
|
|
|
|
|
|
HANDLE hSignalRestart=NULL, hSignalEnd=NULL;
|
|
HANDLE hSignalReady=NULL, hSignalExit=NULL;
|
|
|
|
HINSTANCE hinstMain = NULL;
|
|
HWND hwndSender = NULL, hwndOBS = NULL, hwndD3DDummyWindow = NULL;
|
|
HANDLE textureMutexes[2] = {NULL, NULL};
|
|
int resetCount = 1;
|
|
bool bStopRequested = false;
|
|
bool bCapturing = true;
|
|
bool bTargetAcquired = false;
|
|
bool bCaptureThreadStop = false;
|
|
HANDLE hCaptureThread = NULL;
|
|
|
|
HANDLE hFileMap = NULL;
|
|
LPBYTE lpSharedMemory = NULL;
|
|
UINT sharedMemoryIDCounter = 0;
|
|
char processName[MAX_PATH];
|
|
|
|
HANDLE hInfoFileMap = NULL;
|
|
CaptureInfo *infoMem = NULL;
|
|
|
|
|
|
LARGE_INTEGER clockFreq, startTime;
|
|
LONGLONG prevElapsedTime;
|
|
DWORD startTick;
|
|
|
|
wstring strKeepAlive;
|
|
LONGLONG keepAliveTime = 0;
|
|
|
|
CRITICAL_SECTION d3d8EndMutex;
|
|
CRITICAL_SECTION d3d9EndMutex;
|
|
CRITICAL_SECTION glMutex;
|
|
CRITICAL_SECTION ddrawMutex;
|
|
|
|
void CheckD3D8Capture();
|
|
void CheckD3D9Capture();
|
|
void CheckGLCapture();
|
|
void CheckDDrawCapture();
|
|
|
|
|
|
string CurrentDateTimeString()
|
|
{
|
|
time_t now = time(0);
|
|
struct tm tstruct;
|
|
char buf[80];
|
|
localtime_s(&tstruct, &now);
|
|
strftime(buf, sizeof(buf), "%Y-%m-%d, %X: ", &tstruct);
|
|
return string(buf);
|
|
}
|
|
|
|
string CurrentTimeString()
|
|
{
|
|
time_t now = time(0);
|
|
struct tm tstruct;
|
|
char buf[80];
|
|
localtime_s(&tstruct, &now);
|
|
strftime(buf, sizeof(buf), "%X: ", &tstruct);
|
|
return string(buf);
|
|
}
|
|
|
|
|
|
void WINAPI OSInitializeTimer()
|
|
{
|
|
QueryPerformanceFrequency(&clockFreq);
|
|
QueryPerformanceCounter(&startTime);
|
|
startTick = GetTickCount();
|
|
prevElapsedTime = 0;
|
|
keepAliveTime = 0;
|
|
}
|
|
|
|
LONGLONG WINAPI OSGetTimeMicroseconds()
|
|
{
|
|
LARGE_INTEGER currentTime;
|
|
QueryPerformanceCounter(¤tTime);
|
|
LONGLONG elapsedTime = currentTime.QuadPart -
|
|
startTime.QuadPart;
|
|
|
|
// Compute the number of millisecond ticks elapsed.
|
|
unsigned long msecTicks = (unsigned long)(1000 * elapsedTime /
|
|
clockFreq.QuadPart);
|
|
|
|
// Check for unexpected leaps in the Win32 performance counter.
|
|
// (This is caused by unexpected data across the PCI to ISA
|
|
// bridge, aka south bridge. See Microsoft KB274323.)
|
|
unsigned long elapsedTicks = GetTickCount() - startTick;
|
|
signed long msecOff = (signed long)(msecTicks - elapsedTicks);
|
|
if (msecOff < -100 || msecOff > 100)
|
|
{
|
|
// Adjust the starting time forwards.
|
|
LONGLONG msecAdjustment = min(msecOff *
|
|
clockFreq.QuadPart / 1000, elapsedTime -
|
|
prevElapsedTime);
|
|
startTime.QuadPart += msecAdjustment;
|
|
elapsedTime -= msecAdjustment;
|
|
}
|
|
|
|
// Store the current elapsed time for adjustments next time.
|
|
prevElapsedTime = elapsedTime;
|
|
|
|
// Convert to microseconds.
|
|
LONGLONG usecTicks = (1000000 * elapsedTime /
|
|
clockFreq.QuadPart);
|
|
|
|
return usecTicks;
|
|
}
|
|
|
|
HANDLE WINAPI OSCreateMutex()
|
|
{
|
|
CRITICAL_SECTION *pSection = (CRITICAL_SECTION*)malloc(sizeof(CRITICAL_SECTION));
|
|
InitializeCriticalSection(pSection);
|
|
|
|
return (HANDLE)pSection;
|
|
}
|
|
|
|
void WINAPI OSEnterMutex(HANDLE hMutex)
|
|
{
|
|
if(hMutex)
|
|
EnterCriticalSection((CRITICAL_SECTION*)hMutex);
|
|
}
|
|
|
|
BOOL WINAPI OSTryEnterMutex(HANDLE hMutex)
|
|
{
|
|
if(hMutex)
|
|
return TryEnterCriticalSection((CRITICAL_SECTION*)hMutex);
|
|
return FALSE;
|
|
}
|
|
|
|
void WINAPI OSLeaveMutex(HANDLE hMutex)
|
|
{
|
|
if(hMutex)
|
|
LeaveCriticalSection((CRITICAL_SECTION*)hMutex);
|
|
}
|
|
|
|
void WINAPI OSCloseMutex(HANDLE hMutex)
|
|
{
|
|
if(hMutex)
|
|
{
|
|
DeleteCriticalSection((CRITICAL_SECTION*)hMutex);
|
|
free(hMutex);
|
|
}
|
|
}
|
|
|
|
|
|
void QuickLog(LPCSTR lpText)
|
|
{
|
|
HANDLE hFile = CreateFile(TEXT("d:\\log.txt"), GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, 0, NULL);
|
|
DWORD bla;
|
|
SetFilePointer(hFile, 0, 0, FILE_END);
|
|
WriteFile(hFile, lpText, (DWORD)strlen(lpText), &bla, NULL);
|
|
FlushFileBuffers(hFile);
|
|
CloseHandle(hFile);
|
|
}
|
|
|
|
|
|
UINT InitializeSharedMemoryCPUCapture(UINT textureSize, DWORD *totalSize, MemoryCopyData **copyData, LPBYTE *textureBuffers)
|
|
{
|
|
UINT alignedHeaderSize = (sizeof(MemoryCopyData)+15) & 0xFFFFFFF0;
|
|
UINT alignedTexureSize = (textureSize+15) & 0xFFFFFFF0;
|
|
|
|
*totalSize = alignedHeaderSize + alignedTexureSize*2;
|
|
|
|
wstringstream strName;
|
|
strName << TEXTURE_MEMORY << ++sharedMemoryIDCounter;
|
|
hFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, *totalSize, strName.str().c_str());
|
|
if(!hFileMap)
|
|
return 0;
|
|
|
|
lpSharedMemory = (LPBYTE)MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, *totalSize);
|
|
if(!lpSharedMemory)
|
|
{
|
|
CloseHandle(hFileMap);
|
|
hFileMap = NULL;
|
|
return 0;
|
|
}
|
|
|
|
*copyData = (MemoryCopyData*)lpSharedMemory;
|
|
(*copyData)->texture1Offset = alignedHeaderSize;
|
|
(*copyData)->texture2Offset = alignedHeaderSize+alignedTexureSize;
|
|
(*copyData)->frameTime = 0;
|
|
|
|
textureBuffers[0] = lpSharedMemory+alignedHeaderSize;
|
|
textureBuffers[1] = lpSharedMemory+alignedHeaderSize+alignedTexureSize;
|
|
|
|
return sharedMemoryIDCounter;
|
|
}
|
|
|
|
UINT InitializeSharedMemoryGPUCapture(SharedTexData **texData)
|
|
{
|
|
int totalSize = sizeof(SharedTexData);
|
|
|
|
wstringstream strName;
|
|
strName << TEXTURE_MEMORY << ++sharedMemoryIDCounter;
|
|
hFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, totalSize, strName.str().c_str());
|
|
if(!hFileMap)
|
|
return 0;
|
|
|
|
lpSharedMemory = (LPBYTE)MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, totalSize);
|
|
if(!lpSharedMemory)
|
|
{
|
|
CloseHandle(hFileMap);
|
|
hFileMap = NULL;
|
|
return 0;
|
|
}
|
|
|
|
*texData = (SharedTexData*)lpSharedMemory;
|
|
(*texData)->frameTime = 0;
|
|
|
|
return sharedMemoryIDCounter;
|
|
}
|
|
|
|
void DestroySharedMemory()
|
|
{
|
|
if(lpSharedMemory && hFileMap)
|
|
{
|
|
UnmapViewOfFile(lpSharedMemory);
|
|
CloseHandle(hFileMap);
|
|
|
|
hFileMap = NULL;
|
|
lpSharedMemory = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
bool bD3D8Hooked = false;
|
|
bool bD3D9Hooked = false;
|
|
bool bDXGIHooked = false;
|
|
bool bGLHooked = false;
|
|
bool bDirectDrawHooked = false;
|
|
|
|
inline bool AttemptToHookSomething()
|
|
{
|
|
if (!hwndSender || !hwndD3DDummyWindow)
|
|
return false;
|
|
|
|
bool bFoundSomethingToHook = false;
|
|
if(!bD3D9Hooked) {
|
|
if (InitD3D9Capture()) {
|
|
logOutput << CurrentTimeString() << "D3D9 Present" << endl;
|
|
bFoundSomethingToHook = true;
|
|
bD3D9Hooked = true;
|
|
}
|
|
} else {
|
|
//occasionally, certain applications can reset the d3d9 hook, or clear it, or something.
|
|
//so, we forcibly make sure that the d3d9 function is still hooked.
|
|
CheckD3D9Capture();
|
|
}
|
|
|
|
if(!bDXGIHooked && InitDXGICapture())
|
|
{
|
|
logOutput << CurrentTimeString() << "DXGI Present" << endl;
|
|
bFoundSomethingToHook = true;
|
|
bDXGIHooked = true;
|
|
}
|
|
if(!bGLHooked && InitGLCapture()) {
|
|
logOutput << CurrentTimeString() << "GL Present" << endl;
|
|
bFoundSomethingToHook = true;
|
|
bGLHooked = true;
|
|
} else {
|
|
CheckGLCapture();
|
|
}
|
|
|
|
if(!bDirectDrawHooked && InitDDrawCapture())
|
|
{
|
|
logOutput << CurrentTimeString() << "DirectDraw Present" << endl;
|
|
bFoundSomethingToHook = true;
|
|
bDirectDrawHooked = true;
|
|
}
|
|
|
|
if (!bD3D8Hooked && InitD3D8Capture())
|
|
{
|
|
logOutput << CurrentTimeString() << "D3D8 Present" << endl;
|
|
bFoundSomethingToHook = true;
|
|
bD3D8Hooked = true;
|
|
}
|
|
else {
|
|
CheckD3D8Capture();
|
|
}
|
|
|
|
return bFoundSomethingToHook;
|
|
}
|
|
|
|
fstream logOutput;
|
|
|
|
#define SENDER_WINDOWCLASS TEXT("OBSGraphicsCaptureSender")
|
|
|
|
HANDLE dummyEvent = NULL;
|
|
|
|
|
|
#define TIMER_ID 431879
|
|
BOOL GetMessageTimeout(MSG &msg, DWORD timeout)
|
|
{
|
|
BOOL ret;
|
|
SetTimer(NULL, TIMER_ID, timeout, NULL);
|
|
ret = GetMessage(&msg, NULL, 0, 0);
|
|
KillTimer(NULL, TIMER_ID);
|
|
return ret;
|
|
}
|
|
|
|
static inline HWND CreateDummyWindow(LPCTSTR lpClass, LPCTSTR lpName)
|
|
{
|
|
return CreateWindowEx (0,
|
|
lpClass, lpName,
|
|
WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
|
|
0, 0,
|
|
1, 1,
|
|
NULL,
|
|
NULL,
|
|
hinstMain,
|
|
NULL
|
|
);
|
|
}
|
|
|
|
|
|
static DWORD WINAPI DummyWindowThread(LPVOID lpBla)
|
|
{
|
|
WNDCLASS wc;
|
|
ZeroMemory(&wc, sizeof(wc));
|
|
wc.style = CS_OWNDC;
|
|
wc.hInstance = hinstMain;
|
|
wc.lpfnWndProc = (WNDPROC)DefWindowProc;
|
|
|
|
wc.lpszClassName = TEXT("OBSDummyD3D9WndClassForTheGPUHook");
|
|
if (RegisterClass(&wc)) {
|
|
hwndD3DDummyWindow = CreateDummyWindow(
|
|
TEXT("OBSDummyD3D9WndClassForTheGPUHook"),
|
|
TEXT("OBS OpenGL D3D Temp Device Window")
|
|
);
|
|
|
|
if (!hwndD3DDummyWindow) {
|
|
logOutput << CurrentDateTimeString() << "could not create gl d3d interop dummy window" << endl;
|
|
return 0;
|
|
}
|
|
} else {
|
|
logOutput << CurrentDateTimeString() << "could not create gl d3d interop dummy window class" << endl;
|
|
return 0;
|
|
}
|
|
|
|
wc.lpszClassName = SENDER_WINDOWCLASS;
|
|
if (RegisterClass(&wc)) {
|
|
hwndSender = CreateDummyWindow(
|
|
SENDER_WINDOWCLASS,
|
|
NULL
|
|
);
|
|
|
|
if (!hwndSender) {
|
|
logOutput << CurrentDateTimeString() << "could not create sender window" << endl;
|
|
return 0;
|
|
}
|
|
} else {
|
|
logOutput << CurrentDateTimeString() << "could not create sender window class" << endl;
|
|
return 0;
|
|
}
|
|
|
|
MSG msg;
|
|
while (GetMessage(&msg, NULL, 0, 0)) {
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
DWORD WINAPI CaptureThread(HANDLE hDllMainThread)
|
|
{
|
|
bool bSuccess = false;
|
|
|
|
//wait for dll initialization to finish before executing any initialization code
|
|
if (hDllMainThread)
|
|
{
|
|
WaitForSingleObject(hDllMainThread, 150);
|
|
CloseHandle(hDllMainThread);
|
|
}
|
|
|
|
TCHAR lpLogPath[MAX_PATH];
|
|
SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, lpLogPath);
|
|
wcscat_s(lpLogPath, MAX_PATH, TEXT("\\OBS\\pluginData\\captureHookLog.txt"));
|
|
|
|
dummyEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
|
|
if(!logOutput.is_open())
|
|
logOutput.open(lpLogPath, ios_base::in | ios_base::out | ios_base::trunc, _SH_DENYNO);
|
|
|
|
wstringstream str;
|
|
str << OBS_KEEPALIVE_EVENT << UINT(GetCurrentProcessId());
|
|
strKeepAlive = str.str();
|
|
|
|
logOutput << CurrentDateTimeString() << "we're booting up: " << endl;
|
|
|
|
InitializeCriticalSection(&d3d8EndMutex);
|
|
InitializeCriticalSection(&d3d9EndMutex);
|
|
InitializeCriticalSection(&glMutex);
|
|
InitializeCriticalSection(&ddrawMutex);
|
|
|
|
DWORD procID = GetCurrentProcessId();
|
|
|
|
wstringstream strRestartEvent, strEndEvent, strReadyEvent, strExitEvent, strInfoMemory;
|
|
strRestartEvent << RESTART_CAPTURE_EVENT << procID;
|
|
strEndEvent << END_CAPTURE_EVENT << procID;
|
|
strReadyEvent << CAPTURE_READY_EVENT << procID;
|
|
strExitEvent << APP_EXIT_EVENT << procID;
|
|
strInfoMemory << INFO_MEMORY << procID;
|
|
|
|
hSignalRestart = GetEvent(strRestartEvent.str().c_str());
|
|
hSignalEnd = GetEvent(strEndEvent.str().c_str());
|
|
hSignalReady = GetEvent(strReadyEvent.str().c_str());
|
|
hSignalExit = GetEvent(strExitEvent.str().c_str());
|
|
|
|
memset(processName, 0, MAX_PATH);
|
|
GetModuleBaseNameA(GetCurrentProcess(), NULL, processName, MAX_PATH);
|
|
logOutput << CurrentTimeString() << "CaptureThread: attached to process " << processName << endl;
|
|
|
|
DWORD bla;
|
|
HANDLE hWindowThread = CreateThread(NULL, 0, DummyWindowThread, NULL, 0, &bla);
|
|
if (!hWindowThread) {
|
|
logOutput << CurrentTimeString() << "CaptureThread: could not create window thread for some reason" << endl;
|
|
return 0;
|
|
}
|
|
|
|
CloseHandle(hWindowThread);
|
|
|
|
hInfoFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(CaptureInfo), strInfoMemory.str().c_str());
|
|
if(!hInfoFileMap)
|
|
{
|
|
logOutput << CurrentTimeString() << "CaptureThread: could not info file mapping" << endl;
|
|
return 0;
|
|
}
|
|
|
|
infoMem = (CaptureInfo*)MapViewOfFile(hInfoFileMap, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(CaptureInfo));
|
|
if(!infoMem)
|
|
{
|
|
logOutput << CurrentTimeString() << "CaptureThread: could not map view of info shared memory" << endl;
|
|
CloseHandle(hInfoFileMap);
|
|
hInfoFileMap = NULL;
|
|
return 0;
|
|
}
|
|
|
|
hwndOBS = FindWindow(OBS_WINDOW_CLASS, NULL);
|
|
if(!hwndOBS)
|
|
{
|
|
logOutput << CurrentTimeString() << "CaptureThread: could not find main application window? wtf? seriously?" << endl;
|
|
return 0;
|
|
}
|
|
|
|
textureMutexes[0] = OpenMutex(MUTEX_ALL_ACCESS, FALSE, TEXTURE_MUTEX1);
|
|
if (textureMutexes[0]) {
|
|
textureMutexes[1] = OpenMutex(MUTEX_ALL_ACCESS, FALSE, TEXTURE_MUTEX2);
|
|
if (textureMutexes[1]) {
|
|
while(!AttemptToHookSomething())
|
|
Sleep(50);
|
|
|
|
logOutput << CurrentTimeString() << "(half life scientist) everything.. seems to be in order" << endl;
|
|
|
|
for (size_t n = 0; !bCaptureThreadStop; ++n) {
|
|
if (n % 100 == 0) AttemptToHookSomething();
|
|
Sleep(40);
|
|
}
|
|
|
|
CloseHandle(textureMutexes[1]);
|
|
textureMutexes[1] = NULL;
|
|
} else {
|
|
logOutput << CurrentTimeString() << "could not open texture mutex 2" << endl;
|
|
}
|
|
|
|
CloseHandle(textureMutexes[0]);
|
|
textureMutexes[0] = NULL;
|
|
} else {
|
|
logOutput << CurrentTimeString() << "could not open texture mutex 1" << endl;
|
|
}
|
|
|
|
logOutput << CurrentTimeString() << "WARNING: exit out of the main thread loop somehow" << endl;
|
|
|
|
return 0;
|
|
}
|
|
|
|
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpBlah)
|
|
{
|
|
if(dwReason == DLL_PROCESS_ATTACH)
|
|
{
|
|
wchar_t name[4096];
|
|
|
|
HANDLE hThread = NULL;
|
|
hinstMain = hinstDLL;
|
|
|
|
#if defined _M_X64 && _MSC_VER == 1800
|
|
//workaround AVX2 bug in VS2013, http://connect.microsoft.com/VisualStudio/feedback/details/811093
|
|
_set_FMA3_enable(0);
|
|
#endif
|
|
|
|
GetModuleFileNameW(hinstDLL, name, 4096);
|
|
LoadLibrary(name);
|
|
|
|
HANDLE hDllMainThread = OpenThread(THREAD_ALL_ACCESS, NULL, GetCurrentThreadId());
|
|
|
|
if(!(hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CaptureThread, (LPVOID)hDllMainThread, 0, 0)))
|
|
{
|
|
CloseHandle(hDllMainThread);
|
|
return FALSE;
|
|
}
|
|
hCaptureThread = hThread;
|
|
//CloseHandle(hThread);
|
|
}
|
|
else if(dwReason == DLL_PROCESS_DETACH)
|
|
{
|
|
/*FreeGLCapture();
|
|
FreeD3D9Capture();
|
|
FreeD3D10Capture();
|
|
FreeD3D101Capture();
|
|
FreeD3D11Capture();*/
|
|
|
|
bCaptureThreadStop = true;
|
|
WaitForSingleObject(hCaptureThread, 300);
|
|
|
|
if(hSignalRestart)
|
|
CloseHandle(hSignalRestart);
|
|
if(hSignalEnd)
|
|
CloseHandle(hSignalEnd);
|
|
if(hSignalReady)
|
|
CloseHandle(hSignalReady);
|
|
if(hSignalExit)
|
|
{
|
|
SetEvent(hSignalExit);
|
|
CloseHandle(hSignalExit);
|
|
}
|
|
|
|
if (dummyEvent)
|
|
CloseHandle(dummyEvent);
|
|
|
|
if(infoMem)
|
|
{
|
|
UnmapViewOfFile(lpSharedMemory);
|
|
CloseHandle(hInfoFileMap);
|
|
}
|
|
|
|
hFileMap = NULL;
|
|
lpSharedMemory = NULL;
|
|
|
|
if(hwndSender)
|
|
DestroyWindow(hwndSender);
|
|
|
|
for(UINT i=0; i<2; i++)
|
|
{
|
|
if(textureMutexes[i])
|
|
CloseHandle(textureMutexes[i]);
|
|
}
|
|
|
|
if(logOutput.is_open())
|
|
logOutput.close();
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static bool hooking = true;
|
|
|
|
typedef BOOL (WINAPI *UHWHEXPROC)(HHOOK);
|
|
|
|
extern "C" __declspec(dllexport) LRESULT CALLBACK DummyDebugProc(int code, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
MSG *msg = (MSG*)lParam;
|
|
|
|
if (hooking && msg->message == (WM_USER + 432))
|
|
{
|
|
char uhwhexStr[20];
|
|
HMODULE hU32 = GetModuleHandle(L"USER32");
|
|
|
|
memcpy(uhwhexStr, "PjoinkTkch`yz@de~Qo", 20);
|
|
|
|
for (int i = 0; i < 19; i++) uhwhexStr[i] ^= i ^ 5;
|
|
|
|
UHWHEXPROC unhookWindowsHookEx = (UHWHEXPROC)GetProcAddress(hU32, uhwhexStr);
|
|
if (unhookWindowsHookEx)
|
|
unhookWindowsHookEx((HHOOK)msg->lParam);
|
|
hooking = false;
|
|
}
|
|
|
|
return CallNextHookEx(0, code, wParam, lParam);
|
|
}
|