/*****************************************************************************
*
*  DIJoyCfg.c
*
*  Copyright (c) 1996 Microsoft Corporation.  All Rights Reserved.
*
*  Abstract:
*
*      IDirectInputJoyConfig
*
*  Contents:
*
*      CJoyCfg_New
*
*****************************************************************************/

#include "dinputpr.h"

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

#define sqfl sqflJoyCfg

#if DIRECTINPUT_VERSION > 0x0300

BOOL fVjoydDeviceNotExist = TRUE;

    #pragma BEGIN_CONST_DATA

/*****************************************************************************
 *
 *      Declare the interfaces we will be providing.
 *
 *      WARNING!  If you add a secondary interface, you must also change
 *      CJoyCfg_New!
 *
 *****************************************************************************/

Primary_Interface(CJoyCfg, IDirectInputJoyConfig);

Interface_Template_Begin(CJoyCfg)
Primary_Interface_Template(CJoyCfg, IDirectInputJoyConfig)
Interface_Template_End(CJoyCfg)

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @struct CJoyCfg |
 *
 *          The <i IDirectInputJoyConfig> object.  Note that this is
 *          aggregated onto the main <i IDirectInput> object.
 *
 *  @field  IDirectInputJoyConfig | djc |
 *
 *          The object (containing vtbl).
 *
 *  @field  BOOL | fAcquired:1 |
 *
 *          Set if joystick configuration has been acquired.
 *
 *  @field  BOOL | fCritInited:1 |
 *
 *          Set if the critical section has been initialized.
 *
 *  @field  HKEY | hkTypesW |
 *
 *          Read/write key to access the joystick types.
 *          This key is created only while acquired.
 *
 *  @field  DWORD | idJoyCache |
 *
 *          The identifier of the joystick in the effect shepherd cache,
 *          if there is anything in the cache at all.
 *
 *  @field  IDirectInputEffectShepherd * | pes |
 *
 *          The cached effect shepherd itself.
 *
 *  @field  LONG | cCrit |
 *
 *          Number of times the critical section has been taken.
 *          Used only in XDEBUG to check whether the caller is
 *          releasing the object while another method is using it.
 *
 *  @field  DWORD | thidCrit |
 *
 *          The thread that is currently in the critical section.
 *          Used only in DEBUG for internal consistency checking.
 *
 *  @field  CRITICAL_SECTION | crst |
 *
 *          Object critical section.  Must be taken when accessing
 *          volatile member variables.
 *
 *****************************************************************************/

typedef struct CJoyCfg
{

    /* Supported interfaces */
    IDirectInputJoyConfig djc;

    BOOL fAcquired:1;
    BOOL fCritInited:1;

    HKEY hkTypesW;
    HWND hwnd;

    DWORD discl;

    DWORD idJoyCache;
    LPDIRECTINPUTEFFECTSHEPHERD pes;

    RD(LONG cCrit;)
    D(DWORD thidCrit;)
    CRITICAL_SECTION crst;

} CJoyCfg, JC, *PJC;

typedef LPDIRECTINPUTJOYCONFIG PDJC;


    #define ThisClass CJoyCfg
    #define ThisInterface  IDirectInputJoyConfig
    #define ThisInterfaceT IDirectInputJoyConfig

/*****************************************************************************
 *
 *      Forward references
 *
 *      Not really needed; just a convenience, because Finalize
 *      calls Unacquire to clean up in the case where the caller forgot.
 *
 *****************************************************************************/

STDMETHODIMP CJoyCfg_InternalUnacquire(PV pdd);

/*****************************************************************************
 *
 *  @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 CJoyCfg_CharFromType(t)     ((TCHAR)(L'0' + t))
    #define CJoyCfg_TypeFromChar(tch)   ((tch) - L'0')

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method void | IDirectInputJoyConfig | EnterCrit |
 *
 *          Enter the object critical section.
 *
 *  @cwrap  LPDIRECTINPUTJOYCONFIG | lpDirectInputJoyConfig
 *
 *****************************************************************************/

