/*****************************************************************************
 *
 *  DIJoyReg.c
 *
 *  Copyright (c) 1996 Microsoft Corporation.  All Rights Reserved.
 *
 *  Abstract:
 *
 *      Registry access services for joystick configuration.
 *
 *  Contents:
 *
 *      JoyReg_GetConfig
 *
 *****************************************************************************/

#include "dinputpr.h"

/*****************************************************************************
 *
 *      The sqiffle for this file.
 *
 *****************************************************************************/

#define sqfl sqflJoyReg

#pragma BEGIN_CONST_DATA

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @global JOYREGHWSETTINGS | c_rghwsPredef[] |
 *
 *          Array of predefined hardware settings.
 *
 *****************************************************************************/

JOYREGHWSETTINGS c_rghwsPredef[] = {
    /* dwFlags             dwNumButtons */
    {  0,                             2},  /* JOY_HW_2A_2B_GENERIC         */
    {  0,                             4},  /* JOY_HW_2A_4B_GENERIC         */
    {  JOY_HWS_ISGAMEPAD,             2},  /* JOY_HW_2B_GAMEPAD            */
    {  JOY_HWS_ISYOKE,                2},  /* JOY_HW_2B_FLIGHTYOKE         */
    {  JOY_HWS_HASZ | JOY_HWS_ISYOKE, 2},  /* JOY_HW_2B_FLIGHTYOKETHROTTLE */
    {  JOY_HWS_HASZ,                  2},  /* JOY_HW_3A_2B_GENERIC         */
    {  JOY_HWS_HASZ,                  4},  /* JOY_HW_3A_4B_GENERIC         */
    {  JOY_HWS_ISGAMEPAD,             4},  /* JOY_HW_4B_GAMEPAD            */
    {  JOY_HWS_ISYOKE,                4},  /* JOY_HW_4B_FLIGHTYOKE         */
    {  JOY_HWS_HASZ | JOY_HWS_ISYOKE, 4},  /* JOY_HW_4B_FLIGHTYOKETHROTTLE */
    {  JOY_HWS_HASR                 , 2},  /* JOY_HW_TWO_2A_2B_WITH_Y      */
    /* To prevent the CPL from allowing 
       a user to add a rudder to to JOY_HWS_TWO_2A_2B_WITH_Y case, we 
       will pretend that it already has a rudder. This should not be a problem 
       as this struct is internal to DInput
       */
};

/* Hardware IDs for Predefined Joystick types */
LPCWSTR c_rghwIdPredef[] =
{
    L"GAMEPORT\\VID_045E&PID_0102",  //   L"GAMEPORT\\Generic2A2B",
    L"GAMEPORT\\VID_045E&PID_0103",  //   L"GAMEPORT\\Generic2A4B",
    L"GAMEPORT\\VID_045E&PID_0104",  //   L"GAMEPORT\\Gamepad2B",
    L"GAMEPORT\\VID_045E&PID_0105",  //   L"GAMEPORT\\FlightYoke2B",
    L"GAMEPORT\\VID_045E&PID_0106",  //   L"GAMEPORT\\FlightYokeThrottle2B",
    L"GAMEPORT\\VID_045E&PID_0107",  //   L"GAMEPORT\\Generic3A2B",
    L"GAMEPORT\\VID_045E&PID_0108",  //   L"GAMEPORT\\Generic3A4B",
    L"GAMEPORT\\VID_045E&PID_0109",  //   L"GAMEPORT\\Gamepad4B",
    L"GAMEPORT\\VID_045E&PID_010A",  //   L"GAMEPORT\\FlightYoke4B",
    L"GAMEPORT\\VID_045E&PID_010B",  //   L"GAMEPORT\\FlightYokeThrottle4B",
    L"GAMEPORT\\VID_045E&PID_010C",  //   L"GAMEPORT\\YConnectTwo2A2B",
};

WCHAR c_hwIdPrefix[] = L"GAMEPORT\\";   //  Prefix for custom devices

/*****************************************************************************
 *
 *      The default global port driver.
 *
 *****************************************************************************/

WCHAR c_wszDefPortDriver[] = L"MSANALOG.VXD";

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | JoyReg_GetValue |
 *
 *          Retrieve registry information.  If the data is short, and
 *          the type is <c REG_BINARY>, then the extra is zero-filled.
 *
 *  @parm   HKEY | hk |
 *
 *          Registry key containing fun values.
 *
 *  @parm   LPCTSTR | ptszValue |
 *
 *          Registry value name.
 *
 *  @parm   DWORD | reg |
 *
 *          Registry data type expected.
 *
 *  @parm   LPVOID | pvBuf |
 *
 *          Buffer to receive information from registry.
 *
 *  @parm   DWORD | cb |
 *
 *          Size of recipient buffer, in bytes.
 *
 *  @returns
 *
 *          Returns a COM error code.  The following error codes are
 *          intended to be illustrative and not necessarily comprehensive.
 *
 *          <c DI_OK> = <c S_OK>: The operation completed successfully.
 *
 *          <c S_FALSE>: The binary read was short.  The remainder of the
 *          buffer is zero-filled.
 *
 *          <c E_FAIL>: Error reading value from registry.
 *
 *****************************************************************************/

STDMETHODIMP
    JoyReg_GetValue(HKEY hk, LPCTSTR ptszValue, DWORD reg, PV pvBuf, DWORD cb)
{
    HRESULT hres;
    DWORD cbOut;
    LONG lRc;

    /*
     *  Strings must be handled differently from binaries.
     *
     *  Strings are retrieved in UNICODE and may be short.
     *
     *  Binaries are retrieved as binary (duh) and may be long.
     *
     */

    cbOut = cb;

    if(reg == REG_SZ)
    {
        lRc = RegQueryStringValueW(hk, ptszValue, pvBuf, &cbOut);
        if(lRc == ERROR_SUCCESS)
        {
            hres = S_OK;
        } else
        {
            hres = hresLe(lRc);          /* Else, something bad happened */
        }

    } else
    {

        AssertF(reg == REG_BINARY);

        lRc = RegQueryValueEx(hk, ptszValue, 0, NULL, pvBuf, &cbOut);
        if(lRc == ERROR_SUCCESS)
        {
            if(cb == cbOut)
            {
                hres = S_OK;
            } else
            {

                /*
                 *  Zero out the extra.
                 */
                ZeroBuf(pvAddPvCb(pvBuf, cbOut), cb - cbOut);
                hres = S_FALSE;
            }


        } else if(lRc == ERROR_MORE_DATA)
        {

            /*
             *  Need to double-buffer the call and throw away
             *  the extra...
             */
            LPVOID pv;

            hres = AllocCbPpv(cbOut, &pv);
            // prefix 29344, odd chance that cbOut is 0x0 
            if(SUCCEEDED(hres) && ( pv != NULL)  )
            {
                lRc = RegQueryValueEx(hk, ptszValue, 0, NULL, pv, &cbOut);
                if(lRc == ERROR_SUCCESS) 
                {
                    CopyMemory(pvBuf, pv, cb);
                    hres = S_OK;
                } else
                {
                    ZeroBuf(pvBuf, cb);
                    hres = hresLe(lRc);  /* Else, something bad happened */
                }
                FreePv(pv);
            }

        } else
        {
            if(lRc == ERROR_KEY_DELETED || lRc == ERROR_BADKEY)
            {
                lRc = ERROR_FILE_NOT_FOUND;
            }
            hres = hresLe(lRc);
            ZeroBuf(pvBuf, cb);
        }
    }

#ifdef DEBUG
    /*
     *  Don't whine if the key we couldn't find was
     *  REGSTR_VAL_JOYUSERVALUES, because almost no one has it.
     */
    if(FAILED(hres) &&  lstrcmpi(ptszValue, REGSTR_VAL_JOYUSERVALUES)  )
    {

        SquirtSqflPtszV(sqfl | sqflVerbose,
                        TEXT("Unable to read %s from registry"),
                        ptszValue);
    }
#endif

    return hres;

}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | JoyReg_IsWdmGameport |
 *
 *          To test whether the joy type is WDM device or not.
 *
 *  @parm   HKEY | hk |
 *
 *          Registry key containing fun values.
 *
 *  @returns
 *
 *          S_OK: if it uses WDM driver
 *
 *          E_FAIL>: Not uses WDM driver
 *
 *****************************************************************************/


STDMETHODIMP
    JoyReg_IsWdmGameport( HKEY hk ) 
{
    HRESULT hres = E_FAIL;

    if( hk )
    {
        WCHAR wsz[MAX_JOYSTRING];
        
        // Whistler PREFIX Bug #  45075, 45076
        // Wsz is not initialized
        ZeroX(wsz);

        if( ( SUCCEEDED( JoyReg_GetValue( hk, REGSTR_VAL_JOYOEMHARDWAREID, REG_SZ, 
                                          &wsz, cbX(wsz) ) ) )
          &&( wsz[0] ) )
        {
            hres = S_OK;
        }
        else if( SUCCEEDED( JoyReg_GetValue( hk, REGSTR_VAL_JOYOEMCALLOUT, REG_SZ, 
                                             &wsz, cbX(wsz) ) ) )
        {
            static WCHAR wszJoyhid[] = L"joyhid.vxd";
            int Idx;
            #define WLOWER 0x0020

            CAssertF( cbX(wszJoyhid) <= cbX(wsz) ); 

            /*
             *  Since neither CharUpperW nor lstrcmpiW are really 
             *  implemented on 9x, do it by hand.
             */

            for( Idx=cA(wszJoyhid)-2; Idx>=0; Idx-- )
            {
                if( ( wsz[Idx] | WLOWER ) != wszJoyhid[Idx] )
                {
                    break;
                }
            }

            if( ( Idx < 0 ) && ( wsz[cA(wszJoyhid)-1] == 0 ) )
            {
                hres = S_OK;
            }

            #undef WLOWER
        }

    }

    return hres;
}



#if 0
/*
 * This function should be in diutil.c Putting here is just to keep it together with
 * JoyReg_IsWdmGameport();
 */
