/*****************************************************************************
 *
 *  DIApHack.c
 *
 *  Copyright (c) 1999 Microsoft Corporation.  All Rights Reserved.
 *
 *  Abstract:
 *
 *      Support routines for app hacks
 *
 *  Contents:
 *
 *****************************************************************************/

#include "dinputpr.h"

/*****************************************************************************
 *
 *      The sqiffle for this file.
 *
 *****************************************************************************/
//ISSUE-2001/03/29-timgill Need to sort out a prefixed version of of SquirtSqflPtszV
TCHAR c_tszPrefix[]=TEXT("DINPUT: ");

#define sqfl sqflCompat

typedef enum
{
    DICOMPATID_REACQUIRE,           // Perform auto reaquire if device lost
    DICOMPATID_NOSUBCLASS,          // Do not use subclassing
    DICOMPATID_MAXDEVICENAMELENGTH, // Truncate device names
    DICOMPATID_NATIVEAXISONLY,      // Always report axis data in native mode
    DICOMPATID_NOPOLLUNACQUIRE,     // Don't unaquire the device if a poll fails
	DICOMPATID_SUCCEEDACQUIRE		// Always return a success code for calls to Acquire()
} DIAPPHACKID, *LPDIAPPHACKID;

typedef struct tagAPPHACKENTRY
{
    LPCTSTR             pszName;
    DWORD               cbData;
    DWORD               dwOSMask;
} APPHACKENTRY, *LPAPPHACKENTRY;

typedef struct tagAPPHACKTABLE
{
    LPAPPHACKENTRY      aEntries;
    ULONG               cEntries;
} APPHACKTABLE, *LPAPPHACKTABLE;

