/******************************************************************************** Copyright (C) 2012 Hugh Bailey 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" HANDLE hSignalRestart=NULL, hSignalEnd=NULL; HANDLE hSignalReady=NULL, hSignalExit=NULL; HINSTANCE hinstMain = NULL; HWND hwndSender = NULL, hwndOBS = NULL; HANDLE textureMutexes[2] = {NULL, NULL}; int resetCount = 1; bool bStopRequested = false; bool bCapturing = true; bool bTargetAcquired = false; HANDLE hFileMap = NULL; LPBYTE lpSharedMemory = NULL; UINT sharedMemoryIDCounter = 0; HANDLE hInfoFileMap = NULL; CaptureInfo *infoMem = NULL; LARGE_INTEGER clockFreq, startTime; LONGLONG prevElapsedTime; DWORD startTick; wstring strKeepAlive; LONGLONG keepAliveTime = 0; 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 bD3D9Hooked = false; bool bDXGIHooked = false; bool bGLHooked = false; bool bDirectDrawHooked = false; inline bool AttemptToHookSomething() { bool bFoundSomethingToHook = false; if(!bD3D9Hooked && InitD3D9Capture()) { logOutput << "D3D9 Present" << endl; bFoundSomethingToHook = true; bD3D9Hooked = true; } if(!bDXGIHooked && InitDXGICapture()) { logOutput << "DXGI Present" << endl; bFoundSomethingToHook = true; bDXGIHooked = true; } if(!bGLHooked && InitGLCapture()) { logOutput << "GL Present" << endl; bFoundSomethingToHook = true; bGLHooked = true; } /* if(!bDirectDrawHooked && InitDDrawCapture()) { OutputDebugString(TEXT("DirectDraw Present\r\n")); bFoundSomethingToHook = true; bDirectDrawfHooked = true; }*/ return bFoundSomethingToHook; } fstream logOutput; #define SENDER_WINDOWCLASS TEXT("OBSGraphicsCaptureSender") DWORD WINAPI CaptureThread(HANDLE hDllMainThread) { bool bSuccess = false; //wait for dll initialization to finish before executing any initialization code if(hDllMainThread) { WaitForSingleObject(hDllMainThread, INFINITE); 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")); 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 << "we're booting up.." << endl; WNDCLASS wc; ZeroMemory(&wc, sizeof(wc)); wc.hInstance = hinstMain; wc.lpszClassName = SENDER_WINDOWCLASS; wc.lpfnWndProc = (WNDPROC)DefWindowProc; 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()); hInfoFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(CaptureInfo), strInfoMemory.str().c_str()); if(!hInfoFileMap) { logOutput << "CaptureThread: could not info file mapping" << endl; return 0; } infoMem = (CaptureInfo*)MapViewOfFile(hInfoFileMap, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(CaptureInfo)); if(!infoMem) { logOutput << "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 << "CaptureThread: could not find main application window? wtf? seriously?" << endl; return 0; } if (RegisterClass(&wc)) { hwndSender = CreateWindow(SENDER_WINDOWCLASS, NULL, 0, 0, 0, 0, 0, NULL, 0, hinstMain, 0); if (hwndSender) { textureMutexes[0] = OpenMutex(MUTEX_ALL_ACCESS, FALSE, TEXTURE_MUTEX1); if (textureMutexes[0]) { textureMutexes[1] = OpenMutex(MUTEX_ALL_ACCESS, FALSE, TEXTURE_MUTEX2); if (textureMutexes[1]) { logOutput << "(half life scientist) everything.. seems to be in order" << endl; MSG msg; while (1) { AttemptToHookSomething(); while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } Sleep(50); } CloseHandle(textureMutexes[1]); textureMutexes[1] = NULL; } else { logOutput << "could not open texture mutex 2" << endl; } CloseHandle(textureMutexes[0]); textureMutexes[0] = NULL; } else { logOutput << "could not open texture mutex 1" << endl; } DestroyWindow(hwndSender); } else { logOutput << "could not create sender window" << endl; } } logOutput << "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) { HANDLE hThread = NULL; hinstMain = hinstDLL; 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; } CloseHandle(hThread); } else if(dwReason == DLL_PROCESS_DETACH) { /*FreeGLCapture(); FreeD3D9Capture(); FreeD3D10Capture(); FreeD3D101Capture(); FreeD3D11Capture();*/ if(hSignalRestart) CloseHandle(hSignalRestart); if(hSignalEnd) CloseHandle(hSignalEnd); if(hSignalReady) CloseHandle(hSignalReady); if(hSignalExit) { SetEvent(hSignalExit); CloseHandle(hSignalExit); } 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; }