/*++

 Copyright (c) 2001 Microsoft Corporation

 Module Name:

    VRegistry_DSound.cpp

 Abstract:

    Module to add DSound apphacks with VRegistry

 History:

    08/10/2001  mikrause    Created    

--*/

#include "precomp.h"

IMPLEMENT_SHIM_BEGIN(VirtualRegistry)

#include <windows.h>
#include "shimhookmacro.h"
#include "vregistry.h"
#include "vregistry_dsound.h"

#define DSAPPHACK_MAXNAME   (MAX_PATH + 16 + 16)

BOOL AddDirectSoundAppHack(DWORD dwHack,DWORD dwParam1,DWORD dwParam2);
BOOL GetDirectSoundAppId(LPTSTR pszAppId);

// Available DirectSound hacks
#define DSAPPHACKID_DEVACCEL            1
#define DSAPPHACKID_PADCURSORS          2
#define DSAPPHACKID_CACHEPOSITIONS      3
#define DSAPPHACKID_RETURNWRITEPOS      4
#define DSAPPHACKID_SMOOTHWRITEPOS      5
#define DSAPPHACKID_DISABLEDEVICE       6

/*++

 Function Description:

    Sets the acceleration level the app will be allowed to use.
 
 Arguments:

    IN dwAcceleration - Acceleration level needed.
    IN dwDevicesAffected - Combination of device that this hack applies to.

 Notes:
    
    See vregistry_dsound.h for acceleration levels and device types.

 Returns:
    
    True if app hack applied, false otherwise.

 History:

    08/10/2001 mikrause  Created

--*/

BOOL
AddDSHackDeviceAcceleration(
    IN DWORD dwAcceleration,
    IN DWORD dwDevicesAffected)
{
    return AddDirectSoundAppHack(DSAPPHACKID_DEVACCEL, dwAcceleration,
        dwDevicesAffected);
}

/*++

 Function Description:

    Disabled some category of devices altogether, forces
    playback through emulated path.
 
 Arguments:

    IN dwDevicesAffected - Combination of device that this hack applies to.

 Notes:
    
    See vregistry_dsound.h for device types.

 Returns:
    
    True if app hack applied, false otherwise.

 History:

    08/10/2001 mikrause  Created

--*/

BOOL
AddDSHackDisableDevice(
    IN DWORD dwDevicesAffected)
{
    return AddDirectSoundAppHack(DSAPPHACKID_DISABLEDEVICE, dwDevicesAffected,
        0);
}

/*++

 Function Description:

    Makes IDirectSoundBuffer::GetCurrentPosition() tell the app
    that the play and write cursors are X milliseconds further
    along than they really are.
 
 Arguments:

    IN lCursorPad - Number of milliseconds to pad cursors.

 Returns:
    
    True if app hack applied, false otherwise.

 History:

    08/10/2001 mikrause  Created

--*/

BOOL
AddDSHackPadCursors(
    IN LONG lCursorPad)
{
    return AddDirectSoundAppHack(DSAPPHACKID_PADCURSORS, (DWORD)lCursorPad,
        0);
}

/*++

 Function Description:

    When the app asks for the play cursor, we give it the
    write cursor instead.  The correct way to stream audio
    into a looping dsound buffer is to key off the write cursor,
    but some apps (e.g. QuickTime) use the play cursor instead.
    This apphacks alleviates them.
 
 Arguments:

    IN dwDevicesAffected - Combination of devices to apply hack to.

 Notes:
    
    See vregistry_dsound.h for device types.

 Returns:
    
    True if app hack applied, false otherwise.

 History:

    08/10/2001 mikrause  Created

--*/

BOOL
AddDSHackReturnWritePos(
    IN DWORD dwDevicesAffected)
{
    return AddDirectSoundAppHack(DSAPPHACKID_RETURNWRITEPOS, dwDevicesAffected,
        0);
}

/*++

 Function Description:

    Makes dsound always return a write position which is X
    milliseconds ahead of the play position, rather than
    the �real� write position. 
 
 Arguments:

    IN lCursorPad - Milliseconds of padding.

 Returns:
    
    True if app hack applied, false otherwise.

 History:

    08/10/2001 mikrause  Created

--*/