STDMETHODIMP
    JoyReg_IsWdmGameportFromDeviceInstance( LPTSTR ptszDeviceInst ) 
{
    /*
     * ptszDeviceInst's format is like this: 
     *     HID\VID_045E&PID_0102\0000GAMEPORT&PVID_....
     */

    WCHAR wszDeviceInst[MAX_PATH];
    HRESULT hres = E_FAIL;

    if( ptszDeviceInst )
    {
        memset( wszDeviceInst, 0, cbX(wszDeviceInst) );
        TToU( wszDeviceInst, MAX_PATH, ptszDeviceInst );
        wszDeviceInst[34] = 0;

        if( memcmp( &wszDeviceInst[26], c_hwIdPrefix, 16 ) == 0 )
        {
            hres = S_OK;
        }
    }

    return hres;
}
#endif

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | JoyReg_SetValue |
 *
 *          Write registry information.
 *
 *  @parm   HKEY | hk |
 *
 *          Registry key containing fun values.
 *
 *  @parm   LPCTSTR | ptszValue |
 *
 *          Registry value name.
 *
 *  @parm   DWORD | reg |
 *
 *          Registry data type to set.
 *
 *  @parm   LPCVOID | pvBuf |
 *
 *          Buffer containing information to write to registry.
 *
 *  @parm   DWORD | cb |
 *
 *          Size of buffer, in bytes.  Ignored if writing a string.
 *
 *  @returns
 *
 *          Returns a COM error code.  The following error codes are
 *          intended to be illustrative and not necessarily comprehensive.
 *
 *          <c DI_OK> = <c S_OK>: The operation completed successfully.
 *
 *          <c E_FAIL>: Error writing value to registry.
 *
 *****************************************************************************/

STDMETHODIMP
    JoyReg_SetValue(HKEY hk, LPCTSTR ptszValue, DWORD reg, PCV pvBuf, DWORD cb)
{
    HRESULT hres;
    LONG lRc;

    /*
     *  Strings must be handled differently from binaries.
     *
     *  A null string translates into deleting the key.
     */

    if(reg == REG_SZ)
    {
        lRc = RegSetStringValueW(hk, ptszValue, pvBuf);
    } else
    {
        lRc = RegSetValueEx(hk, ptszValue, 0, reg, pvBuf, cb);
    }

    if(lRc == ERROR_SUCCESS)
    {
        hres = S_OK;
    } else
    {
        RPF("Unable to write %s to registry", ptszValue);
        hres = E_FAIL;          /* Else, something bad happened */
    }

    return hres;

}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | JoyReg_OpenTypeKey |
 *
 *          Open the joystick registry key that corresponds to a
 *          joystick type.
 *
 *  @parm   LPCWSTR | pwszTypeName |
 *
 *          The name of the type.
 *
 *  @parm   DWORD | sam |
 *
 *          Desired security access mask.
 *
 *  @parm   OUT PHKEY | phk |
 *
 *          Receives the opened registry key on success.
 *
 *  @returns
 *
 *          Returns a COM error code.  The following error codes are
 *          intended to be illustrative and not necessarily comprehensive.
 *
 *          <c DI_OK> = <c S_OK>: The operation completed successfully.
 *
 *          <c DIERR_NOTFOUND>: The joystick type was not found.
 *
 *****************************************************************************/

STDMETHODIMP
    JoyReg_OpenTypeKey(LPCWSTR pwszType, DWORD sam, DWORD dwOptions, PHKEY phk)
{
    HRESULT hres;
    HKEY hkTypes;
    EnterProc(JoyReg_OpenTypeKey, (_ "W", pwszType));

    /*
     *  Note that it is not safe to cache the registry key.
     *  If somebody deletes the registry key, our handle
     *  goes stale and becomes useless.
     */

    hres = hresMumbleKeyEx(HKEY_LOCAL_MACHINE, 
                           REGSTR_PATH_JOYOEM, 
                           sam, 
                           REG_OPTION_NON_VOLATILE, 
                           &hkTypes);

    if( SUCCEEDED(hres) )
    {
#ifndef UNICODE
        TCHAR tszType[MAX_PATH];
        UToA( tszType, cA(tszType), pwszType );

        hres = hresMumbleKeyEx(hkTypes, 
                               tszType, 
                               sam, 
                               dwOptions, 
                               phk);
#else

        hres = hresMumbleKeyEx(hkTypes, 
                               pwszType, 
                               sam,
                               dwOptions, 
                               phk);
#endif     

        RegCloseKey(hkTypes);
    }

    if(FAILED(hres))
    {
        *phk = 0;
    }

    ExitBenignOleProcPpv(phk);
    return hres;
}


/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | JoyReg_OpenPropKey |
 *
 *          Open the Dinput properties registry key that corresponds to a
 *          device type. This key contains the OEMMapFile and dwFlags2 information
 *          Nominally the location HKLM/REGSTR_PATH_PRIVATEPROPERTIES/DirectInput.
 *
 *  @parm   LPCWSTR | pwszTypeName |
 *
 *          The name of the type.
 *
 *  @parm   DWORD | sam |
 *
 *          Desired security access mask.
 *
 *  @parm   OUT PHKEY | phk |
 *
 *          Receives the opened registry key on success.
 *
 *  @returns
 *
 *          Returns a COM error code.  The following error codes are
 *          intended to be illustrative and not necessarily comprehensive.
 *
 *          <c DI_OK> = <c S_OK>: The operation completed successfully.
 *
 *          <c DIERR_NOTFOUND>: The type was not found.
 *
 *****************************************************************************/

STDMETHODIMP
JoyReg_OpenPropKey(LPCWSTR pwszType, DWORD sam, DWORD dwOptions, PHKEY phk)
{
    HRESULT hres;
    HKEY hkTypes;
    EnterProc(JoyReg_OpenTypeKey, (_ "W", pwszType));

    /*
     *  Note that it is not safe to cache the registry key.
     *  If somebody deletes the registry key, our handle
     *  goes stale and becomes useless.
     */

    hres = hresMumbleKeyEx(HKEY_LOCAL_MACHINE, 
                           REGSTR_PATH_DITYPEPROP, 
                           sam, 
                           REG_OPTION_NON_VOLATILE, 
                           &hkTypes);

    if ( SUCCEEDED(hres) )
    {
#ifndef UNICODE
        TCHAR tszType[MAX_PATH];
        UToA( tszType, cA(tszType), pwszType );

        hres = hresMumbleKeyEx(hkTypes, 
                               tszType, 
                               sam, 
                               dwOptions, 
                               phk);
#else

        hres = hresMumbleKeyEx(hkTypes, 
                               pwszType, 
                               sam,
                               dwOptions, 
                               phk);
#endif     

        RegCloseKey(hkTypes);
    }

    if (FAILED(hres))
    {
        *phk = 0;
    }

    ExitBenignOleProcPpv(phk);
    return hres;
}


/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | JoyReg_GetTypeInfo |
 *
 *          Obtain information about a non-predefined joystick type.
 *
 *  @parm   LPCWSTR | pwszTypeName |
 *
 *          The name of the type.
 *
 *  @parm   OUT LPDIJOYTYPEINFO | pjti |
 *
 *          Receives information about the joystick type.
 *          The caller is assumed to have validated the
 *          <e DIJOYCONFIG.dwSize> field.
 *
 *  @parm   DWORD | fl |
 *
 *          Zero or more <c DITC_*> flags
 *          which specify which parts of the structure pointed
 *          to by <p pjti> are to be filled in.
 *
 *  @returns
 *
 *          Returns a COM error code.  The following error codes are
 *          intended to be illustrative and not necessarily comprehensive.
 *
 *          <c DI_OK> = <c S_OK>: The operation completed successfully.
 *
 *          <c DIERR_NOTFOUND>: The joystick type was not found.
 *
 *****************************************************************************/

