/*++

 Copyright (c) 2000 Microsoft Corporation

 Module Name:

    ClueFinders3rdGrade.cpp

 Abstract:

    This shim simulates the behaviour of Win9x wrt static controls and 
    Get/SetWindowText. Basically, Win9x stored the resource id for a static
    control in it's name. On NT, this isn't stored.

    We used to set a low-level window hook that catches the CreateWindow calls,
    but gave up because it kept regressing and it would be too expensive for 
    the layer.

 Notes:
    
    This is an app specific shim.

 History:

    06/19/2000 linstev  Created
    11/17/2000 linstev  Made app specific

--*/

#include "precomp.h"

IMPLEMENT_SHIM_BEGIN(ClueFinders3rdGrade)
#include "ShimHookMacro.h"

APIHOOK_ENUM_BEGIN
    APIHOOK_ENUM_ENTRY(GetWindowTextA) 
    APIHOOK_ENUM_ENTRY(SetWindowTextA)
    APIHOOK_ENUM_ENTRY(CreateDialogIndirectParamA) 
APIHOOK_ENUM_END

typedef HMODULE (*_pfn_GetModuleHandleA)(LPCSTR lpModuleName);
 
//
// List of static handles
//

struct HWNDITEM
{
    HWND hWnd;
    DWORD dwRsrcId;
    HWNDITEM *next;
};
HWNDITEM *g_hWndList = NULL;

//
// Handle to use for CallNextHook
//

HHOOK g_hHookCbt = 0;

//
// Critical section for list access
//

CRITICAL_SECTION g_csList;

/*++

 Search the window list for a resource id if GetWindowTextA fails.

--*/

int 
APIHOOK(GetWindowTextA)(
    HWND hWnd,        
    LPSTR lpString,  
    int nMaxCount     
    )
{
    int iRet = ORIGINAL_API(GetWindowTextA)(
        hWnd,
        lpString,
        nMaxCount);

    if (iRet == 0) {
        //
        // Check for Resource Id
        //
    
        EnterCriticalSection(&g_csList);

        HWNDITEM *hitem = g_hWndList;
        while (hitem) {
            if (hitem->hWnd == hWnd) {
                //
                // Copy the resource id into the buffer
                //
                
                if ((hitem->dwRsrcId != (DWORD)-1) && (nMaxCount >= 3)) {
                    MoveMemory(lpString, (LPBYTE) &hitem->dwRsrcId + 1, 3);
                    iRet = 2;
                
                    DPFN( eDbgLevelError, "Returning ResourceId: %08lx for HWND=%08lx", *(LPDWORD)lpString, hWnd);
                }

                break;
            }
            hitem = hitem->next;
        }

        LeaveCriticalSection(&g_csList);
    }

    return iRet;
}
 
/*++

 Hook SetWindowText so the list is kept in sync.

--*/

BOOL 
APIHOOK(SetWindowTextA)(
    HWND hWnd,         
    LPCSTR lpString   
    )
{
    //
    // Set the text for this window if it's in our list
    //

    EnterCriticalSection(&g_csList);
    
    HWNDITEM *hitem = g_hWndList;
    while (hitem) {
        if (hitem->hWnd == hWnd) {
            if (lpString && (*(LPBYTE) lpString == 0xFF)) {
                hitem->dwRsrcId = *(LPDWORD) lpString;
            }

            break;
        }

        hitem = hitem->next;
    }
    
    LeaveCriticalSection(&g_csList);

    return ORIGINAL_API(SetWindowTextA)(hWnd, lpString);
}

/*++

 Hook to find CreateWindow calls and get the attached resource id.

--*/