BOOL
AddDSHackSmoothWritePos(
    IN LONG lCursorPad)
{
    return AddDirectSoundAppHack(DSAPPHACKID_SMOOTHWRITEPOS, 1,
        (DWORD)lCursorPad);
}

/*++

 Function Description:

    Caches the play/write positions last returned by
    GetCurrentPosition(), and reuses them if the app
    calls it again within 5ms (great for apps that
    abuse GetCurrentPosition(), which is more expensive
    on WDM devices than it was on the Win9X VxD devices
    many of these games were tested with).  This hack
    should spring to mind if you see slow or jerky graphics
    or stop-and-go sound � the GetCurrentPosition() calls are
    probably pegging the CPU (to confirm, use debug spew
    level 6 on DSound.dll).
 
 Arguments:

    IN dwDevicesAffected - Combination of devices to apply this hack to.


 Notes:
    
    See vregistry_dsound.h for device types.

 Returns:
    
    True if app hack applied, false otherwise.

 History:

    08/10/2001 mikrause  Created

--*/

BOOL
AddDSHackCachePositions(
    IN DWORD dwDevicesAffected)
{
    return AddDirectSoundAppHack(DSAPPHACKID_CACHEPOSITIONS, dwDevicesAffected,
        0);
}

///////////////////////////////////////////////////////////////////////////////
/*++

 Function Description:

    Adds a DirectSound app hack to the registry.
 
 Arguments:

    IN dwHack - ID of app hack to apply.
    IN dwParam1 - First parameter.  Depends on app hack.
    IN dwParam2 - Second paramter.  Depends on app hack.

 Notes:
    
    See vregistry_dsound.h for more information on specific hacks.

 Returns:
    
    True if app hack applied, false otherwise.

 History:

    08/10/2001 mikrause  Created

--*/

BOOL
AddDirectSoundAppHack(
    IN DWORD dwHack,
    IN DWORD dwParam1,
    IN DWORD dwParam2)
{
    WCHAR  wzAppID[MAX_PATH];
    LPWSTR wzValName;
    DWORD dwData[2];
    DWORD dwDataSize;
    VIRTUALKEY *dsoundKey;
    VIRTUALKEY *appKey;
    VIRTUALVAL *val;

    if (GetDirectSoundAppId(wzAppID) == FALSE)
    {
        DPFN(eDbgLevelError, "Unable to create DirectSound app ID");
        return FALSE;
    }    

    dwData[0] = dwParam1;
    dwData[1] = dwParam2;

    switch(dwHack)
    {
    case DSAPPHACKID_DEVACCEL:
        wzValName = L"DSAPPHACKID_DEVACCEL";
        dwDataSize = 2 * sizeof(DWORD);
        break;

    case DSAPPHACKID_PADCURSORS:
        wzValName = L"DSAPPHACKID_PADCURSORS";
        dwDataSize = 1 * sizeof(DWORD);
        break;

    case DSAPPHACKID_CACHEPOSITIONS:
        wzValName = L"DSAPPHACKID_CACHEPOSITIONS";
        dwDataSize = 1 * sizeof(DWORD);
        break;

    case DSAPPHACKID_RETURNWRITEPOS:
        wzValName = L"DSAPPHACKID_RETURNWRITEPOS";
        dwDataSize = 1 * sizeof(DWORD);
        break;

    case DSAPPHACKID_SMOOTHWRITEPOS:
        wzValName = L"DSAPPHACKID_SMOOTHWRITEPOS";
        dwDataSize = 2 * sizeof(DWORD);
        break;

    case DSAPPHACKID_DISABLEDEVICE:
        wzValName = L"DSAPPHACKID_DISABLEDEVICE";
        dwDataSize = 1 * sizeof(DWORD);
        break;

    default:
        DPFN(eDbgLevelError, "Unknown DirectSound AppHack");
        return FALSE;
    }

    dsoundKey = VRegistry.AddKey(L"HKLM\\System\\CurrentControlSet\\Control\\MediaResources\\DirectSound\\Application Compatibility");
    if (dsoundKey == NULL)
    {
        DPFN(eDbgLevelError, "Cannot create virtual registry key");
        return FALSE;
    }

    appKey = dsoundKey->AddKey(wzAppID);
    if (appKey == NULL)
    {
        DPFN(eDbgLevelError, "Cannot create virtual registry key");
        return FALSE;
    }

    val = appKey->AddValue(wzValName,REG_BINARY,(BYTE*)dwData, dwDataSize);
    if (val == NULL)
    {
        DPFN(eDbgLevelError, "Cannot create virtual registry value");
        return FALSE;
    }

    DPFN(eDbgLevelWarning, "DirectSound Apphack \"%S\" enabled, arguments: %X %X", wzValName, dwData[0], dwData[1]);

    return TRUE;
}

