/*++

 Copyright (c) 1999 Microsoft Corporation

 Module Name:

    Commandos.cpp

 Abstract:

    A hack for Commandos (EIDOS). The game caches a pointer to the ddraw
    primary surface. On NT, after a mode change, the memory can be mapped 
    into a different location - so when they try to write to it, it access
    violates.

    We know from debugging the app where they keep the cached pointer, so
    when they restore the surface, we relock it, and patch the new pointer 
    into their store.

 Notes:

    This is an app specific hack.

 History:

    10/29/1999 linstev  Created

--*/

#include "precomp.h"

IMPLEMENT_SHIM_BEGIN(Commandos)
#include "ShimHookMacro.h"

APIHOOK_ENUM_BEGIN
    APIHOOK_ENUM_ENTRY_DIRECTX_COMSERVER()
APIHOOK_ENUM_END

IMPLEMENT_DIRECTX_COMSERVER_HOOKS()

static LPVOID pLastPrimary = NULL;
static LPDWORD pAppPrimary = NULL;

/*++

 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;
}

/*++

 Find out where they store the pointer.

--*/

HRESULT 
COMHOOK(IDirectDrawSurface, Lock)( 
    LPDIRECTDRAWSURFACE lpDDSurface,
    LPRECT lpDestRect,
    LPDDSURFACEDESC lpDDSurfaceDesc,
    DWORD dwFlags,
    HANDLE hEvent
    )
{
    DDSURFACEDESC ddsd = {sizeof(ddsd)};
    HRESULT hReturn, hr;

    // Retrieve the old function
    _pfn_IDirectDrawSurface_Lock pfnOld = ORIGINAL_COM(IDirectDrawSurface, Lock, lpDDSurface);
        
    // Call the old API
    if (FAILED(hReturn = (*pfnOld)(
            lpDDSurface, 
            lpDestRect, 
            lpDDSurfaceDesc, 
            dwFlags, 
            hEvent)))
    {
        return hReturn;
    }

    // Make sure it's a primary
    hr = lpDDSurface->GetSurfaceDesc(&ddsd);
    if (SUCCEEDED(hr) && 
       (ddsd.ddsCaps.dwCaps & (DDSCAPS_PRIMARYSURFACE | DDSCAPS_VISIBLE)))
    {
		#if defined(_X86_)
        // We know:
        //   1. They cache the primary address in [esi+0x20]
        //   2. They lock the primary more than once 
        //
        // We assume:
        //   1. When they lock the primary, esi+0x20 is a valid pointer

        if ((pLastPrimary) && (!pAppPrimary))
        {
            __asm
            {
                pop edi
                pop esi
                mov eax,pLastPrimary
                
                cmp [esi+0x20],eax
                jne WrongESI

                // [esi+0x20] does contain the cached pointer

                lea eax,[esi+0x20]
                mov pAppPrimary,eax
            
            WrongESI:

                push esi
                push edi
            }
        }
		#endif
        pLastPrimary = lpDDSurfaceDesc->lpSurface;
    }
    
    return hReturn;
}

/*++

 Patch the new pointer directly into their data segment. 

--*/

HRESULT 
COMHOOK(IDirectDrawSurface, Restore)( 
    LPDIRECTDRAWSURFACE lpDDSurface
    )
{
    DDSURFACEDESC ddsd = {sizeof(ddsd)};
    HRESULT hReturn, hr, hrt;
    
    // Retrieve the old function
    _pfn_IDirectDrawSurface_Restore pfnOld = ORIGINAL_COM(IDirectDrawSurface, Restore, lpDDSurface);

    // Call the old API
    if (FAILED(hReturn = (*pfnOld)(lpDDSurface)))
    {
        return hReturn;
    }

    // Make sure it's a primary
    hr = lpDDSurface->GetSurfaceDesc(&ddsd);
    if (SUCCEEDED(hr) && 
       (ddsd.ddsCaps.dwCaps & (DDSCAPS_PRIMARYSURFACE | DDSCAPS_VISIBLE)))
    {
        // Check if we've been set up
        if (!((pLastPrimary) && (pAppPrimary)))
        {
            return hReturn;
        }

        // We must get a pointer here, so keep trying  
        do
        {
            hr = lpDDSurface->Lock(
                NULL, 
                &ddsd, 
                DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT, 
                NULL);

            if (hr == DDERR_SURFACELOST)
            {
                // Don't care about result
                (*pfnOld)(lpDDSurface);     
            }
        } while (hr == DDERR_SURFACELOST);

        // Patch the new pointer into their memory
        pLastPrimary = ddsd.lpSurface;
        if ((pLastPrimary) && (pAppPrimary))
        {
            *pAppPrimary = (DWORD_PTR)pLastPrimary;
        }

        // Unlock the surface
        lpDDSurface->Unlock(NULL);
    }

    return hReturn;
}

/*++

 Register hooked functions

--*/

HOOK_BEGIN

    APIHOOK_ENTRY_DIRECTX_COMSERVER()
    COMHOOK_ENTRY(DirectDraw, IDirectDraw, CreateSurface, 6)
    COMHOOK_ENTRY(DirectDraw, IDirectDrawSurface, Lock, 25)
    COMHOOK_ENTRY(DirectDraw, IDirectDrawSurface, Restore, 27)

HOOK_END


IMPLEMENT_SHIM_END