STDMETHODIMP
    JoyReg_GetTypeInfo(LPCWSTR pwszType, LPDIJOYTYPEINFO pjti, DWORD fl)
{
    HRESULT hres;
    HKEY hk;
    EnterProc(JoyReg_GetTypeInfo, (_ "Wx", pwszType, fl));


    ZeroX(pjti->clsidConfig);
    hres = JoyReg_OpenTypeKey(pwszType, KEY_QUERY_VALUE, REG_OPTION_NON_VOLATILE, &hk);

    if(SUCCEEDED(hres))
    {

        if(fl & DITC_REGHWSETTINGS)
        {
            hres = JoyReg_GetValue(hk,
                                   REGSTR_VAL_JOYOEMDATA, REG_BINARY,
                                   &pjti->hws, cbX(pjti->hws));
            if(FAILED(hres))
            {
                goto closedone;
            }
        }

        /*
         *  Note that this never fails.
         */
        if(fl & DITC_CLSIDCONFIG)
        {
            TCHAR tszGuid[ctchGuid];
            LONG lRc;

            lRc = RegQueryString(hk, REGSTR_VAL_CPLCLSID, tszGuid, cA(tszGuid));

            if(lRc == ERROR_SUCCESS &&
               ParseGUID(&pjti->clsidConfig, tszGuid))
            {
                /* Guid is good */
            } else
            {
                ZeroX(pjti->clsidConfig);
            }
        }
        if(fl & DITC_DISPLAYNAME)
        {
            hres = JoyReg_GetValue(hk,
                                   REGSTR_VAL_JOYOEMNAME, REG_SZ,
                                   pjti->wszDisplayName,
                                   cbX(pjti->wszDisplayName));
            if(FAILED(hres))
            {
                goto closedone;
            }
        }

        if(fl & DITC_CALLOUT)
        {
            hres = JoyReg_GetValue(hk,
                                   REGSTR_VAL_JOYOEMCALLOUT, REG_SZ,
                                   pjti->wszCallout,
                                   cbX(pjti->wszCallout));
            if(FAILED(hres))
            {
                ZeroX(pjti->wszCallout);
                hres = S_FALSE;
                goto closedone;
            }
        }

        if( fl & DITC_HARDWAREID )
        {
            hres = JoyReg_GetValue(hk,
                                   REGSTR_VAL_JOYOEMHARDWAREID, REG_SZ,
                                   pjti->wszHardwareId,
                                   cbX(pjti->wszHardwareId));
            if( FAILED(hres))
            {
                ZeroX(pjti->wszHardwareId);
                hres = S_FALSE;
                goto closedone;
            }
        }

        if( fl & DITC_FLAGS1 ) 
        {
            hres = JoyReg_GetValue(hk,
                                   REGSTR_VAL_FLAGS1, REG_BINARY, 
                                   &pjti->dwFlags1, 
                                   cbX(pjti->dwFlags1) );
            if( FAILED(hres) )
            {
                pjti->dwFlags1 = 0x0;
                hres = S_FALSE;
                goto closedone;
            }
            pjti->dwFlags1 &= JOYTYPE_FLAGS1_GETVALID;
        }

        hres = S_OK;

closedone:;
        RegCloseKey(hk);

    } else
    {
        // ISSUE-2001/03/29-timgill debug string code should be higher
        // (MarcAnd) this really should be at least sqflError but
        // this happens a lot, probably due to not filtering out predefs
        SquirtSqflPtszV(sqfl | sqflBenign,
            TEXT( "IDirectInputJoyConfig::GetTypeInfo: Nonexistent type %lS" ),
            pwszType);
        hres = DIERR_NOTFOUND;
    }

    ExitOleProc();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | JoyReg_SetTypeInfo |
 *
 *          Store information about a non-predefined joystick type
 *          into the registry.
 *
 *  @parm   HKEY | hkTypeW |
 *
 *          Registry key to the types branch with write access.
 *
 *  @parm   LPCWSTR | pwszTypeName |
 *
 *          The name of the type.
 *
 *  @parm   IN LPCDIJOYTYPEINFO | pjti |
 *
 *          Contains information about the joystick type.
 *
 *  @parm   DWORD | fl |
 *
 *          Zero or more <c DITC_*> flags
 *          which specify which parts of the structure pointed
 *          to by <p pjti> contain values which are to be set.
 *
 *  @returns
 *
 *          Returns a COM error code.  The following error codes are
 *          intended to be illustrative and not necessarily comprehensive.
 *
 *          <c DI_OK> = <c S_OK>: The operation completed successfully.
 *
 *          <c DIERR_NOTFOUND>: The joystick type was not found.
 *
 *****************************************************************************/

STDMETHODIMP
    JoyReg_SetTypeInfo(HKEY hkTypesW,
                       LPCWSTR pwszType, LPCDIJOYTYPEINFO pjti, DWORD fl)
{
    HRESULT hres;
    ULONG lRc;
    EnterProc(JoyRegSetTypeInfo, (_ "Wx", pwszType, fl));

    if(fl & DITC_INREGISTRY)
    {
        HKEY hk;
        DWORD dwOptions = 0;


        if( fl & DITC_VOLATILEREGKEY )
        {
            dwOptions = REG_OPTION_VOLATILE;
        }else
        {
           dwOptions = REG_OPTION_NON_VOLATILE;    
        }

#ifndef UNICODE
        {
            TCHAR tszType[MAX_PATH];

            UToA(tszType, cA(tszType), pwszType);

            hres = hresMumbleKeyEx(hkTypesW, 
                                   tszType, 
                                   DI_KEY_ALL_ACCESS, 
                                   dwOptions, 
                                   &hk);

        }
#else
        hres = hresMumbleKeyEx(hkTypesW, 
                               pwszType, 
                               DI_KEY_ALL_ACCESS, 
                               dwOptions, 
                               &hk);
#endif

        if( SUCCEEDED(hres) )
        {

            if(fl & DITC_REGHWSETTINGS)
            {
                hres = JoyReg_SetValue(hk, REGSTR_VAL_JOYOEMDATA, REG_BINARY,
                                       (PV)&pjti->hws, cbX(pjti->hws));
                if(FAILED(hres))
                {
                    goto closedone;
                }
            }

            if(fl & DITC_CLSIDCONFIG)
            {
                if(IsEqualGUID(&pjti->clsidConfig, &GUID_Null))
                {
                    lRc = RegDeleteValue(hk, REGSTR_VAL_CPLCLSID);

                    /*
                     *  It is not an error if the key does not already exist.
                     */
                    if(lRc == ERROR_FILE_NOT_FOUND)
                    {
                        lRc = ERROR_SUCCESS;
                    }
                } else
                {
                    TCHAR tszGuid[ctchNameGuid];
                    NameFromGUID(tszGuid, &pjti->clsidConfig);
                    lRc = RegSetValueEx(hk, REGSTR_VAL_CPLCLSID, 0, REG_SZ,
                                        (PV)&tszGuid[ctchNamePrefix], ctchGuid * cbX(tszGuid[0]) );
                }
                if(lRc == ERROR_SUCCESS)
                {
                } else
                {
                    hres = E_FAIL;
                    goto closedone;
                }
            }

            /* ISSUE-2001/03/29-timgill Needs more data checking
               Should make sure string is terminated properly */
            if(fl & DITC_DISPLAYNAME)
            {
                hres = JoyReg_SetValue(hk,
                                       REGSTR_VAL_JOYOEMNAME, REG_SZ,
                                       pjti->wszDisplayName,
                                       cbX(pjti->wszDisplayName));
                if(FAILED(hres))
                {
                    goto closedone;
                }
            }

            /* ISSUE-2001/03/29-timgill Needs more data checking
               Should make sure string is terminated properly */
            if(fl & DITC_CALLOUT)
            {
                hres = JoyReg_SetValue(hk,
                                       REGSTR_VAL_JOYOEMCALLOUT, REG_SZ,
                                       pjti->wszCallout,
                                       cbX(pjti->wszCallout));
                if(FAILED(hres))
                {
                    hres = S_FALSE;
                    //continue to go
                }
            }

            if( fl & DITC_HARDWAREID )
            {
                hres = JoyReg_SetValue(hk,
                                       REGSTR_VAL_JOYOEMHARDWAREID, REG_SZ,
                                       pjti->wszHardwareId,
                                       cbX(pjti->wszHardwareId) );
                if( FAILED(hres) )
                {
                    hres = S_FALSE;
                    goto closedone;
                }
            }

            if( fl & DITC_FLAGS1 ) 
            {
                AssertF( (pjti->dwFlags1 & ~JOYTYPE_FLAGS1_SETVALID) == 0x0 );
                hres = JoyReg_SetValue(hk,
                                       REGSTR_VAL_FLAGS1, REG_BINARY, 
                                       (PV)&pjti->dwFlags1, 
                                       cbX(pjti->dwFlags1) );
                if( FAILED(hres) )
                {
                    hres = S_FALSE;
                    goto closedone;
                }
            }
            
            hres = S_OK;

            closedone:;
            RegCloseKey(hk);

        } else
        {
            hres = E_FAIL;              /* Registry problem */
        }
    } else
    {
        hres = S_OK;                    /* Vacuous success */
    }

    ExitOleProc();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | JoyReg_OpenConfigKey |
 *
 *          Open the registry key that accesses joystick configuration data.
 *
 *          Warning!  Do not cache this regkey.
 *
 *          If the user deletes the key and then re-creates it,
 *          the opened key will go stale and will become useless.
 *          You have to close the key and reopen it.
 *          To avoid worrying about that case, merely open it every time.
 *
 *  @parm   UINT | idJoy |
 *
 *          Joystick number.
 *
 *  @parm   DWORD | sam |
 *
 *          Access level desired.
 *
 *  @parm   IN PJOYCAPS | pcaps |
 *
 *          Receives joystick capabilities information.
 *          If this parameter is <c NULL>, then joystick
 *          capabilities information is not returned.
 *
 *  @parm   IN DWORD  | dwOptions |
 *          Option flags to RegCreateEx
 *
 *  @parm   PHKEY | phk |
 *
 *          Receives created registry key.
 *
 *  @returns
 *
 *          Returns a COM error code.  The following error codes are
 *          intended to be illustrative and not necessarily comprehensive.
 *
 *          <c DI_OK> = <c S_OK>: The operation completed successfully.
 *
 *          hresLe(ERROR_FILE_NOT_FOUND): The key does not exist.
 *
 *****************************************************************************/

STDMETHODIMP
    JoyReg_OpenConfigKey(UINT idJoy, DWORD sam, PJOYCAPS pcaps, DWORD dwOptions, PHKEY phk)
{
    HRESULT hres;
    MMRESULT mmrc = MMSYSERR_ERROR;
    JOYCAPS caps;
    EnterProc(JoyReg_OpenConfigKey, (_ "uxp", idJoy, sam, pcaps));

    /*
     *  If caller doesn't care, then just dump the caps into our
     *  private buffer.
     */
    if(pcaps == NULL)
    {
        pcaps = &caps;
    }

    /*
     *  If we can't get the dev caps for the specified joystick,
     *  then use the magic joystick id "-1" to get non-specific
     *  caps.
     */
    if( fWinnt )
    {
        ZeroX(*pcaps);
        lstrcpy(pcaps->szRegKey, REGSTR_SZREGKEY );
        mmrc = JOYERR_NOERROR;
    } else 
    {
        mmrc = joyGetDevCaps(idJoy, pcaps, cbX(*pcaps));
        if( mmrc != JOYERR_NOERROR ) {
            mmrc = joyGetDevCaps((DWORD)-1, pcaps, cbX(*pcaps));
        }
    }

    if(mmrc == JOYERR_NOERROR)
    {

        TCHAR tsz[cA(REGSTR_PATH_JOYCONFIG) +
                  1 +                           /* backslash */
                  cA(pcaps->szRegKey) +
                  1 +                           /* backslash */
                  cA(REGSTR_KEY_JOYCURR) + 1];        

        /* tsz = MediaResources\Joystick\<drv>\CurrentJoystickSettings */
        wsprintf(tsz, TEXT("%s\\%s\\") REGSTR_KEY_JOYCURR,
                 REGSTR_PATH_JOYCONFIG, pcaps->szRegKey);


        hres = hresMumbleKeyEx(HKEY_LOCAL_MACHINE, tsz, sam, REG_OPTION_VOLATILE, phk);

    } else
    {
        hres = E_FAIL;
    }

    ExitBenignOleProc();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | JoyReg_OpenSaveKey |
 *
 *          Open the registry key that accesses joystick saved configurations
 *
 *          Warning!  Do not cache this regkey.
 *
 *          If the user deletes the key and then re-creates it,
 *          the opened key will go stale and will become useless.
 *          You have to close the key and reopen it.
 *          To avoid worrying about that case, merely open it every time.
 *
 *  @parm   DWORD | dwType |
 *
 *          Joystick type.
 *
 *          This is either one of the standard ones in the range
 *
 *  @parm   IN LPCDIJOYCONFIG | pcfg |
 *
 *          If the dwType represents an OEM type, this should point to a
 *          configuration data structure containing a valid wszType.
 *
 *  @parm   DWORD | sam |
 *
 *          Access level desired.
 *
 *  @parm   PHKEY | phk |
 *
 *          Receives created registry key.
 *
 *  @returns
 *
 *          Returns a COM error code.  The following error codes are
 *          intended to be illustrative and not necessarily comprehensive.
 *
 *          <c DI_OK> = <c S_OK>: The operation completed successfully.
 *
 *          hresLe(ERROR_FILE_NOT_FOUND): The key does not exist.
 *
 *****************************************************************************/

STDMETHODIMP
    JoyReg_OpenSaveKey(DWORD dwType, LPCDIJOYCONFIG pcfg, DWORD sam, PHKEY phk)
{
    HRESULT hres;
    MMRESULT mmrc = MMSYSERR_ERROR;
    JOYCAPS caps;
    DWORD   dwOptions = 0;
    EnterProc(JoyReg_OpenSaveKey, (_ "upx", dwType, pcfg, sam));

    /*
     *  use the magic joystick id "-1" to get non-specific caps.
     */

    if( fWinnt )
    {
        ZeroX(caps);
        lstrcpy(caps.szRegKey, REGSTR_SZREGKEY );
        mmrc = JOYERR_NOERROR;
    } else 
    {
        mmrc = joyGetDevCaps((DWORD)-1, &caps, cbX(caps));
    }

    if(mmrc == JOYERR_NOERROR)
    {
        TCHAR tsz[cA(REGSTR_PATH_JOYCONFIG) +
                  1 +                           /* backslash */
                  cA(caps.szRegKey) +
                  1 +                           /* backslash */
                  cA(REGSTR_KEY_JOYSETTINGS) +
                  1 +                           /* backslash */
                  max( cA(REGSTR_KEY_JOYPREDEFN), cA(pcfg->wszType) ) + 1 ];

        /* tsz = MediaResources\Joystick\<drv>\JoystickSettings\<Type> */
        if( dwType >= JOY_HW_PREDEFMAX )
        {
            wsprintf(tsz, TEXT("%s\\%s\\%s\\%ls"),
                     REGSTR_PATH_JOYCONFIG, caps.szRegKey, REGSTR_KEY_JOYSETTINGS, pcfg->wszType);
        } else
        {
            /*
             *  We will probably never have more than the current 11 predefined
             *  joysticks.  Assume no more than 99 so %d is as many characters.
             */
            wsprintf(tsz, TEXT("%s\\%s\\%s\\" REGSTR_KEY_JOYPREDEFN),
                     REGSTR_PATH_JOYCONFIG, caps.szRegKey, REGSTR_KEY_JOYSETTINGS, dwType );
        }


        if( pcfg->hwc.dwUsageSettings & JOY_US_VOLATILE )
            dwOptions = REG_OPTION_VOLATILE;
        else
            dwOptions = REG_OPTION_NON_VOLATILE;

        hres = hresMumbleKeyEx(HKEY_LOCAL_MACHINE, tsz, sam, dwOptions, phk);

    } else
    {
        hres = E_FAIL;
    }

    ExitOleProc();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | JoyReg_GetSetConfigValue |
 *
 *          Retrieve or update configuration information about a joystick,
 *          as stored in the registry instance key.
 *
 *  @parm   HKEY | hk |
 *
 *          Registry key containing fun values.
 *
 *  @parm   LPCTSTR | ptszNValue |
 *
 *          Registry value name, with "%d" where a joystick number
 *          should be.
 *
 *  @parm   UINT | idJoy |
 *
 *          Zero-based joystick number.
 *
 *  @parm   DWORD | reg |
 *
 *          Registry data type expected.
 *
 *  @parm   LPVOID | pvBuf |
 *
 *          Buffer to receive information from registry (if getting)
 *          or containing value to set.
 *
 *  @parm   DWORD | cb |
 *
 *          Size of buffer, in bytes.
 *
 *  @parm   BOOL | fSet |
 *
 *          Nonzer if the value should be set; otherwise, it will be
 *          retrieved.
 *
 *  @returns
 *
 *          Returns a COM error code.  The following error codes are
 *          intended to be illustrative and not necessarily comprehensive.
 *
 *          <c DI_OK> = <c S_OK>: The operation completed successfully.
 *
 *          <c E_FAIL>: Error reading/writing value to/from registry.
 *
 *****************************************************************************/

STDMETHODIMP
    JoyReg_GetSetConfigValue(HKEY hk, LPCTSTR ptszNValue, UINT idJoy,
                             DWORD reg, PV pvBuf, DWORD cb, BOOL fSet)
{
    HRESULT hres;
    int ctch;

    /* Extra +12 because a UINT can be as big as 4 billion */
    TCHAR tsz[max(
                 max(
                    max(cA(REGSTR_VAL_JOYNCONFIG),
                        cA(REGSTR_VAL_JOYNOEMNAME)),
                    cA(REGSTR_VAL_JOYNOEMCALLOUT)),
                 cA(REGSTR_VAL_JOYNFFCONFIG)) + 12 + 1];

    ctch = wsprintf(tsz, ptszNValue, idJoy + 1);
    AssertF(ctch < cA(tsz));

    if(fSet)
    {
        hres = JoyReg_SetValue(hk, tsz, reg, pvBuf, cb);
    } else
    {
        hres = JoyReg_GetValue(hk, tsz, reg, pvBuf, cb);
    }

    return hres;

}


/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | hresIdJoypInstanceGUID |
 *
 *          Given a joystick ID obtain the corresponding GUID.
 *          This routine differs in implementation on WINNT and WIN9x
 *          On WINNT there are no predefined GUID for Joystick IDs.
 *
 *  @parm   IN UINT | idJoy |
 *
 *          Joystick identification number.
 *
 *  @parm   OUT LPGUID | lpguid |
 *
 *          Receives the joystick GUID. If no mapping exists,
 *          GUID_NULL is passed back
 *
 *  On Windows NT all joysticks are HID devices. The corresponding function
 *  for WINNT is defined in diWinnt.c
 *
 *****************************************************************************/

HRESULT EXTERNAL hResIdJoypInstanceGUID_95
    (
    UINT    idJoy,
    LPGUID  lpguid
    )
{
    HRESULT hRes;

    hRes = S_OK;
    if( idJoy < cA(rgGUID_Joystick) )
    {
        *lpguid = rgGUID_Joystick[idJoy];
    } else
    {
        hRes = DIERR_NOTFOUND;
        ZeroX(*lpguid);
    }
    return hRes;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | JoyReg_GetConfigInternal |
 *
 *          Obtain information about a joystick's configuration.
 *
 *  @parm   UINT | uiJoy |
 *
 *          Joystick identification number.
 *
 *  @parm   PJOYCAPS | pcaps |
 *
 *          Receives information about the joystick capabilities.
 *          If this parameter is <c NULL>, then joystick
 *          capabilities information is not returned.
 *
 *  @parm   OUT LPDIJOYCONFIG | pcfg |
 *
 *          Receives information about the joystick configuration.
 *          The caller is assumed to have validated the
 *          <e DIJOYCONFIG.dwSize> field.
 *
 *  @parm   DWORD | fl |
 *
 *          Zero or more <c DIJC_*> flags
 *          which specify which parts of the structure pointed
 *          to by <p pjc> are to be filled in.
 *
 *  @returns
 *
 *          Returns a COM error code.  The following error codes are
 *          intended to be illustrative and not necessarily comprehensive.
 *
 *          <c DI_OK> = <c S_OK>: The operation completed successfully.
 *
 *          <c DIERR_NOMOREITEMS>: No more joysticks.
 *
 *****************************************************************************/

STDMETHODIMP
    JoyReg_GetConfigInternal(UINT idJoy, PJOYCAPS pcaps,
                             LPDIJOYCONFIG pcfg, DWORD fl)
{
    HRESULT hres = E_FAIL;

    EnterProc(JoyReg_GetConfigInternal, (_ "upx", idJoy, pcaps, pcfg, fl));

    AssertF((fl & ~DIJC_GETVALID) == 0);

    /* We only support (0/16) joysticks */
    if( idJoy < cJoyMax )
    {

        /* Force a rescan of all HID device list
         * Some device may have been attached
         * since we last looked
         */
        DIHid_BuildHidList(FALSE);

        if(fl & DIJC_GUIDINSTANCE)
        {
            hres = hResIdJoypInstanceGUID_WDM(idJoy, &pcfg->guidInstance);

            if( (hres != S_OK) && !fWinnt) {
                hres = hResIdJoypInstanceGUID_95(idJoy, &pcfg->guidInstance);
            }
            
            if( FAILED(hres) )
            {
                goto done;
            }
        }

        if( fl & DIJC_INREGISTRY )
        {
            HKEY hk;
            /* Does the registry entry exist ? */
            hres = JoyReg_OpenConfigKey(idJoy, KEY_QUERY_VALUE, pcaps,REG_OPTION_NON_VOLATILE , &hk);
            if(SUCCEEDED(hres))
            {
                if(fl & DIJC_REGHWCONFIGTYPE)
                {
                    hres = JoyReg_GetConfigValue(
                                                hk, REGSTR_VAL_JOYNCONFIG,
                                                idJoy, REG_BINARY,
                                                &pcfg->hwc, cbX(pcfg->hwc));
                    if(FAILED(hres))
                    {
                        goto closedone;
                    }

                    pcfg->wszType[0] = TEXT('\0');
                    if( (pcfg->hwc.dwUsageSettings & JOY_US_ISOEM) ||
                        ( !fWinnt && (pcfg->hwc.dwType >= JOY_HW_PREDEFMIN) 
                                  && (pcfg->hwc.dwType < JOY_HW_PREDEFMAX) ) )
                    {
                        hres = JoyReg_GetConfigValue(
                                                    hk, REGSTR_VAL_JOYNOEMNAME, idJoy, REG_SZ,
                                                    pcfg->wszType, cbX(pcfg->wszType));
                        if(FAILED(hres))
                        {
                            goto closedone;
                        }
                    }
                }


                if(fl & DIJC_CALLOUT)
                {
                    pcfg->wszCallout[0] = TEXT('\0');
                    hres = JoyReg_GetConfigValue(
                                                hk, REGSTR_VAL_JOYNOEMCALLOUT, idJoy, REG_SZ,
                                                pcfg->wszCallout, cbX(pcfg->wszCallout));
                    if(FAILED(hres))
                    {
                        ZeroX(pcfg->wszCallout);
                        hres = S_FALSE;
                        /* Note that we fall through and let hres = S_OK */
                    }
                }


                if(fl & DIJC_GAIN)
                {
                    /*
                     *  If there is no FF configuration, then
                     *  default to DI_FFNOMINALMAX gain.
                     */
                    hres = JoyReg_GetConfigValue(hk,
                                                 REGSTR_VAL_JOYNFFCONFIG,
                                                 idJoy, REG_BINARY,
                                                 &pcfg->dwGain, cbX(pcfg->dwGain));

                    if(SUCCEEDED(hres) && ISVALIDGAIN(pcfg->dwGain))
                    {
                        /* Leave it alone; it's good */
                    } else
                    {
                        hres = S_FALSE;
                        pcfg->dwGain = DI_FFNOMINALMAX;
                        /* Note that we fall through and let hres = S_OK */
                    }
                }

                if( fl & DIJC_WDMGAMEPORT )
                {
                    PBUSDEVICEINFO pbdi;
                    /*
                     * If there is no Gameport Associated with this device
                     * then it must be a USB device
                     */

                    DllEnterCrit();
                    if( pbdi = pbdiFromJoyId(idJoy) )
                    {
                        pcfg->guidGameport = pbdi->guid;
                        //lstrcpyW(pcfg->wszGameport, pbdi->wszDisplayName);
                    } else
                    {
                        ZeroX(pcfg->guidGameport);
                        //pcfg->wszGameport[0] = TEXT('\0');
                    }

                    DllLeaveCrit();
                }

            }

        closedone:
            RegCloseKey(hk);
        }
    } else
    {
        hres = DIERR_NOMOREITEMS;
    }

done:
    ExitBenignOleProc();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | JoyReg_GetConfig |
 *
 *          Obtain information about a joystick's configuration,
 *          taking the MSGAME.VXD driver into account.
 *
 *  @parm   UINT | uiJoy |
 *
 *          Joystick identification number.
 *
 *  @parm   PJOYCAPS | pcaps |
 *
 *          Receives information about the joystick capabilities.
 *          If this parameter is <c NULL>, then joystick
 *          capabilities information is not returned.
 *
 *  @parm   OUT LPDIJOYCONFIG | pcfg |
 *
 *          Receives information about the joystick configuration.
 *          The caller is assumed to have validated the
 *          <e DIJOYCONFIG.dwSize> field.
 *
 *  @parm   DWORD | fl |
 *
 *          Zero or more <c DIJC_*> flags
 *          which specify which parts of the structure pointed
 *          to by <p pjc> are to be filled in.
 *
 *  @returns
 *
 *          Returns a COM error code.  The following error codes are
 *          intended to be illustrative and not necessarily comprehensive.
 *
 *          <c DI_OK> = <c S_OK>: The operation completed successfully.
 *
 *          <c DIERR_NOMOREITEMS>: No more joysticks.
 *
 *****************************************************************************/

STDMETHODIMP
    JoyReg_GetConfig(UINT idJoy, PJOYCAPS pcaps, LPDIJOYCONFIG pcfg, DWORD fl)
{
    HRESULT hres;
    GUID    guid;

    EnterProc(JoyReg_GetConfig, (_ "upx", idJoy, pcaps, pcfg, fl));

    AssertF((fl & ~DIJC_GETVALID) == 0);

    /* 
     * First determine if the joystick exits 
     * On NT, we use WDM driver.
     * On Win9x, if WDM fails, use static guids.
     */
    hres = hResIdJoypInstanceGUID_WDM(idJoy, &guid);

    if( (hres != S_OK) && !fWinnt ) {
        hres = hResIdJoypInstanceGUID_95(idJoy, &guid);
    }

    if( SUCCEEDED( hres) )
    {

        hres = JoyReg_GetConfigInternal(idJoy, pcaps, pcfg, fl);

      #ifndef WINNT
        /***************************************************
         *
         *  Beginning of hack for Sidewinder Gamepad.
         *
         *  The gamepad needs to be polled six times before
         *  it realizes what is going on.
         *
         ***************************************************/

        if(SUCCEEDED(hres) && (fl & DIJC_CALLOUT))
        {

            static WCHAR s_wszMSGAME[] = L"MSGAME.VXD";

            if(memcmp(pcfg->wszCallout, s_wszMSGAME, cbX(s_wszMSGAME)) == 0)
            {
                JOYINFOEX ji;
                int i;
                DWORD dwWait;

                SquirtSqflPtszV(sqfl,
                                TEXT("Making bonus polls for Sidewinder"));

                /*
                 *  It's a Sidewinder.  Make six
                 *  bonus polls to shake the stick into submission.
                 *
                 *  Actually, we make 16 bonus polls.  The Sidewinder
                 *  guys said that five or six would be enough.
                 *  They're wrong.
                 *
                 *  I also sleep 10ms between each poll because that
                 *  seems to help a bit.
                 */
                ji.dwSize = cbX(ji);
                ji.dwFlags = JOY_RETURNALL;
                for(i = 0; i < 16; i++)
                {
                    MMRESULT mmrc = joyGetPosEx(idJoy, &ji);
                    SquirtSqflPtszV(sqfl,
                                    TEXT("joyGetPosEx(%d) = %d"),
                                    idJoy, mmrc);
                    Sleep(10);
                }

                /*
                 *  Now sleep for some time.  They forgot to tell us
                 *  this.
                 *
                 *  Bonus hack!  The amount of time we need to sleep
                 *  is CPU-speed dependent, so we'll grab the sleep
                 *  time from the registry to allow us to tweak it
                 *  later.
                 *
                 *  What a bunch of lamers.
                 */
                dwWait = RegQueryDIDword(NULL, REGSTR_VAL_GAMEPADDELAY, 100);
                if(dwWait > 10 * 1000)
                {
                    dwWait = 10 * 1000;
                }

                Sleep(dwWait);

                /*
                 *  And then check again.
                 */
                hres = JoyReg_GetConfigInternal(idJoy, pcaps, pcfg, fl);
            }
        }

        /***************************************************
         *
         *  End of hack for Sidewinder Gamepad.
         *
         ***************************************************/
      #endif //#ifndef WINNT    
      
    }

    return hres;
}

/* This never happens on NT */

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | JoyReg_JoyIdToDeviceInterface_95 |
 *
 *          Given a joystick ID number, obtain the device interface
 *          corresponding to it.
 *
 *  @parm   UINT | idJoy |
 *
 *          Joystick ID number, zero-based.
 *
 *  @parm   PVXDINITPARMS | pvip |
 *
 *          Receives init parameters from the driver.
 *
 *  @parm   LPTSTR | ptszBuf |
 *
 *          A buffer of size <c MAX_PATH> in which the device interface
 *          path is built.  Note that we can get away with a buffer of
 *          this size, since the code path exists only on Windows 95,
 *          and Windows 95 does not support paths longer than <c MAX_PATH>.
 *          (I.e., there ain't no \\?\ support in Win95.)
 *
 *  @returns
 *
 *          A pointer to the part of the <p ptszBuf> buffer that
 *          contains the actual device interface path.
 *
 *****************************************************************************/

LPSTR EXTERNAL
    JoyReg_JoyIdToDeviceInterface_95(UINT idJoy, PVXDINITPARMS pvip, LPSTR ptszBuf)
{
    UINT cwch;
    HRESULT hres;
    LPSTR ptszRc;

    if( fWinnt )
        return NULL;

    hres = Hel_Joy_GetInitParms(idJoy, pvip);
    if(SUCCEEDED(hres))
    {

        /*
         *  The length counter includes the terminating null.
         */
        cwch = LOWORD(pvip->dwFilenameLengths);

        /*
         *  The name that comes from HID is "\DosDevices\blah"
         *  but we want to use "\\.\blah".  So check if it indeed
         *  of the form "\DosDevices\blah" and if so, convert it.
         *  If not, then give up.
         *
         *  For the string to possibly be a "\DosDevices\", it
         *  needs to be of length 12 or longer.
         */

        if(cwch >= 12 && cwch < MAX_PATH)
        {

            /*
             *  WideCharToMultiByte does parameter validation so we
             *  don't have to.
             */
            WideCharToMultiByte(CP_ACP, 0, pvip->pFilenameBuffer, cwch,
                                ptszBuf, MAX_PATH, 0, 0);

            /*
             *  The 11th (zero-based) character must be a backslash.
             *  And the value of cwch had better be right.
             */
            if(ptszBuf[cwch-1] == ('\0') && ptszBuf[11] == ('\\'))
            {

                /*
                 *  Wipe out the backslash and make sure the lead-in
                 *  is "\DosDevices".
                 */
                ptszBuf[11] = ('\0');
                if(lstrcmpiA(ptszBuf, ("\\DosDevices")) == 0)
                {
                    /*
                     *  Create a "\\.\" at the start of the string.
                     *  Note!  This code never runs on Alphas so we
                     *  can do evil unaligned data accesses.
                     *
                     *  (Actually, 8 is a multiple of 4, so everything
                     *  is aligned after all.)
                     */
                    *(LPDWORD)&ptszBuf[8] = 0x5C2E5C5C;

                    ptszRc = &ptszBuf[8];
                } else
                {
                    ptszRc = NULL;
                }
            } else
            {
                ptszRc = NULL;
            }
        } else
        {
            ptszRc = NULL;
        }
    } else
    {
        ptszRc = NULL;
    }

    return ptszRc;
}


/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   void | JoyReg_SetCalibration |
 *
 *          Store information about a joystick's configuration,
 *          shadowing the information back into the HID side of
 *          things as well.
 *
 *  @parm   UINT | uiJoy |
 *
 *          Joystick identification number.
 *
 *  @parm   LPJOYREGHWCONFIG | phwc |
 *
 *          Contains information about the joystick capabilities.
 *          This value supercedes the value in the <p pcfg>.
 *
 *  @parm   LPCDIJOYCONFIG | pcfg |
 *
 *          Contains information about the joystick configuration.
 *          The caller is assumed to have validated all fields.
 *
 *****************************************************************************/

STDMETHODIMP
    TFORM(CDIObj_FindDevice)(PV pdiT, REFGUID rguid,
                             LPCTSTR ptszName, LPGUID pguidOut);

void EXTERNAL
    JoyReg_SetCalibration(UINT idJoy, LPJOYREGHWCONFIG phwc)
{
    HRESULT hres;
    VXDINITPARMS vip;
    GUID guid;
    CHAR tsz[MAX_PATH];
    LPSTR pszPath;
    TCHAR ptszPath[MAX_PATH];
    EnterProc(JoyReg_SetCalibration, (_ "up", idJoy, phwc));

    pszPath = JoyReg_JoyIdToDeviceInterface_95(idJoy, &vip, tsz);

    if( pszPath )
#ifdef UNICODE
        AToU( ptszPath, MAX_PATH, pszPath );
#else
        lstrcpy( (LPSTR)ptszPath, pszPath );
#endif

    if(pszPath &&
       SUCCEEDED(CDIObj_FindDeviceInternal(ptszPath, &guid)))
    {
        IDirectInputDeviceCallback *pdcb;
#ifdef DEBUG
        CREATEDCB CreateDcb;
#endif

#ifdef DEBUG
        /*
         *  If the associated HID device got unplugged, then
         *  the instance GUID is no more.  So don't get upset
         *  if we can't find it.  But if we do find it, then
         *  it had better be a HID device.
         *
         *  CHid_New will properly fail if the associated
         *  device is not around.
         */
        hres = hresFindInstanceGUID(&guid, &CreateDcb, 1);
        AssertF(fLimpFF(SUCCEEDED(hres), CreateDcb == CHid_New));
#endif

        if(SUCCEEDED(hres = CHid_New(0, &guid,
                                     &IID_IDirectInputDeviceCallback,
                                     (PPV)&pdcb)))
        {
            LPDIDATAFORMAT pdf;

            /*
             *  The VXDINITPARAMS structure tells us where JOYHID
             *  decided to place each of the axes.  Follow that
             *  table to put them into their corresponding location
             *  in the HID side.
             */
            hres = pdcb->lpVtbl->GetDataFormat(pdcb, &pdf);
            if(SUCCEEDED(hres))
            {
                UINT uiAxis;
                DIPROPINFO propi;

                propi.pguid = DIPROP_SPECIFICCALIBRATION;

                /*
                 *  For each axis...
                 */
                for(uiAxis = 0; uiAxis < 6; uiAxis++)
                {
                    DWORD dwUsage = vip.Usages[uiAxis];
                    /*
                     *  If the axis is mapped to a usage...
                     */
                    if(dwUsage)
                    {
                        /*
                         *  Convert the usage into an object index.
                         */
                        hres = pdcb->lpVtbl->MapUsage(pdcb, dwUsage,
                                                      &propi.iobj);
                        if(SUCCEEDED(hres))
                        {
                            DIPROPCAL cal;

                            /*
                             *  Convert the old-style calibration into
                             *  a new-style calibration.
                             */
#define CopyCalibration(f, ui) \
                cal.l##f = (&phwc->hwv.jrvHardware.jp##f.dwX)[ui]

                            CopyCalibration(Min, uiAxis);
                            CopyCalibration(Max, uiAxis);
                            CopyCalibration(Center, uiAxis);

#undef CopyCalibration

                            /*
                             *  Set the calibration property on the object.
                             */
                            propi.dwDevType =
                                pdf->rgodf[propi.iobj].dwType;
                            hres = pdcb->lpVtbl->SetProperty(pdcb, &propi,
                                                             &cal.diph);
                        }
                    }
                }
            }

            Invoke_Release(&pdcb);
        }
    }

    ExitProc();
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | JoyReg_SetHWConfig |
 *
 *          Store information about a joystick's <t JOYREGHWCONFIG>.
 *
 *  @parm   UINT | uiJoy |
 *
 *          Joystick identification number.
 *
 *  @parm   LPJOYREGHWCONFIG | phwc |
 *
 *          Contains information about the joystick capabilities.
 *          This value supercedes the value in the <p pcfg>.
 *
 *  @parm   LPCDIJOYCONFIG | pcfg |
 *
 *          Contains information about the joystick configuration.
 *          The caller is assumed to have validated all fields.
 *
 *  @parm   HKEY | hk |
 *
 *          The type key we are munging.
 *
 *
 *  @returns
 *
 *          Returns a COM error code.  The following error codes are
 *          intended to be illustrative and not necessarily comprehensive.
 *
 *          <c DI_OK> = <c S_OK>: The operation completed successfully.
 *
 *****************************************************************************/

HRESULT INTERNAL
    JoyReg_SetHWConfig(UINT idJoy, LPJOYREGHWCONFIG phwc, LPCDIJOYCONFIG pcfg,
                       HKEY hk)
{
    HRESULT hres;
    HKEY hkSave;
    DWORD dwSam;

    /*
     *  The caller has set phwc->dwType, so use it to determine
     *  where the data comes from or goes to.
     */
    if( phwc->dwType == JOY_HW_NONE )
    {
        /*
         *  Nothing to do
         */
    } else if( phwc->dwType == JOY_HW_CUSTOM )
    {
        /*  ISSUE-2001/03/29-timgill Custom HWConfig not handled correctly
         *  We don't know the type name and the only time we can look
         *  it up is when were modifying an existing config so although we
         *  could store the config, we'd never be able to get it back.
         *  Should return no better than S_FALSE.  This will have to wait.
         */
    } else
    {
        /*
         *  Try to access saved values
         */

        // ISSUE-2001/03/29-timgill Dangerous type cast
        PDWORD pdw = (PDWORD)&phwc->hwv;

        dwSam = KEY_QUERY_VALUE;

        while( pdw < &phwc->dwType )
        {
            if( *pdw )
            {
                /*
                 *  Real config data so write it
                 */
                dwSam = KEY_SET_VALUE;
                break;
            }
            pdw++;
        }

        /*
         *  If the device is autoloaded and yet the user is manually assigning it
         *  to an ID, set the volatile flag.  The flag will be set to the driver
         *  defined value if a driver ever gets hotplug assigned to this ID but if
         *  not, this makes sure that the settings are removed on next reboot.
         */
        if(phwc->hws.dwFlags & JOY_HWS_AUTOLOAD)
        {
            phwc->dwUsageSettings |= JOY_US_VOLATILE;
        }

        hres = JoyReg_OpenSaveKey( phwc->dwType, pcfg, dwSam, &hkSave );

        if( SUCCEEDED(hres) )
        {
            if( dwSam == KEY_SET_VALUE )
            {
                hres = JoyReg_SetConfigValue(hkSave, REGSTR_VAL_JOYNCONFIG,
                                             idJoy, REG_BINARY,
                                             phwc, cbX(*phwc));
                if( FAILED(hres) )
                {
                    // Report the error but live with it
                    RPF("JoyReg_SetConfig: failed to set saved config %08x", hres );
                }
            } else
            {
                JOYREGHWCONFIG hwc;

                /*
                 *  Read it into an extra buffer because we only want it
                 *  if it's complete.
                 */
                hres = JoyReg_GetConfigValue(hkSave, REGSTR_VAL_JOYNCONFIG,
                                             idJoy, REG_BINARY,
                                             &hwc, cbX(hwc));
                if( hres == S_OK )
                {
                    // Assert hws is first and no gap before dwUsageSettings
                    CAssertF( FIELD_OFFSET( JOYREGHWCONFIG, hws ) == 0 );
                    CAssertF( FIELD_OFFSET( JOYREGHWCONFIG, dwUsageSettings ) == sizeof( hwc.hws ) );

                    // Copy the whole structure except the hws
                    memcpy( &phwc->dwUsageSettings, &hwc.dwUsageSettings, 
                        sizeof( hwc ) - sizeof( hwc.hws ) );
                }
            }

            RegCloseKey( hkSave );
        }
        /*
         *  If we failed to read, there's probably nothing there and the
         *  structure is set up already for a blank config.
         *  If we failed to write there probably not much we can do
         */
    }


    hres = JoyReg_SetConfigValue(hk, REGSTR_VAL_JOYNCONFIG,
                                 idJoy, REG_BINARY,
                                 phwc, cbX(*phwc));
    if(FAILED(hres))
    {
        goto done;
    }

    if(phwc->dwUsageSettings & JOY_US_ISOEM)
    {

        hres = JoyReg_SetConfigValue(
                                    hk, REGSTR_VAL_JOYNOEMNAME, idJoy, REG_SZ,
                                    pcfg->wszType, cbX(pcfg->wszType));

    } else
    {
        hres = JoyReg_SetConfigValue(
                                    hk, REGSTR_VAL_JOYNOEMNAME, idJoy, REG_SZ,
                                    0, 0);
    }

    done:;

    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | JoyReg_SetConfig |
 *
 *          Store information about a joystick's configuration.
 *
 *  @parm   UINT | uiJoy |
 *
 *          Joystick identification number.
 *
 *  @parm   JOYREGHWCONFIG | phwc |
 *
 *          Contains information about the joystick capabilities.
 *          This value supercedes the value in the <p pcfg>.
 *          It may be modified if we needed to load the config
 *          info from the saved settings.
 *
 *  @parm   LPCDIJOYCONFIG | pcfg |
 *
 *          Contains information about the joystick configuration.
 *          The caller is assumed to have validated all fields.
 *
 *  @parm   DWORD | fl |
 *
 *          Zero or more <c DIJC_*> flags
 *          which specify which parts of the structures pointed
 *          to by <p phwc> and <p pjc> are to be written out.
 *
 *  @returns
 *
 *          Returns a COM error code.  The following error codes are
 *          intended to be illustrative and not necessarily comprehensive.
 *
 *          <c DI_OK> = <c S_OK>: The operation completed successfully.
 *
 *****************************************************************************/

JOYREGHWVALUES      null_hwv = { 0};

STDMETHODIMP
    JoyReg_SetConfig(UINT idJoy, LPJOYREGHWCONFIG phwc,
                     LPCDIJOYCONFIG pcfg, DWORD fl)
{
    HRESULT hres;
    EnterProc(JoyReg_SetConfig, (_ "uppx", idJoy, phwc, pcfg, fl));

    AssertF((fl & ~DIJC_INTERNALSETVALID) == 0);

    if(idJoy < cJoyMax )
    {

        if(fl & DIJC_INREGISTRY)
        {
            HKEY hk;
            DWORD dwOptions = 0;

            hres = JoyReg_OpenConfigKey(idJoy, KEY_SET_VALUE, NULL, dwOptions, &hk);

            if(SUCCEEDED(hres))
            {

                if(fl & DIJC_REGHWCONFIGTYPE)
                {
                    hres = JoyReg_SetHWConfig(idJoy, phwc, pcfg, hk);

                    if(FAILED(hres))
                    {
                        goto closedone;
                    }

                    if(fl & DIJC_UPDATEALIAS)
                    {
                        JoyReg_SetCalibration(idJoy, phwc);
                    }

                }

                if(fl & DIJC_CALLOUT)
                {
                    hres = JoyReg_SetConfigValue(
                                                hk, REGSTR_VAL_JOYNOEMCALLOUT, idJoy, REG_SZ,
                                                pcfg->wszCallout, cbX(pcfg->wszCallout));
                    if(FAILED(hres))
                    {
                        hres = S_FALSE;
                        //continue to go
                    }
                }

                if(fl & DIJC_GAIN)
                {
                    if(ISVALIDGAIN(pcfg->dwGain))
                    {

                        /*
                         *  If restoring to nominal, then the key
                         *  can be deleted; the default value will
                         *  be assumed subsequently.
                         */
                        if(pcfg->dwGain == DI_FFNOMINALMAX)
                        {
                            hres = JoyReg_SetConfigValue(hk,
                                                         TEXT("Joystick%dFFConfiguration"),
                                                         idJoy, REG_SZ, 0, 0);
                        } else
                        {
                            hres = JoyReg_SetConfigValue(hk,
                                                         TEXT("Joystick%dFFConfiguration"),
                                                         idJoy, REG_BINARY,
                                                         &pcfg->dwGain, cbX(pcfg->dwGain));
                        }

                        if(FAILED(hres))
                        {
                            hres = S_FALSE;
                            goto closedone;
                        }
                    } else
                    {
                        RPF("ERROR: SetConfig: Invalid dwGain");
                        hres = E_INVALIDARG;
                        goto closedone;
                    }
                }

                hres = S_OK;

                closedone:;
                RegCloseKey(hk);
            }
        } else
        {
            hres = S_OK;
        }

    } else
    {
        hres = E_FAIL;
    }

    ExitOleProc();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   int | ibJoyPosAxis |
 *
 *          Returns the offset of the <p iAxis>'th joystick axis
 *          in the <t JOYPOS> structure.
 *
 *  @parm   int | iAxis |
 *
 *          The index of the requested axis.  X, Y, Z, R, U and V are
 *          respctively zero through five.
 *
 *  @returns
 *
 *          The offset relative to the structure.
 *
 *****************************************************************************/

#define ibJoyPosAxis(iAxis)                                         \
        (FIELD_OFFSET(JOYPOS, dwX) + cbX(DWORD) * (iAxis))          \

#define pJoyValue(jp, i)                                            \
        (LPDWORD)pvAddPvCb(&(jp), ibJoyPosAxis(i))                  \

/*
 *  The following doesn't do anything at runtime.  It is a compile-time
 *  check that everything is okay.
 */
void INLINE
    JoyReg_CheckJoyPosAxis(void)
{
#define CheckAxis(x)    \
        CAssertF(ibJoyPosAxis(iJoyPosAxis##x) == FIELD_OFFSET(JOYPOS, dw##x))

    CheckAxis(X);
    CheckAxis(Y);
    CheckAxis(Z);
    CheckAxis(R);
    CheckAxis(U);
    CheckAxis(V);

#undef CheckAxis
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | JoyReg_IsValidUserValues |
 *
 *          Retermine whether the values are ostensibly valid.
 *
 *  @parm   IN LPCDIJOYUSERVALUES | pjuv |
 *
 *          Contains information about the user joystick configuration.
 *
 *  @returns
 *
 *          Returns a COM error code.  The following error codes are
 *          intended to be illustrative and not necessarily comprehensive.
 *
 *          <c DI_OK> = <c S_OK>: The operation completed successfully.
 *
 *          <c DIERR_INVALIDPARAM> = <c E_INVALIDARG>:
 *          Something looks bad.
 *
 *****************************************************************************/

STDMETHODIMP
    JoyReg_IsValidUserValues(LPCDIJOYUSERVALUES pjuv)
{
    HRESULT hres;
    int iAxis;

    /*
     *  First set up the values to values that are out of range so
     *  that we will fall back to defaults.
     */
    for(iAxis = 0; iAxis < cJoyPosAxisMax; iAxis++)
    {
        if((int)*pJoyValue(pjuv->ruv.jrvRanges.jpMax, iAxis) < 0)
        {
            RPF("JOYUSERVALUES: Negative jpMax not a good idea");
            goto bad;
        }
        if(*pJoyValue(pjuv->ruv.jrvRanges.jpMin, iAxis) >
           *pJoyValue(pjuv->ruv.jrvRanges.jpMax, iAxis))
        {
            RPF("JOYUSERVALUES: Min > Max not a good idea");
            goto bad;
        }

        if(!fInOrder(0, *pJoyValue(pjuv->ruv.jpDeadZone, iAxis), 100))
        {
            RPF("JOYUSERVALUES: DeadZone > 100 not a good idea");
            goto bad;
        }
    }

    hres = S_OK;

    return hres;

    bad:;
    hres = E_INVALIDARG;
    return hres;

}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | JoyReg_GetUserValues |
 *
 *          Obtain information about user settings for the joystick.
 *
 *
 *  @parm   IN OUT LPDIJOYUSERVALUES | pjuv |
 *
 *          Receives information about the user joystick configuration.
 *          The caller is assumed to have validated the
 *          <e DIJOYUSERVALUES.dwSize> field.
 *
 *  @parm   DWORD | fl |
 *
 *          Zero or more <c DIJU_*> flags specifying which parts
 *          of the <t DIJOYUSERVALUES> structure contain values
 *          which are to be retrieved.
 *
 *  @returns
 *
 *          Returns a COM error code.  The following error codes are
 *          intended to be illustrative and not necessarily comprehensive.
 *
 *          <c DI_OK> = <c S_OK>: The operation completed successfully.
 *
 *          <c DIERR_INVALIDPARAM> = <c E_INVALIDARG>: One or more
 *          parameters was invalid.
 *
 *****************************************************************************/

STDMETHODIMP
    JoyReg_GetUserValues(LPDIJOYUSERVALUES pjuv, DWORD fl)
{
    HRESULT hres;
    HKEY hk;
    LONG lRc;
    EnterProc(JoyReg_GetUserValues, (_ "px", pjuv, fl));

    hres = S_OK;                    /* If nothing happens, then success */

    if(fl & DIJU_USERVALUES)
    {

        /*
         *  Okay, now get the user settings.
         *
         *  If anything goes wrong, then just limp with the default values.
         */
        lRc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGSTR_PATH_JOYCONFIG,
                           0, KEY_QUERY_VALUE, &hk);
        if(lRc == ERROR_SUCCESS)
        {

            hres = JoyReg_GetValue(hk, REGSTR_VAL_JOYUSERVALUES,
                                   REG_BINARY, &pjuv->ruv, cbX(pjuv->ruv));
            if(SUCCEEDED(hres))
            {
                /*
                 *  Sanity-check the values.  If anything is screwy,
                 *  then fall back to the defaults.
                 */
                hres = JoyReg_IsValidUserValues(pjuv);

            }

            if(FAILED(hres))
            {
                /*
                 *  Oh well.  Just use the default values, then.
                 *
                 *  Stolen from ibmjoy\msjstick.c.
                 */
                ZeroMemory(&pjuv->ruv, cbX(pjuv->ruv));

#define DEFAULT_RANGE_MAX 65535
#define DEFAULT_TIMEOUT   5000
#define DEFAULT_DEADZONE  5

                pjuv->ruv.jrvRanges.jpMax.dwX = DEFAULT_RANGE_MAX;
                pjuv->ruv.jrvRanges.jpMax.dwY = DEFAULT_RANGE_MAX;
                pjuv->ruv.jrvRanges.jpMax.dwZ = DEFAULT_RANGE_MAX;
                pjuv->ruv.jrvRanges.jpMax.dwR = DEFAULT_RANGE_MAX;
                pjuv->ruv.jrvRanges.jpMax.dwU = DEFAULT_RANGE_MAX;
                pjuv->ruv.jrvRanges.jpMax.dwV = DEFAULT_RANGE_MAX;
                pjuv->ruv.jpDeadZone.dwX = DEFAULT_DEADZONE;
                pjuv->ruv.jpDeadZone.dwY = DEFAULT_DEADZONE;
                pjuv->ruv.dwTimeOut = DEFAULT_TIMEOUT;
            }

            RegCloseKey(hk);
        }
    }

    if(fl & DIJU_INDRIVERREGISTRY)
    {
        hres = JoyReg_OpenConfigKey((UINT)-1, KEY_QUERY_VALUE, NULL, FALSE, &hk);

        if(SUCCEEDED(hres))
        {

            if(fl & DIJU_GLOBALDRIVER)
            {

                LONG lRc;

                /*
                 *  If it doesn't work, then return the default value
                 *  of "MSANALOG.VXD".  We can't blindly use
                 *  JoyReg_GetValue, because that treats a nonexistent
                 *  value as having a default of the null string.
                 */
                lRc = RegQueryValueEx(hk, REGSTR_VAL_JOYOEMCALLOUT,
                                      0, 0, 0, 0);
                if((lRc == ERROR_SUCCESS || lRc == ERROR_MORE_DATA) &&
                   SUCCEEDED(
                            hres = JoyReg_GetValue(hk, REGSTR_VAL_JOYOEMCALLOUT,
                                                   REG_SZ, pjuv->wszGlobalDriver,
                                                   cbX(pjuv->wszGlobalDriver))))
                {
                    /* Yay, it worked */
                } else
                {
                    CopyMemory(pjuv->wszGlobalDriver,
                               c_wszDefPortDriver,
                               cbX(c_wszDefPortDriver));
                }

            }

            if(fl & DIJU_GAMEPORTEMULATOR)
            {

                /*
                 *  If it doesn't work, then just return a null string.
                 */
                hres = JoyReg_GetValue(hk, REGSTR_VAL_JOYGAMEPORTEMULATOR,
                                       REG_SZ, pjuv->wszGameportEmulator,
                                       cbX(pjuv->wszGameportEmulator));
                if(FAILED(hres))
                {
                    pjuv->wszGameportEmulator[0] = TEXT('\0');
                }

            }

            RegCloseKey(hk);
        }

    }

    /*
     *  Warning!  CJoy_InitRanges() assumes this never fails.
     */
    hres = S_OK;

    ExitOleProcR();
    return hres;
}


/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | JoyReg_SetUserValues |
 *
 *          Store information about user settings for the joystick.
 *
 *
 *  @parm   IN LPCDIJOYUSERVALUES | pjuv |
 *
 *          Contains information about the user joystick configuration.
 *          The caller is assumed to have validated the
 *          <e DIJOYUSERVALUES.dwSize> field.
 *
 *  @parm   DWORD | fl |
 *
 *          Zero or more <c DIJU_*> flags specifying which parts
 *          of the <t DIJOYUSERVALUES> structure contain values
 *          which are to be set.
 *
 *  @returns
 *
 *          Returns a COM error code.  The following error codes are
 *          intended to be illustrative and not necessarily comprehensive.
 *
 *          <c DI_OK> = <c S_OK>: The operation completed successfully.
 *
 *          <c DIERR_INVALIDPARAM> = <c E_INVALIDARG>: One or more
 *          parameters was invalid.
 *
 *****************************************************************************/

STDMETHODIMP
    JoyReg_SetUserValues(LPCDIJOYUSERVALUES pjuv, DWORD fl)
{
    HRESULT hres = E_FAIL;
    HKEY hk;
    EnterProc(JoyReg_SetUserValues, (_ "px", pjuv, fl));

    if(fl & DIJU_USERVALUES)
    {

        /*
         *  See if the values are sane.
         */
        if(fl & DIJU_USERVALUES)
        {
            hres = JoyReg_IsValidUserValues(pjuv);
            if(FAILED(hres))
            {
                goto done;
            }
        }

        /*
         *  Off to the registry we go.
         */

        hres = hresMumbleKeyEx(HKEY_LOCAL_MACHINE, 
                               REGSTR_PATH_JOYCONFIG, 
                               DI_KEY_ALL_ACCESS, 
                               REG_OPTION_NON_VOLATILE, 
                               &hk);

        if(SUCCEEDED(hres))
        {

            hres = JoyReg_SetValue(hk, REGSTR_VAL_JOYUSERVALUES,
                                   REG_BINARY, &pjuv->ruv,
                                   cbX(pjuv->ruv));
            RegCloseKey(hk);

            if(FAILED(hres))
            {
                goto done;
            }
        } else
        {
            goto done;
        }
    }

    if(fl & DIJU_INDRIVERREGISTRY)
    {

        hres = JoyReg_OpenConfigKey((UINT)-1, KEY_SET_VALUE, NULL, FALSE, &hk);

        if(SUCCEEDED(hres))
        {

            if(fl & DIJU_GLOBALDRIVER)
            {
                /*
                 *  This is a weird key.  The default value is
                 *  "MSANALOG.VXD", so if we get a null string, we
                 *  can't use JoyReg_SetValue, because that will
                 *  delete the key.
                 */
                if(pjuv->wszGlobalDriver[0])
                {
                    hres = JoyReg_SetValue(hk, REGSTR_VAL_JOYOEMCALLOUT,
                                           REG_SZ, pjuv->wszGlobalDriver,
                                           cbX(pjuv->wszGlobalDriver));
                } else
                {
                    LONG lRc;
                    lRc = RegSetValueEx(hk, REGSTR_VAL_JOYOEMCALLOUT, 0,
                                        REG_SZ, (PV)TEXT(""), cbCtch(1));
                    if(lRc == ERROR_SUCCESS)
                    {
                        hres = S_OK;
                    } else
                    {
                        RPF("Unable to write %s to registry",
                            REGSTR_VAL_JOYOEMCALLOUT);
                        hres = E_FAIL;  /* Else, something bad happened */
                    }
                }
                if(FAILED(hres))
                {
                    goto regdone;
                }
            }

            if(fl & DIJU_GAMEPORTEMULATOR)
            {

                hres = JoyReg_SetValue(hk, REGSTR_VAL_JOYGAMEPORTEMULATOR,
                                       REG_SZ, pjuv->wszGameportEmulator,
                                       cbX(pjuv->wszGameportEmulator));
                if(FAILED(hres))
                {
                    goto regdone;
                }
            }

            regdone:;
            RegCloseKey(hk);

        } else
        {
            goto done;
        }
    }

    done:;
    ExitOleProcR();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | JoyReg_OpenFFKey |
 *
 *          Given a type key, move to its force feedback subkey.
 *
 *  @parm   HKEY | hkType |
 *
 *          The parent type key.
 *
 *  @parm   REGSAM | sam |
 *
 *          Access level desired.
 *
 *  @parm   PHKEY | phk |
 *
 *          Receives created registry key.
 *
 *  @returns
 *
 *          Returns a COM error code.  The following error codes are
 *          intended to be illustrative and not necessarily comprehensive.
 *
 *          <c DI_OK> = <c S_OK>: The operation completed successfully.
 *
 *          <c DIERR_NOTFOUND>: Couldn't open the key.
 *
 *****************************************************************************/

STDMETHODIMP
    JoyReg_OpenFFKey(HKEY hkType, REGSAM sam, PHKEY phk)
{
    HRESULT hres;
    EnterProc(JoyReg_OpenFFKey, (_ "xx", hkType, sam));

    *phk = 0;

    if(hkType)
    {
        if(RegOpenKeyEx(hkType, TEXT("OEMForceFeedback"), 0, sam, phk) == 0)
        {
            hres = S_OK;
        } else
        {
            hres = E_FAIL;
        }
    } else
    {
        hres = DIERR_NOTFOUND;
    }

    ExitBenignOleProc();
    return hres;
}



/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   TCHAR | CJoyCfg_CharFromType |
 *
 *          Convert a predefined type number to a character.
 *
 *  @func   UINT | CJoyCfg_TypeFromChar |
 *
 *          Convert a character back to a predefined type number.
 *
 *****************************************************************************/

#define JoyCfg_CharFromType(t)     ((TCHAR)(L'0' + t))
#define JoyCfg_TypeFromChar(tch)   ((tch) - L'0')

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @func   HRESULT | JoyReg_GetPredefTypeInfo |
 *
 *          Obtain information about a predefined joystick type.
 *
 *  @parm   LPCWSTR | pwszType |
 *
 *          Points to the name of the type.  It is known to begin
 *          with a "#".  The remainder has not yet been parsed.
 *
 *  @parm   IN OUT LPDIJOYTYPEINFO | pjti |
 *
 *          Receives information about the joystick type,
 *          already validated.
 *
 *  @parm   DWORD | dwFlags |
 *
 *          Zero or more <c DITC_*> flags
 *          which specify which parts of the structure pointed
 *          to by <p pjti> are to be filled in.
 *
 *  @returns
 *
 *          Returns a COM error code.  The following error codes are
 *          intended to be illustrative and not necessarily comprehensive.
 *
 *          <c DI_OK> = <c S_OK>: The operation completed successfully.
 *
 *          <c DIERR_NOTFOUND>: The joystick type was not found.
 *
 *****************************************************************************/

HRESULT EXTERNAL
    JoyReg_GetPredefTypeInfo(LPCWSTR pwszType, LPDIJOYTYPEINFO pjti, DWORD fl)
{
    HRESULT hres;
    UINT itype;
    EnterProcI(JoyReg_GetPredefTypeInfo, (_ "Wpx", pwszType, pjti, fl));

    //(MarcAnd) These TEXT('blah') things should be L'blah' as the string is always wide
    AssertF(pwszType[0] == TEXT('#'));

    itype = JoyCfg_TypeFromChar(pwszType[1]);

    if(fInOrder(JOY_HW_PREDEFMIN, itype, JOY_HW_PREDEFMAX) &&
       pwszType[2] == TEXT('\0'))
    {
        /*
         *  No real point in checking the bits in fl, since
         *  setting it up is so easy.
         */
        pjti->hws = c_rghwsPredef[itype - JOY_HW_PREDEFMIN];
        LoadStringW(g_hinst, IDS_PREDEFJOYTYPE + itype,
                    pjti->wszDisplayName, cA(pjti->wszDisplayName));
        pjti->wszCallout[0] = TEXT('\0');
        
        ZeroX(pjti->clsidConfig);

        if(fl & DITC_FLAGS1 )
        {
            pjti->dwFlags1 = 0x0;
        }

        if( fl & DITC_HARDWAREID )
        {
            lstrcpyW(pjti->wszHardwareId, c_rghwIdPredef[itype-JOY_HW_PREDEFMIN] );
        }

        hres = S_OK;
    } else
    {
        hres = DIERR_NOTFOUND;
    }

    ExitOleProc();
    return hres;
}


#if 0  //don't delete it now.
/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | JoyCfg_GetIDByOemName |
 *
 *          Get the Id by OEMNAME
 *
 *  @parm   IN LPTSTR | szOEMNAME |
 *
 *          String used to find the ID.
 *
 *  @parm   IN LPUNIT | lpID |
 *
 *          The ID to get.
 *
 *  @returns
 *
 *          A COM success code unless the current configuration key could not
 *          be opened, or could not find the OEMNAME.
 *
 *****************************************************************************/

HRESULT EXTERNAL JoyReg_GetIDByOemName( LPTSTR szOemName, PUINT pId )
{
    HRESULT hres = E_FAIL;
    LONG    lRc;
    HKEY    hkCurrCfg;
    UINT    JoyId;
    TCHAR   szTestName[MAX_JOYSTRING];
    TCHAR   szOemNameKey[MAX_JOYSTRING];
    DWORD   cb;

    EnterProcI(JoyReg_GetIDByOemName, (_ "sp", szOemName, pId ));

    hres = JoyReg_OpenConfigKey( (UINT)(-1), KEY_WRITE, NULL, REG_OPTION_NON_VOLATILE, &hkCurrCfg );

    if( SUCCEEDED( hres ) )
    {
        for( JoyId = 0; (JoyId < 16) || ( lRc == ERROR_SUCCESS ); JoyId++ )
        {
            wsprintf( szOemNameKey, REGSTR_VAL_JOYNOEMNAME, JoyId+1 );
            cb = sizeof( szTestName );
            lRc = RegQueryValueEx( hkCurrCfg, szOemNameKey, 0, NULL, (PBYTE)szTestName, &cb );
            if( lRc == ERROR_SUCCESS )
            {
                if( !lstrcmpi( szOemName, szTestName ) )
                {
                    *pId = JoyId;
                    pId ++;
                    hres = S_OK;
                    break;
                }
            }
        }

    }
    else
    {
        SquirtSqflPtszV(sqfl | sqflError,
            TEXT("JoyReg_OpenConfigKey failed code 0x%08x"), hres );
    }

    ExitOleProc();

    return hres;

} /* JoyReg_GetIDByOemName */
#endif

