/*++ Copyright (c) 1999 Microsoft Corporation Module Name: TurkeyHunter.cpp Abstract: In Win9x, IDirectDraw::GetDC simply locks the surface and creates a DC around it via internal GDI calls. On NT, GDI supports DCs obtained from DirectDraw surfaces. Some games, like Turkey Hunter, use Surface::Unlock to get usage of the surface back, instead of Surface::ReleaseDC. Ordinarily we could simply make the unlock call the DirectDraw ReleaseDC, except that they continue using the DC after they've unlocked the surface. Notes: This is a general purpose hack. History: 01/20/2000 linstev Created --*/ #include "precomp.h" IMPLEMENT_SHIM_BEGIN(TurkeyHunter) #include "ShimHookMacro.h" APIHOOK_ENUM_BEGIN APIHOOK_ENUM_ENTRY_DIRECTX_COMSERVER() APIHOOK_ENUM_END IMPLEMENT_DIRECTX_COMSERVER_HOOKS() // Link list of open DCs struct DC { DC *next; HDC hdc; HBITMAP hbmp; DWORD dwWidth, dwHeight; LPDIRECTDRAWSURFACE lpDDSurface; BOOL bBad; }; DC *g_DCList = NULL; HRESULT COMHOOK(IDirectDrawSurface, ReleaseDC)( LPDIRECTDRAWSURFACE lpDDSurface, HDC hDC); /*++ Hook create surface so we can be sure we're being called. --*/ HRESULT COMHOOK(IDirectDraw, CreateSurface)( PVOID pThis, LPDDSURFACEDESC lpDDSurfaceDesc, LPDIRECTDRAWSURFACE* lplpDDSurface, IUnknown* pUnkOuter ) { HRESULT hReturn; _pfn_IDirectDraw_CreateSurface pfnOld = ORIGINAL_COM(IDirectDraw, CreateSurface, pThis); if (SUCCEEDED(hReturn = (*pfnOld)( pThis, lpDDSurfaceDesc, lplpDDSurface, pUnkOuter))) { HookObject( NULL, IID_IDirectDrawSurface, (PVOID*)lplpDDSurface, NULL, FALSE); } return hReturn; } /*++ Hook create surface so we can be sure we're being called. --*/ HRESULT COMHOOK(IDirectDraw2, CreateSurface)( PVOID pThis, LPDDSURFACEDESC lpDDSurfaceDesc, LPDIRECTDRAWSURFACE* lplpDDSurface, IUnknown* pUnkOuter ) { HRESULT hReturn; _pfn_IDirectDraw2_CreateSurface pfnOld = ORIGINAL_COM(IDirectDraw2, CreateSurface, pThis); if (SUCCEEDED(hReturn = (*pfnOld)( pThis, lpDDSurfaceDesc, lplpDDSurface, pUnkOuter))) { HookObject( NULL, IID_IDirectDrawSurface, (PVOID*)lplpDDSurface, NULL, FALSE); } return hReturn; } /*++ Fake a DC - or rather produce a normal GDI DC that doesn't have the surface memory backing it. --*/ HRESULT COMHOOK(IDirectDrawSurface, GetDC)( LPDIRECTDRAWSURFACE lpDDSurface, HDC FAR *lphDC ) { HRESULT hReturn = DDERR_GENERIC; _pfn_IDirectDrawSurface_ReleaseDC pfnOldReleaseDC = NULL; _pfn_IDirectDrawSurface_GetDC pfnOld = NULL; DDSURFACEDESC ddsd = {sizeof(DDSURFACEDESC)}; HDC hdc = 0; HBITMAP hbmp = 0; HGDIOBJ hOld = 0; DC *pdc = NULL; if (!lphDC || !lpDDSurface) { DPFN( eDbgLevelError, "Invalid parameters"); goto Exit; } // Original GetDC pfnOld = ORIGINAL_COM(IDirectDrawSurface, GetDC, (LPVOID) lpDDSurface); if (!pfnOld) { DPFN( eDbgLevelError, "Old GetDC not found"); goto Exit; } if (FAILED(hReturn = (*pfnOld)( lpDDSurface, lphDC))) { DPFN( eDbgLevelError, "IDirectDraw::GetDC Failed"); goto Exit; } // We need the surface desc for the surface width and height lpDDSurface->GetSurfaceDesc(&ddsd); // Create a DC to be used by the app hdc = CreateCompatibleDC(0); if (!hdc) { DPFN( eDbgLevelError, "CreateDC failed"); goto Exit; } // Create the DIB Section BITMAPINFO bmi; ZeroMemory(&bmi, sizeof(bmi)); bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 24; bmi.bmiHeader.biCompression = BI_RGB; bmi.bmiHeader.biWidth = ddsd.dwWidth; bmi.bmiHeader.biHeight = ddsd.dwHeight; hbmp = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, NULL, NULL, 0); if (!hbmp) { DPFN( eDbgLevelError, "CreateDIBSection failed"); goto Exit; } // Select the DIB Section into the DC hOld = SelectObject(hdc, hbmp); BitBlt(hdc, 0, 0, ddsd.dwWidth, ddsd.dwHeight, *lphDC, 0, 0, SRCCOPY); // Original ReleaseDC pfnOldReleaseDC = ORIGINAL_COM(IDirectDrawSurface, ReleaseDC, (LPVOID) lpDDSurface); if (!pfnOldReleaseDC) { DPFN( eDbgLevelError, "Old ReleaseDC not found"); goto Exit; } // Release the DirectDraw DC (*pfnOldReleaseDC)(lpDDSurface, *lphDC); // Return the DC we just created *lphDC = hdc; // Add this to our DC list pdc = (DC *) malloc(sizeof DC); if (pdc) { pdc->next = g_DCList; g_DCList = pdc; pdc->hdc = hdc; pdc->lpDDSurface = lpDDSurface; pdc->hbmp = hbmp; pdc->dwHeight = ddsd.dwHeight; pdc->dwWidth = ddsd.dwWidth; pdc->bBad = FALSE; } else { DPFN( eDbgLevelError, "Out of memory"); goto Exit; } hReturn = DD_OK; Exit: if (hReturn != DD_OK) { if (hOld && hdc) { SelectObject(hdc, hOld); } if (hbmp) { DeleteObject(hbmp); } if (hdc) { DeleteDC(hdc); } } return DD_OK; } /*++ ReleaseDC has to copy the data back into the surface. --*/ HRESULT COMHOOK(IDirectDrawSurface, ReleaseDC)( LPDIRECTDRAWSURFACE lpDDSurface, HDC hDC ) { HRESULT hReturn = DDERR_GENERIC; // Original ReleaseDC _pfn_IDirectDrawSurface_ReleaseDC pfnOld = ORIGINAL_COM(IDirectDrawSurface, ReleaseDC, (LPVOID) lpDDSurface); // Run the list to see if we need to do anything DC *pdc = g_DCList, *last = NULL; while (pdc) { if ((pdc->lpDDSurface == lpDDSurface) && (pdc->hdc == hDC)) { // Remove it from the list if (last) { last->next = pdc->next; } else { g_DCList = pdc->next; } break; } last = pdc; pdc = pdc->next; } // We were in the list and someone used Unlock. if (pdc && (pdc->bBad)) { // Original GetDC _pfn_IDirectDrawSurface_GetDC pfnOldGetDC = ORIGINAL_COM(IDirectDrawSurface, GetDC, (LPVOID)pdc->lpDDSurface); // Original ReleaseDC _pfn_IDirectDrawSurface_ReleaseDC pfnOldReleaseDC = ORIGINAL_COM(IDirectDrawSurface, ReleaseDC, (LPVOID)pdc->lpDDSurface); if (pfnOldGetDC && pfnOldReleaseDC) { // Copy everything back onto the surface HDC hTempDC; HGDIOBJ hOld = SelectObject(hDC, pdc->hbmp); if (SUCCEEDED((*pfnOldGetDC)(pdc->lpDDSurface, &hTempDC))) { BitBlt(hTempDC, 0, 0, pdc->dwWidth, pdc->dwHeight, hDC, 0, 0, SRCCOPY); (*pfnOldReleaseDC)(pdc->lpDDSurface, hTempDC); } SelectObject(hDC, hOld); // Delete the DIB Section DeleteObject(pdc->hbmp); // Delete the DC DeleteDC(hDC); hReturn = DD_OK; } } else { if (pfnOld) { // Didn't need to fake hReturn = (*pfnOld)(lpDDSurface, hDC); } } // Free the list item if (pdc) { free(pdc); } return hReturn; } /*++ This is where we detect if Surface::Unlock was called after a Surface::GetDC. --*/ HRESULT COMHOOK(IDirectDrawSurface, Unlock)( LPDIRECTDRAWSURFACE lpDDSurface, LPVOID lpSurfaceData ) { HRESULT hRet = DDERR_GENERIC; // Walk the list to see if we're in it. DC *pdc = g_DCList; while (pdc) { if (pdc->lpDDSurface == lpDDSurface) { pdc->bBad = TRUE; break; } pdc = pdc->next; } if (!pdc) { // Original Unlock _pfn_IDirectDrawSurface_Unlock pfnOld = ORIGINAL_COM(IDirectDrawSurface, Unlock, (LPVOID)lpDDSurface); if (pfnOld) { // This is just a normal unlock hRet = (*pfnOld)(lpDDSurface, lpSurfaceData); } } else { // We never really locked in the first place, so no harm done. hRet = DD_OK; } return hRet; } /*++ This is a problem case where they Blt after the Surface::Unlock, but before the Surface::ReleaseDC. --*/ HRESULT COMHOOK(IDirectDrawSurface, Blt)( LPDIRECTDRAWSURFACE lpDDDestSurface, LPRECT lpDestRect, LPDIRECTDRAWSURFACE lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwFlags, LPDDBLTFX lpDDBltFX ) { HRESULT hRet = DDERR_GENERIC; // Original Blt _pfn_IDirectDrawSurface_Blt pfnOld = ORIGINAL_COM(IDirectDrawSurface, Blt, (LPVOID) lpDDDestSurface); if (!pfnOld) { return hRet; } // Are we in the bad state DC *pdc = g_DCList; while (pdc) { if (pdc->lpDDSurface == lpDDDestSurface) { break; } pdc = pdc->next; } if (!pdc) { return (*pfnOld)( lpDDDestSurface, lpDestRect, lpDDSrcSurface, lpSrcRect, dwFlags, lpDDBltFX); } // To get here, there must be an outstanding DC on this surface // Original GetDC _pfn_IDirectDrawSurface_GetDC pfnOldGetDC = ORIGINAL_COM(IDirectDrawSurface, GetDC, (LPVOID) lpDDDestSurface); // Original ReleaseDC _pfn_IDirectDrawSurface_ReleaseDC pfnOldReleaseDC = ORIGINAL_COM(IDirectDrawSurface, ReleaseDC, (LPVOID) lpDDDestSurface); if (!pfnOldGetDC || !pfnOldReleaseDC) { return hRet; } // Copy the DC contents to the surface HDC hTempDC; HGDIOBJ hOld = SelectObject(pdc->hdc, pdc->hbmp); if (SUCCEEDED((*pfnOldGetDC)(lpDDDestSurface, &hTempDC))) { BitBlt(hTempDC, 0, 0, pdc->dwWidth, pdc->dwHeight, pdc->hdc, 0, 0, SRCCOPY); (*pfnOldReleaseDC)(lpDDDestSurface, hTempDC); } // Do the ddraw Blt hRet = (*pfnOld)( lpDDDestSurface, lpDestRect, lpDDSrcSurface, lpSrcRect, dwFlags, lpDDBltFX); // Copy stuff back to the DC if (SUCCEEDED((*pfnOldGetDC)(lpDDDestSurface, &hTempDC))) { BitBlt(pdc->hdc, 0, 0, pdc->dwWidth, pdc->dwHeight, hTempDC, 0, 0, SRCCOPY); (*pfnOldReleaseDC)(lpDDDestSurface, hTempDC); } SelectObject(pdc->hdc, hOld); return hRet; } /*++ Register hooked functions --*/ HOOK_BEGIN APIHOOK_ENTRY_DIRECTX_COMSERVER() COMHOOK_ENTRY(DirectDraw, IDirectDraw, CreateSurface, 6) COMHOOK_ENTRY(DirectDraw, IDirectDraw2, CreateSurface, 6) COMHOOK_ENTRY(DirectDraw, IDirectDrawSurface, GetDC, 17) COMHOOK_ENTRY(DirectDraw, IDirectDrawSurface, ReleaseDC, 26) COMHOOK_ENTRY(DirectDraw, IDirectDrawSurface, Unlock, 32) COMHOOK_ENTRY(DirectDraw, IDirectDrawSurface, Blt, 5) HOOK_END IMPLEMENT_SHIM_END