LRESULT 
CALLBACK 
CBTProcW(
    int nCode,      
    WPARAM wParam,  
    LPARAM lParam   
    )
{
    HWND hWnd = (HWND) wParam;
    LPCBT_CREATEWNDW pCbtWnd;

    switch (nCode) {
    case HCBT_CREATEWND:

        //
        // Add to our list of windows if it's a static - or we don't know 
        //
        
        pCbtWnd = (LPCBT_CREATEWNDW) lParam;

        if (pCbtWnd && pCbtWnd->lpcs && pCbtWnd->lpcs->lpszClass && 
            (IsBadReadPtr(pCbtWnd->lpcs->lpszClass, 4) || 
             (_wcsicmp(pCbtWnd->lpcs->lpszClass, L"static") == 0))) {
            HWNDITEM *hitem = (HWNDITEM *) malloc(sizeof(HWNDITEM));

            if (hitem) {
                hitem->hWnd = hWnd;

                //
                // Check for a resource id in the name
                //
                
                if (pCbtWnd->lpcs->lpszName && 
                    (*(LPBYTE) pCbtWnd->lpcs->lpszName == 0xFF)) {
                    hitem->dwRsrcId = *(LPDWORD) pCbtWnd->lpcs->lpszName;
                } else {
                    hitem->dwRsrcId = (DWORD)-1;
                }

                //
                // Update our list
                // 
                
                EnterCriticalSection(&g_csList);
                
                hitem->next = g_hWndList;
                g_hWndList = hitem;
       
                LeaveCriticalSection(&g_csList);

                DPFN( eDbgLevelError, "CreateWindow HWND=%08lx, ResourceId=%08lx", hitem->hWnd, hitem->dwRsrcId);
            } else {
                DPFN( eDbgLevelError, "Failed to allocate list item");
            }
        }
        
        break;

    case HCBT_DESTROYWND:
        
        //
        // Remove the window from our list
        //
        
        EnterCriticalSection(&g_csList);

        HWNDITEM *hitem = g_hWndList, *hprev = NULL;
        
        while (hitem) {
            if (hitem->hWnd == hWnd) {
                if (hprev) {
                    hprev->next = hitem->next;
                } else {
                    g_hWndList = hitem->next;
                }

                free(hitem);

                DPFN( eDbgLevelError, "DestroyWindow %08lx", hWnd);

                break;
            }
            hprev = hitem;
            hitem = hitem->next;
        }

        LeaveCriticalSection(&g_csList);

        break;
    }

    return CallNextHookEx(g_hHookCbt, nCode, wParam, lParam);
}

/*++

 Hook CreateDialog which is where the problem occurs

--*/

HWND
APIHOOK(CreateDialogIndirectParamA)(
    HINSTANCE hInstance,        
    LPCDLGTEMPLATE lpTemplate,  
    HWND hWndParent,            
    DLGPROC lpDialogFunc,       
    LPARAM lParamInit           
    )
{
    if (!g_hHookCbt) {
        g_hHookCbt = SetWindowsHookExW(WH_CBT, CBTProcW, GetModuleHandleW(0), 0);
        DPFN( eDbgLevelInfo, "[CreateDialogIndirectParamA] Hook added");
    }

    HWND hRet = ORIGINAL_API(CreateDialogIndirectParamA)(  
        hInstance,
        lpTemplate,
        hWndParent,
        lpDialogFunc,
        lParamInit);

    if (g_hHookCbt) {
        UnhookWindowsHookEx(g_hHookCbt);
        g_hHookCbt = 0;
    }

    return hRet;
}

/*++

 Register hooked functions

--*/

BOOL
NOTIFY_FUNCTION(
    DWORD fdwReason)
{
    if (fdwReason == DLL_PROCESS_ATTACH) {
        //
        // Initialize our critical section here
        //
        
        InitializeCriticalSection(&g_csList);

    } else if (fdwReason == DLL_PROCESS_DETACH) {
        //
        // Clear the hook
        //

        if (g_hHookCbt) {
            UnhookWindowsHookEx(g_hHookCbt);
        }
    }


    return TRUE;
}

HOOK_BEGIN

    CALL_NOTIFY_FUNCTION

    APIHOOK_ENTRY(USER32.DLL, GetWindowTextA)
    APIHOOK_ENTRY(USER32.DLL, SetWindowTextA)
    APIHOOK_ENTRY(USER32.DLL, CreateDialogIndirectParamA)

HOOK_END

IMPLEMENT_SHIM_END