void INLINE
    CJoyCfg_EnterCrit(PJC this)
{
    EnterCriticalSection(&this->crst);
    D(this->thidCrit = GetCurrentThreadId());
    RD(InterlockedIncrement(&this->cCrit));
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method void | IDirectInputJoyConfig | LeaveCrit |
 *
 *          Leave the object critical section.
 *
 *  @cwrap  LPDIRECTINPUTJOYCONFIG | lpDirectInputJoyConfig
 *
 *****************************************************************************/

void INLINE
    CJoyCfg_LeaveCrit(PJC this)
{
    #ifdef XDEBUG
    AssertF(this->cCrit);
    AssertF(this->thidCrit == GetCurrentThreadId());
    if(InterlockedDecrement(&this->cCrit) == 0)
    {
        D(this->thidCrit = 0);
    }
    #endif
    LeaveCriticalSection(&this->crst);
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @mfunc  BOOL | CJoyCfg | InCrit |
 *
 *          Nonzero if we are in the critical section.
 *
 *  @cwrap  LPDIRECTINPUTJOYCONFIG | lpDirectInputJoyConfig
 *
 *****************************************************************************/

    #ifdef DEBUG

BOOL INTERNAL
    CJoyCfg_InCrit(PJC this)
{
    return this->cCrit && this->thidCrit == GetCurrentThreadId();
}

    #endif

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CJoyCfg | IsAcquired |
 *
 *          Check that the device is acquired.
 *
 *  @cwrap  LPDIRECTINPUTJOYCONFIG | lpDirectInputJoyConfig
 *
 *  @returns
 *
 *          Returns
 *          <c S_OK> if all is well, or <c DIERR_NOTACQUIRED> if
 *          the device is not acquired.
 *
 *
 *****************************************************************************/

    #ifndef XDEBUG
\
        #define CJoyCfg_IsAcquired_(pdd, z)                                 \
       _CJoyCfg_IsAcquired_(pdd)                                    \

    #endif

    HRESULT INLINE
    CJoyCfg_IsAcquired_(PJC this, LPCSTR s_szProc)
{
    HRESULT hres;

    if(this->fAcquired)
    {
        hres = S_OK;
    } else
    {
        RPF("ERROR %s: Not acquired", s_szProc);
        hres = DIERR_NOTACQUIRED;
    }
    return hres;
}

    #define CJoyCfg_IsAcquired(pdd)                                     \
        CJoyCfg_IsAcquired_(pdd, s_szProc)                          \


/*****************************************************************************
 *
 *      CJoyCfg::QueryInterface   (from IUnknown)
 *      CJoyCfg::AddRef           (from IUnknown)
 *      CJoyCfg::Release          (from IUnknown)
 *
 *****************************************************************************/

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CJoyCfg | QueryInterface |
 *
 *          Gives a client access to other interfaces on an object.
 *
 *  @parm   IN REFIID | riid |
 *
 *          The requested interface's IID.
 *
 *  @parm   OUT LPVOID * | ppvObj |
 *
 *          Receives a pointer to the obtained interface.
 *
 *  @returns
 *
 *          Returns a COM error code.
 *
 *  @xref   OLE documentation for <mf IUnknown::QueryInterface>.
 *
 *****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CJoyCfg | AddRef |
 *
 *          Increments the reference count for the interface.
 *
 *  @returns
 *
 *          Returns the object reference count.
 *
 *  @xref   OLE documentation for <mf IUnknown::AddRef>.
 *
 *****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CJoyCfg | Release |
 *
 *          Decrements the reference count for the interface.
 *          If the reference count on the object falls to zero,
 *          the object is freed from memory.
 *
 *  @returns
 *
 *      Returns the object reference count.
 *
 *  @xref   OLE documentation for <mf IUnknown::Release>.
 *
 *****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CJoyCfg | QIHelper |
 *
 *      We don't have any dynamic interfaces and simply forward
 *      to <f Common_QIHelper>.
 *
 *  @parm   IN REFIID | riid |
 *
 *      The requested interface's IID.
 *
 *  @parm   OUT LPVOID * | ppvObj |
 *
 *      Receives a pointer to the obtained interface.
 *
 *****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CJoyCfg | AppFinalize |
 *
 *          We don't have any weak pointers, so we can just
 *          forward to <f Common_Finalize>.
 *
 *  @parm   PV | pvObj |
 *
 *          Object being released from the application's perspective.
 *
 *****************************************************************************/

    #ifdef DEBUG

Default_QueryInterface(CJoyCfg)
Default_AddRef(CJoyCfg)
Default_Release(CJoyCfg)

    #else

        #define CJoyCfg_QueryInterface   Common_QueryInterface
        #define CJoyCfg_AddRef           Common_AddRef
        #define CJoyCfg_Release          Common_Release

    #endif

    #define CJoyCfg_QIHelper         Common_QIHelper
    #define CJoyCfg_AppFinalize      Common_AppFinalize

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CJoyCfg | InternalUnacquire |
 *
 *          Do the real work of an unacquire.
 *
 *          See <mf IDirectInputJoyConfig::Unacquire> for more
 *          information.
 *
 *  @cwrap  LPDIRECTINPUTJOYCONFIG | lpDirectInputJoyConfig
 *
 *  @returns
 *
 *          Returns a COM error code.
 *          See <mf IDirectInputJoyConfig::Unacquire> for more
 *          information.
 *
 *****************************************************************************/

STDMETHODIMP
    CJoyCfg_InternalUnacquire(PJC this)
{
    HRESULT hres;
    EnterProc(CJoyCfg_InternalUnacquire, (_ "p", this));

    /*
     *  Must protect with the critical section to prevent somebody from
     *  interfering with us while we're unacquiring.
     */
    CJoyCfg_EnterCrit(this);

    if(this->fAcquired)
    {

        AssertF(this->hkTypesW);

        RegCloseKey(this->hkTypesW);

        this->hkTypesW = 0;

        Invoke_Release(&this->pes);

        Excl_Unacquire(&IID_IDirectInputJoyConfig, this->hwnd, this->discl);

        this->fAcquired = 0;
        hres = S_OK;
    } else
    {
        hres = S_FALSE;
    }

    CJoyCfg_LeaveCrit(this);

    ExitOleProc();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   void | CJoyCfg_Finalize |
 *
 *          Releases the resources of the device.
 *
 *  @parm   PV | pvObj |
 *
 *          Object being released.  Note that it may not have been
 *          completely initialized, so everything should be done
 *          carefully.
 *
 *****************************************************************************/

void INTERNAL
    CJoyCfg_Finalize(PV pvObj)
{
    PJC this = pvObj;

    #ifdef XDEBUG
    if(this->cCrit)
    {
        RPF("IDirectInputJoyConfig::Release: Another thread is using the object; crash soon!");
    }
    #endif

    if(this->fAcquired)
    {
        CJoyCfg_InternalUnacquire(this);
    }

    AssertF(this->pes == 0);

    if(this->hkTypesW)
    {
        RegCloseKey(this->hkTypesW);
    }

    if(this->fCritInited)
    {
        DeleteCriticalSection(&this->crst);
    }

}

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputJoyConfig | SetCooperativeLevel |
 *
 *          Establish the cooperativity level for the instance of
 *          the device.
 *
 *          The only cooperative levels supported for the
 *          <i IDirectInputJoyConfig> interface are
 *          <c DISCL_EXCLUSIVE> and <c DISCL_BACKGROUND>.
 *
 *  @cwrap  LPDIRECTINPUTJOYCONFIG | lpDirectInputJoyConfig
 *
 *  @parm   HWND | hwnd |
 *
 *          The window associated with the interface. This parameter
 *          must be non-NULL and must be a top-level window.
 *
 *          It is an error to destroy the window while it is still
 *          associated with an <i IDirectInputJoyConfig> interface.
 *
 *  @parm   DWORD | dwFlags |
 *
 *          Flags which describe the cooperativity level associated
 *          with the device.
 *
 *          The value must be
 *          <c DISCL_EXCLUSIVE> <vbar> <c DISCL_BACKGROUND>.
 *
 *  @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>:  The
 *          <p hwnd> parameter is not a valid pointer.
 *
 *****************************************************************************/

STDMETHODIMP
    CJoyCfg_SetCooperativeLevel(PDJC pdjc, HWND hwnd, DWORD dwFlags)
{
    HRESULT hres;
    EnterProcR(IDirectInputJoyConfig::SetCooperativityLevel,
               (_ "pxx", pdjc, hwnd, dwFlags));

    if(SUCCEEDED(hres = hresPv(pdjc)))
    {
        PJC this = _thisPvNm(pdjc, djc);

        if(dwFlags != (DISCL_EXCLUSIVE | DISCL_BACKGROUND))
        {
            RPF("%s: Cooperative level must be "
                "DISCL_EXCLUSIVE | DISCL_BACKGROUND", s_szProc);
            hres = E_NOTIMPL;
        } else if(GetWindowPid(hwnd) == GetCurrentProcessId())
        {
            this->hwnd = hwnd;
            this->discl = dwFlags;
            hres = S_OK;
        } else
        {
            RPF("ERROR %s: window must belong to current process", s_szProc);
            hres = E_HANDLE;
        }
    }

    ExitOleProcR();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputJoyConfig | Acquire |
 *
 *          Acquire "joystick configuration mode".  Only one application can
 *          be in joystick configuration mode at a time; subsequent
 *          applications will receive the error <c DIERR_OTHERAPPHASPRIO>.
 *
 *          After entering configuration mode, the application may
 *          make alterations to the global joystick configuration
 *          settings.  It is encouraged that the application
 *          re-check the existing settings before installing the new
 *          ones in case another application had changed the settings
 *          in the interim.
 *
 *  @cwrap  LPDIRECTINPUTJOYCONFIG | lpDirectInputJoyConfig
 *
 *  @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_OTHERAPPHASPRIO>: Another application is already
 *          in joystick configuration mode.
 *
 *          <c DIERR_INSUFFICIENTPRIVS>: The current user does not have
 *          the necessary permissions to alter the joystick configuration.
 *
 *          <c DIERR_DEVICECHANGE>: Another application has changed
 *          the global joystick configuration.  The interface needs
 *          to be re-initialized.
 *
 *****************************************************************************/

STDMETHODIMP
    CJoyCfg_Acquire(PDJC pdjc)
{
    HRESULT hres;
    EnterProcR(IDirectInputJoyConfig::Acquire, (_ "p", pdjc));

    if(SUCCEEDED(hres = hresPv(pdjc)))
    {
        PJC this = _thisPvNm(pdjc, djc);

        /*
         *  Must protect with the critical section to prevent somebody from
         *  acquiring or changing the data format while we're acquiring.
         */
        CJoyCfg_EnterCrit(this);

        if(this->discl == 0)
        {
            RPF("%s: Cooperative level not yet set", s_szProc);
            hres = E_FAIL;
            goto done;
        }

        if(this->fAcquired)
        {
            AssertF(this->hkTypesW);
            hres = S_FALSE;
        } else if(SUCCEEDED(hres = Excl_Acquire(&IID_IDirectInputJoyConfig,
                                                this->hwnd, this->discl)))
        {
            AssertF(this->hkTypesW == 0);


            hres = hresMumbleKeyEx(HKEY_LOCAL_MACHINE, 
                                   REGSTR_PATH_JOYOEM, 
                                   DI_KEY_ALL_ACCESS, 
                                   REG_OPTION_NON_VOLATILE, 
                                   &this->hkTypesW);

            if(SUCCEEDED(hres) )
            {
                this->fAcquired = 1;
            } else
            {
                RegCloseKey(this->hkTypesW);
                this->hkTypesW = 0;
                hres = DIERR_INSUFFICIENTPRIVS;
            }

        }

        done:;
        CJoyCfg_LeaveCrit(this);
    }

    ExitOleProcR();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputJoyConfig | Unacquire |
 *
 *          Unacquire "joystick configuration mode".  Before unacquiring
 *          configuration mode, the application must perform an
 *          <mf IDirectInputJoyConfig::SendNotify> to propagate
 *          the changes in the joystick configuration
 *          to all device drivers and applications.
 *
 *          Applications which hold interfaces to a joystick which is
 *          materially affected by a change in configuration will
 *          receive the <c DIERR_DEVICECHANGE> error code until the
 *          device is re-initialized.
 *
 *          Examples of material changes to configuration include
 *          altering the number of axes or the number of buttons.
 *          In comparison, changes to device calibration
 *          are handled internally by
 *          DirectInput and are transparent to the application.
 *
 *  @cwrap  LPDIRECTINPUTJOYCONFIG | lpDirectInputJoyConfig
 *
 *  @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_NOTACQUIRED>: Joystick configuration mode was
 *          not acquired.
 *
 *****************************************************************************/

STDMETHODIMP
    CJoyCfg_Unacquire(PDJC pdjc)
{
    HRESULT hres;
    EnterProcR(IDirectInputJoyConfig::Unacquire, (_ "p", pdjc));

    if(SUCCEEDED(hres = hresPv(pdjc)))
    {
        PJC this = _thisPvNm(pdjc, djc);

        hres = CJoyCfg_InternalUnacquire(this);

    }

    ExitOleProcR();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputJoyConfig | SendNotify |
 *
 *          Notifies device drivers and applications that changes to
 *          the device configuration have been made.  An application
 *          which changes device configurations must invoke this
 *          method after the changes have been made (and before
 *          unacquiring).
 *
 *  @cwrap  LPDIRECTINPUTJOYCONFIG | lpDirectInputJoyConfig
 *
 *  @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_NOTACQUIRED>: Joystick configuration mode was
 *          not acquired.
 *
 *****************************************************************************/

STDMETHODIMP
    CJoyCfg_SendNotify(PDJC pdjc)
{
    HRESULT hres;
    EnterProcR(IDirectInputJoyConfig::SendNotify, (_ "p", pdjc));

    if(SUCCEEDED(hres = hresPv(pdjc)))
    {
        PJC this = _thisPvNm(pdjc, djc);

        CJoyCfg_EnterCrit(this);

        if(this->fAcquired)
        {
          #ifdef WINNT
            Excl_SetConfigChangedTime( GetTickCount() );
            PostMessage(HWND_BROADCAST, g_wmJoyChanged, 0, 0L);   
          #else
            joyConfigChanged(0);
          #endif

            /*
             *  If we don't have a joyConfigChanged, it's probably just 
             *  because we're running on NT and don't need it.
             */
            hres = S_OK;
        } else
        {
            hres = DIERR_NOTACQUIRED;
        }

        CJoyCfg_LeaveCrit(this);
    }

    ExitOleProcR();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | JoyCfg_ConvertCurrentConfigs |
 *
 *          Converts any OEMType name matching the first input string and 
 *          replaces it with the other input string.
 *
 *  @parm   IN LPTSTR | szFindType |
 *
 *          String to match.
 *
 *  @parm   IN LPTSTR | szReplaceType |
 *
 *          String to replace any matches instances.
 *
 *  @returns
 *
 *          A COM success code unless the current configuration key could not
 *          be opened, or a type that needed to be replaced could not be 
 *          overwritten.
 *
 *****************************************************************************/

HRESULT JoyCfg_ConvertCurrentConfigs( LPTSTR szFindType, LPTSTR szReplaceType )
{
    HRESULT hres;
    LONG    lRc;
    HKEY    hkCurrCfg;
    UINT    JoyId;
    TCHAR   szTestType[MAX_JOYSTRING];
    TCHAR   szTypeName[MAX_JOYSTRING];
    DWORD   cb;

    EnterProcI(JoyCfg_ConvertCurrentConfigs, (_ "ss", szFindType, szReplaceType ));

    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( szTypeName, REGSTR_VAL_JOYNOEMNAME, JoyId+1 );
            cb = sizeof( szTestType );
            lRc = RegQueryValueEx( hkCurrCfg, szTypeName, 0, NULL, (PBYTE)szTestType, &cb );
            if( lRc == ERROR_SUCCESS )
            {
                if( !lstrcmpi( szTestType, szFindType ) )
                {
                    cb = sizeof( szReplaceType) * (1 + lstrlen( szReplaceType ));
                    lRc = RegSetValueEx( hkCurrCfg, szTypeName, 0, REG_SZ, (PBYTE)szReplaceType, cb );
                    if( lRc != ERROR_SUCCESS )
                    {
                        SquirtSqflPtszV(sqfl | sqflError,
                            TEXT("RegSetValueEx failed to replace type of %s 0x%08x"), 
                            szTypeName, lRc );
                        /* This is the only error that counts as an error in this loop */
                        hres = hresReg( lRc );
                    }
                }
            }
        }

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

    ExitOleProc();

    return hres;

} /* JoyCfg_ConvertCurrentConfigs */


#ifdef WINNT
/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | JoyCfg_FixHardwareId |
 *
 *          Fixes the hardwareId for an analog type by assinging a VID/PID to 
 *          it and recreating the type using that hardwareId.
 *
 *  @parm   IN HKEY | hkTypesR |
 *
 *          Handle of key opened to the root of types.
 *
 *  @parm   IN HKEY | hkSrc |
 *
 *          Handle of key opened to the original type.
 *
 *  @parm   IN PTCHAR | ptszPrefName |
 *
 *          VID&PID name from the HardwareID if present, NULL otherwise
 *
 *  @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 key is valid
 *          <c DI_NOEFFECT> = <c S_FALSE> The key should be ignored
 *
 *          <c OLE_E_ENUM_NOMORE> = The key has been fixed but enumeration
 *                                  must be restarted.
 *          <c DIERR_OUTOFMEMORY> = <c E_OUTOFMEMORY>:  Out of memory.
 *
 *****************************************************************************/

HRESULT INTERNAL
    JoyCfg_FixHardwareId( HKEY hkTypesR, HKEY hkSrc, PTCHAR szSrcType , PTCHAR ptszPrefName)
{
    HRESULT hres;
    HKEY    hkNew;
    BYTE    PIDlow;
    DWORD   ClassLen;
    PTCHAR  szClassName;
    TCHAR   szDestType[sizeof( ANALOG_ID_ROOT ) + 2];  //Two digits will be appended
    TCHAR   szHardwareId[MAX_JOYSTRING];

    EnterProcI(JoyCfg_FixHardwareId, (_ "xxss", hkTypesR, hkSrc, szSrcType, ptszPrefName));

    hres = hresReg( RegQueryInfoKey(  hkSrc,              // handle to key to query
                                      NULL,               // Class
                                      &ClassLen,          // ClassLen
                                      NULL,               // Reserved
                                      NULL, NULL, NULL,   // NumSubKeys, MaxSubKeyLen, MaxClassLen
                                      NULL, NULL, NULL,   // NumValues, MaxValueNameLen, MaxValueLen
                                      NULL, NULL ) );     // Security descriptor, last write

    if( SUCCEEDED( hres ) )
    {
        ClassLen++;
        hres = AllocCbPpv( ClassLen * sizeof(szClassName[0]), &szClassName );
        if( SUCCEEDED( hres ) )
        {
            hres = hresReg( RegQueryInfoKey(  hkSrc,              // handle to key to query
                                              szClassName,        // Class
                                              &ClassLen,          // ClassLen
                                              NULL,               // Reserved
                                              NULL, NULL, NULL,   // NumSubKeys, MaxSubKeyLen, MaxClassLen
                                              NULL, NULL, NULL,   // NumValues, MaxValueNameLen, MaxValueLen
                                              NULL, NULL ) );     // Security descriptor, last write
            if( FAILED( hres ) )
            {
                SquirtSqflPtszV(sqfl | sqflError,
                    TEXT("RegQueryInfoKey on type %s for class name failed 0x%04x"), 
                    szSrcType, LOWORD(hres) );
            }
        }
        else
        {
            SquirtSqflPtszV(sqfl | sqflError,
                TEXT("Failed to allocate %d bytes for class name of type %s, error 0x%04x"), 
                ClassLen, szSrcType, LOWORD(hres) );
        }
    }
    else
    {
        SquirtSqflPtszV(sqfl | sqflError,
            TEXT("RegQueryInfoKey on type %s for class name length failed 0x%04x"), 
            szSrcType, LOWORD(hres) );
        /* Make sure not to free an uninitialized pointer */
        szClassName = NULL;
    }

    if( SUCCEEDED( hres ) )
    {
        for( PIDlow = JOY_HW_PREDEFMAX; PIDlow; PIDlow++ )
        {
            if (ptszPrefName)
            {
                lstrcpy( szDestType, ptszPrefName);
#ifdef UNICODE
                CharUpperW(szDestType);
#else
                CharUpper(szDestType);
#endif
            }
            else
            {
                wsprintf( szDestType, TEXT("%s%02X"), ANALOG_ID_ROOT, PIDlow );
            }
            hres = hresRegCopyKey( hkTypesR, szSrcType, szClassName, hkTypesR, szDestType, &hkNew );
            if( hres == S_OK )
            {
                hres = hresRegCopyBranch( hkSrc, hkNew );

                if( SUCCEEDED( hres ) )
                {
                    if (!ptszPrefName)
                    {
#ifdef MULTI_SZ_HARDWARE_IDS
                        /*
                         *  Make up the hardwareId using the assigned PID with a generic hardwareId appended
                         */
                        int CharIdx = 0;
                        while( TRUE )
                        {
                            CharIdx += wsprintf( &szHardwareId[CharIdx], TEXT("%s%s%02X"), TEXT("GamePort\\"), ANALOG_ID_ROOT, PIDlow );
                            CharIdx++;    /* Leave NULL terminator in place */
                            if( PIDlow )
                            {
                                PIDlow = 0; /* Trash this value to make the generic PID on second iteration */
                            }
                            else
                            {
                                break;
                            }
                        }
                        szHardwareId[CharIdx++] = TEXT('\0'); /* MULTI_SZ */

                        hres = hresReg( RegSetValueEx( hkNew, REGSTR_VAL_JOYOEMHARDWAREID, 0, 
                            REG_MULTI_SZ, (PBYTE)szHardwareId, (DWORD)( sizeof(szHardwareId[0]) * CharIdx ) ) );
                        if( FAILED( hres ) )
                        {
                            SquirtSqflPtszV(sqfl | sqflBenign,
                                TEXT("JoyCfg_FixHardwareId: failed to write hardware ID %s"), szHardwareId );
                        }
#else
                        /*
                         *  Make up the hardwareId using the assigned PID
                         */
                        int CharIdx = 0;
                        CharIdx = wsprintf( szHardwareId, TEXT("%s%s%02X"), TEXT("GamePort\\"), ANALOG_ID_ROOT, PIDlow );
                        CharIdx++;    /* Leave NULL terminator in place */

                        hres = hresReg( RegSetValueEx( hkNew, REGSTR_VAL_JOYOEMHARDWAREID, 0, 
                            REG_SZ, (PBYTE)szHardwareId, (DWORD)( sizeof(szHardwareId[0]) * CharIdx ) ) );
                        if( FAILED( hres ) )
                        {
                            SquirtSqflPtszV(sqfl | sqflBenign,
                                TEXT("JoyCfg_FixHardwareId: failed to write hardware ID %s"), szHardwareId );
                        }
#endif
                    }
                }

                RegCloseKey( hkNew );
                if( SUCCEEDED( hres ) )
                {
                    hres = JoyCfg_ConvertCurrentConfigs( szSrcType, szDestType );
                }

                DIWinnt_RegDeleteKey( hkTypesR, ( SUCCEEDED( hres ) ) ? szSrcType
                                                                      : szDestType );
                break;
            }
            else if( SUCCEEDED( hres ) )
            {
                /*
                 *  The key already existed so keep looking
                 */
                RegCloseKey( hkNew );
            }
            else
            {
                /*
                 *  RegCopyKey should have already posted errors
                 */
                break;
            }
        }
        if( !PIDlow )
        {
            SquirtSqflPtszV(sqfl | sqflBenign,
                TEXT("JoyCfg_FixHardwareId: no free analog keys for type %s"), 
                szSrcType );
            hres = DIERR_NOTFOUND;
        }
    }

    if( szClassName )
    {
        FreePpv( &szClassName );
    }


    ExitOleProc();

    return( hres );        
} /* JoyCfg_FixHardwareId */
#endif

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | JoyCfg_CheckTypeKey |
 *
 *          Checks the contents of a type key for validity on the current OS
 *          and if not valid, try to make it so.
 *
 *          Only custom analog types can be fixed and this only needs to be 
 *          done on a WDM enabled OS as non-WDM requirements are a sub-set of
 *          the WDM ones.
 *
 *  @parm   IN HKEY | hkTypesR |
 *
 *          Handle of key opened to the root of types.
 *
 *  @parm   IN LPTSTR | szType |
 *
 *          Receives a pointer either an ansi or UNICODE key name to test.
 *
 *  @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 key is valid
 *          <c DI_NOEFFECT> = <c S_FALSE> The key should be ignored
 *
 *          <c OLE_E_ENUM_NOMORE> = The key has been fixed but enumeration
 *                                  must be restarted.
 *          <c DIERR_OUTOFMEMORY> = <c E_OUTOFMEMORY>:  Out of memory.
 *
 *****************************************************************************/

HRESULT INTERNAL
    JoyCfg_CheckTypeKey( HKEY hkTypesR, LPTSTR szType )
{
    HRESULT hres;
    HKEY hk;
    LONG lRc;
    DWORD cb;

    TCHAR tszCallout[MAX_JOYSTRING];
    TCHAR tszHardwareId[MAX_JOYSTRING];
#ifdef WINNT
    JOYREGHWSETTINGS hws;
    TCHAR* ptszLastSlash=NULL;
#endif

    EnterProcI(JoyCfg_CheckTypeKey, (_ "xs",hkTypesR, szType));

    /*
     *  Open read only just in case we don't have better permission to any 
     *  of the type sub-keys.
     */
    lRc = RegOpenKeyEx( hkTypesR, szType, 0, KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, &hk );

    if(lRc == ERROR_SUCCESS )
    {
        /*
         *  Gather the needed results using standard registry functions so 
         *  that the exact return code is known.
         */

        lRc = RegQueryValueEx(hk, REGSTR_VAL_JOYOEMNAME, NULL, NULL, NULL, NULL );

#ifdef WINNT
        if(lRc == ERROR_SUCCESS )
        {
            cb = cbX(hws);
            lRc = RegQueryValueEx(hk, REGSTR_VAL_JOYOEMDATA, NULL, NULL, (PBYTE)&hws, &cb );
            if( ( lRc == ERROR_SUCCESS ) && ( hws.dwFlags & JOY_HWS_AUTOLOAD ) )
            {
                /*
                 *  WARNING goto
                 *  If we have a name and JOY_HWS_AUTOLOAD is set, that's all we need
                 */
                RegCloseKey( hk );
                hres = S_OK;
                goto fast_out;
            }
             
            if( lRc == ERROR_FILE_NOT_FOUND )
            {
                hws.dwFlags = 0;
                lRc = ERROR_SUCCESS;
            }
        }
#endif

        if(lRc == ERROR_SUCCESS )
        {
            cb = cbX(tszCallout);
            lRc = RegQueryValueEx(hk, REGSTR_VAL_JOYOEMCALLOUT, NULL, NULL, (PBYTE)tszCallout, &cb );
            if( lRc == ERROR_FILE_NOT_FOUND )
            {
                tszCallout[0] = TEXT('\0');
                lRc = ERROR_SUCCESS;
            }
        }

        if(lRc == ERROR_SUCCESS )
        {
            cb = cbX(tszHardwareId);
            lRc = RegQueryValueEx(hk, REGSTR_VAL_JOYOEMHARDWAREID, NULL, NULL, (PBYTE)tszHardwareId, &cb );
            if( lRc == ERROR_FILE_NOT_FOUND )
            {
                tszHardwareId[0] = TEXT('\0');
                lRc = ERROR_SUCCESS;
            }
#ifdef WINNT
            else
            {
                TCHAR* ptsz;
                for (ptsz = tszHardwareId;*ptsz!='\0';++ptsz)
                {
                    if (*ptsz == '\\')
                    {
                        ptszLastSlash = ptsz;
                    }
                }
                if (ptszLastSlash)
                {
                    ptszLastSlash++; //next char is the one we want
                }
            }
#endif
        }


        if(lRc != ERROR_SUCCESS )
        {
            RegCloseKey( hk );
        }
    }

    if(lRc == ERROR_SUCCESS )
    {
#ifdef WINNT
        SHORT DontCare;
#endif
        WCHAR wszType[18];

        TToU( wszType, cA(wszType),szType );

        /*
         *  Work out the status of this type based on the OS and the registry data
         *
         *  Note on 98 we allow WDM types to be enumerated but do not convert 
         *  analog types to WDM.  We may want to convert analog types if we get 
         *  WDM gameport drivers appear for gameports that are incompatible with 
         *  msanalog.
         */

#define HAS_VIDPID ( ParseVIDPID( &DontCare, &DontCare, wszType ) )
#define HAS_HARDWARE_ID ( tszHardwareId[0] != TEXT('\0') )
#define HAS_OEMCALLOUT ( tszCallout[0] != TEXT('\0') )
#define IS_ANALOG \
        ( tszHardwareId[ sizeof( ANALOG_ID_ROOT ) - 1 ] = TEXT('\0'), \
          ( !lstrcmpi( tszHardwareId, ANALOG_ID_ROOT ) ) )
#define IS_WIN98 (HidD_GetHidGuid)

#ifdef WINNT
        if (HAS_HARDWARE_ID)
        {
            //Need to check if there is a VID and PID in the HW ID
            if (ParseVIDPID( &DontCare, &DontCare, ptszLastSlash ))
            {
                //If the type VIDPID doesn't match the HardwareId VIDPID
                //we need to fix it
                if (!lstrcmpi(ptszLastSlash, wszType))
                {
                    SquirtSqflPtszV(sqfl | sqflVerbose,
                      TEXT("OEMHW %s(%s) and/or Type %s have matching VID/PID"), 
                      tszHardwareId,ptszLastSlash,wszType);
                    hres = S_OK;
                }
                else
                {
                    hres = OLE_E_ENUM_NOMORE;
                    SquirtSqflPtszV(sqfl | sqflVerbose,
                      TEXT("OEMHW %s(%s) and/or Type %s have non-matching VID/PID. Fix Needed."), 
                      tszHardwareId,ptszLastSlash,wszType);
                }
            }
            else
            {
                hres = S_OK; //no VIDPID in the type
                SquirtSqflPtszV(sqfl | sqflVerbose,
                      TEXT("OEMHW %s(%s) and/or Type %s have no VID/PID"), 
                      tszHardwareId,ptszLastSlash,wszType);
            }
        }
        else
        {
            if (HAS_VIDPID)
            {
                hres = DIERR_MOREDATA;
            }
            else
            {
                if (HAS_OEMCALLOUT)
                {
                    hres = S_FALSE;
                }
                else
                {
                    hres = OLE_E_ENUM_NOMORE;
                }
            }
        }
#else
        hres = (IS_WIN98) ? S_OK                                                        /* Anything goes on 98 */
                          : (HAS_OEMCALLOUT) ? S_OK                                     /* Win9x device, OK */
                                             : (HAS_HARDWARE_ID) ? (IS_ANALOG) ? S_OK   /* Analog type, OK */
                                                                               : S_FALSE /* WDM device, ignore */
                                                                 : S_OK;                /* Analog type, OK */
#endif
                                                                
        switch( hres )
        {
#ifdef WINNT
        case DIERR_MOREDATA:
            /*
             *  The device is not marked as autoload but has a VID/PID type 
             *  name.  If the OEMCallout is blank or "joyhid.vxd" we'll assume 
             *  the type should be autoload and correct it.
             *  If there's any other value, we could assume either that we 
             *  have a bogus Win9x driver type key and hide it or that the 
             *  device is autoload.  
             *  Safest route, now that our expose code is smart enough to not 
             *  expose a device without a hardware ID, is to enumerate it as 
             *  non-autoload as Win2k did.  It won't work if you try to add 
             *  it but at least the type will be enumerated if the device 
             *  does show up from PnP (so nobody will get confused by a 
             *  device without a type).
             *
             *  ISSUE-2001/01/04-MarcAnd should use common joyhid string
             *  Not sure if the compiler/linker will resolve the various 
             *  instances of L"joyhid.vxd" to a single string.  Should 
             *  reference the same one to be certain.
             */

            if( !HAS_OEMCALLOUT 
             || ( !lstrcmpi( tszCallout, L"joyhid.vxd" ) ) )
            {
                HKEY hkSet;

                /*
                 *  Need to open a new handle for the key as the one we have 
                 *  is read-only.
                 */
                lRc = RegOpenKeyEx( hkTypesR, szType, 0, KEY_SET_VALUE, &hkSet );

                if( lRc == ERROR_SUCCESS )
                {
                    hws.dwFlags |= JOY_HWS_AUTOLOAD;
                    cb = cbX(hws);
                    lRc = RegSetValueEx( hkSet, REGSTR_VAL_JOYOEMDATA, 0, 
                        REG_BINARY, (PBYTE)&hws, (DWORD)( cbX(hws) ) );

                    if( lRc == ERROR_SUCCESS )
                    {
                        SquirtSqflPtszV(sqfl | sqflTrace,
                                TEXT("FIXED Type %s to have JOY_HWS_AUTOLOAD"), szType );
                    }
                    else
                    {
                        SquirtSqflPtszV(sqfl | sqflBenign,
                                TEXT("Failed to set JOY_HWS_AUTOLOAD on Type %s (rc=%d,le=%d)"), 
                                szType, lRc, GetLastError() );
                    }

                    RegCloseKey( hkSet );
                }
                else
                {
                    SquirtSqflPtszV(sqfl | sqflBenign,
                            TEXT("Failed to open Type %s to fix JOY_HWS_AUTOLOAD(rc=%d,le=%d)"), 
                            szType, lRc, GetLastError() );
                }
            }
            else
            {
                SquirtSqflPtszV(sqfl | sqflBenign,
                        TEXT("Type %s with OEMCallout<%s> has no HardwareId so cannot be added"), 
                        szType, tszCallout );
            }
            
            /*
             *  Whether or not we fixed this, we want to enumerate the key.
             */
            hres = S_OK;
            break;

        case OLE_E_ENUM_NOMORE:
            {
                HRESULT hres0;
                hres0 = JoyCfg_FixHardwareId( hkTypesR, hk, szType , ptszLastSlash);
                if( FAILED( hres0 ) )
                {
                    /*
                     *  Failed to fix type it must be ignored to avoid an infinite loop
                     */
                    SquirtSqflPtszV(sqfl | sqflBenign,
                            TEXT("Ignoring type %s as fix failed"), szType );
                    hres = S_FALSE;
                }
                else
                {
                    SquirtSqflPtszV(sqfl | sqflTrace,
                            TEXT("FIXED Type %s with HardwareId<%s> and OEMCallout<%s>"), 
                            szType, tszHardwareId, tszCallout );
                }
            }
            break;
#endif
        case S_FALSE:
            SquirtSqflPtszV(sqfl | sqflBenign,
                    TEXT("Ignoring type %s with HardwareId<%s> and OEMCallout<%s>"), 
                    szType, tszHardwareId, tszCallout );
            break;
        case S_OK:
            SquirtSqflPtszV(sqfl | sqflTrace,
                    TEXT("Enumerating type %s with HardwareId<%s> and OEMCallout<%s>"), 
                    szType, tszHardwareId, tszCallout );
            break;
        }

        RegCloseKey( hk );

#undef HAS_VIDPID
#undef HAS_HARDWARE_ID
#undef HAS_OEMCALLOUT
#undef IS_ANALOG
#undef IS_WIN98

    }
    else
    {
        SquirtSqflPtszV(sqfl | sqflBenign,
            TEXT("Ignoring type %s due to registry error 0x%08x"), szType, lRc );
        /*
         *  It seems a bit bogus, to return success for an error but this 
         *  makes sure the key is ignored and enumeration will proceed.
         */
        hres = S_FALSE;
    }
#ifdef WINNT
fast_out:;
#endif

    ExitOleProc();

    return( hres );

} /* JoyCfg_CheckTypeKey */


/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CDIJoyCfg | SnapTypes |
 *
 *          Snapshot the list of subkeys for OEM types.
 *
 *  @cwrap  LPDIRECTINPUTJOYCONFIG | lpDirectInputJoyConfig
 *
 *  @parm   OUT LPWSTR * | ppwszz |
 *
 *          Receives a pointer to a UNICODEZZ
 *          list of type names.  Note that the returned list
 *          is pre-populated with the predefined types, too.
 *
 *          We need to snapshot the names up front because
 *          the caller might create or delete OEM types during the
 *          enumeration.
 *
 *          As we enumerate we check each key for validity and repair any 
 *          analog custom configurations that we can.
 *
 *  @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_OUTOFMEMORY> = <c E_OUTOFMEMORY>:  Out of memory.
 *
 *****************************************************************************/

HRESULT INTERNAL
    CJoyCfg_SnapTypes(PJC this, LPWSTR *ppwszz)
{
    HRESULT hres;
    LONG    lRc;
    HKEY    hkTypesR;
    DWORD   chkSub;
    BOOL    fRetry;

    EnterProcI(CJoyCfg_SnapTypes, (_ "p", this));

    RD(*ppwszz = 0);

    /*
     *  If an analog configuration needs to be fixed, the enumeration is 
     *  restarted because adding/removing keys may mess with the key indicies.
     *  Since registry keys can go stale, start from scratch.
     */
    
    do
    {
        fRetry=FALSE;

        /*
         *  Note that it is not safe to cache the registry key in
         *  the object.  If somebody deletes the registry key, our
         *  cached handle goes stale and becomes useless.
         */
        lRc = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                           REGSTR_PATH_JOYOEM, 0,
                           KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, &hkTypesR);

        /*
         *  Note also that if the registry key is not available,
         *  we still want to return the predefined types.
         */

        if(lRc == ERROR_SUCCESS)
        {
            lRc = RegQueryInfoKey(hkTypesR, 0, 0, 0, &chkSub,
                                  0, 0, 0, 0, 0, 0, 0);

            if(lRc == ERROR_SUCCESS  )
            {
            } else
            {
                chkSub = 0;
            }
        } else
        {
            hkTypesR = 0;
            chkSub = 0;
        }


        /*
         *  Each predefined name is of the form #n\0,
         *  which is 3 characters.
         */
        hres = AllocCbPpv(cbCwch( chkSub  * MAX_JOYSTRING +
                                 (JOY_HW_PREDEFMAX - JOY_HW_PREDEFMIN)
                                 * 3 + 1), ppwszz);

        // Not really a bug,we never get to this point with a NULL ptr, 
        // but lets keep prefix happy Manbugs: 29340
        if(SUCCEEDED(hres) && *ppwszz != NULL ){
            DWORD dw;
            LPWSTR pwsz;

            /*
             *  First add the predef keys.
             */
            for(dw = JOY_HW_PREDEFMIN, pwsz = *ppwszz;
               dw < JOY_HW_PREDEFMAX; dw++)
            {
                *pwsz++ = L'#';
                *pwsz++ = CJoyCfg_CharFromType(dw);
                *pwsz++ = L'\0';
            }

            /*
             *  Now add the named keys.
             */
            for(dw = 0; dw < chkSub; dw++)
            {
        #ifdef UNICODE
                lRc = RegEnumKey(hkTypesR, dw, pwsz, MAX_JOYSTRING);
        #else
                CHAR sz[MAX_JOYSTRING];
                lRc = RegEnumKey(hkTypesR, dw, sz, MAX_JOYSTRING);
        #endif
                if(lRc == ERROR_SUCCESS )
                {
            #ifdef UNICODE
                    hres = JoyCfg_CheckTypeKey( hkTypesR, pwsz );
            #else
                    hres = JoyCfg_CheckTypeKey( hkTypesR, sz );
            #endif
                    if( FAILED( hres ) )
                    {
                        /*
                         *  Had to fix type so restart
                         */
                        FreePpv( ppwszz );
                        break;
                    }

                    if( hres != S_OK )
                    {
                        /*
                         *  Ignore this type
                         */
                        continue;
                    }

            #ifdef UNICODE
                    pwsz += lstrlenW(pwsz) + 1;
            #else
                    pwsz += AToU(pwsz, MAX_JOYSTRING, sz);
            #endif
                }
                else
                {
                }
            }        

            if( SUCCEEDED( hres ) )
            {
                *pwsz = L'\0';              /* Make it ZZ */

                hres = S_OK;
            }
            else
            {
                fRetry = TRUE;
            }
        }

        if(hkTypesR)
        {
            RegCloseKey(hkTypesR);
        }

    } while( fRetry );

    ExitOleProcPpv(ppwszz);
    return hres;
}

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputJoyConfig | EnumTypes |
 *
 *          Enumerate the joystick types currently supported by
 *          DirectInput.  A "joystick type" describes how DirectInput
 *          should communicate with a joystick device.  It includes
 *          information such as the presence and
 *          locations of each of the axes and the number of buttons
 *          supported by the device.
 *
 *  @cwrap  LPDIRECTINPUTJOYCONFIG | lpDirectInputJoyConfig
 *
 *  @parm   LPDIJOYTYPECALLBACK | lpCallback |
 *
 *          Points to an application-defined callback function.
 *          For more information, see the description of the
 *          <f DIEnumJoyTypeProc> callback function.
 *
 *  @parm   IN LPVOID | pvRef |
 *
 *          Specifies a 32-bit application-defined
 *          value to be passed to the callback function.  This value
 *          may be any 32-bit value; it is prototyped as an <t LPVOID>
 *          for convenience.
 *
 *  @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.
 *          Note that if the callback stops the enumeration prematurely,
 *          the enumeration is considered to have succeeded.
 *
 *          <c DIERR_INVALIDPARAM> = <c E_INVALIDARG>:  The
 *          callback procedure returned an invalid status code.
 *
 *  @cb     BOOL CALLBACK | DIEnumJoyTypeProc |
 *
 *          An application-defined callback function that receives
 *          DirectInput joystick types as a result of a call to the
 *          <om IDirectInputJoyConfig::EnumTypes> method.
 *
 *  @parm   IN LPCWSTR | pwszTypeName |
 *
 *          The name of the joystick type.  A buffer of <c MAX_JOYSTRING>
 *          characters will be sufficient to hold the type name.
 *          The type name should never be shown to the end user; instead,
 *          the "display name" should be shown.  Use
 *          <mf IDirectInputJoyConfig::GetTypeInfo> to obtain the
 *          display name of a joystick type.
 *
 *          Type names that begin with a sharp character ("#")
 *          represent predefined types which cannot be modified
 *          or deleted.
 *
 *  @parm   IN OUT LPVOID | pvRef |
 *          Specifies the application-defined value given in the
 *          <mf IDirectInputJoyConfig::EnumTypes> function.
 *
 *  @returns
 *
 *          Returns <c DIENUM_CONTINUE> to continue the enumeration
 *          or <c DIENUM_STOP> to stop the enumeration.
 *
 *  @devnote
 *
 *  EnumTypes must snapshot because people will try to get/set/delete
 *  during the enumeration.
 *
 *  EnumTypes enumerates the predefined types as "#digit".
 *
 *****************************************************************************/

STDMETHODIMP
    CJoyCfg_EnumTypes(PDJC pdjc, LPDIJOYTYPECALLBACK ptc, LPVOID pvRef)
{
    HRESULT hres;
    EnterProcR(IDirectInputJoyConfig::EnumTypes, (_ "ppx", pdjc, ptc, pvRef));

    if(SUCCEEDED(hres = hresPv(pdjc)) &&
       SUCCEEDED(hres = hresFullValidPfn(ptc, 1)))
    {
        PJC this = _thisPvNm(pdjc, djc);
        LPWSTR pwszKeys;

        hres = CJoyCfg_SnapTypes(this, &pwszKeys);
        if(SUCCEEDED(hres))
        {
            LPWSTR pwsz;

            /*
             *  Surprise!  Win95 implements lstrlenW.
             */
            for(pwsz = pwszKeys; *pwsz; pwsz += lstrlenW(pwsz) + 1)
            {
                BOOL fRc;

                /*
                 *  WARNING!  "goto" here!  Make sure that nothing
                 *  is held while we call the callback.
                 */
                fRc = Callback(ptc, pwsz, pvRef);

                switch(fRc)
                {
                    case DIENUM_STOP: goto enumdoneok;
                    case DIENUM_CONTINUE: break;
                    default:
                        RPF("%s: Invalid return value from callback", s_szProc);
                        ValidationException();
                        break;
                }
            }

            FreePpv(&pwszKeys);
            hres = DIPort_SnapTypes(&pwszKeys);
            if(SUCCEEDED(hres))
            {
                LPWSTR pwsz;
    
                /*
                 *  Surprise!  Win95 implements lstrlenW.
                 */
                for(pwsz = pwszKeys; *pwsz; pwsz += lstrlenW(pwsz) + 1)
                {
                    BOOL fRc;
    
                    /*
                     *  WARNING!  "goto" here!  Make sure that nothing
                     *  is held while we call the callback.
                     */
                    fRc = Callback(ptc, pwsz, pvRef);
    
                    switch(fRc)
                    {
                        case DIENUM_STOP: goto enumdoneok;
                        case DIENUM_CONTINUE: break;
                        default:
                            RPF("%s: Invalid return value from callback", s_szProc);
                            ValidationException();
                            break;
                    }
                }
            }

            enumdoneok:;
            FreePpv(&pwszKeys);
            hres = S_OK;
        }

        hres = S_OK;
    }

    ExitOleProcR();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputJoyConfig | GetTypeInfo |
 *
 *          Obtain information about a joystick type.
 *
 *  @cwrap  LPDIRECTINPUTJOYCONFIG | lpDirectInputJoyConfig
 *
 *  @parm   LPCWSTR | pwszTypeName |
 *
 *          Points to the name of the type, previously obtained
 *          from a call to <mf IDirectInputJoyConfig::EnumTypes>.
 *
 *  @parm   IN OUT LPDIJOYTYPEINFO | pjti |
 *
 *          Receives information about the joystick type.
 *          The caller "must" initialize the <e DIJOYTYPEINFO.dwSize>
 *          field before calling this method.
 *
 *  @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_INVALIDPARAM> = <c E_INVALIDARG>: One or more
 *          parameters was invalid.
 *
 *          <c DIERR_NOTFOUND>: The joystick type was not found.
 *
 *****************************************************************************/

STDMETHODIMP
    CJoyCfg_GetTypeInfo(PDJC pdjc, LPCWSTR pwszType,
                        LPDIJOYTYPEINFO pjti, DWORD fl)
{
    HRESULT hres;
    EnterProcR(IDirectInputJoyConfig::GetTypeInfo,
               (_ "pWpx", pdjc, pwszType, pjti, fl));

    if(SUCCEEDED(hres = hresPv(pdjc)) &&
       SUCCEEDED(hres = hresFullValidReadStrW(pwszType, MAX_JOYSTRING, 1)) &&
    #if DIRECTINPUT_VERSION >= 0x05B2
       SUCCEEDED(hres = hresFullValidWritePxCb2(pjti,
                                                DIJOYTYPEINFO_DX6,
                                                DIJOYTYPEINFO_DX5, 2)) &&
    #else
       SUCCEEDED(hres = hresFullValidWritePxCb(pjti, DIJOYTYPEINFO, 2)) &&
    #endif
       SUCCEEDED( (pjti->dwSize == cbX(DIJOYTYPEINFO_DX6)
                   ? ( hres = hresFullValidFl(fl, DITC_GETVALID, 3) )
                   : ( hres = hresFullValidFl(fl, DITC_GETVALID_DX5, 3)) ) ) )
    {

        PJC this = _thisPvNm(pdjc, djc);
        GUID    guid;
        BOOL    fParseGuid;

#ifndef UNICODE
        TCHAR   tszType[MAX_PATH/4];

        UToT( tszType, cA(tszType), pwszType );
        fParseGuid = ParseGUID(&guid, tszType);
#else
        fParseGuid = ParseGUID(&guid, pwszType);
#endif

        if(pwszType[0] == TEXT('#'))
        {
            hres = JoyReg_GetPredefTypeInfo(pwszType, pjti, fl);
        } else if( fParseGuid )
        {
            hres = DIBusDevice_GetTypeInfo(&guid, pjti, fl);
        }else
        {
            hres = JoyReg_GetTypeInfo(pwszType, pjti, fl);
        }
        
    }
    ExitOleProcR();
    return hres;
}



/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | hresFullValidStructStr |
 *
 *          Validate a string field in a struct.
 *
 *  @parm   IN LPCWSTR | pwsz |
 *
 *          String to be validated.
 *
 *  @parm   UINT | cwch |
 *
 *          Maximum string length.
 *
 *  @parm   LPCSTR | pszName |
 *
 *          Field name.
 *
 *  @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.
 *
 *****************************************************************************/

    #ifndef XDEBUG
\
        #define hresFullValidStructStr_(pwsz, cwch, pszName, z, i)             \
       _hresFullValidStructStr_(pwsz, cwch)                            \

    #endif

    #define hresFullValidStructStr(Struct, f, iarg)                          \
        hresFullValidStructStr_(Struct->f, cA(Struct->f), #f, s_szProc,iarg)\


    HRESULT INLINE
    hresFullValidStructStr_(LPCWSTR pwsz, UINT cwch, LPCSTR pszName,
                            LPCSTR s_szProc, int iarg)
{
    HRESULT hres;

    if(SUCCEEDED(hres = hresFullValidReadStrW(pwsz, cwch, iarg)))
    {
    } else
    {
    #ifdef XDEBUG
        RPF("%s: Invalid value for %s",  s_szProc, pszName);
    #endif
    }
    return hres;
}

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputJoyConfig | SetTypeInfo |
 *
 *          Creates a new joystick type
 *          or redefine information about an existing joystick type.
 *
 *  @cwrap  LPDIRECTINPUTJOYCONFIG | lpDirectInputJoyConfig
 *
 *  @parm   LPCWSTR | pwszTypeName |
 *
 *          Points to the name of the type.  The name of the type may
 *          not exceed MAX_JOYSTRING characters, including the terminating
 *          null character.
 *
 *          If the type name does not already exist, then it is created.
 *
 *          You cannot change the type information for a predefined type.
 *
 *          The name may not begin with
 *          a "#" character.  Types beginning with "#" are reserved
 *          by DirectInput.
 *
 *  @parm   IN LPDIJOYTYPEINFO | pjti |
 *
 *          Contains information about the joystick type.
 *
 *  @parm   DWORD | dwFlags |
 *
 *          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_NOTACQUIRED>: Joystick configuration has not been
 *          acquired.  You must call <mf IDirectInputJoyConfig::Acquire>
 *          before you can alter joystick configuration settings.
 *
 *          <c DIERR_INVALIDPARAM> = <c E_INVALIDARG>: One or more
 *          parameters was invalid.
 *
 *          <c DIERR_READONLY>: Attempted to change a predefined type.
 *
 *****************************************************************************/

STDMETHODIMP
    CJoyCfg_SetTypeInfo(PDJC pdjc, LPCWSTR pwszType,
                        LPCDIJOYTYPEINFO pjti, DWORD fl)
{
    HRESULT hres;
    EnterProcR(IDirectInputJoyConfig::SetTypeInfo,
               (_ "pWpx", pdjc, pwszType, pjti, fl));


    if(SUCCEEDED(hres = hresPv(pdjc)) &&
       SUCCEEDED(hres = hresFullValidReadStrW(pwszType, MAX_JOYSTRING, 1)) &&

    #if DIRECTINPUT_VERSION >= 0x05B2
       SUCCEEDED(hres = hresFullValidReadPxCb2(pjti,
                                               DIJOYTYPEINFO_DX6,
                                               DIJOYTYPEINFO_DX5, 2)) &&
       (!fWinnt || SUCCEEDED(hres = hresFullValidFl(pjti->dwFlags1, JOYTYPE_FLAGS1_SETVALID, 3) ) ) &&
    #else
       SUCCEEDED(hres = hresFullValidWritePxCb(pjti, DIJOYTYPEINFO, 2)) &&
    #endif
       SUCCEEDED( (pjti->dwSize == cbX(DIJOYTYPEINFO_DX6)
                   ? ( hres = hresFullValidFl(fl, DITC_SETVALID, 3) )
                   : ( hres = hresFullValidFl(fl, DITC_SETVALID_DX5, 3)) ) ) &&
       fLimpFF(fl & DITC_HARDWAREID,
               SUCCEEDED(hres = hresFullValidStructStr(pjti, wszHardwareId, 2))) &&
       fLimpFF(fl & DITC_DISPLAYNAME,
               SUCCEEDED(hres = hresFullValidStructStr(pjti, wszDisplayName, 2))) &&
       fLimpFF(fl & DITC_CALLOUT,
               SUCCEEDED(hres = hresFullValidStructStr(pjti, wszCallout, 2)))
      )
    {

        PJC this = _thisPvNm(pdjc, djc);

        CJoyCfg_EnterCrit(this);

        if(SUCCEEDED(hres = CJoyCfg_IsAcquired(this)))
        {

            switch(pwszType[0])
            {

                case L'\0':
                    RPF("%s: Invalid pwszType (null)", s_szProc);
                    hres = E_INVALIDARG;
                    break;

                case L'#':
                    RPF("%s: Invalid pwszType (predefined)", s_szProc);
                    hres = DIERR_READONLY;
                    break;

                default:
                    hres = JoyReg_SetTypeInfo(this->hkTypesW, pwszType, pjti, fl);
                    break;
            }
        }
        CJoyCfg_LeaveCrit(this);
    }
    ExitOleProcR();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputJoyConfig | DeleteType |
 *
 *          Removes information about a joystick type.
 *
 *          Use this method with caution; it is the caller's responsibility
 *          to ensure that no joystick refers to the deleted type.
 *
 *  @cwrap  LPDIRECTINPUTJOYCONFIG | lpDirectInputJoyConfig
 *
 *  @parm   LPCWSTR | pwszTypeName |
 *
 *          Points to the name of the type.  The name of the type may
 *          not exceed <c MAX_PATH> characters, including the terminating
 *          null character.
 *
 *          The name may not begin with
 *          a "#" character.  Types beginning with "#" are reserved
 *          by DirectInput.
 *
 *  @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_NOTACQUIRED>: Joystick configuration has not been
 *          acquired.  You must call <mf IDirectInputJoyConfig::Acquire>
 *          before you can alter joystick configuration settings.
 *
 *          <c DIERR_INVALIDPARAM> = <c E_INVALIDARG>: One or more
 *          parameters was invalid.
 *
 *****************************************************************************/

STDMETHODIMP
    CJoyCfg_DeleteType(PDJC pdjc, LPCWSTR pwszType)
{
    HRESULT hres;
    EnterProcR(IDirectInputJoyConfig::DeleteType, (_ "pW", pdjc, pwszType));

    if(SUCCEEDED(hres = hresPv(pdjc)) &&
       SUCCEEDED(hres = hresFullValidReadStrW( pwszType, MAX_JOYSTRING, 1)))
    {

        PJC this = _thisPvNm(pdjc, djc);

        CJoyCfg_EnterCrit(this);

        if(SUCCEEDED(hres = CJoyCfg_IsAcquired(this)))
        {
            LONG lRc;
            switch(pwszType[0])
            {

                case L'\0':
                    RPF("%s: Invalid pwszType (null)", s_szProc);
                    hres = E_INVALIDARG;
                    break;

                case L'#':
                    RPF("%s: Invalid pwszType (predefined)", s_szProc);
                    hres = DIERR_READONLY;
                    break;

                default:

                    if( fWinnt ) 
                    {
    #ifdef UNICODE
                        lRc = DIWinnt_RegDeleteKey(this->hkTypesW, (LPTSTR)pwszType);
    #else
                        CHAR sz[MAX_PATH];
                        UToA( sz, cA(sz), pwszType );
                        lRc = DIWinnt_RegDeleteKey(this->hkTypesW, (LPTSTR)sz);
    #endif
                    } else {
    #ifdef UNICODE
                        lRc = RegDeleteKey(this->hkTypesW, (LPTSTR)pwszType);
    #else
                        CHAR sz[MAX_PATH];
                        UToA( sz, cA(sz), pwszType );
                        lRc = RegDeleteKey(this->hkTypesW, (LPTSTR)sz);
    #endif
                    }
    
/*
    #ifdef WINNT
                        lRc = DIWinnt_RegDeleteKey(this->hkTypesW, pwszType);
    #else
                        lRc = RegDeleteKeyW(this->hkTypesW, pwszType);
    #endif
*/

                    if(lRc == ERROR_SUCCESS)
                    {
                        hres = S_OK;
                    } else
                    {
                        if(lRc == ERROR_KEY_DELETED || lRc == ERROR_BADKEY)
                        {
                            lRc = ERROR_FILE_NOT_FOUND;
                        }
                        hres = hresLe(lRc);
                    }
                    break;
            }
        }
        CJoyCfg_LeaveCrit(this);
    }
    ExitOleProcR();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputJoyConfig | GetConfig |
 *
 *          Obtain information about a joystick's configuration.
 *
 *  @cwrap  LPDIRECTINPUTJOYCONFIG | lpDirectInputJoyConfig
 *
 *  @parm   UINT | uiJoy |
 *
 *          Joystick identification number.  This is a nonnegative
 *          integer.  To enumerate joysticks, begin with joystick
 *          zero and increment the joystick number by one until the
 *          function returns <c DIERR_NOMOREITEMS>.
 *
 *          Yes, it's different from all other DirectX enumerations,
 *          but I felt rather strange enumerating integers.
 *
 *  @parm   IN OUT LPDIJOYCONFIG | pjc |
 *
 *          Receives information about the joystick configuration.
 *          The caller "must" initialize the <e DIJOYCONFIG.dwSize>
 *          field before calling this method.
 *
 *  @parm   DWORD | dwFlags |
 *
 *          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 S_FALSE>: The specified joystick has not yet been
 *          configured.
 *
 *          <c DIERR_INVALIDPARAM> = <c E_INVALIDARG>: One or more
 *          parameters was invalid.
 *
 *          <c DIERR_NOMOREITEMS>: No more joysticks.
 *
 *****************************************************************************/

STDMETHODIMP
    CJoyCfg_GetConfig(PDJC pdjc, UINT uiJoy, LPDIJOYCONFIG pjc, DWORD fl)
{
    HRESULT hres;
    EnterProcR(IDirectInputJoyConfig::GetConfig,
               (_ "pupx", pdjc, uiJoy, pjc, fl));

    if(SUCCEEDED(hres = hresPv(pdjc)) &&
    #if DIRECTINPUT_VERSION >= 0x05B2
       SUCCEEDED(hres = hresFullValidWritePxCb2(pjc,
                                                DIJOYCONFIG_DX6,
                                                DIJOYCONFIG_DX5, 2)) &&
    #else
       SUCCEEDED(hres = hresFullValidWritePxCb(pjc, DIJOYCONFIG, 2)) &&
    #endif
       SUCCEEDED( (pjc->dwSize == cbX(DIJOYCONFIG)
                   ? (hres = hresFullValidFl(fl, DIJC_GETVALID, 3) )
                   : (hres = hresFullValidFl(fl, DIJC_GETVALID_DX5, 3)))) )
    {

        PJC this = _thisPvNm(pdjc, djc);

        CJoyCfg_EnterCrit(this);

        /*
         *  Note that we always get the DIJC_REGHWCONFIGTYPE because
         *  we need to check if the joystick type is "none".
         */
        hres = JoyReg_GetConfig(uiJoy, NULL, pjc, fl | DIJC_REGHWCONFIGTYPE);

        if(SUCCEEDED(hres))
        {
#ifndef WINNT           
            static WCHAR s_wszMSGAME[] = L"MSGAME.VXD";

            if(memcmp(pjc->wszCallout, s_wszMSGAME, cbX(s_wszMSGAME)) == 0)
            {
                 ; // do nothing
            } else 
#endif            
            if(fInOrder(JOY_HW_PREDEFMIN, pjc->hwc.dwType,
                        JOY_HW_PREDEFMAX))
            {
                pjc->wszType[0] = TEXT('#');
                pjc->wszType[1] = CJoyCfg_CharFromType(pjc->hwc.dwType);
                pjc->wszType[2] = TEXT('\0');

            }

            if(pjc->hwc.dwType == JOY_HW_NONE)
            {
                hres = S_FALSE;
            } else
            {
                hres = S_OK;
            }

            /*
             *  In DEBUG, re-scramble the hwc and type if the caller
             *  didn't ask for it.
             */
            if(!(fl & DIJC_REGHWCONFIGTYPE))
            {
                ScrambleBuf(&pjc->hwc, cbX(pjc->hwc));
                ScrambleBuf(&pjc->wszType, cbX(pjc->wszType));
            }
        }

        CJoyCfg_LeaveCrit(this);
    }
    ExitBenignOleProcR();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CJoyCfg | UpdateGlobalGain |
 *
 *          Create the device callback so we can talk to its driver and
 *          tell it to change the gain value.
 *
 *          This function must be called under the object critical section.
 *
 *  @cwrap  PJC | this
 *
 *  @parm   DWORD | idJoy |
 *
 *          The joystick identifier.
 *
 *  @parm   DWORD | dwCplGain |
 *
 *          New global gain.
 *
 *  @returns
 *
 *          Returns a COM error code.
 *
 *****************************************************************************/

STDMETHODIMP
    CJoyCfg_UpdateGlobalGain(PJC this, DWORD idJoy, DWORD dwCplGain)
{
    HRESULT hres;
    EnterProcI(CJoyCfg_UpdateGlobalGain, (_ "puu", this, idJoy, dwCplGain));

    AssertF(CJoyCfg_InCrit(this));

    /*
     *  Create the deviceeffect shepherd if we don't already have it.
     */

    if(this->pes && idJoy == this->idJoyCache)
    {
        hres = S_OK;
    } else if(idJoy < cA(rgGUID_Joystick))
    {
        PCGUID rguid;
    #ifdef DEBUG
        CREATEDCB CreateDcb;
    #endif
        IDirectInputDeviceCallback *pdcb;

        /*
         *  Assume the creation will work.
         */
        this->idJoyCache = idJoy;

        /*
         *  Out with the old...
         */
        Invoke_Release(&this->pes);

        /*
         *  And in with the new...
         */
        rguid = &rgGUID_Joystick[idJoy];

    #ifdef DEBUG
        hres = hresFindInstanceGUID(rguid, &CreateDcb, 1);
        AssertF(SUCCEEDED(hres));
        AssertF(CreateDcb == CJoy_New);
    #endif

        if(SUCCEEDED(hres = CJoy_New(0, rguid,
                                     &IID_IDirectInputDeviceCallback,
                                     (PPV)&pdcb)))
        {
            hres = pdcb->lpVtbl->CreateEffect(pdcb, &this->pes);

            Invoke_Release(&pdcb);
        }

    } else
    {
        hres = DIERR_DEVICENOTREG;
    }

    /*
     *  If we have an effect shepherd, then tell it what the new
     *  global gain is.
     */
    if(SUCCEEDED(hres))
    {
        AssertF(this->pes && idJoy == this->idJoyCache);

        hres = this->pes->lpVtbl->SetGlobalGain(this->pes, dwCplGain);
    }


    ExitOleProc();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputJoyConfig | SetConfig |
 *
 *          Create or redefine configuration information about a joystick.
 *
 *  @cwrap  LPDIRECTINPUTJOYCONFIG | lpDirectInputJoyConfig
 *
 *  @parm   UINT | idJoy |
 *
 *          Zero-based joystick identification number.
 *
 *  @parm   IN LPDIJOYCONFIG | pcfg |
 *
 *          Contains information about the joystick.
 *
 *  @parm   DWORD | dwFlags |
 *
 *          Zero or more <c DIJC_*> flags
 *          which specify which parts of the structure pointed
 *          to by <p pjc> contain information 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_NOTACQUIRED>: Joystick configuration has not been
 *          acquired.  You must call <mf IDirectInputJoyConfig::Acquire>
 *          before you can alter joystick configuration settings.
 *
 *          <c DIERR_INVALIDPARAM> = <c E_INVALIDARG>: One or more
 *          parameters was invalid.
 *
 *  @devnote
 *
 *          This one is tricky.  If the type begins with a sharp, then
 *          it's an internal type.  And if it is null, then it's a
 *          custom type.
 *
 *
 *****************************************************************************/

STDMETHODIMP
    CJoyCfg_SetConfig(PDJC pdjc, UINT idJoy, LPCDIJOYCONFIG pcfg, DWORD fl)
{
    HRESULT hres;
    EnterProcR(IDirectInputJoyConfig::SetConfig,
               (_ "pupx", pdjc, idJoy, pcfg, fl));


    if(SUCCEEDED(hres = hresPv(pdjc)) &&
    #if DIRECTINPUT_VERSION >= 0x05B2
       SUCCEEDED(hres = hresFullValidReadPxCb2(pcfg,
                                               DIJOYCONFIG_DX6,
                                               DIJOYCONFIG_DX5, 2)) &&
    #else
       SUCCEEDED(hres = hresFullValidReadPxCb(pcfg, DIJOYCONFIG, 2)) &&
    #endif
       SUCCEEDED( (pcfg->dwSize == cbX(DIJOYCONFIG)
                   ? ( hres = hresFullValidFl(fl, DIJC_SETVALID, 3) )
                   : ( hres = hresFullValidFl(fl, DIJC_SETVALID_DX5,3)) )) &&
       fLimpFF(fl & DIJC_REGHWCONFIGTYPE,
               SUCCEEDED(hres = hresFullValidStructStr(pcfg, wszType, 2))) &&
       fLimpFF(fl & DIJC_CALLOUT,
               SUCCEEDED(hres = hresFullValidStructStr(pcfg, wszCallout, 2))) &&
       fLimpFF(fl & DIJC_WDMGAMEPORT,
               SUCCEEDED(hres = hresFullValidGuid(&pcfg->guidGameport, 2)))
      )
    {

        PJC this = _thisPvNm(pdjc, djc);

        CJoyCfg_EnterCrit(this);

        if(SUCCEEDED(hres = CJoyCfg_IsAcquired(this)))
        {
            JOYREGHWCONFIG jwc;

            if(fl & DIJC_REGHWCONFIGTYPE)
            {
                LPDWORD lpStart, lp;

                jwc = pcfg->hwc;

                /*
                 * Need to check whether the whole jwc is zero.
                 * If all are zero, we won't set it to JOY_HW_CUSTOM type.
                 * See manbug: 39542.
                 */
                for( lpStart=(LPDWORD)&jwc, lp=(LPDWORD)&jwc.dwReserved; lp >= lpStart; lp-- ) {
                    if( *lp ) {
                        break;
                    }
                }

                if( lp < lpStart ) {
                    goto _CONTINUE_SET;
                }

                jwc.dwUsageSettings &= ~JOY_US_ISOEM;

                if(pcfg->wszType[0] == TEXT('\0'))
                {
                    jwc.dwType = JOY_HW_CUSTOM;
                } else if(pcfg->wszType[0] == TEXT('#'))
                {
                    jwc.dwType = CJoyCfg_TypeFromChar(pcfg->wszType[1]);
                    if(fInOrder(JOY_HW_PREDEFMIN, jwc.dwType,
                                JOY_HW_PREDEFMAX) &&
                       pcfg->wszType[2] == TEXT('\0'))
                    {
                        /*
                         * If we want to use WDM for predefined devices, 
                         * then take away the comments.
                         *
                           fl |= DIJC_WDMGAMEPORT;
                         */
                    } else
                    {
                        RPF("%s: Invalid predefined type \"%ls\"",
                            s_szProc, pcfg->wszType);
                        hres = E_INVALIDARG;
                        goto done;
                    }
                } else
                {
                    HKEY hk;

                    /*
                     *  The precise value of jwc.dwType is not relevant.
                     *  The Windows 95 joystick control panel sets the
                     *  value to JOY_HW_PREDEFMAX + id, so we will too.
                     */
                    jwc.dwUsageSettings |= JOY_US_ISOEM;
                    jwc.dwType = JOY_HW_PREDEFMAX + idJoy;

                    if( !(fl & DIJC_WDMGAMEPORT) ) {
                        hres = JoyReg_OpenTypeKey(pcfg->wszType, MAXIMUM_ALLOWED, REG_OPTION_NON_VOLATILE, &hk);

                        if( SUCCEEDED( hres ) ) {
                            hres = JoyReg_IsWdmGameport( hk );
                            if( SUCCEEDED(hres) ) {
                                fl |= DIJC_WDMGAMEPORT;
                            }
                        }
                    }
                }
            }

_CONTINUE_SET:

            if( fWinnt ) {
                fl |= DIJC_WDMGAMEPORT;
            }

            if( (fl & DIJC_WDMGAMEPORT) && (cbX(*pcfg) >= cbX(DIJOYCONFIG_DX6)) )
            {
                if( fWinnt ||   // in Winnt
                    !(pcfg->hwc.hws.dwFlags & JOY_HWS_ISANALOGPORTDRIVER) ||  //USB joystick
                    fVjoydDeviceNotExist )   // WDM gameport joystick and no VJOYD is used.
                { 
                    if( IsEqualGUID(&pcfg->guidInstance, &GUID_NULL) )
                    {
                        hres = DIWdm_SetConfig(idJoy, &jwc, pcfg, fl );
                        if( SUCCEEDED(hres) )
                        {
                        }
                        goto done;
                    } else
                    {
                        /*
                         * Since pcfg is not null, we set it here to avoid calling
                         * DIWdm_JoyHidMapping. Even if it fails, it doesn't hurt anything.
                         */
                        hres = JoyReg_SetConfig(idJoy, &jwc, pcfg, fl);

                        hres = DIWdm_SetJoyId(&pcfg->guidInstance, idJoy);
                    }
                } else {
                    /*
                     * This is in Win9X, and VJOYD devices are being used.
                     * We don't want to add WDM device at the same time.
                     */

                    hres = E_FAIL;
                     
                }
            } else {
               hres = JoyReg_SetConfig(idJoy, &jwc, pcfg, DIJC_UPDATEALIAS | fl);
              
               if (SUCCEEDED(hres)) {
                  #ifdef WINNT
                    PostMessage(HWND_BROADCAST, g_wmJoyChanged, idJoy+1, 0L);   
                  #else
                    joyConfigChanged(0);
                  #endif
                    
                    if( !fWinnt ) {
                        fVjoydDeviceNotExist = FALSE;
                    }
               }
            }

        }

        done:;
        CJoyCfg_LeaveCrit(this);
    }

    ExitOleProcR();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputJoyConfig | DeleteConfig |
 *
 *          Delete configuration information about a joystick.
 *
 *  @cwrap  LPDIRECTINPUTJOYCONFIG | lpDirectInputJoyConfig
 *
 *  @parm   UINT | idJoy |
 *
 *          Zero-based joystick identification number.
 *
 *  @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_NOTACQUIRED>: Joystick configuration has not been
 *          acquired.  You must call <mf IDirectInputJoyConfig::Acquire>
 *          before you can alter joystick configuration settings.
 *
 *          <c DIERR_INVALIDPARAM> = <c E_INVALIDARG>: One or more
 *          parameters was invalid.
 *
 *****************************************************************************/

DIJOYCONFIG c_djcReset = {
    cbX(c_djcReset),                    /* dwSize               */
    { 0},                              /* guidInstance         */
    { 0},                              /* hwc                  */
    DI_FFNOMINALMAX,                    /* dwGain               */
    { 0},                              /* wszType              */
    { 0},                              /* wszCallout           */
};

STDMETHODIMP
    CJoyCfg_DeleteConfig(PDJC pdjc, UINT idJoy)
{
    HRESULT hres;

    EnterProcR(IDirectInputJoyConfig::DeleteConfig, (_ "pu", pdjc, idJoy));

    if(SUCCEEDED(hres = hresPv(pdjc)))
    {

        PJC this = _thisPvNm(pdjc, djc);

        CJoyCfg_EnterCrit(this);

        if(SUCCEEDED(hres = CJoyCfg_IsAcquired(this)))
        {

            HKEY hk;
            TCHAR tsz[MAX_JOYSTRING];
            DIJOYCONFIG dijcfg;

            hres = DIWdm_DeleteConfig(idJoy);

            if( !fWinnt && hres == DIERR_DEVICENOTREG ) {
                fVjoydDeviceNotExist = TRUE;
            }

            /*
             *  To delete it, set everything to the Reset values and
             *  delete the configuration subkey.
             */
            if( ( SUCCEEDED(hres) || hres == DIERR_DEVICENOTREG ) &&
                SUCCEEDED(hres = JoyReg_SetConfig(idJoy, &c_djcReset.hwc,
                                                 &c_djcReset, DIJC_SETVALID)) &&
                SUCCEEDED(hres = JoyReg_OpenConfigKey(idJoy, MAXIMUM_ALLOWED,
                                                     NULL, REG_OPTION_VOLATILE, &hk)))
            {

                wsprintf(tsz, TEXT("%u"), idJoy + 1);

                // DIWinnt_RegDeleteKey:: name is a mismomer, the function
                // recursively deletes the key and all subkeys.
                DIWinnt_RegDeleteKey(hk, tsz);

                RegCloseKey(hk);

              #ifndef WINNT
                joyConfigChanged(0);
              #endif

                hres = S_OK;
            }
        
            if( FAILED(hres) )
            {
                if( FAILED( JoyReg_GetConfig(idJoy, NULL, &dijcfg, DIJC_REGHWCONFIGTYPE | DIJC_GUIDINSTANCE) ) )
                {
                /* No config exists, so vacuous success on delete */
                    hres = S_FALSE;
                }
            }
        }

        CJoyCfg_LeaveCrit(this);
    }
    ExitOleProcR();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputJoyConfig | GetUserValues |
 *
 *          Obtain information about user settings for the joystick.
 *
 *
 *  @cwrap  LPDIRECTINPUTJOYCONFIG | lpDirectInputJoyConfig
 *
 *  @parm   IN OUT LPDIJOYUSERVALUES | pjuv |
 *
 *          Receives information about the user joystick configuration.
 *          The caller "must" initialize the <e DIJOYUSERVALUES.dwSize>
 *          field before calling this method.
 *
 *  @parm   DWORD | dwFlags |
 *
 *          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
    CJoyCfg_GetUserValues(PDJC pdjc, LPDIJOYUSERVALUES pjuv, DWORD fl)
{
    HRESULT hres;
    EnterProcR(IDirectInputJoyConfig::GetUserValues,
               (_ "ppx", pdjc, pjuv, fl));

    if(SUCCEEDED(hres = hresPv(pdjc)) &&
       SUCCEEDED(hres = hresFullValidWritePxCb(pjuv, DIJOYUSERVALUES, 2)) &&
       SUCCEEDED(hres = hresFullValidFl(fl, DIJU_GETVALID, 3)))
    {
        PJC this = _thisPvNm(pdjc, djc);

        hres = JoyReg_GetUserValues(pjuv, fl);
    }
    ExitOleProcR();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | hresFullValidUVStr |
 *
 *          Validate a string field in a <t DIJOYUSERVALUES>.
 *
 *  @parm   IN LPCWSTR | pwsz |
 *
 *          String to be validated.
 *
 *  @parm   UINT | cwch |
 *
 *          Maximum string length.
 *
 *  @parm   LPCSTR | pszName |
 *
 *          Field name.
 *
 *  @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.
 *
 *****************************************************************************/

    #ifndef XDEBUG

        #define hresFullValidUVStr_(pwsz, cwch, pszName, z, i)              \
       _hresFullValidUVStr_(pwsz, cwch)                             \

    #endif

    #define hresFullValidUVStr(pjuv, f, iarg)                           \
        hresFullValidUVStr_(pjuv->f, cA(pjuv->f), #f, s_szProc,iarg)\


HRESULT INLINE
    hresFullValidUVStr_(LPCWSTR pwsz, UINT cwch, LPCSTR pszName,
                        LPCSTR s_szProc, int iarg)
{
    HRESULT hres;

    if(SUCCEEDED(hres = hresFullValidReadStrW(pwsz, cwch, iarg)))
    {
    } else
    {
    #ifdef XDEBUG
        RPF("%s: Invalid value for DIJOYUSERVALUES.%s", s_szProc, pszName);
    #endif
    }
    return hres;
}

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputJoyConfig | SetUserValues |
 *
 *          Set the user settings for the joystick.
 *
 *  @cwrap  LPDIRECTINPUTJOYCONFIG | lpDirectInputJoyConfig
 *
 *  @parm   IN LPCDIJOYUSERVALUES | pjuv |
 *
 *          Contains information about the new user joystick settings.
 *
 *  @parm   DWORD | dwFlags |
 *
 *          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_NOTACQUIRED>: Joystick configuration has not been
 *          acquired.  You must call <mf IDirectInputJoyConfig::Acquire>
 *          before you can alter joystick configuration settings.
 *
 *          <c DIERR_INVALIDPARAM> = <c E_INVALIDARG>: One or more
 *          parameters was invalid.
 *
 *****************************************************************************/

STDMETHODIMP
    CJoyCfg_SetUserValues(PDJC pdjc, LPCDIJOYUSERVALUES pjuv, DWORD fl)
{
    HRESULT hres;
    EnterProcR(IDirectInputJoyConfig::SetUserValues,
               (_ "pp", pdjc, pjuv, fl));

    if(SUCCEEDED(hres = hresPv(pdjc)) &&
       SUCCEEDED(hres = hresFullValidReadPxCb(pjuv, DIJOYUSERVALUES, 2)) &&
       fLimpFF(fl & DIJU_GLOBALDRIVER,
               SUCCEEDED(hres = hresFullValidUVStr(pjuv,
                                                   wszGlobalDriver, 2))) &&
       fLimpFF(fl & DIJU_GAMEPORTEMULATOR,
               SUCCEEDED(hres = hresFullValidUVStr(pjuv,
                                                   wszGameportEmulator, 2))) &&
       SUCCEEDED(hres = hresFullValidFl(fl, DIJU_SETVALID, 3)))
    {

        PJC this = _thisPvNm(pdjc, djc);

        CJoyCfg_EnterCrit(this);

        if(SUCCEEDED(hres = CJoyCfg_IsAcquired(this)))
        {
            hres = JoyReg_SetUserValues(pjuv, fl);
        }

        CJoyCfg_LeaveCrit(this);
    }
    ExitOleProcR();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputJoyConfig | AddNewHardware |
 *
 *          Displays the "Add New Hardware" dialog to
 *          guide the user through installing
 *          new game controller.
 *
 *  @cwrap  LPDIRECTINPUTJOYCONFIG | lpDirectInputJoyConfig
 *
 *  @parm   HWND | hwndOwner |
 *
 *          Window to act as owner window for UI.
 *
 *  @parm   REFGUID | rguidClass |
 *
 *          <t GUID> which specifies the class of the hardware device
 *          to be added.  DirectInput comes with the following
 *          class <t GUIDs> already defined:
 *
 *          <c GUID_KeyboardClass>: Keyboard devices.
 *
 *          <c GUID_MouseClass>: Mouse devices.
 *
 *          <c GUID_MediaClass>: Media devices, including joysticks.
 *
 *          <c GUID_HIDClass>: HID devices.
 *
 *  @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.
 *
 *          <c DIERR_INVALIDCLASSINSTALLER>: The "media" class installer
 *          could not be found or is invalid.
 *
 *          <c DIERR_CANCELLED>: The user cancelled the operation.
 *
 *          <c DIERR_BADINF>: The INF file for the device the user
 *          selected could not be found or is invalid or is damaged.
 *
 *          <c S_FALSE>: DirectInput could not determine whether the
 *          operation completed successfully.
 *
 *****************************************************************************/

STDMETHODIMP
    CJoyCfg_AddNewHardware(PDJC pdjc, HWND hwnd, REFGUID rguid)
{
    HRESULT hres;
    EnterProcR(IDirectInputJoyConfig::AddNewHardware,
               (_ "pxG", pdjc, hwnd, rguid));

    if(SUCCEEDED(hres = hresPv(pdjc)) &&
       SUCCEEDED(hres = hresFullValidHwnd0(hwnd, 1)) &&
       SUCCEEDED(hres = hresFullValidGuid(rguid, 2)))
    {

        PJC this = _thisPvNm(pdjc, djc);

        hres = AddNewHardware(hwnd, rguid);

    }
    ExitOleProcR();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputJoyConfig | OpenTypeKey |
 *
 *          Open the registry key associated with a joystick type.
 *
 *          Control panel applications can use this key to store
 *          per-type persistent information, such as global
 *          configuration parameters.
 *
 *          Such private information should be kept in a subkey
 *          named "OEM"; do not store private information in the
 *          main type key.
 *
 *          Control panel applications can also use this key to
 *          read configuration information, such as the strings
 *          to use for device calibration prompts.
 *
 *          The application should use <f RegCloseKey> to close
 *          the registry key.
 *
 *  @cwrap  LPDIRECTINPUTJOYCONFIG | lpDirectInputJoyConfig
 *
 *  @parm   LPCWSTR | pwszType |
 *
 *          Points to the name of the type.  The name of the type may
 *          not exceed <c MAX_PATH> characters, including the terminating
 *          null character.
 *
 *          The name may not begin with
 *          a "#" character.  Types beginning with "#" are reserved
 *          by DirectInput.
 *
 *  @parm   REGSAM | regsam |
 *
 *          Registry security access mask.  This can be any of the
 *          values permitted by the <f RegOpenKeyEx> function.
 *          If write access is requested, then joystick
 *          configuration must first have been acquired.
 *          If only read access is requested, then acquisition is
 *          not required.
 *
 *  @parm   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_NOTACQUIRED>: Joystick configuration has not been
 *          acquired.  You must call <mf IDirectInputJoyConfig::Acquire>
 *          before you can open a joystick type configuration key
 *          for writing.
 *
 *          <c DIERR_INVALIDPARAM> = <c E_INVALIDARG>: One or more
 *          parameters was invalid.
 *
 *          <c MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ErrorCode)>:
 *          A Win32 error code if access to the key is denied by
 *          registry permissions or some other external factor.
 *
 *****************************************************************************/

STDMETHODIMP
    CJoyCfg_OpenTypeKey(PDJC pdjc, LPCWSTR pwszType, REGSAM sam, PHKEY phk)
{
    HRESULT hres;
    EnterProcR(IDirectInputJoyConfig::OpenTypeKey,
               (_ "pWx", pdjc, pwszType, sam));

    if(SUCCEEDED(hres = hresPv(pdjc)) &&
       SUCCEEDED(hres = hresFullValidReadStrW(pwszType, MAX_JOYSTRING, 1)) &&
       SUCCEEDED(hres = hresFullValidPcbOut(phk, cbX(*phk), 3)))
    {

        PJC this = _thisPvNm(pdjc, djc);

        if(pwszType[0] != TEXT('#'))
        {
            /*
             *  Attempting to get write access requires acquisition.
             */
            if(fLimpFF(IsWriteSam(sam),
                       SUCCEEDED(hres = CJoyCfg_IsAcquired(this))))
            {
                hres = JoyReg_OpenTypeKey(pwszType, sam, REG_OPTION_NON_VOLATILE, phk);
            }
        } else
        {
            RPF("%s: Invalid pwszType (predefined)", s_szProc);
            hres = E_INVALIDARG;
        }
    }
    ExitOleProcR();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | hresFullValidRegSam |
 *
 *          Validate a registry access mask.  The mask must be nonzero.
 *
 *  @parm   REGSAM | sam |
 *
 *          Access mask, must not be zero.
 *
 *  @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.
 *
 *****************************************************************************/

    #ifndef XDEBUG

        #define hresFullValidRegSam_(sam, z, i)                             \
       _hresFullValidRegSam_(sam)                                   \

    #endif

    #define hresFullValidRegSam(sam, iarg)                              \
        hresFullValidRegSam_(sam, s_szProc, iarg)                   \


HRESULT INLINE
    hresFullValidRegSam_(REGSAM sam, LPCSTR s_szProc, int iarg)
{
    HRESULT hres;

    if(sam == 0)
    {
    #ifdef XDEBUG
        RPF("%s: Invalid registry access mask", s_szProc);
    #endif
        hres = E_INVALIDARG;
    } else
    {
        hres = S_OK;
    }
    return hres;
}

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputJoyConfig | OpenConfigKey |
 *
 *          Open the registry key associated with a
 *          joystick configuration.
 *
 *          Control panel applications can use this key to store
 *          per-joystick persistent information, such as
 *          button mappings.
 *
 *          Such private information should be kept in a subkey
 *          named "OEM"; do not store private information in the
 *          main configuration key.
 *
 *          The application should use <f RegCloseKey> to close
 *          the registry key.
 *
 *  @cwrap  LPDIRECTINPUTJOYCONFIG | lpDirectInputJoyConfig
 *
 *  @parm   UINT | idJoy |
 *
 *          Zero-based joystick identification number.
 *
 *  @parm   REGSAM | regsam |
 *
 *          Registry security access mask.  This can be any of the
 *          values permitted by the <f RegOpenKeyEx> function.
 *          If write access is requested, then joystick
 *          configuration must first have been acquired.
 *          If only read access is requested, then acquisition is
 *          not required.
 *
 *          At least one access mask must be specified.
 *
 *  @parm   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_NOTACQUIRED>: Joystick configuration has not been
 *          acquired.  You must call <mf IDirectInputJoyConfig::Acquire>
 *          before you can open a joystick type configuration key
 *          for writing.
 *
 *          <c DIERR_INVALIDPARAM> = <c E_INVALIDARG>: One or more
 *          parameters was invalid.
 *
 *          <c DIERR_NOTFOUND>: The application attempted to open
 *          the configuration key for reading, but no configuration
 *          key for the joystick has yet been created.
 *          Applications should proceed
 *          as if the key were empty.
 *
 *          <c MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ErrorCode)>:
 *          A Win32 error code if access to the key is denied by
 *          registry permissions or some other external factor.
 *
 *****************************************************************************/

STDMETHODIMP
    CJoyCfg_OpenConfigKey(PDJC pdjc, UINT idJoy, REGSAM sam, PHKEY phk)
{
    HRESULT hres;
    EnterProcR(IDirectInputJoyConfig::OpenConfigKey,
               (_ "pux", pdjc, idJoy, sam));

    if(SUCCEEDED(hres = hresPv(pdjc)) &&
       SUCCEEDED(hres = hresFullValidRegSam(sam, 2)) &&
       SUCCEEDED(hres = hresFullValidPcbOut(phk, cbX(*phk), 3)))
    {

        PJC this = _thisPvNm(pdjc, djc);

        /*
         *  Attempting to get write access requires acquisition.
         */
        if(fLimpFF(IsWriteSam(sam),
                   SUCCEEDED(hres = CJoyCfg_IsAcquired(this))))
        {
          // Since it always fails on Winnt, we don't need bother to run it at all.
          #ifndef WINNT
            if( idJoy < cA(rgGUID_Joystick) )
            {
                HKEY hkMain;
                hres = JoyReg_OpenConfigKey(idJoy, sam, NULL, REG_OPTION_NON_VOLATILE, &hkMain);
                if(SUCCEEDED(hres))
                {
                    TCHAR tsz[20];

                    wsprintf(tsz, TEXT("%u"), idJoy + 1);
                    hres = hresMumbleKeyEx(hkMain, tsz, sam, REG_OPTION_NON_VOLATILE, phk);
                    RegCloseKey(hkMain);
                }
            } else
          #endif  
            {
                hres = E_INVALIDARG;
            }
        }
    }
    ExitOleProcR();
    return hres;
}

/*****************************************************************************
 *
 *      CJoyCfg_New       (constructor)
 *
 *****************************************************************************/

STDMETHODIMP
    CJoyCfg_New(PUNK punkOuter, RIID riid, PPV ppvObj)
{
    HRESULT hres;
    EnterProcI(IDirectInputJoyConfig8::<constructor>,
               (_ "p", ppvObj));
    
    if (SUCCEEDED(hres = hresFullValidPcbOut(ppvObj, cbX(*ppvObj), 3)))
    {
        LPVOID pvTry = NULL;
        hres = Common_NewRiid(CJoyCfg, punkOuter, riid, &pvTry);

        if(SUCCEEDED(hres))
        {
            /* Must use _thisPv in case of aggregation */
            PJC this = _thisPv(pvTry);

            this->fCritInited = fInitializeCriticalSection(&this->crst);
            if( this->fCritInited )
            {
                *ppvObj = pvTry;
                hres = S_OK;
            }
            else
            {
                Common_Unhold(this);
                *ppvObj = NULL;
                hres = E_OUTOFMEMORY;
            }
        }
    }

    ExitOleProcPpvR(ppvObj);
    return hres;
}

/*****************************************************************************
 *
 *      The long-awaited vtbls and templates
 *
 *****************************************************************************/

    #pragma BEGIN_CONST_DATA

    #define CJoyCfg_Signature        0x6766434B      /* "JCfg" */

Primary_Interface_Begin(CJoyCfg, IDirectInputJoyConfig)
CJoyCfg_Acquire,
CJoyCfg_Unacquire,
CJoyCfg_SetCooperativeLevel,
CJoyCfg_SendNotify,
CJoyCfg_EnumTypes,
CJoyCfg_GetTypeInfo,
CJoyCfg_SetTypeInfo,
CJoyCfg_DeleteType,
CJoyCfg_GetConfig,
CJoyCfg_SetConfig,
CJoyCfg_DeleteConfig,
CJoyCfg_GetUserValues,
CJoyCfg_SetUserValues,
CJoyCfg_AddNewHardware,
CJoyCfg_OpenTypeKey,
CJoyCfg_OpenConfigKey,
Primary_Interface_End(CJoyCfg, IDirectInputJoyConfig)

#endif /* DIRECTINPUT_VERSION > 0x0300 */
    