#define BEGIN_DECLARE_APPHACK_ENTRIES(name) \
            APPHACKENTRY name[] = {

#define DECLARE_APPHACK_ENTRY(name, type, osmask) \
                { TEXT(#name), sizeof(type), osmask },

#define END_DECLARE_APPHACK_ENTRIES() \
            };

#define BEGIN_DECLARE_APPHACK_TABLE(name) \
            APPHACKTABLE name = 

#define DECLARE_APPHACK_TABLE(entries) \
                { entries, cA(entries) }

#define END_DECLARE_APPHACK_TABLE() \
            ;

#define DIHACKOS_WIN2K (0x00000001L)
#define DIHACKOS_WIN9X (0x00000002L)

BEGIN_DECLARE_APPHACK_ENTRIES(g_aheAppHackEntries)
    DECLARE_APPHACK_ENTRY(ReAcquire,            BOOL,  DIHACKOS_WIN2K )
    DECLARE_APPHACK_ENTRY(NoSubClass,           BOOL,  DIHACKOS_WIN2K )
    DECLARE_APPHACK_ENTRY(MaxDeviceNameLength,  DWORD, DIHACKOS_WIN2K | DIHACKOS_WIN9X )
    DECLARE_APPHACK_ENTRY(NativeAxisOnly,       BOOL,  DIHACKOS_WIN2K | DIHACKOS_WIN9X )
    DECLARE_APPHACK_ENTRY(NoPollUnacquire,      BOOL,  DIHACKOS_WIN2K | DIHACKOS_WIN9X )
	DECLARE_APPHACK_ENTRY(SucceedAcquire,       BOOL,  DIHACKOS_WIN2K )
END_DECLARE_APPHACK_ENTRIES()

BEGIN_DECLARE_APPHACK_TABLE(g_ahtAppHackTable)
    DECLARE_APPHACK_TABLE(g_aheAppHackEntries)
END_DECLARE_APPHACK_TABLE()


/***************************************************************************
 *
 *  AhGetOSMask
 *
 *  Description:
 *      Gets the mask for the current OS
 *      This mask should be used when we get app hacks for more than just 
 *      Win2k such that hacks can be applied selectively per OS.
 *      For now just #define a value as constant.
 *
 *  Arguments:
 *      none
 *
 *  Returns: 
 *      DWORD: Mask of flags applicable for the current OS.
 *
 ***************************************************************************/

#ifdef WINNT
#define AhGetOSMask() DIHACKOS_WIN2K 
#else
#define AhGetOSMask() DIHACKOS_WIN9X 
#endif

/***************************************************************************
 *
 *  AhGetCurrentApplicationPath
 *
 *  Description:
 *      Gets the full path to the current application's executable.
 *
 *  Arguments:
 *      LPTSTR [out]: receives application id.  This buffer is assumed to be 
 *                   at least MAX_PATH characters in size.
 *      LPTSTR * [out]: receives pointer to executable part of the path.
 *
 *  Returns: 
 *      BOOL: TRUE on success.
 *
 ***************************************************************************/

BOOL AhGetCurrentApplicationPath
(
    LPTSTR                  pszPath,
    LPTSTR *                ppszModule
)
{
    BOOL                    fSuccess                = TRUE;
    TCHAR                   szOriginal[MAX_PATH];

    EnterProcI(AhGetCurrentApplicationPath, (_ ""));

    fSuccess = GetModuleFileName(GetModuleHandle(NULL), szOriginal, cA(szOriginal));

    if(fSuccess)
    {
        fSuccess = ( GetFullPathName(szOriginal, MAX_PATH, pszPath, ppszModule) != 0 );
    }

    ExitProcF(fSuccess);

    return fSuccess;
}


/***************************************************************************
 *
 *  AhGetApplicationId
 *
 *  Description:
 *      Gets the id used to identify the current application.
 *
 *  Arguments:
 *      LPTSTR [out]: receives application id.
 *
 *  Arguments:
 *      LPTSTR [out optional]: receives application name.
 *
 *  Returns: 
 *      BOOL: TRUE on success.
 *
 ***************************************************************************/

BOOL AhGetApplicationId
(
    LPTSTR                  pszAppId,
    LPTSTR                  pszAppName
)
{
    HANDLE                  hFile                   = NULL;
    TCHAR                   szExecutable[MAX_PATH];
    LPTSTR                  pszModule;
    IMAGE_NT_HEADERS        nth;
    IMAGE_DOS_HEADER        dh;
    DWORD                   cbRead;
    DWORD                   dwFileSize;
    BOOL                    fSuccess;

    EnterProcI(AhGetApplicationId, (_ ""));
    
    AssertF( pszAppId );

    // Get the application path
    fSuccess = AhGetCurrentApplicationPath(szExecutable, &pszModule);

    if(fSuccess)
    {
        SquirtSqflPtszV(sqfl | sqflVerbose, TEXT("%sApplication executable path: %s"), c_tszPrefix, szExecutable);
        SquirtSqflPtszV(sqfl | sqflVerbose, TEXT("%sApplication module: %s"), c_tszPrefix, pszModule);
    }
                    
    // Open the executable
    if(fSuccess)
    {
        hFile = CreateFile(szExecutable, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);

        if(!(( hFile ) && ( hFile != INVALID_HANDLE_VALUE )))
        {
            SquirtSqflPtszV(sqfl | sqflError, TEXT("%sCreateFile failed to open %s with error %lu"), c_tszPrefix, 
                szExecutable, GetLastError());
            fSuccess = FALSE;
        }
    }

    // Read the executable's DOS header
    if(fSuccess)
    {
        fSuccess = ReadFile(hFile, &dh, sizeof(dh), &cbRead, NULL);

        if(!fSuccess || sizeof(dh) != cbRead)
        {
            SquirtSqflPtszV(sqfl | sqflError, TEXT("%sUnable to read DOS header"), c_tszPrefix);
            fSuccess = FALSE;
        }
    }

    if(fSuccess && IMAGE_DOS_SIGNATURE != dh.e_magic)
    {
        SquirtSqflPtszV(sqfl | sqflError, TEXT("%sInvalid DOS signature"), c_tszPrefix);
        fSuccess = FALSE;
    }

    // Read the executable's PE header
    if(fSuccess)
    {
        cbRead = SetFilePointer(hFile, dh.e_lfanew, NULL, FILE_BEGIN);

        if((LONG)cbRead != dh.e_lfanew)
        {
            SquirtSqflPtszV(sqfl | sqflError, TEXT("%sUnable to seek to PE header"), c_tszPrefix);
            fSuccess = FALSE;
        }
    }

    if(fSuccess)
    {
        fSuccess = ReadFile(hFile, &nth, sizeof(nth), &cbRead, NULL);

        if(!fSuccess || sizeof(nth) != cbRead)
        {
            SquirtSqflPtszV(sqfl | sqflError, TEXT("%sUnable to read PE header"), c_tszPrefix);
            fSuccess = FALSE;
        }
    }

    if(fSuccess && IMAGE_NT_SIGNATURE != nth.Signature)
    {
        SquirtSqflPtszV(sqfl | sqflError, TEXT("%sInvalid PE signature"), c_tszPrefix);
        fSuccess = FALSE;
    }

    // Get the executable's size
    if(fSuccess)
    {
        // Assuming < 4 GB
        dwFileSize = GetFileSize(hFile, NULL);

        if((DWORD)(-1) == dwFileSize)
        {
            SquirtSqflPtszV(sqfl | sqflError, TEXT("%sUnable to get file size"), c_tszPrefix);
            fSuccess = FALSE;
        }
    }

    // Create the application id
    if(fSuccess)
    {
        CharUpper(pszModule);
        wsprintf(pszAppId, TEXT("%s%8.8lX%8.8lX"), pszModule, nth.FileHeader.TimeDateStamp, dwFileSize);
        
        if( pszAppName ) 
        {
            lstrcpy(pszAppName, pszModule);
        }

        SquirtSqflPtszV(sqfl | sqflTrace, TEXT("%sApplication id: %s"), c_tszPrefix, pszAppId);
    }

    // Clean up
    if( hFile != NULL )
    {
        CloseHandle( hFile );
    }

    ExitProcF(fSuccess);

    return fSuccess;
}


/***************************************************************************
 *
 *  AhOpenApplicationKey
 *
 *  Description:
 *      Opens or creates the application's root key.
 *
 *  Arguments:
 *      LPCTSTR [in]: application id.
 *
 *  Returns: 
 *      HKEY: registry key handle.
 *
 ***************************************************************************/

HKEY AhOpenApplicationKey
(
    LPCTSTR                 pszAppId
)
{

#ifdef DEBUG

    TCHAR                   szName[0x100]   = { 0 };
    LONG                    cbName          = sizeof(szName);

#endif // DEBUG

    HKEY                    hkeyAll = NULL;
    HKEY                    hkeyApp = NULL;
    HRESULT                 hr;

    EnterProcI(AhOpenApplicationKey, (_ ""));
    
    // Open the parent key
    hr = hresMumbleKeyEx( HKEY_LOCAL_MACHINE, 
        REGSTR_PATH_DINPUT TEXT("\\") REGSTR_KEY_APPHACK, KEY_READ, 0, &hkeyAll );

    if(SUCCEEDED(hr))
    {
        hr = hresMumbleKeyEx( hkeyAll, pszAppId, KEY_READ, 0, &hkeyApp );

        RegCloseKey( hkeyAll );
#ifdef DEBUG

        // Query for the application description
        if(SUCCEEDED(hr))
        {
            JoyReg_GetValue( hkeyApp, NULL, REG_SZ, szName, cbName );
            SquirtSqflPtszV(sqfl | sqflTrace, 
                TEXT( "%sApplication description: %ls"), c_tszPrefix, szName );
        }

#endif // DEBUG
    }

    ExitProc();

    return hkeyApp;
}


/***************************************************************************
 *
 *  AhGetHackValue
 *
 *  Description:
 *      Queries an apphack value.
 *
 *  Arguments:
 *      HKEY [in]: application registry key.
 *      DSAPPHACKID [in]: apphack id.
 *      LPVOID [out]: receives apphack data.
 *      DWORD [in]: size of above data buffer.
 *
 *  Returns: 
 *      BOOL: TRUE on success.
 *
 ***************************************************************************/

BOOL AhGetHackValue
(
    HKEY                    hkey,
    DWORD                   dwOSMask,
    DIAPPHACKID             ahid,
    LPVOID                  pvData,
    DWORD                   cbData
)
{
    HRESULT                 hr;
    
    EnterProcI(AhGetHackValue, (_ ""));
    
    AssertF(ahid < (DIAPPHACKID)g_ahtAppHackTable.cEntries);
    AssertF(cbData == g_ahtAppHackTable.aEntries[ahid].cbData);

    if( !( dwOSMask & g_ahtAppHackTable.aEntries[ahid].dwOSMask ) )
    {
        hr = DI_OK;
    }
    else
    {
        hr = JoyReg_GetValue( hkey, g_ahtAppHackTable.aEntries[ahid].pszName, 
            REG_BINARY, pvData, cbData );
        if( !SUCCEEDED( hr ) )
        {
            SquirtSqflPtszV(sqfl | sqflBenign, 
                TEXT("%sfailed to read value \"%s\", code 0x%08x"), 
                c_tszPrefix, g_ahtAppHackTable.aEntries[ahid].pszName, hr);
        }
    }

    ExitProcF(DI_OK == hr);

    return DI_OK == hr;
}


/***************************************************************************
 *
 *  AhGetAppHacks
 *
 *  Description:
 *      Gets all app-hacks for the current application.
 *
 *  Arguments:
 *      LPDSAPPHACKS [out]: receives app-hack data.
 *
 *  Returns: 
 *      BOOL: TRUE if any apphacks exist for the current application.
 *
 ***************************************************************************/

BOOL AhGetAppHacks
(
    LPDIAPPHACKS            pahAppHacks
)
{
    static const DIAPPHACKS ahDefaults                  = { FALSE, FALSE, FALSE, FALSE, FALSE, MAX_PATH };
    TCHAR                   szAppId[MAX_PATH + 8 + 8] = { 0 };
    HKEY                    hkey                        = NULL;
    BOOL                    fSuccess;
    DWORD                   dwOSMask;
    
    EnterProcI(AhGetAppHacks, (_ ""));
    
    // Assume defaults
    CopyMemory(pahAppHacks, &ahDefaults, sizeof(ahDefaults));
    
    // Get the OS version mask
    dwOSMask = AhGetOSMask();

    // Get the application id
    fSuccess = AhGetApplicationId(szAppId, NULL);

    if(fSuccess)
    {
        SquirtSqflPtszV(sqfl | sqflTrace, TEXT("%sFinding apphacks for %s..."), c_tszPrefix, szAppId);
    }

    // Open the application key
    if(fSuccess)
    {
        hkey = AhOpenApplicationKey(szAppId);
        fSuccess = ( hkey && (hkey != INVALID_HANDLE_VALUE ) );
    }

#define GET_APP_HACK( hackid, field ) \
        if( !AhGetHackValue( hkey, dwOSMask, hackid, &pahAppHacks->##field, sizeof(pahAppHacks->##field) ) ) \
        { \
            pahAppHacks->##field = ahDefaults.##field; \
        }

    // Query all apphack values
    if(fSuccess)
    {
        GET_APP_HACK( DICOMPATID_REACQUIRE,             fReacquire );
        GET_APP_HACK( DICOMPATID_NOSUBCLASS,            fNoSubClass );
        GET_APP_HACK( DICOMPATID_MAXDEVICENAMELENGTH,   nMaxDeviceNameLength );
        GET_APP_HACK( DICOMPATID_NATIVEAXISONLY,        fNativeAxisOnly );
        GET_APP_HACK( DICOMPATID_NOPOLLUNACQUIRE,       fNoPollUnacquire );
		GET_APP_HACK( DICOMPATID_SUCCEEDACQUIRE,        fSucceedAcquire );
    }

#undef GET_APP_HACK

    if(fSuccess)
    {
        SquirtSqflPtszV(sqfl | sqflTrace, TEXT("%sfReacquire:    %d"), c_tszPrefix, pahAppHacks->fReacquire );
        SquirtSqflPtszV(sqfl | sqflTrace, TEXT("%sfNoSubClass:   %d"), c_tszPrefix, pahAppHacks->fNoSubClass );
        SquirtSqflPtszV(sqfl | sqflTrace, TEXT("%snMaxDeviceNameLength:   %d"), c_tszPrefix, pahAppHacks->nMaxDeviceNameLength );
        SquirtSqflPtszV(sqfl | sqflTrace, TEXT("%sfNativeAxisOnly:   %d"), c_tszPrefix, pahAppHacks->fNativeAxisOnly );
        SquirtSqflPtszV(sqfl | sqflTrace, TEXT("%sfNoPollUnacquire:   %d"), c_tszPrefix, pahAppHacks->fNoPollUnacquire );
    	SquirtSqflPtszV(sqfl | sqflTrace, TEXT("%sfSucceedAcquire:    %d"), c_tszPrefix, pahAppHacks->fSucceedAcquire );
	}
    else
    {
        SquirtSqflPtszV(sqfl | sqflTrace, TEXT("%sNo apphacks exist"), c_tszPrefix);
    }

    // Clean up
    if( hkey )
    {
        RegCloseKey(hkey);
    }

    ExitProc();

    return fSuccess;
}



HRESULT EXTERNAL AhAppRegister(DWORD dwVer)
{
    TCHAR           szAppName[MAX_PATH];
    TCHAR           szAppId[MAX_PATH + 8 + 8] = { 0 };

    BOOL fSuccess;
    HRESULT hr = E_FAIL;

    fSuccess = AhGetApplicationId(szAppId, szAppName);

    if (fSuccess)
    {
        HKEY hKey;

        hr = hresMumbleKeyEx( HKEY_CURRENT_USER, 
            REGSTR_PATH_LASTAPP, KEY_WRITE, 0, &hKey );

        if( SUCCEEDED(hr) )
        {
            FILETIME ftSysTime;
            GetSystemTimeAsFileTime( &ftSysTime );
            RegSetValueEx(hKey, DIRECTINPUT_REGSTR_VAL_VERSION, 0x0, REG_BINARY, (PUCHAR) &dwVer, cbX(dwVer) );
            RegSetValueEx(hKey, DIRECTINPUT_REGSTR_VAL_NAME, 0x0, REG_SZ, (PUCHAR) szAppName, cbCtch(lstrlen(szAppName)+1) );
            RegSetValueEx(hKey, DIRECTINPUT_REGSTR_VAL_ID, 0x0, REG_SZ, (PUCHAR) szAppId, cbCtch(lstrlen(szAppId)+1) );
            RegSetValueEx(hKey, DIRECTINPUT_REGSTR_VAL_LASTSTART, 0x0, REG_BINARY, (PUCHAR)&ftSysTime, cbX(ftSysTime));
            RegCloseKey(hKey);        
        }
    }
    return hr;
}