// Arguments:
//   LPTSTR szExecPath: full pathname of the app (e.g. C:\program files\foo\foo.exe)
//   LPTSTR szExecName: executable name of the app (e.g. foo.exe)
//   LPTSTR pszAppId: returns the dsound app ID.  (Pass in an array of DSAPPHACK_MAXNAME TCHARs.)
// Return code:
//   BOOL: true if we obtained the application ID successfully.

///////////////////////////////////////////////////////////////////////////////
/*++

 Function Description:

    Gets an AppID for the running application.
 
 Arguments:
    IN OUT wzAppId: Buffer for the dsound app ID.  (Pass in an array of DSAPPHACK_MAXNAME TCHARs.)

 Returns:
    
    TRUE if app ID created, FALSE otherwise.

 History:

    08/10/2001 mikrause  Created

--*/
BOOL
GetDirectSoundAppId(
    IN OUT LPWSTR wzAppId)
{
    WCHAR wzExecPath[MAX_PATH];
    LPWSTR wzExecName;
    IMAGE_NT_HEADERS nth;
    IMAGE_DOS_HEADER dh;
    DWORD cbRead;
    DWORD dwFileSize;
    HANDLE hFile = INVALID_HANDLE_VALUE;
    BOOL fSuccess = FALSE;

    __try
    {
        // Get the name of the running EXE, and its full path.
        if (GetModuleFileNameW(NULL, wzExecPath, MAX_PATH) == FALSE)
        {
            __leave;
        }

        wzExecName = wcsrchr(wzExecPath, L'\\');
        if (wzExecName == NULL)
        {
            __leave;;
        }

        wzExecName++;
        
        // Open the executable
        hFile = CreateFile(wzExecPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
        if (hFile == INVALID_HANDLE_VALUE)
        {
            __leave;
        }

        // Read the executable's DOS header
        fSuccess = ReadFile(hFile, &dh, sizeof(dh), &cbRead, NULL);
        if (!fSuccess || cbRead != sizeof(dh))
        {
            // Log("Unable to read DOS header");
            __leave;
        }    

        if (dh.e_magic != IMAGE_DOS_SIGNATURE)
        {
            // Log("Invalid DOS signature");
            __leave;
        }

        // Read the executable's PE header
        cbRead = SetFilePointer(hFile, dh.e_lfanew, NULL, FILE_BEGIN);
        if ((LONG)cbRead != dh.e_lfanew)
        {
            // Log("Unable to seek to PE header");
            __leave;
        }    
        
        if ((ReadFile(hFile, &nth, sizeof(nth), &cbRead, NULL) == FALSE)
             || cbRead != sizeof(nth))
        {
            // Log("Unable to read PE header");
            __leave;
        }

        if (nth.Signature != IMAGE_NT_SIGNATURE)
        {
            // Log("Invalid PE signature");
            __leave;
        }

        // Get the executable's size
        // Assuming < 4 GB
        dwFileSize = GetFileSize(hFile, NULL);
        if (dwFileSize == INVALID_FILE_SIZE )
        {
            // Log("Unable to get file size");
            __leave;
        }    

        // Create the application ID 
        if (FAILED(StringCchPrintfW(
                                 wzAppId,
                                 MAX_PATH,
                                 L"%s%8.8lX%8.8lX",
                                 wzExecName,
                                 nth.FileHeader.TimeDateStamp,
                                 dwFileSize)))
        {
           __leave;
        }
        CharUpperW(wzAppId);

        fSuccess = TRUE;
    }
    __finally
    {
        if (hFile != INVALID_HANDLE_VALUE)
        {
            CloseHandle(hFile);
        }
    }

    return fSuccess;
}

IMPLEMENT_SHIM_END