/*****************************************************************************
 *
 *  DIDev.c
 *
 *  Copyright (c) 1996 Microsoft Corporation.  All Rights Reserved.
 *
 *  Abstract:
 *
 *      The standard implementation of IDirectInputDevice.
 *
 *      This is the device-independent part.  the device-dependent
 *      part is handled by the IDirectInputDeviceCallback.
 *
 *      And the IDirectInputEffect support lives in didevef.c.
 *
 *  Contents:
 *
 *      CDIDev_CreateInstance
 *
 *****************************************************************************/

#include "dinputpr.h"
#define INCLUDED_BY_DIDEV
#include "didev.h"


/*****************************************************************************
 *
 *      Declare the interfaces we will be providing.
 *
 *****************************************************************************/

Interface_Template_Begin(CDIDev)
Primary_Interface_Template(CDIDev, TFORM(ThisInterfaceT))
Secondary_Interface_Template(CDIDev, SFORM(ThisInterfaceT))
Interface_Template_End(CDIDev)

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @global PDD * | g_rgpddForeground |
 *
 *          A list of all devices which have obtained foreground
 *          acquisition.  Items on the list have been
 *          held (not AddRef'd).
 *
 *  @global UINT | g_cpddForeground |
 *
 *          The number of entries in <p g_rgpddForeground>.  When foreground
 *          activation is lost, all objects in the array are unacquired.
 *
 *
 *  @global UINT | g_cpddForegroundMax |
 *
 *          The size of the <p g_rgpddForeground> array, including dummy
 *          spaces that are not yet in use.
 *
 *****************************************************************************/


/*
 *  ISSUE-2001/03/29-timgill We assume that all-zeros is a valid initialization.
 */
    GPA g_gpaExcl;
    #define g_hgpaExcl      (&g_gpaExcl)


/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | IDirectInputDevice | NotAcquired |
 *
 *          Check that the device is not acquired.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *  @returns
 *
 *          Returns
 *          <c S_OK> if all is well, or <c DIERR_ACQUIRED> if
 *          the device is acquired.
 *
 *****************************************************************************/

#ifndef XDEBUG

    #define IDirectInputDevice_NotAcquired_(pdd, z)                     \
       _IDirectInputDevice_NotAcquired_(pdd)                        \

#endif

HRESULT INLINE
    IDirectInputDevice_NotAcquired_(PDD this, LPCSTR s_szProc)
{
    HRESULT hres;

    if(!this->fAcquired)
    {
        hres = S_OK;
    } else
    {
        RPF("ERROR %s: May not be called while device is acquired", s_szProc);
        hres = DIERR_ACQUIRED;
    }
    return hres;
}

#define IDirectInputDevice_NotAcquired(pdd)                         \
        IDirectInputDevice_NotAcquired_(pdd, s_szProc)              \

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CDIDev | IsExclAcquired |
 *
 *          Check that the device is acquired exclusively.
 *
 *          The device critical section must already be held.
 *
 *  @cwrap  PDD | this
 *
 *  @returns
 *
 *          <c S_OK> if the device is exclusively acquired.
 *
 *          <c DIERR_INPUTLOST> if acquisition has been lost.
 *
 *          <c DIERR_NOTEXCLUSIVEACQUIRED> the device is acquired,
 *          but not exclusively, or if the device is not acquired
 *          at all.
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_IsExclAcquired_(PDD this, LPCSTR s_szProc)
{
    HRESULT hres;

    AssertF(CDIDev_InCrit(this));

    if(this->discl & DISCL_EXCLUSIVE)
    {
        if(this->fAcquired)
        {
            hres = S_OK;
        } else
        {
            hres = this->hresNotAcquired;
            if(hres == DIERR_NOTACQUIRED)
            {
                hres = DIERR_NOTEXCLUSIVEACQUIRED;
            }
        }
    } else
    {
        hres = DIERR_NOTEXCLUSIVEACQUIRED;
    }

    if(s_szProc && hres == DIERR_NOTEXCLUSIVEACQUIRED)
    {
        RPF("ERROR %s: Device is not acquired in exclusive mode", s_szProc);
    }
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method void | IDirectInputDevice | EnterCrit |
 *
 *          Enter the object critical section.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *****************************************************************************/

void EXTERNAL
    CDIDev_EnterCrit_(struct CDIDev *this, LPCTSTR lptszFile, UINT line)
{
#ifdef XDEBUG
    if( ! _TryEnterCritSec(&this->crst) )
    {
        SquirtSqflPtszV(sqflCrit, TEXT("Device CritSec blocked @%s,%d"), lptszFile, line);    
        EnterCriticalSection(&this->crst);
    }

    SquirtSqflPtszV(sqflCrit, TEXT("Device CritSec Entered @%s,%d"), lptszFile, line);    
#else        
    EnterCriticalSection(&this->crst);
#endif

    this->thidCrit = GetCurrentThreadId();
    InterlockedIncrement(&this->cCrit);
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method void | IDirectInputDevice | LeaveCrit |
 *
 *          Leave the object critical section.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *****************************************************************************/

void EXTERNAL
    CDIDev_LeaveCrit_(struct CDIDev *this, LPCTSTR lptszFile, UINT line)
{
#ifdef XDEBUG
    AssertF(this->cCrit);
    AssertF(this->thidCrit == GetCurrentThreadId());
    SquirtSqflPtszV(sqflCrit | sqflVerbose, TEXT("Device CritSec Leaving @%s,%d"), lptszFile, line);    
#endif

    if(InterlockedDecrement(&this->cCrit) == 0)
    {
        this->thidCrit = 0;
    }
    LeaveCriticalSection(&this->crst);
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method void | IDirectInputDevice | SetNotifyEvent |
 *
 *          Set the event associated with the device, if any.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *****************************************************************************/

void EXTERNAL
    CDIDev_SetNotifyEvent(PDD this)
{
    if(this->hNotify)
    {
        SetEvent(this->hNotify);
    }
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method void | IDirectInputDevice | SetForcedUnacquiredFlag |
 *
 *          When forced unacquired happens, set the fOnceForcedUnacquired flag.
 *
 *****************************************************************************/

void EXTERNAL
    CDIDev_SetForcedUnacquiredFlag(PDD this)
{
    /*
     *  This is an internal interface, so we can skimp on validation.
     */
    this->fOnceForcedUnacquired = 1;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method BOOL | CDIDev | InCrit |
 *
 *          Nonzero if we are in the critical section.
 *
 *****************************************************************************/

#ifdef DEBUG

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

#endif

#ifdef DEBUG

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method BOOL | IDirectInputDevice | IsConsistent |
 *
 *          Check that various state variables are consistent.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *****************************************************************************/

    #define VerifyF(f,m)  if( !(f) ) { fRc = 0; RPF( m ); }

BOOL INTERNAL
    CDIDev_IsConsistent(PDD this)
{
    BOOL fRc = 1;

    /*
     *  If acquired, then we must have a translation table, a state manager,
     *  and a device callback.
     */

    if(this->fAcquired)
    {
        VerifyF( this->pdix, "Acquired device has no translation table" )
        VerifyF( this->GetState, "Acquired device has no state manager" )
        VerifyF( this->pdcb != c_pdcbNil, "Acquired device has no device callback" )
    }

    /*
     *  If buffering, then must have a device callback.
     */
    if(this->celtBuf)
    {
        VerifyF( this->pdcb != c_pdcbNil, "Buffered device has no device callback" )
    }

    /*
     *  If managing an instance, then make sure buffer variables are
     *  consistent.
     */
    if(this->pvi)
    {
        if(this->celtBuf)
        {
            VerifyF( this->pvi->pBuffer, "Null internal data buffer" )
            VerifyF( this->pvi->pEnd, "Null internal end of buffer pointer" )
            VerifyF( this->pvi->pHead, "Null internal head of buffer pointer" )
            VerifyF( this->pvi->pTail, "Null internal tail of buffer pointer" )

            VerifyF( this->pvi->pBuffer < this->pvi->pEnd, "Internal buffer pointers invalid" )
            VerifyF(fInOrder((DWORD)(UINT_PTR)this->pvi->pBuffer,
                             (DWORD)(UINT_PTR)this->pvi->pHead,
                             (DWORD)(UINT_PTR)this->pvi->pEnd), "Head of internal buffer pointer invalid" )
            VerifyF(fInOrder((DWORD)(UINT_PTR)this->pvi->pBuffer,
                             (DWORD)(UINT_PTR)this->pvi->pTail,
                             (DWORD)(UINT_PTR)this->pvi->pEnd), "Tail of internal buffer pointer invalid" )
        } else
        {
            VerifyF( ( this->pvi->pBuffer == 0
                     &&this->pvi->pEnd == 0
                     &&this->pvi->pHead == 0
                     &&this->pvi->pTail == 0), "Inactive internal buffer has non-zero pointers" )
        }
    }

    /*
     *  The cooperative levels must match the cached window handle.
     */
    VerifyF(fLimpFF(this->discl & (DISCL_FOREGROUND | DISCL_EXCLUSIVE),
                    this->hwnd), "Cooperative level does not match window" );

    return fRc;
}

    #undef VerifyF

#endif

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputDevice | QueryInterface |
 *
 *          Gives a client access to other interfaces on an object.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *  @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    EXTERNAL
 *
 *  @method HRESULT | IDirectInputDevice | AddRef |
 *
 *          Increments the reference count for the interface.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *  @returns
 *
 *          Returns the object reference count.
 *
 *  @xref   OLE documentation for <mf IUnknown::AddRef>.
 *
 *****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputDevice | Release |
 *
 *          Decrements the reference count for the interface.
 *          If the reference count on the object falls to zero,
 *          the object is freed from memory.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *  @returns
 *
 *          Returns the object reference count.
 *
 *  @xref   OLE documentation for <mf IUnknown::Release>.
 *
 *****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CDIDev | 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.
 *
 *****************************************************************************/

#ifdef DEBUG

Default_QueryInterface(CDIDev)
Default_AddRef(CDIDev)
Default_Release(CDIDev)

#else

    #define CDIDev_QueryInterface           Common_QueryInterface
    #define CDIDev_AddRef                   Common_AddRef
    #define CDIDev_Release                  Common_Release
#endif

#define CDIDev_QIHelper         Common_QIHelper

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | CDIDev_Reset |
 *
 *          Releases all the resources of a generic device that
 *          are associated with a particular device instance.
 *
 *          This method is called in preparation for reinitialization.
 *
 *          It is the responsibility of the caller to have taken
 *          any necessary critical sections.
 *
 *
 *  @parm   PV | pvObj |
 *
 *          Object being reset.  Note that it may not have been
 *          completely initialized, so everything should be done
 *          carefully.
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_Reset(PDD this)
{
    HRESULT hres;

    if(!this->fAcquired)
    {

        /*
         *  Note! that we must release the driver before releasing
         *  the callback, because the callback will unload the
         *  driver DLL.
         *
         *  We cannot allow people to reset the device
         *  while there are still effects outstanding,
         *  because that would cause us to throw away the
         *  callback while the effects are still using
         *  the effect driver!
         */
        if(this->gpaEff.cpv == 0)
        {
            Invoke_Release(&this->pes);
            Invoke_Release(&this->pdcb);
            this->pdcb = c_pdcbNil;
            FreePpv(&this->pdix);
            FreePpv(&this->rgiobj);
            FreePpv(&this->pvBuffer);
            FreePpv(&this->pvLastBuffer);
            FreePpv(&this->rgdwAxesOfs);
            FreePpv(&this->rgemi);
            FreePpv(&this->rgdwPOV);

            AssertF(!this->fAcquired);
            AssertF(!this->fAcquiredInstance);

            if(this->hNotify)
            {
                CloseHandle(this->hNotify);
            }

            ZeroBuf(&this->hwnd, FIELD_OFFSET(DD, celtBufMax) -
                    FIELD_OFFSET(DD, hwnd));
            ZeroX(this->guid);
            this->celtBufMax = DEVICE_MAXBUFFERSIZE;
            this->GetDeviceState = CDIDev_GetAbsDeviceState;
            this->hresNotAcquired = DIERR_NOTACQUIRED;
            this->fCook = 0;

            AssertF(this->hNotify == 0);
            AssertF(this->cemi == 0);
            AssertF(this->didcFF == 0);
            this->dwGain = 10000;               /* Default to full gain */
            this->dwAutoCenter = DIPROPAUTOCENTER_ON; /* Default to centered */
            GPA_InitFromZero(&this->gpaEff);

            hres = S_OK;

        } else
        {
            RPF("IDirectInputDevice::Initialize: Device still has effects");
            hres = DIERR_HASEFFECTS;
        }
    } else
    {
        RPF("IDirectInputDevice::Initialize: Device is busy");
        hres = DIERR_ACQUIRED;
    }
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   void | CDIDev_AppFinalize |
 *
 *          The application has performed its final release.
 *
 *          If the device is acquired, then unacquire it.
 *
 *          Release the holds on all the created effects that
 *          we have been hanging onto for enumeration purposes.
 *
 *  @parm   PV | pvObj |
 *
 *          Object being released.  Note that it may not have been
 *          completely initialized, so everything should be done
 *          carefully.
 *
 *****************************************************************************/

void INTERNAL
    CDIDev_AppFinalize(PV pvObj)
{
    PDD this = pvObj;

    if(this->fAcquired)
    {
        RPF("IDirectInputDevice::Release: Forgot to call Unacquire()");
        CDIDev_InternalUnacquire(pvObj);
        /*
         *  If our refcount is wrong, we may have just freed ourselves.
         *  PREFIX picks this up (mb:34651) however there is no point in 
         *  adding checks in case we have bugs in refcounting as that has to 
         *  be bug free.
         */
    }

    if(this->fCritInited)
    {
        /*
         *  Stop all the effects, if they are playing.
         *
         *  Then unhold them (because we're done).
         *
         *  ISSUE-2001/03/29-timgill Need to totally remove all effects created by a destroyed device
         *  We also need to neuter them so they don't
         *  do anything any more.  Otherwise, an app might
         *  destroy the parent device and then try to mess with
         *  an effect created by that device after the device
         *  is gone.
         *
         *  Note that we cannot call the effect while inside our
         *  private little critical section, because the effect
         *  may need to do crazy things to stop itself.
         *
         *  (It will almost certainly call back up into the device
         *  to remove itself from the list of created effects.)
         */
        UINT ipdie;
        PPDIE rgpdie;
        UINT cpdie;

        CDIDev_EnterCrit(this);

        rgpdie = (PV)this->gpaEff.rgpv;
        cpdie = this->gpaEff.cpv;
        GPA_Init(&this->gpaEff);

        CDIDev_LeaveCrit(this);

        for(ipdie = 0; ipdie < cpdie; ipdie++)
        {
            AssertF(rgpdie[ipdie]);
            // 7/19/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers.
            SquirtSqflPtszV(sqfl | sqflError,
                            TEXT("Device %p forgot to destroy effect %p"),
                            this, rgpdie[ipdie]);

            IDirectInputEffect_Stop(rgpdie[ipdie]);
            Common_Unhold(rgpdie[ipdie]);
        }
        FreePpv(&rgpdie);
    }
}

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

void INTERNAL
    CDIDev_Finalize(PV pvObj)
{
    HRESULT hres;
    PDD this = pvObj;

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

    AssertF(!this->fAcquired);

    Invoke_Release(&this->pMS);

    /*
     *  Note that we cannot take the critical section because it
     *  might not exist.  (We might've died during initialization.)
     *  Fortunately, we finalize only after every possible client
     *  (both internal and external) has done its final Release(),
     *  so it's impossible for any other method to get called at
     *  this point.
     */
    hres = CDIDev_Reset(this);
    AssertF(SUCCEEDED(hres));

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

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method void | CDIDev | GetVersions |
 *
 *          Obtain version information from the device.
 *
 *          First try to get it from the effect driver.  If
 *          that doesn't work, then get it from the VxD.
 *          And if that doesn't work, then tough.
 *
 *  @parm   IN OUT LPDIDRIVERVERSIONS | pvers |
 *
 *          Receives version information.
 *
 *****************************************************************************/

void INLINE
    CDIDev_GetVersions(PDD this, LPDIDRIVERVERSIONS pvers)
{
    HRESULT hres;

    /*
     *  Pre-fill with zeros in case nobody implements GetVersions.
     */
    pvers->dwSize = cbX(*pvers);
    pvers->dwFirmwareRevision = 0;
    pvers->dwHardwareRevision = 0;
    pvers->dwFFDriverVersion  = 0;

    hres = CDIDev_CreateEffectDriver(this);
    /*
     *  Prefix raises a warning (mb:34561) that this->pes could be NULL
     *  however CDIDev_CreateEffectDriver only succeeds if it is not.
     */
    if(SUCCEEDED(hres) &&
       SUCCEEDED(hres = this->pes->lpVtbl->
                 GetVersions(this->pes, pvers)))
    {
    } else
    {
        hres = this->pdcb->lpVtbl->GetVersions(this->pdcb, pvers);
    }
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | IDirectInputDevice | GetCapabilitiesHelper |
 *
 *          Obtains information about the device.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *  @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 lpDirectInputDevice> or
 *          <p lpdc> parameter is invalid.
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_GetCapabilitiesHelper(PV pdd)
{
    HRESULT hres;
    PDD this = _thisPv(pdd);
    LPDIDEVCAPS pdc = (PV)&this->dc3;

    pdc->dwSize = cbX(this->dc3);
    hres = this->pdcb->lpVtbl->GetCapabilities(this->pdcb, pdc);

    if(SUCCEEDED(hres))
    {
        /*
         *  We'll handle the DIDC_EMULATED and
         *  DIDC_POLLEDDATAFORMAT bits to save the callback
         *  some trouble.
         */
        AssertF(this->pvi);
        if(this->pvi->fl & VIFL_EMULATED)
        {
            pdc->dwFlags |= DIDC_EMULATED;
        }
        if(this->fPolledDataFormat)
        {
            pdc->dwFlags |= DIDC_POLLEDDATAFORMAT;
        }

        /*
         *  Add in the force feedback flags, too.
         */
        pdc->dwFlags |= this->didcFF;

        hres = S_OK;
    }

    return hres;
}

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputDevice | GetCapabilities |
 *
 *          Obtains information about the device.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *  @parm   IN OUT LPDIDEVCAPS | lpdc |
 *
 *          Points to a <t DIDEVCAPS> structure that is filled in
 *          by the function.  The <e DIDEVCAPS.dwSize>
 *          field "must" be filled in
 *          by the application before calling this method.
 *          See the documentation of the <t DIDEVCAPS> structure
 *          for additional information.
 *
 *  @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 lpDirectInputDevice> or
 *          <p lpdc> parameter is invalid.
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_GetCapabilities(PV pdd, LPDIDEVCAPS pdc _THAT)
{
    HRESULT hres;
    EnterProcR(IDirectInputDevice8::GetCapabilities, (_ "pp", pdd, pdc));

    if(SUCCEEDED(hres = hresPvT(pdd)) &&
       SUCCEEDED(hres = hresFullValidWritePxCb2(pdc,
                                                DIDEVCAPS_DX5,
                                                DIDEVCAPS_DX3, 1)))
    {
        PDD this = _thisPv(pdd);

        CDIDev_EnterCrit(this);

        /*
         *  For the convenience of the callback, zero out all the fields,
         *  save for the dwSize.
         */
        ZeroBuf(pvAddPvCb(pdc, cbX(DWORD)), pdc->dwSize - cbX(DWORD));
        hres = this->pdcb->lpVtbl->GetCapabilities(this->pdcb, pdc);

        if(SUCCEEDED(hres))
        {
            /*
             *  We'll handle the DIDC_EMULATED and
             *  DIDC_POLLEDDATAFORMAT bits to save the callback
             *  some trouble.
             */
            AssertF(this->pvi);
            if(this->pvi->fl & VIFL_EMULATED)
            {
                pdc->dwFlags |= DIDC_EMULATED;
            }
            if(this->fPolledDataFormat)
            {
                pdc->dwFlags |= DIDC_POLLEDDATAFORMAT;
            }

            /*
             *  Add in the force feedback flags, too.
             */
            pdc->dwFlags |= this->didcFF;

            /*
             *  If the caller wants force feedback parameters, then
             *  set them, too.
             */
            if(pdc->dwSize >= cbX(DIDEVCAPS_DX5))
            {
                DIDRIVERVERSIONS vers;

                pdc->dwFFSamplePeriod      = this->ffattr.dwFFSamplePeriod;
                pdc->dwFFMinTimeResolution = this->ffattr.dwFFMinTimeResolution;

                CDIDev_GetVersions(this, &vers);
                pdc->dwFirmwareRevision    = vers.dwFirmwareRevision;
                pdc->dwHardwareRevision    = vers.dwHardwareRevision;
                pdc->dwFFDriverVersion     = vers.dwFFDriverVersion;

            }
            hres = S_OK;
        }

        CDIDev_LeaveCrit(this);

        ScrambleBit(&pdc->dwDevType, DIDEVTYPE_RANDOM);
        ScrambleBit(&pdc->dwFlags,   DIDC_RANDOM);

    }

    ExitOleProcR();
    return hres;
}


#ifdef XDEBUG

CSET_STUBS(GetCapabilities, (PV pdd, LPDIDEVCAPS pdc), (pdd, pdc THAT_))

#else

    #define CDIDev_GetCapabilitiesA         CDIDev_GetCapabilities
    #define CDIDev_GetCapabilitiesW         CDIDev_GetCapabilities

#endif

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CDIDev | GetDataFormat |
 *
 *          Get the data format for the device if we don't have it already.
 *
 *  @parm   PDD | this |
 *
 *          Device object.
 *
 *  @returns
 *
 *          COM return code.
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_GetDataFormat(PDD this)
{
    HRESULT hres;
    LPDIDATAFORMAT pdf;

    /*
     *  If the DIDATAFORMAT structure changes, you also need to invent
     *  a new DCB message (DIDM_GETDATAFORMAT2), and then do
     *  the right thing when faced with a mix of old and new.
     */

    hres = this->pdcb->lpVtbl->GetDataFormat(this->pdcb, &pdf);


    /*
     * Note!  We don't support external drivers in this release,
     * so it's okay to treat these are Assert's and not try to recover.
     */

    if(SUCCEEDED(hres))
    {
        AssertF(pdf->dwSize == sizeof(this->df));
        this->df = *pdf;

        AssertF(!IsBadReadPtr(pdf->rgodf, cbCxX(pdf->dwNumObjs, ODF)));

        /*
         *  Prepare the axis goo in case the app sets relative mode.
         */
        if(SUCCEEDED(hres = ReallocCbPpv(pdf->dwDataSize,
                                         &this->pvLastBuffer)) &&
           SUCCEEDED(hres = ReallocCbPpv(cbCdw(pdf->dwNumObjs),
                                         &this->rgdwAxesOfs)))
        {

            UINT iobj;
            this->cAxes = 0;

            for(iobj = 0; iobj < pdf->dwNumObjs; iobj++)
            {
                AssertF(pdf->rgodf[iobj].dwOfs < pdf->dwDataSize);
                if(pdf->rgodf[iobj].dwType & DIDFT_AXIS)
                {
                    this->rgdwAxesOfs[this->cAxes++] = pdf->rgodf[iobj].dwOfs;
                }
            }
        }

    }
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CDIDev | GetPolled |
 *
 *          Determine whether the device is polled.
 *
 *  @parm   PDD | this |
 *
 *          Device object.
 *
 *  @returns
 *
 *          COM result code.
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_GetPolled(PDD this)
{
    HRESULT hres;
    DIDEVCAPS_DX3 dc;

    /*
     *  We intentionally use a DIDEVCAPS_DX3 because going for
     *  a full DIDEVCAPS_DX5 requires us to load the force
     *  feedback driver which is pointless for our current
     *  goal.
     */
    ZeroX(dc);
    dc.dwSize = cbX(dc);

    hres = this->pdcb->lpVtbl->GetCapabilities(this->pdcb, (PV)&dc);
    if(SUCCEEDED(hres))
    {
        if(dc.dwFlags & DIDC_POLLEDDEVICE)
        {
            this->hresPolled = DI_POLLEDDEVICE;
        } else
        {
            this->hresPolled = S_OK;
        }
    }
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CDIDev | GetObjectInfoHelper |
 *
 *          Set up all the information we can deduce ourselves and
 *          have the callback finish.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *  @parm   LPCDIPROPINFO | ppropi |
 *
 *          Object descriptor.
 *
 *  @parm   LPDIDEVICEOBJECTINSTANCEW | pdoiW |
 *
 *          Structure to receive result.
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_GetObjectInfoHelper(PDD this, LPCDIPROPINFO ppropi,
                               LPDIDEVICEOBJECTINSTANCEW pdoiW)
{
    HRESULT hres;

    AssertF(IsValidSizeDIDEVICEOBJECTINSTANCEW(pdoiW->dwSize));
    pdoiW->guidType = *this->df.rgodf[ppropi->iobj].pguid;
    /* 
     *  Always report the internal data format offset here.  
     *  This is the way DX3 and DX5 worked until DX7 changed the behavior to
     *  report the user data format value if there was one.  That was very 
     *  confusing so revert it for DX8.
     */
    pdoiW->dwOfs    =  this->df.rgodf[ppropi->iobj].dwOfs;
    pdoiW->dwType   =  this->df.rgodf[ppropi->iobj].dwType;
    pdoiW->dwFlags  =  this->df.rgodf[ppropi->iobj].dwFlags;
    ScrambleBit(&pdoiW->dwFlags, DIDOI_RANDOM);



    /*
     *  Wipe out everything starting at tszName.
     */
    ZeroBuf(&pdoiW->tszName,
            pdoiW->dwSize - FIELD_OFFSET(DIDEVICEOBJECTINSTANCEW, tszName));

    hres = this->pdcb->lpVtbl->GetObjectInfo(this->pdcb, ppropi, pdoiW);

    return hres;
}

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputDevice | EnumObjects |
 *
 *          Enumerate the input sources (buttons, axes)
 *          available on a device.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *  @parm   IN LPDIENUMDEVICEOBJECTSCALLBACK | lpCallback |
 *
 *          Callback function.
 *
 *  @parm   IN LPVOID | pvRef |
 *
 *          Reference data (context) for callback.
 *
 *  @parm   IN DWORD | fl |
 *
 *          Flags specifying the type(s) of objects to be enumerated.
 *          See the section "DirectInput Data Format Types" for a
 *          list of flags that can be passed.
 *
 *          Furthermore, the enumeration can be restricted to objects
 *          from a single HID link collection by using the
 *          <f DIDFT_ENUMCOLLECTION> macro.
 *
 *  @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
 *          <p fl> parameter contains invalid flags, or the callback
 *          procedure returned an invalid status code.
 *
 *  @cb     BOOL CALLBACK | DIEnumDeviceObjectsProc |
 *
 *          An application-defined callback function that receives
 *          DirectInputDevice objects as a result of a call to the
 *          <om IDirectInputDevice::EnumObjects> method.
 *
 *  @parm   IN LPCDIDEVICEOBJECTINSTANCE | lpddoi |
 *
 *          A <t DIDEVICEOBJECTINSTANCE> structure which describes
 *          the object being enumerated.
 *
 *  @parm   IN OUT LPVOID | pvRef |
 *          Specifies the application-defined value given in the
 *          <mf IDirectInputDevice::EnumObjects> function.
 *
 *  @returns
 *
 *          Returns <c DIENUM_CONTINUE> to continue the enumeration
 *          or <c DIENUM_STOP> to stop the enumeration.
 *
 *  @ex
 *
 *          To enumerate all axis objects:
 *
 *          |
 *
 *          // C++
 *          HRESULT hr = pDevice->EnumObjects(EnumProc, RefData, DIDFT_AXIS);
 *
 *          // C
 *          hr = IDirectInputDevice_EnumObjects(pDevice, EnumProc, RefData,
 *                                              DIDFT_AXIS);
 *
 *  @ex
 *
 *          To enumerate all objects in the third HID link collection:
 *
 *          |
 *
 *          // C++
 *          HRESULT hr = pDevice->EnumObjects(EnumProc, RefData,
 *                                            DIDFT_ENUMCOLLECTION(3));
 *
 *          // C
 *          hr = IDirectInputDevice_EnumObjects(pDevice, EnumProc, RefData,
 *                                              DIDFT_ENUMCOLLECTION(3));
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_EnumObjectsW
    (PV pddW, LPDIENUMDEVICEOBJECTSCALLBACKW pec, LPVOID pvRef, DWORD fl)
{
    HRESULT hres;
    EnterProcR(IDirectInputDevice8::EnumObjectsW, (_ "ppx", pddW, pec, fl));

    if(SUCCEEDED(hres = hresPvW(pddW)) &&
       SUCCEEDED(hres = hresFullValidPfn(pec, 1)) &&
       SUCCEEDED(hres = hresFullValidFl(fl, DIDFT_ENUMVALID, 3)))
    {
        PDD this = _thisPvNm(pddW, ddW);
        DWORD flExclude;
        WORD wCollection;

        /*
         *  We snapshot the object information underneath the critical
         *  section so we don't blow up if another thread Reset()s
         *  the device in the middle of an enumeration.  The DIDATAFORMAT
         *  contains pointers to the dcb, so we need to AddRef the
         *  dcb as well.
         */
        AssertF(!CDIDev_InCrit(this));
        CDIDev_EnterCrit(this);

        if(this->pdcb != c_pdcbNil)
        {
            DIPROPINFO propi;
            DIDATAFORMAT df;
            IDirectInputDeviceCallback *pdcb;

            pdcb = this->pdcb;
            OLE_AddRef(pdcb);
            df = this->df;

            CDIDev_LeaveCrit(this);
            AssertF(!CDIDev_InCrit(this));

            flExclude = 0;

            /* Exclude alises if necessary */
            if( !(fl & DIDFT_ALIAS) )
            {
                flExclude |= DIDFT_ALIAS;
            } else
            {
                fl &= ~DIDFT_ALIAS;
            }

            /* Exclude Vendor Defined fields */
            if( !(fl & DIDFT_VENDORDEFINED) )
            {
                flExclude |= DIDFT_VENDORDEFINED;
            } else
            {
                fl &= ~DIDFT_VENDORDEFINED;
            }

            if(fl == DIDFT_ALL)
            {
                fl = DIDFT_ALLOBJS;
            }

            /*
             *  Pull out the link collection we are enumerating.
             *  Note: Backwards compatibility hack.  We can't
             *  use link collection 0 to mean "no parent" because
             *  0 means "don't care".  So instead, we use 0xFFFF
             *  to mean "no parent".  This means that we need to
             *  interchange 0 and 0xFFFF before entering the main
             *  loop.
             */
            wCollection = DIDFT_GETCOLLECTION(fl);
            switch(wCollection)
            {

            case 0:
                wCollection = 0xFFFF;
                break;

            case DIDFT_GETCOLLECTION(DIDFT_NOCOLLECTION):
                wCollection = 0;
                break;
            }

            propi.pguid = 0;

            for(propi.iobj = 0; propi.iobj < df.dwNumObjs; propi.iobj++)
            {
                propi.dwDevType = df.rgodf[propi.iobj].dwType;
                if((propi.dwDevType & fl & DIDFT_TYPEMASK) &&
                   fHasAllBitsFlFl(propi.dwDevType, fl & DIDFT_ATTRMASK) &&
                   !(propi.dwDevType & flExclude))
                {
                    DIDEVICEOBJECTINSTANCEW doiW;
                    doiW.dwSize = cbX(doiW);

                    hres = CDIDev_GetObjectInfoHelper(this, &propi, &doiW);
                    if(SUCCEEDED(hres) &&
                       fLimpFF(wCollection != 0xFFFF,
                               doiW.wCollectionNumber == wCollection))
                    {
                        BOOL fRc = Callback(pec, &doiW, pvRef);

                        switch(fRc)
                        {
                        case DIENUM_STOP: goto enumdoneok;
                        case DIENUM_CONTINUE: break;
                        default:
                            RPF("IDirectInputDevice::EnumObjects: Invalid return value from enumeration callback");
                            ValidationException();
                            break;
                        }
                    } else
                    {
                        if( hres == DIERR_OBJECTNOTFOUND ) {
                            // This can only happen on Keyboard.
                            continue;
                        } else {
                            goto enumdonefail;
                        }
                    }
                }
            }

            enumdoneok:;
            hres = S_OK;
            enumdonefail:;

            OLE_Release(pdcb);

        } else
        {
            CDIDev_LeaveCrit(this);
            RPF("ERROR: IDirectInputDevice: Not initialized");
            hres = DIERR_NOTINITIALIZED;
        }
    }

    ExitOleProcR();
    return hres;
}

#define CDIDev_EnumObjects2W            CDIDev_EnumObjectsW

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | IDirectInputDeviceA | EnumObjectsCallbackA |
 *
 *          Custom callback that wraps
 *          <mf IDirectInputDeviceW::EnumObjects> which
 *          translates the UNICODE string to ANSI.
 *
 *  @parm   IN LPCDIENUMDEVICEOBJECTINSTANCE | pdoiW |
 *
 *          Structure to be translated to ANSI.
 *
 *  @parm   IN LPVOID | pvRef |
 *
 *          Pointer to <t struct ENUMDEVICEOBJECTINFO> which describes
 *          the original callback.
 *
 *  @returns
 *
 *          Returns whatever the original callback returned.
 *
 *****************************************************************************/

typedef struct ENUMOBJECTSINFO
{
    LPDIENUMDEVICEOBJECTSCALLBACKA pecA;
    PV pvRef;
} ENUMOBJECTSINFO, *PENUMOBJECTSINFO;

BOOL CALLBACK
    CDIDev_EnumObjectsCallbackA(LPCDIDEVICEOBJECTINSTANCEW pdoiW, PV pvRef)
{
    PENUMOBJECTSINFO peoi = pvRef;
    BOOL fRc;
    DIDEVICEOBJECTINSTANCEA doiA;
    EnterProc(CDIDev_EnumObjectsCallbackA,
              (_ "GxxWp", &pdoiW->guidType,
               pdoiW->dwOfs,
               pdoiW->dwType,
               pdoiW->tszName, pvRef));

    doiA.dwSize = cbX(doiA);
    ObjectInfoWToA(&doiA, pdoiW);

    fRc = peoi->pecA(&doiA, peoi->pvRef);

    ExitProcX(fRc);
    return fRc;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | IDirectInputDeviceA | EnumObjects |
 *
 *          Enumerate the input sources (buttons, axes)
 *          available on a device, in ANSI.
 *          See <mf IDirectInputDevice::EnumObjects> for more information.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *  @parm   IN LPDIENUMDEVICEOBJECTSCALLBACK | lpCallback |
 *
 *          Same as <mf IDirectInputDeviceW::EnumObjects>, except in ANSI.
 *
 *  @parm   IN LPVOID | pvRef |
 *
 *          Same as <mf IDirectInputDeviceW::EnumObjects>.
 *
 *  @parm   IN DWORD | fl |
 *
 *          Same as <mf IDirectInputDeviceW::EnumObjects>.
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_EnumObjectsA
    (PV pddA, LPDIENUMDEVICEOBJECTSCALLBACKA pec, LPVOID pvRef, DWORD fl)
{
    HRESULT hres;
    EnterProcR(IDirectInputDevice8A::EnumDevices,
               (_ "pppx", pddA, pec, pvRef, fl));

    /*
     *  EnumObjectsW will validate the rest.
     */
    if(SUCCEEDED(hres = hresPvA(pddA)) &&
       SUCCEEDED(hres = hresFullValidPfn(pec, 1)))
    {
        ENUMOBJECTSINFO eoi = { pec, pvRef};
        PDD this = _thisPvNm(pddA, ddA);

        hres = CDIDev_EnumObjectsW(&this->ddW, CDIDev_EnumObjectsCallbackA,
                                   &eoi, fl);
    }

    ExitOleProcR();
    return hres;
}

#define CDIDev_EnumObjects2A            CDIDev_EnumObjectsA

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputDevice | SetEventNotification |
 *
 *          Specify the event that should be set when the device
 *          state changes, or turns off such notifications.
 *
 *          A device state change is defined as any of the following:
 *
 *          - A change in the position of an axis.
 *
 *          - A change in the state (pressed or released) of a button.
 *
 *          - A change in the direction of a POV control.
 *
 *          - Loss of acquisition.
 *
 *          "It is an error" to call <f CloseHandle> on the event
 *          while it has been selected into an <i IDirectInputDevice>
 *          object.  You must call
 *          <mf IDirectInputDevice::SetEventNotification> with the
 *          <p hEvent> parameter set to NULL before closing the
 *          event handle.
 *
 *          The event notification handle cannot be changed while the
 *          device is acquired.
 *
 *          If the function is successful, then the application can
 *          use the event handle in the same manner as any other
 *          Win32 event handle.  Examples of usage are shown below.
 *          For additional information on using Win32 wait functions,
 *          see the Win32 SDK and related documentation.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *  @parm   IN HANDLE | hEvent |
 *
 *          Specifies the event handle which will be set when the
 *          device state changes.  It "must" be an event
 *          handle.  DirectInput will <f SetEvent> the handle when
 *          the state of the device changes.
 *
 *          The application should create the handle via the
 *          <f CreateEvent> function.  If the event is created as
 *          an automatic-reset event, then the operating system will
 *          automatically reset the event once a wait has been
 *          satisfied.  If the event is created as a manual-reset
 *          event, then it is the application's responsibility to
 *          call <f ResetEvent> to reset it.  DirectInput will not
 *          call <f ResetEvent> for event notification handles.
 *
 *          If the <p hEvent> is zero, then notification is disabled.
 *
 *  @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_HANDLEEXISTS>: The  <i IDirectInputDevice> object
 *          already has an event notification handle
 *          associated with it.  DirectInput supports only one event
 *          notification handle per <i IDirectInputDevice> object.
 *
 *          <c DIERR_ACQUIRED>: The <i IDirectInputDevice> object
 *          has been acquired.  You must <mf IDirectInputDevice::Unacquire>
 *          the device before you can change the notification state.
 *
 *          <c E_INVALIDARG>: The thing isn't an event handle.
 *
 *  @ex
 *
 *          To check if the handle is currently set without blocking:
 *
 *          |
 *
 *          dwResult = WaitForSingleObject(hEvent, 0);
 *          if (dwResult == WAIT_OBJECT_0) {
 *              // Event is set.  If the event was created as
 *              // automatic-reset, then it has also been reset.
 *          }
 *
 *  @ex
 *
 *          The following example illustrates blocking
 *          indefinitely until the event is set.  Note that this
 *          behavior is <y strongly> discouraged because the thread
 *          will not respond to the system until the wait is
 *          satisfied.  (In particular, the thread will not respond
 *          to Windows messages.)
 *
 *          |
 *
 *          dwResult = WaitForSingleObject(hEvent, INFINITE);
 *          if (dwResult == WAIT_OBJECT_0) {
 *              // Event has been set.  If the event was created as
 *              // automatic-reset, then it has also been reset.
 *          }
 *
 *  @ex
 *
 *          The following example illustrates a typical message loop
 *          for a message-based application that uses two events.
 *
 *          |
 *
 *          HANDLE ah[2] = { hEvent1, hEvent2 };
 *
 *          while (TRUE) {
 *
 *              dwResult = MsgWaitForMultipleObjects(2, ah, FALSE,
 *                                                   INFINITE, QS_ALLINPUT);
 *              switch (dwResult) {
 *              case WAIT_OBJECT_0:
 *                  // Event 1 has been set.  If the event was created as
 *                  // automatic-reset, then it has also been reset.
 *                  ProcessInputEvent1();
 *                  break;
 *
 *              case WAIT_OBJECT_0 + 1:
 *                  // Event 2 has been set.  If the event was created as
 *                  // automatic-reset, then it has also been reset.
 *                  ProcessInputEvent2();
 *                  break;
 *
 *              case WAIT_OBJECT_0 + 2:
 *                  // A Windows message has arrived.  Process messages
 *                  // until there aren't any more.
 *                  while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
 *                      if (msg.message == WM_QUIT) {
 *                          goto exitapp;
 *                      }
 *                      TranslateMessage(&msg);
 *                      DispatchMessage(&msg);
 *                  }
 *                  break;
 *
 *              default:
 *                  // Unexpected error.
 *                  Panic();
 *                  break;
 *              }
 *          }
 *
 *  @ex
 *
 *          The following example illustrates a typical application loop
 *          for a non-message-based application that uses two events.
 *
 *          |
 *
 *          HANDLE ah[2] = { hEvent1, hEvent2 };
 *          DWORD dwWait = 0;
 *
 *          while (TRUE) {
 *
 *              dwResult = MsgWaitForMultipleObjects(2, ah, FALSE,
 *                                                   dwWait, QS_ALLINPUT);
 *              dwWait = 0;
 *
 *              switch (dwResult) {
 *              case WAIT_OBJECT_0:
 *                  // Event 1 has been set.  If the event was created as
 *                  // automatic-reset, then it has also been reset.
 *                  ProcessInputEvent1();
 *                  break;
 *
 *              case WAIT_OBJECT_0 + 1:
 *                  // Event 2 has been set.  If the event was created as
 *                  // automatic-reset, then it has also been reset.
 *                  ProcessInputEvent2();
 *                  break;
 *
 *              case WAIT_OBJECT_0 + 2:
 *                  // A Windows message has arrived.  Process messages
 *                  // until there aren't any more.
 *                  while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
 *                      if (msg.message == WM_QUIT) {
 *                          goto exitapp;
 *                      }
 *                      TranslateMessage(&msg);
 *                      DispatchMessage(&msg);
 *                  }
 *                  break;
 *
 *              default:
 *                  // No input or messages waiting.
 *                  // Do a frame of the game.
 *                  // If the game is idle, then tell the next wait
 *                  // to wait indefinitely for input or a message.
 *                  if (!DoGame()) {
 *                      dwWait = INFINITE;
 *                  }
 *                  // Poll for data in case the device is not
 *                  // interrupt-driven.
 *                  IDirectInputDevice8_Poll(pdev);
 *                  break;
 *              }
 *          }
 *
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_SetEventNotification(PV pdd, HANDLE h _THAT)
{
    HRESULT hres;
    EnterProcR(IDirectInputDevice8::SetEventNotification, (_ "px", pdd, h));

    if(SUCCEEDED(hres = hresPvT(pdd)))
    {
        PDD this = _thisPv(pdd);

        /*
         *  Must protect with the critical section to prevent somebody from
         *  acquiring or setting a new event handle while we're changing it.
         */
        CDIDev_EnterCrit(this);

        if(!this->fAcquired)
        {
            /*
             *  Don't operate on the original handle because
             *  the app might decide to do something *interesting* to it
             *  on another thread.
             */

            hres = DupEventHandle(h, &h);

            if(SUCCEEDED(hres))
            {
                /*
                 *  Resetting the event serves two purposes.
                 *
                 *  1. It performs parameter validation for us, and
                 *  2. The event must be reset while the device is
                 *     not acquired.
                 */
                if(fLimpFF(h, ResetEvent(h)))
                {

                    if(!this->hNotify || !h)
                    {
                        hres = this->pdcb->lpVtbl->
                               SetEventNotification(this->pdcb, h);
                        
                        /*
                         *  All dcb's use default handling for now so 
                         *  assert the callback returns S_FALSE
                         *  so we are reminded to change this if need be.
                         *  An uninitialized device would fail but don't 
                         *  break if we hit one of those, just assert that 
                         *  we won't accidentally call a HEL on it.
                         */
                        AssertF( ( hres == S_FALSE )
                            || ( ( hres == DIERR_NOTINITIALIZED ) && !this->pvi ) );

                        if(this->pvi)
                        {
                            VXDDWORDDATA vhd;
                            vhd.pvi = this->pvi;
                            vhd.dw = (DWORD)(UINT_PTR)h;
                            hres = Hel_SetNotifyHandle(&vhd);
                            AssertF(SUCCEEDED(hres)); /* Should never fail */
                            h = (HANDLE)(UINT_PTR)pvExchangePpvPv64(&this->hNotify, (UINT_PTR)h);
                            hres = this->hresPolled;
                        }
                        else
                        {
                            /*
                             *  ISSUE-2001/03/29-timgill Is this actually an error case?
                             *  Can this ever validly occur?
                             *  if yes, don't we need any of the above?
                             */
                            RPF( "Device internal data missing on SetEventNotification" );
                        }

                    } else
                    {
                        hres = DIERR_HANDLEEXISTS;
                    }
                } else
                {
                    RPF( "Handle not for Event in SetEventNotification" );
                    hres = E_HANDLE;
                }

                /*
                 *  Close the old handle if we swapped one out
                 *  or our duplicate if something went wrong
                 */
                if(h != 0)
                {
                    CloseHandle(h);
                }
            }
        } else
        {
            hres = DIERR_ACQUIRED;
        }
        CDIDev_LeaveCrit(this);
    }

    ExitOleProc();
    return hres;
}

#ifdef XDEBUG

CSET_STUBS(SetEventNotification, (PV pdd, HANDLE h), (pdd, h THAT_))

#else

    #define CDIDev_SetEventNotificationA    CDIDev_SetEventNotification
    #define CDIDev_SetEventNotificationW    CDIDev_SetEventNotification

#endif

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | IDirectInputDevice | hresMapHow |
 *
 *          Map the <p dwObj> and <p dwHow> fields into an object index.
 *
 *  @parm   DWORD | dwObj |
 *
 *          Object identifier.
 *
 *  @parm   DWORD | dwHow |
 *
 *          How <p dwObj> should be interpreted.
 *
 *  @parm   OUT LPDIPROPINFO | ppropi |
 *
 *          Receives object index and <p dwDevType>.
 *
 *****************************************************************************/

#ifndef XDEBUG

    #define CDIDev_hresMapHow_(this, dwObj, dwHow, ppropi, z)           \
       _CDIDev_hresMapHow_(this, dwObj, dwHow, ppropi)              \

#endif

STDMETHODIMP
    CDIDev_hresMapHow_(PDD this, DWORD dwObj, DWORD dwHow,
                       LPDIPROPINFO ppropi, LPCSTR s_szProc)
{
    HRESULT hres;

    if(this->pdcb != c_pdcbNil)
    {
        int iobj = 0;

        switch(dwHow)
        {

        case DIPH_DEVICE:
            if(dwObj == 0)
            {
                ppropi->iobj = 0xFFFFFFFF;
                ppropi->dwDevType = 0;
                hres = S_OK;
            } else
            {
                RPF("%s: dwObj must be zero if DIPH_DEVICE", s_szProc);
                hres = E_INVALIDARG;
            }
            break;

        case DIPH_BYOFFSET:
            if(this->pdix && this->rgiobj)
            {
                if(dwObj < this->dwDataSize)
                {
                    iobj = this->rgiobj[dwObj];
                    if(iobj >= 0)
                    {
                        AssertF(this->pdix[iobj].dwOfs == dwObj);
                        ppropi->iobj = iobj;
                        ppropi->dwDevType = this->df.rgodf[iobj].dwType;
                        hres = S_OK;
                        goto done;
                    } else
                    {
                        AssertF(iobj == -1);
                    }
                }

                SquirtSqflPtszV(sqfl | sqflBenign,
                                TEXT("%S: Invalid offset in dwObj. You may use DIPH_BYID to enum it."), s_szProc);

                //RPF("%s: Invalid offset in dwObj", s_szProc);

            } else
            {
                RPF("%s: Must have a data format to use if DIPH_BYOFFSET", s_szProc);
            }
            hres = DIERR_OBJECTNOTFOUND;
            goto done;

        case DIPH_BYID:
            for(iobj = this->df.dwNumObjs; --iobj >= 0; )
            {
                if(DIDFT_FINDMATCH(this->df.rgodf[iobj].dwType, dwObj))
                {
                    ppropi->iobj = iobj;
                    ppropi->dwDevType = this->df.rgodf[iobj].dwType;
                    hres = S_OK;
                    goto done;
                }
            }
            RPF("%s: Invalid ID in dwObj", s_szProc);
            hres = DIERR_OBJECTNOTFOUND;
            break;

        case DIPH_BYUSAGE:
            hres = IDirectInputDeviceCallback_MapUsage(this->pdcb,
                                                       dwObj, &ppropi->iobj);
            if(SUCCEEDED(hres))
            {
                ppropi->dwDevType = this->df.rgodf[ppropi->iobj].dwType;
            }
            break;

        default:
            RPF("%s: Invalid dwHow", s_szProc);
            hres = E_INVALIDARG;
            break;
        }

        done:;
    } else
    {
        RPF("ERROR: IDirectInputDevice: Not initialized");
        hres = DIERR_NOTINITIALIZED;
    }

    return hres;
}

#define CDIDev_hresMapHow(this, dwObj, dwHow, pdwOut)               \
       CDIDev_hresMapHow_(this, dwObj, dwHow, pdwOut, s_szProc)     \

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputDevice | GetObjectInfo |
 *
 *          Obtains information about an object.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *  @parm   OUT LPDIDEVICEOBJECTINSTANCE | pdidoi |
 *
 *          Receives information about the object.
 *          The caller "must" initialize the <e DIDEVICEOBJECTINSTANCE.dwSize>
 *          field before calling this method.
 *
 *  @parm   DWORD | dwObj |
 *
 *          Identifies the object for which the property is to be
 *          accessed.  The meaning of this value depends on the
 *          value of the <p dwHow> parameter.
 *          See the documentation of the <t DIPROPHEADER>
 *          structure for additional information.
 *
 *  @parm   DWORD | dwHow |
 *
 *          Identifies how <p dwObj> is to be interpreted.
 *          It must be one of the <c DIPH_*> values.
 *          See the documentation of the <t DIPROPHEADER>
 *          structure for additional information.
 *
 *  @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_OBJECTNOTFOUND>:  The specified object does not
 *          exist.
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_GetObjectInfoW(PV pddW, LPDIDEVICEOBJECTINSTANCEW pdoiW,
                          DWORD dwObj, DWORD dwHow)
{
    HRESULT hres;
    EnterProcR(IDirectInputDevice8::GetObjectInfo,
               (_ "ppxx", pddW, pdoiW, dwObj, dwHow));

    if(SUCCEEDED(hres = hresPvW(pddW)) &&
       SUCCEEDED(hres = hresFullValidWritePxCb2(pdoiW,
                                                DIDEVICEOBJECTINSTANCE_DX5W,
                                                DIDEVICEOBJECTINSTANCE_DX3W, 1)))
    {
        PDD this = _thisPvNm(pddW, ddW);
        DIPROPINFO propi;

        /*
         *  Must protect with the critical section to prevent
         *  another thread from Reset()ing behind our back.
         */
        CDIDev_EnterCrit(this);

        propi.pguid = 0;
        /*
         *  Prefix picks up that ppropi->iobj is uninitialized (mb:34557) in 
         *  the case of a BY_USAGE dwHow.  However, the value is only an 
         *  output for MapUsage which will always either set it or fail so 
         *  there is no error.
         */

        hres = CDIDev_hresMapHow(this, dwObj, dwHow, &propi);

        if(SUCCEEDED(hres))
        {
            if(dwHow != DIPH_DEVICE)
            {
                hres = CDIDev_GetObjectInfoHelper(this, &propi, pdoiW);
            } else
            {
                RPF("%s: Invalid dwHow", s_szProc);
                hres = E_INVALIDARG;
            }
        }

        if(FAILED(hres))
        {
            ScrambleBuf(&pdoiW->guidType,
                        pdoiW->dwSize -
                        FIELD_OFFSET(DIDEVICEOBJECTINSTANCEW, guidType));
        }

        CDIDev_LeaveCrit(this);
    }

    ExitBenignOleProcR();
    return hres;
}

#define CDIDev_GetObjectInfo2W          CDIDev_GetObjectInfoW

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | IDirectInputDeviceA | GetObjectInfo |
 *
 *          ANSI version of same.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *  @parm   OUT LPDIDEVICEOBJECTINSTANCEA | pdoiA |
 *
 *          Receives information about the device's identity.
 *
 *  @parm   DWORD | dwObj |
 *
 *          Identifies the object for which the property is to be
 *          accessed.
 *
 *  @parm   DWORD | dwHow |
 *
 *          Identifies how <p dwObj> is to be interpreted.
 *
 *  @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
    CDIDev_GetObjectInfoA(PV pddA, LPDIDEVICEOBJECTINSTANCEA pdoiA,
                          DWORD dwObj, DWORD dwHow)
{
    HRESULT hres;
    EnterProcR(IDirectInputDevice8::GetObjectInfo,
               (_ "ppxx", pddA, pdoiA, dwObj, dwHow));

    if(SUCCEEDED(hres = hresPvA(pddA)) &&
       SUCCEEDED(hres = hresFullValidWritePxCb2(pdoiA,
                                                DIDEVICEOBJECTINSTANCE_DX5A,
                                                DIDEVICEOBJECTINSTANCE_DX3A, 1)))
    {
        PDD this = _thisPvNm(pddA, ddA);
        DIDEVICEOBJECTINSTANCEW doiW;

        doiW.dwSize = cbX(DIDEVICEOBJECTINSTANCEW);

        hres = CDIDev_GetObjectInfoW(&this->ddW, &doiW, dwObj, dwHow);

        if(SUCCEEDED(hres))
        {
            ObjectInfoWToA(pdoiA, &doiW);
            hres = S_OK;
        }
    }

    ExitBenignOleProcR();
    return hres;
}

#define CDIDev_GetObjectInfo2A          CDIDev_GetObjectInfoA

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputDevice | GetDeviceInfo |
 *
 *          Obtains information about the device's identity.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *  @parm   OUT LPDIDEVICEINSTANCE | pdidi |
 *
 *          Receives information about the device's identity.
 *          The caller "must" initialize the <e DIDEVICEINSTANCE.dwSize>
 *          field before calling this method.
 *
 *          If <e DIDEVICEINSTANCE.dwSize> is equal to the size of
 *          the <t DIDEVICEINSTANCE_DX3> structure, then a
 *          DirectX 3.0-compatible structure is returned instead of
 *          a DirectX 5.0 structure.
 *
 *  @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
    CDIDev_GetDeviceInfoW(PV pddW, LPDIDEVICEINSTANCEW pdidiW)
{
    HRESULT hres;
    EnterProcR(IDirectInputDevice8::GetDeviceInfo, (_ "pp", pddW, pdidiW));

    if(SUCCEEDED(hres = hresPvW(pddW)) &&
       SUCCEEDED(hres = hresFullValidWritePxCb2(pdidiW,
                                                DIDEVICEINSTANCE_DX5W,
                                                DIDEVICEINSTANCE_DX3W, 1)))
    {
        PDD this = _thisPvNm(pddW, ddW);

        /*
         *  Must protect with the critical section to prevent
         *  another thread from Reset()ing behind our back.
         */
        CDIDev_EnterCrit(this);

        pdidiW->guidInstance = this->guid;
        pdidiW->guidProduct  = this->guid;

        /*
         *  Don't overwrite the dwSize, guidInstance, or guidProduct.
         *  Start at the dwDevType.
         */

        ZeroBuf(&pdidiW->dwDevType,
                pdidiW->dwSize - FIELD_OFFSET(DIDEVICEINSTANCEW, dwDevType));

        hres = this->pdcb->lpVtbl->GetDeviceInfo(this->pdcb, pdidiW);

        if(FAILED(hres))
        {
            ScrambleBuf(&pdidiW->guidInstance,
                        cbX(DIDEVICEINSTANCEW) -
                        FIELD_OFFSET(DIDEVICEINSTANCEW, guidInstance));
        }

        CDIDev_LeaveCrit(this);
    }

    ExitOleProcR();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   void | DeviceInfoWToA |
 *
 *          Convert a <t DIDEVICEINSTANCEW> to a <t DIDEVICEINSTANCE_DX3A>
 *          <t DIDEVICEINSTANCE_DX5A> or a <t DIDEVICEINSTANCE_DX8A>.
 *
 *  @parm   LPDIDIDEVICEINSTANCEA | pdiA |
 *
 *          Destination.
 *
 *  @parm   LPCDIDIDEVICEINSTANCEW | pdiW |
 *
 *          Source.
 *
 *****************************************************************************/

void EXTERNAL
    DeviceInfoWToA(LPDIDEVICEINSTANCEA pdiA, LPCDIDEVICEINSTANCEW pdiW)
{
    EnterProc(DeviceInfoWToA, (_ "pp", pdiA, pdiW));

    AssertF(pdiW->dwSize == sizeof(DIDEVICEINSTANCEW));

    AssertF(IsValidSizeDIDEVICEINSTANCEA(pdiA->dwSize));

    pdiA->guidInstance = pdiW->guidInstance;
    pdiA->guidProduct  = pdiW->guidProduct;
    pdiA->dwDevType    = pdiW->dwDevType;

    UToA(pdiA->tszInstanceName, cA(pdiA->tszInstanceName), pdiW->tszInstanceName);
    UToA(pdiA->tszProductName, cA(pdiA->tszProductName), pdiW->tszProductName);

    if(pdiA->dwSize >= cbX(DIDEVICEINSTANCE_DX5A))
    {
        pdiA->guidFFDriver       = pdiW->guidFFDriver;
        pdiA->wUsage             = pdiW->wUsage;
        pdiA->wUsagePage         = pdiW->wUsagePage;
    }

    ExitProc();
}


/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   void | Device8WTo8A |
 *
 *          Convert a <t LPDIRECTINPUTDEVICE8W> to a <t LPDIRECTINPUTDEVICE8A>.
 *
 *  @parm   LPDIRECTINPUTDEVICE8A * | ppdid8A |
 *
 *          Destination.
 *
 *  @parm   LPDIRECTINPUTDEVICE8W | pdid8W |
 *
 *          Source.
 *
 *****************************************************************************/

void EXTERNAL
    Device8WTo8A(LPDIRECTINPUTDEVICE8A *ppdid8A, LPDIRECTINPUTDEVICE8W pdid8W)
{
    PDD this;

    EnterProc(Device8WTo8A, (_ "pp", ppdid8A, pdid8W));

    this = _thisPvNm(pdid8W, ddW);
    *ppdid8A = (ThisInterfaceA*)&this->ddA;

    ExitProc();
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | IDirectInputDeviceA | GetDeviceInfo |
 *
 *          ANSI version of same.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *  @parm   OUT LPDIDEVICEINSTANCEA | pdidiA |
 *
 *          Receives information about the device's identity.
 *
 *  @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
    CDIDev_GetDeviceInfoA(PV pddA, LPDIDEVICEINSTANCEA pdidiA)
{
    HRESULT hres;
    EnterProcR(IDirectInputDevice8::GetDeviceInfo, (_ "pp", pddA, pdidiA));

    if(SUCCEEDED(hres = hresPvA(pddA)) &&
       SUCCEEDED(hres = hresFullValidWritePxCb2(pdidiA,
                                                DIDEVICEINSTANCE_DX5A,
                                                DIDEVICEINSTANCE_DX3A, 1)))
    {
        PDD this = _thisPvNm(pddA, ddA);
        DIDEVICEINSTANCEW diW;

        diW.dwSize = cbX(DIDEVICEINSTANCEW);

        hres = CDIDev_GetDeviceInfoW(&this->ddW, &diW);

        if(SUCCEEDED(hres))
        {
            DeviceInfoWToA(pdidiA, &diW);
            hres = S_OK;
        }
    }

    ExitOleProcR();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method void | CDIDev | UnhookCwp |
 *
 *          Remove the CallWndProc handler.
 *
 *          See <mf CDIDev::InstallCwp> for details.
 *
 *****************************************************************************/

HWND g_hwndExclusive;
HHOOK g_hhkCwp;

void INTERNAL
    CDIDev_UnhookCwp(void)
{
    DllEnterCrit();

    if(g_hhkCwp)
    {
        UnhookWindowsHookEx(g_hhkCwp);
        g_hhkCwp = 0;
        g_hwndExclusive = 0;
    }

    DllLeaveCrit();
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CDIDev | AddForegroundDevice |
 *
 *          Add ourselves to the list of devices that need to be
 *          unacquired when the window loses foreground activation.
 *
 *  @parm   PDD | this |
 *
 *          Device to be added.
 *
 *  @devnote
 *
 *          Note that we do not need to AddRef the device, because
 *          <f CDIDev_Finalize> will unacquire the device for us
 *          automatically, so we will never be freed while still
 *          acquired.
 *
 *          (Note that if we did choose to AddRef, it must be done
 *          outside the DLL critical
 *          section, in order to preserve the semaphore hierarchy.)
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_AddForegroundDevice(PDD this)
{
    HRESULT hres;
    DllEnterCrit();

    hres = GPA_Append(g_hgpaExcl, this);
    Common_Hold(this);
    DllLeaveCrit();

    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method void | CDIDev | DelForegroundDevice |
 *
 *          Remove ourselves from the list, if we're there.
 *          It is not an error if we aren't on the list, because
 *          in the case of forced unacquire, the list is blanked
 *          out in order to avoid race conditions where somebody
 *          tries to acquire a device immediately upon receiving
 *          foreground activation, before we get a chance to
 *          unacquire all the old guys completely.
 *
 *  @parm   PDD | this |
 *
 *          Device to be removed.
 *
 *  @devnote
 *
 *          Note that the Unhold must be done outside the DLL critical
 *          section, in order to preserve the semaphore hierarchy.
 *
 *          Theoretically, the unhold will never drop the reference count
 *          to zero (because the latest it could be called is during
 *          the AppFinalize, where there is stll the outstanding refcount
 *          from the external ref that hasn't been released yet).
 *
 *          But it's better to play it safe and always release the
 *          object outside.
 *
 *****************************************************************************/

void INTERNAL
    CDIDev_DelForegroundDevice(PDD this)
{
    HRESULT hres;

    DllEnterCrit();

    hres = GPA_DeletePtr(g_hgpaExcl, this);
    if(hres == hresUs(0))
    {            /* If the last one went away */
        GPA_Term(g_hgpaExcl);           /* Free the tracking memory */
        CDIDev_UnhookCwp();             /* Then unhook ourselves */
    }

    DllLeaveCrit();

    if(SUCCEEDED(hres))
    {
        Common_Unhold(this);
    }
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   LRESULT | CDIDev_CallWndProc |
 *
 *          Thread-specific CallWndProc handler.
 *
 *          Note that we need only one of these, since only the foreground
 *          window will require a hook.
 *
 *  @parm   int | nCode |
 *
 *          Notification code.
 *
 *  @parm   WPARAM | wp |
 *
 *          "Specifies whether the message is sent by the current process."
 *          We don't care.
 *
 *  @parm   LPARAM | lp |
 *
 *          Points to a <t CWPSTRUCT> which describes the message.
 *
 *  @returns
 *
 *          Always chains to the next hook.
 *
 *****************************************************************************/

LRESULT CALLBACK
    CDIDev_CallWndProc(int nCode, WPARAM wp, LPARAM lp)
{
    LRESULT rc;
    LPCWPSTRUCT pcwp = (LPCWPSTRUCT)lp;
  #ifdef WINNT
    static BOOL fKillFocus = FALSE;
    static BOOL fIconic = FALSE;

    fIconic = FALSE;

    /*
     * This part of code is to fix Windows bug 430051.
     * The logic is: if WM_KILLFOCUS is followed by WM_SIZE(minimized),
     * then the app is minimized from full screen mode.
     * This combination should only happen to full screen mode game using DDraw.
     */
    if(pcwp->message == WM_KILLFOCUS)
    {
        fKillFocus = TRUE;
    } else if(pcwp->message == WM_SETFOCUS)
    {
        fKillFocus = FALSE;
    } else if (pcwp->message == WM_SIZE)
    {
        if(pcwp->wParam == SIZE_MINIMIZED){
            if( fKillFocus ) {
                fIconic = TRUE;
                fKillFocus = FALSE;
            }else{
                fKillFocus = FALSE;
            }
        } else {
            fKillFocus = FALSE;
        }
    }
  #endif
  
    rc = CallNextHookEx(g_hhkCwp, nCode, wp, lp);

    if( nCode == HC_ACTION && (pcwp->message == WM_ACTIVATE 
                             #ifdef WINNT
                               || fIconic
                             #endif
                               ) 
      )
    {
        PDD *rgpdid;
        UINT ipdid, cpdid;

      #ifdef WINNT
        fIconic = FALSE;
      #endif

        /*
         *  We cannot mess with items while inside the DLL critical section,
         *  because that would violate our semaphore hierarchy.
         *
         *  Instead, we stash the active item list and replace it with
         *  an empty list.  Then, outside the DLL critical section, we
         *  calmly operate on each item.
         */
        DllEnterCrit();
        rgpdid = (PV)g_hgpaExcl->rgpv;
        cpdid = g_hgpaExcl->cpv;
        GPA_Init(g_hgpaExcl);

        /*
         *  Some sanity checking here.
         */

        for(ipdid = 0; ipdid < cpdid; ipdid++)
        {
            AssertF(rgpdid[ipdid]);
        }

        DllLeaveCrit();

        /*
         *  Note that InternalUnacquire will set the notification
         *  event so the app knows that input was lost.
         */
        for(ipdid = 0; ipdid < cpdid; ipdid++)
        {
            AssertF(rgpdid[ipdid]);
            // 7/19/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers.
            SquirtSqflPtszV(sqfl,
                            TEXT("Forcing unacquire of %p due to focus loss"),
                            rgpdid[ipdid]);
            CDIDev_InternalUnacquire(rgpdid[ipdid]);
            /*
             *  Prefix notices (mb:34651) that the above could release the 
             *  interface, causing the following to party on garbage but this 
             *  should be OK as long as we don't have a refcounting bug.
             */
            Common_Unhold(rgpdid[ipdid]);
        }
        FreePpv(&rgpdid);

        CDIDev_UnhookCwp();
    }

    return rc;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CDIDev | CanAcquire |
 *
 *          Determine whether the device may be acquired exclusively.
 *
 *          If exclusive access is not requested, then the function
 *          succeeds vacuously.
 *
 *          If exclusive access is requested, then the window must
 *          be the foreground window and must belong to the current
 *          process.
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_CanAcquire(PDD this)
{
    HRESULT hres;

    AssertF(CDIDev_IsConsistent(this));
    if(this->discl & DISCL_FOREGROUND)
    {
        HWND hwndForeground = GetForegroundWindow();

		AssertF(this->hwnd);
        /*
         *  Note that we don't have to do an IsWindow() on this->hwnd,
         *  because GetForegroundWindow() will always return a valid
         *  window or NULL.  Since we already tested this->hwnd != 0
         *  above, the only way the equality can occur is if the window
         *  handle is indeed valid.
         */
        if(this->hwnd == hwndForeground && !IsIconic(this->hwnd))
        {
            /*
             *  Need to make sure that the window "still" belongs to
             *  this process, in case the window handle got recycled.
             */
            DWORD idProcess;
            GetWindowThreadProcessId(this->hwnd, &idProcess);
            if(idProcess == GetCurrentProcessId())
            {
                hres = S_OK;
            } else
            {
                /*
                 *  Put a permanently invalid handle here so that we
                 *  won't accidentally take a new window that happens
                 *  to get a recycled handle value.
                 */
                this->hwnd = INVALID_HANDLE_VALUE;
                RPF("Error: Window destroyed while associated with a device");
                hres = E_INVALIDARG;
            }
        } else
        {
            hres = DIERR_OTHERAPPHASPRIO;
        }
    } else
    {                        /* No window; vacuous success */
        hres = S_OK;
    }
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CDIDev | InstallCwp |
 *
 *          Install the CallWndProc handler.
 *
 *          There is a bit of subtlety in the way this works.
 *          Since only foreground windows may acquire exclusive access,
 *          we need only one hook (for there is but one foreground
 *          window in the system).
 *
 *          _NT_:  Does NT handle this correctly in the face of
 *          multiple window stations?
 *
 *          The tricky part is that input loss occurs asynchronously.
 *          So a device that registers a <f CallWindowProc> hook doesn't
 *          find out that the input has been lost until the app next
 *          calls <f Unacquire>.
 *
 *          So the way this is done is via a collection of global
 *          variables (which must be accessed atomically).
 *
 *          <p g_hhkCwp> is the hook handle itself.  It is zero when
 *          no hook is installed.
 *
 *          Note that we install the windows hook while inside both the
 *          object critical section and the DLL critical section.
 *          You might think we'd risk deadlocking with the hook procedure,
 *          in case the window asynchronously deactivates while we're
 *          installing the hook.  But your worries are unfounded:
 *          If the window is on the current thread, then window messages
 *          won't be dispatched because we never call <f GetMessage> or
 *          go into a modal loop.  And if the window is on another thread,
 *          then that other thread will simply have to wait until we're done.
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_InstallCwp(PDD this)
{
    HRESULT hres;

    if(this->discl & DISCL_FOREGROUND)
    {
        AssertF(this->hwnd);
        hres = CDIDev_CanAcquire(this);
        if(SUCCEEDED(hres))
        {
            hres = CDIDev_AddForegroundDevice(this);
            if(SUCCEEDED(hres))
            {
                DllEnterCrit();
                if(!g_hhkCwp)
                {            /* We're the first one */
                    g_hwndExclusive = this->hwnd;
                    g_hhkCwp = SetWindowsHookEx(WH_CALLWNDPROC,
                                                CDIDev_CallWndProc, g_hinst,
                                                GetWindowThreadProcessId(this->hwnd, 0));
                } else
                {
                    AssertF(g_hwndExclusive == this->hwnd);
                }

                DllLeaveCrit();
                /*
                 *  There is a race condition up above, where the foreground
                 *  window can change between the call to CanAcquire() and
                 *  the call to SetWindowsHookEx().  Close the window by
                 *  checking a second time after the hook is installed.
                 *
                 *  If we leave the window open, it's possible that we will
                 *  perform a physical acquire while the wrong window has
                 *  foreground activation.  Then, of course, we are never told
                 *  that *our* window lost activation, and the physical device
                 *  remains acquired forever.
                 */
                hres = CDIDev_CanAcquire(this);
                if(SUCCEEDED(hres))
                {
                } else
                {
                    SquirtSqflPtszV(sqflError,
                                    TEXT("Window no longer foreground; ")
                                    TEXT("punting acquire"));
                    CDIDev_InternalUnacquire(this);
                }
            }
        } else
        {
            hres = DIERR_OTHERAPPHASPRIO;
        }
    } else
    {                        /* No window; vacuous success */
        hres = S_OK;
    }

    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | IDirectInputDevice | RealUnacquire |
 *
 *          Release access to the device, even if the device was only
 *          partially acquired.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *  @returns
 *
 *          None.
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_RealUnacquire(PDD this)
{
    HRESULT hres;

    hres = this->pdcb->lpVtbl->Unacquire(this->pdcb);
    if(hres == S_FALSE)
    {
        if(this->fAcquiredInstance)
        {
            this->fAcquiredInstance = 0;
            hres = Hel_UnacquireInstance(this->pvi);
            AssertF(SUCCEEDED(hres));
        } else
        {
            hres = S_OK;
        }
    }

    return hres;
}


/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CDIDev | FFAcquire |
 *
 *          The device has been successfully acquired.  Do any
 *          necessary force feedback related acquisition goo.
 *
 *  @cwrap  PDD | pdd
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_FFAcquire(PDD this)
{
    HRESULT hres;

    AssertF(CDIDev_InCrit(this));

    if(this->pes && (this->discl & DISCL_EXCLUSIVE))
    {
        if(SUCCEEDED(hres = this->pes->lpVtbl->SendForceFeedbackCommand(
                                                                       this->pes, &this->sh,
                                                                       DISFFC_FORCERESET)))
        {

            CDIDev_RefreshGain(this);

            /*
             *  If the center spring is to be disabled,
             *  then disable the center spring.
             */
            if(!this->dwAutoCenter)
            {
                this->pes->lpVtbl->SendForceFeedbackCommand(
                                                           this->pes, &this->sh,
                                                           DISFFC_STOPALL);
            }

            hres = S_OK;
        }

    } else
    {
        hres = S_OK;
    }

    return hres;
}

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputDevice | Acquire |
 *
 *          Obtains access to the device.
 *
 *          Device acquisition does not reference-count.  If a device is
 *          acquired twice then unacquired once, the device is unacquired.
 *
 *          Before the device can be acquired, a data format must
 *          first be set via the <mf IDirectInputDevice::SetDataFormat>
 *          method.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *  @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 device has already been acquired.  Note
 *          that this value is a success code.
 *
 *          <c DIERR_OTHERAPPHASPRIO>: Access to the device was not granted.
 *          The most common cause of this is attempting to acquire a
 *          device with the <c DISCL_FOREGROUND> cooperative level when
 *          the associated window is not foreground.
 *
 *          This error code is also returned if an attempt to
 *          acquire a device in exclusive mode fails because the device
 *          is already acquired in exclusive mode by somebody else.
 *
 *          <c DIERR_INVALIDPARAM> = <c E_INVALIDARG>:  The device
 *          does not have a selected data format.
 */
/*
 *          The point at which we take the exclusive semaphore is important.
 *          We should do it after preliminary validation of foreground
 *          permission, so that we don't accidentally lock out somebody
 *          else who legitimately has permission.
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_Acquire(PV pdd _THAT)
{
    HRESULT hres;
    EnterProcR(IDirectInputDevice8::Acquire, (_ "p", pdd));

    if(SUCCEEDED(hres = hresPvT(pdd)))
    {
        PDD this = _thisPv(pdd);

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

        /*
         *  The app explicitly messed with acquisition.  Any problems
         *  retrieving data are now the apps' fault.
         */
        this->hresNotAcquired = DIERR_NOTACQUIRED;

        //We now need a pvi even in where the device doesn't use VxDs
        if(this->pdix && this->pvi)
        {
            if(this->pvi->fl & VIFL_ACQUIRED)
            {
                hres = S_FALSE;
            } else if(SUCCEEDED(hres = CDIDev_CanAcquire(this)))
            {

                hres = Excl_Acquire(&this->guid, this->hwnd, this->discl);
                if(SUCCEEDED(hres))
                {

                    if(SUCCEEDED(hres = CDIDev_FFAcquire(this)))
                    {
                        hres = this->pdcb->lpVtbl->Acquire(this->pdcb);
                        if(SUCCEEDED(hres))
                        {
                            if(hres == S_FALSE)
                            {
                                hres = Hel_AcquireInstance(this->pvi);
                                if(SUCCEEDED(hres))
                                {
                                    this->fAcquiredInstance = 1;

                                    /*
                                     *  If relative mode, need to prime the
                                     *  pvLastBuffer with the current state.
                                     */

                                    if(this->pvi->fl & VIFL_RELATIVE)
                                    {
                                        hres = this->pdcb->lpVtbl->GetDeviceState(
                                                                                 this->pdcb, this->pvLastBuffer);
                                        if(FAILED(hres))
                                        {
                                            goto unacquire;
                                        }
                                    }

                                } else
                                {
                                    goto unacquire;
                                }
                            }

                            /*
                             *  Note that InstallCwp must be the last thing
                             *  we do, because it will add us to the foreground
                             *  list, and none of our error exit paths remove us.
                             */
                            hres = CDIDev_InstallCwp(this);
                            if(SUCCEEDED(hres))
                            {
                                this->fAcquired = 1;
                                this->fOnceAcquired = 1;

                                /*
                                 *  From now on, if we lose acquisition,
                                 *  it's not the app's fault.
                                 */
                                this->hresNotAcquired = DIERR_INPUTLOST;

                                hres = S_OK;
                            } else
                            {
                                goto unacquire;
                            }

                        } else
                        {
                            unacquire:;
                            CDIDev_RealUnacquire(this);
                        }
                    }
                }
            }
        } else
        {
            hres = E_INVALIDARG;
        }

        CDIDev_LeaveCrit(this);
    }

    ExitOleProcR();
    return hres;
}

#ifdef XDEBUG

CSET_STUBS(Acquire, (PV pdd), (pdd THAT_))

#else

    #define CDIDev_AcquireA                 CDIDev_Acquire
    #define CDIDev_AcquireW                 CDIDev_Acquire

#endif

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | IDirectInputDevice | InternalUnacquire |
 *
 *          This does the real work of unacquiring.  The internal
 *          version bypasses the "the app requested this" flag,
 *          so when the app goes to request something, it gets
 *          <c DIERR_INPUTLOST> instead of <c DIERR_NOTACQUIRED>.
 *
 *          If the application error code is <c DIERR_INPUTLOST>, then
 *          we will also signal the associated event so that it knows
 *          that the state changed.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_InternalUnacquire(PDD this)
{
    HRESULT hres;
    EnterProcR(IDirectInputDevice8::InternalUnacquire, (_ "p", this));

    /*
     *  Must protect with the critical section to prevent confusing other
     *  methods which change their behavior depending on whether the device
     *  is acquired.
     */
    CDIDev_EnterCrit(this);

    if(this->fAcquired)
    {
        AssertF(this->pdcb != c_pdcbNil);
        this->fAcquired = 0;
        Excl_Unacquire(&this->guid, this->hwnd, this->discl);
        if(this->discl & DISCL_FOREGROUND)
        {
            AssertF(this->hwnd);
            CDIDev_DelForegroundDevice(this);
          #ifdef WINNT
            if( IsIconic(this->hwnd) ) {
                this->fUnacquiredWhenIconic = 1;
            }
          #endif
            /*
             *  Prefix notices (mb:34651) that the above could release the 
             *  interface, causing the following to party on garbage but only 
             *  if we have a refcounting bug so "By design".
             */
        }
        /*
         *  ISSUE-2001/03/29-timgill multithreading means we cannot rely on Excl_Unaquire() return values
         *  We cannot trust the return value (if we made one)
         *  of Excl_Unacquire, because another instance may have
         *  snuck in and acquired the device after we Excl_Unacquire'd
         *  it and started doing force feedback on it.
         *
         *  We need to fix this with the joystick mutex.
         */
        if(this->pes && (this->discl & DISCL_EXCLUSIVE))
        {
            this->pes->lpVtbl->SendForceFeedbackCommand(
                                                       this->pes, &this->sh, DISFFC_RESET);
            this->sh.dwTag = 0;
        }

        hres = CDIDev_RealUnacquire(this);
        if(this->hresNotAcquired == DIERR_INPUTLOST)
        {
            CDIDev_SetNotifyEvent(this);
        }
    } else
    {
        hres = S_FALSE;
    }

    CDIDev_LeaveCrit(this);


    ExitOleProcR();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputDevice | Unacquire |
 *
 *          Release access to the device.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *  @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 object is not currently acquired.
 *          This may have been caused by a prior loss of input.
 *          Note that this is a success code.
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_Unacquire(PV pdd _THAT)
{
    HRESULT hres;
    EnterProcR(IDirectInputDevice8::Unacquire, (_ "p", pdd));

    if(SUCCEEDED(hres = hresPvT(pdd)))
    {
        PDD this = _thisPv(pdd);

        /*
         *  The app explicitly messed with acquisition.  Any problems
         *  retrieving data are now the apps' fault.
         */
        this->hresNotAcquired = DIERR_NOTACQUIRED;

        hres = CDIDev_InternalUnacquire(this);

    }

    ExitOleProcR();
    return hres;
}

#ifdef XDEBUG

CSET_STUBS(Unacquire, (PV pdd), (pdd THAT_))

#else

    #define CDIDev_UnacquireA               CDIDev_Unacquire
    #define CDIDev_UnacquireW               CDIDev_Unacquire

#endif

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method PDIPROPVALIDINFO | IDirectInputDevice | ppviFind |
 *
 *          Locate the DIPROPVALIDINFO structure that describes
 *          the predefined property.
 *
 *  @parm   const GUID * | pguid |
 *
 *          Property guid, or predefined property.
 *
 *  @returns
 *
 *          Pointer to a const <t DIPROPVALIDINFO> that describes
 *          what is and is not valid for this property.
 *
 *          Returns 0 if the property is not one of the predefined
 *          properties.
 *
 *****************************************************************************/

#pragma BEGIN_CONST_DATA

typedef struct DIPROPVALIDINFO
{
    PCGUID  pguid;                      /* Property name */
    DWORD   dwSize;                     /* expected size */
    DWORD   fl;                         /* flags */
} DIPROPVALIDINFO, *PDIPROPVALIDINFO;

/*
 *  Note that the flags are negative in sense.
 *  This makes validation easier.
 */
#define DIPVIFL_NOTDEVICE   0x00000001  /* Cannot be device */
#define DIPVIFL_NOTOBJECT   0x00000002  /* Cannot be object */
#define DIPVIFL_READONLY    0x00000004  /* Cannot be set */
#define DIPVIFL_NOTPRIVATE  0x00000008  /* Cannot handle private pvi */
#define DIPVIFL_NOTACQUIRED 0x00000010  /* Cannot modify while acquired */

DIPROPVALIDINFO c_rgpvi[] = {

    {
        DIPROP_BUFFERSIZE,
        cbX(DIPROPDWORD),
        DIPVIFL_NOTOBJECT | DIPVIFL_NOTPRIVATE | DIPVIFL_NOTACQUIRED,
    },

    {
        DIPROP_AXISMODE,
        cbX(DIPROPDWORD),
        DIPVIFL_NOTOBJECT | DIPVIFL_NOTPRIVATE | DIPVIFL_NOTACQUIRED,
    },

    {
        DIPROP_GRANULARITY,
        cbX(DIPROPDWORD),
        DIPVIFL_NOTDEVICE | DIPVIFL_READONLY | DIPVIFL_NOTACQUIRED,
    },

    {
        DIPROP_RANGE,
        cbX(DIPROPRANGE),
        DIPVIFL_NOTDEVICE | DIPVIFL_NOTACQUIRED,
    },

    /*
     *  Note that you can set the dead zone on the entire device.
     *  This is the same as applying it to each axis individually.
     */
    {
        DIPROP_DEADZONE,
        cbX(DIPROPDWORD),
        DIPVIFL_NOTACQUIRED,
    },

    /*
     *  Note that you can set the saturation on the entire device.
     *  This is the same as applying it to each axis individually.
     */
    {
        DIPROP_SATURATION,
        cbX(DIPROPDWORD),
        DIPVIFL_NOTACQUIRED,
    },

    /*
     *  Note that you can change the gain either while acquired
     *  or not.  Your choice.
     */
    {
        DIPROP_FFGAIN,
        cbX(DIPROPDWORD),
        DIPVIFL_NOTOBJECT,
    },

    /*
     *  Note that the FF load is meaningful only when acquired,
     *  so we'd better not complain if they access it while acquired!
     */
    {
        DIPROP_FFLOAD,
        cbX(DIPROPDWORD),
        DIPVIFL_NOTOBJECT | DIPVIFL_READONLY,
    },

    {
        DIPROP_AUTOCENTER,
        cbX(DIPROPDWORD),
        DIPVIFL_NOTOBJECT | DIPVIFL_NOTACQUIRED,
    },

    {
        DIPROP_CALIBRATIONMODE,
        cbX(DIPROPDWORD),
        DIPVIFL_NOTOBJECT | DIPVIFL_NOTACQUIRED,
    },

    {
        DIPROP_CALIBRATION,
        cbX(DIPROPCAL),
        DIPVIFL_NOTDEVICE | DIPVIFL_NOTACQUIRED,
    },

    {
        DIPROP_GUIDANDPATH,
        cbX(DIPROPGUIDANDPATH),
        DIPVIFL_NOTOBJECT | DIPVIFL_READONLY,
    },

    {
        DIPROP_INSTANCENAME,
        cbX(DIPROPSTRING),
        DIPVIFL_NOTOBJECT,
    },

    {
        DIPROP_PRODUCTNAME,
        cbX(DIPROPSTRING),
        DIPVIFL_NOTOBJECT,
    },

    {
        DIPROP_MAXBUFFERSIZE,
        cbX(DIPROPDWORD),
        DIPVIFL_NOTOBJECT | DIPVIFL_NOTPRIVATE | DIPVIFL_NOTACQUIRED,
    },


    {
        DIPROP_JOYSTICKID,
        cbX(DIPROPDWORD),
        DIPVIFL_NOTOBJECT |  DIPVIFL_NOTACQUIRED,
    },

    {
        DIPROP_GETPORTDISPLAYNAME,
        cbX(DIPROPSTRING),
        DIPVIFL_NOTOBJECT | DIPVIFL_READONLY,
    },

    /*
     *  Note that you can change the report ID  while acquired
     *  or not.  Your choice.
     */
    {
        DIPROP_ENABLEREPORTID,
        cbX(DIPROPDWORD),
        0x0,
    },
#if 0
    {
        DIPROP_SPECIFICCALIBRATION,
        cbX(DIPROPCAL),
        DIPVIFL_NOTDEVICE | DIPVIFL_NOTACQUIRED,
    },
#endif

    {
        DIPROP_PHYSICALRANGE,
        cbX(DIPROPRANGE),
        DIPVIFL_NOTDEVICE | DIPVIFL_NOTACQUIRED,
    },

    {
        DIPROP_LOGICALRANGE,
        cbX(DIPROPRANGE),
        DIPVIFL_NOTDEVICE | DIPVIFL_NOTACQUIRED,
    },

    {
        DIPROP_KEYNAME,
        cbX(DIPROPSTRING),
        DIPVIFL_NOTDEVICE | DIPVIFL_NOTACQUIRED,
    },

    {
        DIPROP_SCANCODE,
        cbX(DIPROPDWORD),
        DIPVIFL_NOTDEVICE | DIPVIFL_NOTACQUIRED,
    },

    {
        DIPROP_APPDATA,
        cbX(DIPROPPOINTER),
        DIPVIFL_NOTDEVICE | DIPVIFL_NOTACQUIRED,
    },

    {
        DIPROP_CPOINTS,
        cbX(DIPROPCPOINTS),
        DIPVIFL_NOTDEVICE | DIPVIFL_NOTACQUIRED,
    },

    {
        DIPROP_VIDPID,
        cbX(DIPROPDWORD),
        DIPVIFL_NOTOBJECT | DIPVIFL_READONLY,
    },

    {
        DIPROP_USERNAME,
        cbX(DIPROPSTRING),
        DIPVIFL_NOTOBJECT | DIPVIFL_READONLY,
    },

    {
        DIPROP_TYPENAME,
        cbX(DIPROPSTRING),
        DIPVIFL_NOTOBJECT | DIPVIFL_READONLY,
    },

    {
        DIPROP_MAPFILE,
        cbX(DIPROPSTRING),
        DIPVIFL_NOTOBJECT | DIPVIFL_READONLY,
    },

};

#pragma END_CONST_DATA

STDMETHODIMP_(PDIPROPVALIDINFO)
CDIDev_ppviFind(PCGUID pguid)
{
    PDIPROPVALIDINFO ppvi;
    UINT ipvi;

    for(ipvi = 0, ppvi = c_rgpvi; ipvi < cA(c_rgpvi); ipvi++, ppvi++)
    {
        if(ppvi->pguid == pguid)
        {
            goto found;
        }
    }
    ppvi = 0;

    found:
    return ppvi;

}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | IDirectInputDevice | hresValidProp |
 *
 *          Check that the property structure makes sense.
 *          Returns the object index for further processing.
 *
 *  @parm   const GUID * | pguid |
 *
 *          Property guid, or predefined property.
 *
 *  @parm   LPCDIPROPHEADER | pdiph |
 *
 *          Propery header structure.
 *
 *  @parm   BOOL | fWrite |
 *
 *          Whether property should be validate for writing.
 *
 *  @parm   OUT LPDIPROPINFO | ppropi |
 *
 *          Receives object index.
 *
 *****************************************************************************/

typedef BOOL (WINAPI *PFNBAD)(PCV pv, UINT cb);

STDMETHODIMP
    CDIDev_hresValidProp(PDD this, const GUID *pguid, LPCDIPROPHEADER pdiph,
                         BOOL fWrite, LPDIPROPINFO ppropi)
{
    HRESULT hres;
    PFNBAD pfnBad;
    EnterProcR(IDirectInputDevice8::Get/SetProperty,
               (_ "pxpx", this, pguid, pdiph, fWrite));

    AssertF(CDIDev_InCrit(this));

    if(fWrite)
    {
        pfnBad = (PFNBAD)IsBadWritePtr;
    } else
    {
        pfnBad = (PFNBAD)IsBadReadPtr;
    }

    if(!pfnBad(pdiph, cbX(DIPROPHEADER)) &&
       pdiph->dwHeaderSize == cbX(DIPROPHEADER) &&
       pdiph->dwSize % 4 == 0 &&
       pdiph->dwSize >= pdiph->dwHeaderSize &&
       !pfnBad(pdiph, pdiph->dwSize))
    {

        /*
         *  Now convert the item descriptor into an index.
         */
        hres = CDIDev_hresMapHow(this, pdiph->dwObj, pdiph->dwHow, ppropi);

        if(SUCCEEDED(hres))
        {

            /*
             *  Now validate the property id or guid.
             */
            if(HIWORD((UINT_PTR)pguid) == 0)
            {

                PDIPROPVALIDINFO ppvi;

                ppvi = CDIDev_ppviFind(pguid);

                /*
                 *  Note that if we don't find the GUID in our list,
                 *  we fail it straight away.  This prevents ISVs
                 *  from trying to create properties in the Microsoft
                 *  Reserved range.
                 */
                if(ppvi)
                {
                    if( ppvi->pguid == DIPROP_CALIBRATION ) {
                        if( pdiph->dwSize == ppvi->dwSize ||
                            pdiph->dwSize == cbX(DIPROPCALPOV) ) 
                        {
                            hres = S_OK;
                        } else {
                            RPF("%s: Arg 2: Invalid dwSize for property", s_szProc);
                            hres = E_INVALIDARG;
                        }
                    } else if( pdiph->dwSize == ppvi->dwSize )
                    {
                        if( pguid == DIPROP_KEYNAME || pguid == DIPROP_SCANCODE ) {
                            // Fix Manbug 28888.
                            // DIPROP_KEYNAME and DIPROP_SCANCODE are not a property for device.
                            if( pdiph->dwHow == DIPH_DEVICE ) {
                                hres = E_INVALIDARG;
                            }
                        }
                        else 
                        {
                            if(fWrite)
                            {
                                ScrambleBuf((PV)(pdiph+1), pdiph->dwSize - cbX(DIPROPHEADER) );
                            }
                            hres = S_OK;
                        }
                    } else
                    {
                        RPF("%s: Arg 2: Invalid dwSize for property", s_szProc);
                        hres = E_INVALIDARG;
                    }
                } else
                {
                    RPF("%s: Arg 1: Unknown property", s_szProc);
                    hres = E_NOTIMPL;
                }

            } else
            {
                hres = hresFullValidGuid(pguid, 1);
            }
        }
    } else
    {
        RPF("%s: Arg 2: Invalid pointer", s_szProc);
        hres = E_INVALIDARG;
    }

    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CDIDev | hresValidDefProp |
 *
 *          Determine whether the property is something we can handle
 *          in the default property handler.
 *
 *  @parm   IN LPCDIPROPINFO | ppropi |
 *
 *          Information describing the property being retrieved.
 *
 *  @parm   DWORD | dwFlags |
 *
 *          Flags for forbidden things.
 *          <c DIPVIFL_READONLY> if being validated for writing.
 *
 *  @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>: Passes validation.
 *
 *          <c E_NOTIMPL>: Not something we handle.
 *
 *
 *****************************************************************************/

HRESULT INTERNAL
    CDIDev_hresValidDefProp(PDD this, LPCDIPROPINFO ppropi, DWORD dwFlags)
{
    HRESULT hres;
    PDIPROPVALIDINFO ppvi;
    EnterProc(CDIDev_hresValidDefProp,
              (_ "pGxx", this, ppropi->pguid, ppropi->dwDevType, dwFlags));

    /*
     *  Note that it's okay if the device is acquired.  We want to
     *  allow GetProperty to succeed on an acquired device.
     */
    AssertF(CDIDev_InCrit(this));

    ppvi = CDIDev_ppviFind(ppropi->pguid);

    if(ppvi)
    {
        if(ppropi->iobj == 0xFFFFFFFF)
        {
            dwFlags |= DIPVIFL_NOTDEVICE;    /* Fail if devices forbidden */
        } else
        {
            dwFlags |= DIPVIFL_NOTOBJECT;    /* Fail if objects forbidden */
        }
        if(this->pvi == 0)
        {
            dwFlags |= DIPVIFL_NOTPRIVATE;   /* Fail if privates forbidden */
        }
        /*
         *  If attempting to modify property and we are acquired,
         *  then also set the "but not while acquired" filter.
         */
        if((dwFlags & DIPVIFL_READONLY) && this->fAcquired)
        {
            dwFlags |= DIPVIFL_NOTACQUIRED;  /* Fail if r/o while acq'd */
        }

        if((ppvi->fl & dwFlags) == 0)
        {
            hres = S_OK;            /* Seems reasonable */
        } else
        {
            if(ppvi->fl & dwFlags & DIPVIFL_READONLY)
            {
                RPF("SetProperty: Property is read-only");
                hres = DIERR_READONLY;
            } else if(ppvi->fl & dwFlags & DIPVIFL_NOTACQUIRED)
            {
                RPF("SetProperty: Cannot change property while acquired");
                hres = DIERR_ACQUIRED;
            } else
            {
                RPF("Get/SetProperty: Property does not exist for that object");
                hres = E_NOTIMPL;       /* Cannot do that */
            }
        }

    } else
    {
        RPF("Get/SetProperty: Property does not exist");
        hres = E_NOTIMPL;           /* Definitely way out */
    }

    ExitOleProc();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CDIDev | DefGetProperty |
 *
 *          Default implementation of <mf IDirectInputDevice::GetProperty>
 *          to handle properties which the device decides not to implement.
 *
 *  @parm   IN LPCDIPROPINFO | ppropi |
 *
 *          Information describing the property being retrieved.
 *
 *  @parm   OUT LPDIPROPHEADER | pdiph |
 *
 *          Where to put the property value.
 *
 *  @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 pdiph> parameter is not a valid pointer.
 *
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_DefGetProperty(PDD this, LPCDIPROPINFO ppropi, LPDIPROPHEADER pdiph)
{
    HRESULT hres;
    EnterProc(CDIDev_DefGetProperty,
              (_ "pGx", this, ppropi->pguid, ppropi->dwDevType));

    AssertF(CDIDev_InCrit(this));

    hres = CDIDev_hresValidDefProp(this, ppropi, 0);
    if(SUCCEEDED(hres))
    {
        LPDIPROPDWORD pdipdw = (PV)pdiph;
        LPDIPROPRANGE pdiprg = (PV)pdiph;
        LPDIPROPPOINTER pdipptr = (PV)pdiph;
        LPDIPROPSTRING pdipwsz = (PV)pdiph;

        switch((DWORD)(UINT_PTR)ppropi->pguid)
        {

        case (DWORD)(UINT_PTR)DIPROP_BUFFERSIZE:
            AssertF(this->pvi);         /* Validation should've caught this */
            pdipdw->dwData = this->celtBuf;
            hres = S_OK;
            break;

        case (DWORD)(UINT_PTR)DIPROP_AXISMODE:
            AssertF(this->pvi);         /* Validation should've caught this */
            if(this->pvi->fl & VIFL_RELATIVE)
            {
                pdipdw->dwData = DIPROPAXISMODE_REL;
            } else
            {
                pdipdw->dwData = DIPROPAXISMODE_ABS;
            }
            hres = S_OK;
            break;

        case (DWORD)(UINT_PTR)DIPROP_GRANULARITY:

            if(DIDFT_GETTYPE(ppropi->dwDevType) & DIDFT_AXIS)
            {
                /* Default axis granularity is 1 */
                pdipdw->dwData = 1;
                hres = S_OK;
            } else
            {
                /*
                 * Buttons don't have granularity.
                 * POVs must be handled by device driver.
                 */
                RPF("GetProperty: Object doesn't have a granularity");
                hres = E_NOTIMPL;
            }
            break;

        case (DWORD)(UINT_PTR)DIPROP_RANGE:
            if(DIDFT_GETTYPE(ppropi->dwDevType) & DIDFT_RELAXIS)
            {
                /* Default rel-axis range is infinite */
                pdiprg->lMin = DIPROPRANGE_NOMIN;
                pdiprg->lMax = DIPROPRANGE_NOMAX;
                hres = S_OK;
            } else
            {
                /*
                 * Device driver must handle abs axis range.
                 * Buttons and POVs don't have range.
                 */
                RPF("GetProperty: Object doesn't have a range");
                hres = E_NOTIMPL;
            }
            break;

        case (DWORD)(UINT_PTR)DIPROP_MAXBUFFERSIZE:
            pdipdw->dwData = this->celtBufMax;
            hres = S_OK;
            break;

        case (DWORD)(UINT_PTR)DIPROP_APPDATA:
            if( ( this->df.rgodf[ppropi->iobj].dwType & DIDFT_CONTROLOBJS )
             &&!( this->df.rgodf[ppropi->iobj].dwType & DIDFT_NODATA ) )
            {
                if( this->pdix )
                {
                    if( this->pdix[ppropi->iobj].dwOfs != 0xFFFFFFFF )
                    {
                        pdipptr->uData = this->pdix[ppropi->iobj].uAppData;
                    }
                    else
                    {
                        hres = DIERR_OBJECTNOTFOUND;
                    }
                }
                else
                {
                    RPF("GetProperty: Need data format/semantic map applied to have app data");
                    hres = DIERR_NOTINITIALIZED;
                }
            }
            else
            {
                RPF("SetProperty: app data only valid for input controls");
                hres = E_NOTIMPL;
            }
            break;

        case (DWORD)(UINT_PTR)DIPROP_USERNAME:
            hres = CMap_GetDeviceUserName( &this->guid, pdipwsz->wsz );
            break;

        case (DWORD)(UINT_PTR)DIPROP_FFGAIN:
            pdipdw->dwData = this->dwGain;
            hres = S_OK;
            break;

        case (DWORD)(UINT_PTR)DIPROP_FFLOAD:
            hres = CDIDev_GetLoad(this, &pdipdw->dwData);
            break;

        case (DWORD)(UINT_PTR)DIPROP_AUTOCENTER:
            if(this->didcFF & DIDC_FORCEFEEDBACK)
            {
                pdipdw->dwData = this->dwAutoCenter;
                hres = S_OK;
            } else
            {
                hres = E_NOTIMPL;
            }
            break;

        default:
            /*
             *  The user is asking for some property that simply
             *  makes no sense here.  E.g., asking for the dead
             *  zone on a keyboard.
             */
            SquirtSqflPtszV(sqfl | sqflBenign, 
                            TEXT("GetProperty: Property 0x%08x not supported on device"),
                            (DWORD)(UINT_PTR)ppropi->pguid );
            hres = E_NOTIMPL;
            break;

        }
    }

    ExitOleProc();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputDevice | GetProperty |
 *
 *          Obtain information about a device or object in a device.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *  @parm   IN REFGUID | rguidProp |
 *
 *          The identity of the property to be obtained.  This can be
 *          one of the predefined <c DIPROP_*> values, or it may
 *          be a private GUID.
 *
 *  @parm   IN LPDIPROPHEADER | pdiph |
 *
 *          Points to the <t DIPROPHEADER> portion of a structure
 *          which dependson the property.
 *
 *  @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 pdiph> parameter is not a valid pointer, or the
 *          <p dwHow> field is invalid, or the <p dwObj> field
 *          is not zero when <p dwHow> is set to <c DIPH_DEVICE>.
 *
 *          <c DIERR_OBJECTNOTFOUND>:  The specified object does not
 *          exist.
 *
 *          <c DIERR_UNSUPPORTED> = <c E_NOTIMPL>:  The property
 *          is not supported by the device or object.
 *
 *  @ex
 *
 *          The following "C" code fragment illustrates how to obtain
 *          the value of the <c DIPROP_BUFFERSIZE> property.
 *
 *          |
 *
 *          DIPROPDWORD dipdw;
 *          HRESULT hres;
 *          dipdw.diph.dwSize = sizeof(DIPROPDWORD);
 *          dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
 *          dipdw.diph.dwObj  = 0;                   // device property
 *          hres = IDirectInputDevice_GetProperty(pdid, DIPROP_BUFFERSIZE, &dipdw.diph);
 *          if (SUCCEEDED(hres)) {
 *              // dipdw.dwData contains the value of the property
 *          }
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_GetProperty(PV pdd, REFGUID rguid, LPDIPROPHEADER pdiph _THAT)
{
    HRESULT hres;
    EnterProcR(IDirectInputDevice8::GetProperty, (_ "pxp", pdd, rguid, pdiph));

    if(SUCCEEDED(hres = hresPvT(pdd)))
    {
        PDD this = _thisPv(pdd);
        DIPROPINFO propi;

        /*
         *  Must protect with the critical section to prevent somebody
         *  acquiring or changing the property we are reading.  We need
         *  to do this before validating, to prevent an acquisition.
         */
        CDIDev_EnterCrit(this);

        propi.pguid = rguid;
        if(SUCCEEDED(hres = CDIDev_hresValidProp(this, rguid, pdiph,
                                                 1, &propi)))
        {
            /*
             *  Prefix picks up that ppropi->iobj is uninitialized (mb:34682) 
             *  in the case of a BY_USAGE dwHow.  However, the value is only 
             *  an output for MapUsage which will always either set it or 
             *  fail so there is no error.
             */
            hres = this->pdcb->lpVtbl->GetProperty(this->pdcb, &propi, pdiph);

            if(hres == E_NOTIMPL)
            {
                hres = CDIDev_DefGetProperty(this, &propi, pdiph);
            }

        }

        CDIDev_LeaveCrit(this);
    }

    ExitBenignOleProcR();
    return hres;
}

#ifdef XDEBUG

CSET_STUBS(GetProperty, (PV pdm, REFGUID rguid, LPDIPROPHEADER pdiph),
           (pdm, rguid, pdiph THAT_))

#endif


/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CDIDev | SetAxisMode |
 *
 *          Default handler for clients trying to set the axis mode.
 *          If the device doesn't handle axis modes natively, then
 *          we'll fake it ourselves.
 *
 *  @parm   DWORD | dwMode |
 *
 *          Desired new mode.
 *
 *  @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.
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_SetAxisMode(PDD this, DWORD dwMode)
{
    HRESULT hres;
    EnterProcR(IDirectInputDevice8::SetProperty(AXISMODE),
               (_ "px", this, dwMode));

    AssertF(this->pvi);                 /* Validation should've caught this */

    hres = hresFullValidFl(dwMode, DIPROPAXISMODE_VALID, 2);
    if(SUCCEEDED(hres))
    {
        if(dwMode & DIPROPAXISMODE_REL)
        {
            this->GetDeviceState = CDIDev_GetRelDeviceState;
            this->pvi->fl |= VIFL_RELATIVE;
        } else
        {
            this->GetDeviceState = CDIDev_GetAbsDeviceState;
            this->pvi->fl &= ~VIFL_RELATIVE;
        }
        if(this->cAxes)
        {
            hres = S_OK;
        } else
        {
            hres = DI_PROPNOEFFECT;
        }
    }

    ExitOleProc();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CDIDev | SetAutoCenter |
 *
 *          Default handler for clients trying to set the
 *          auto-center property.
 *
 *          If the device doesn't have control over the
 *          auto-center spring, then we fail.
 *
 *  @parm   DWORD | dwMode |
 *
 *          Desired new mode.
 *
 *  @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.
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_SetAutoCenter(PDD this, DWORD dwMode)
{
    HRESULT hres;
    EnterProcR(IDirectInputDevice8::SetProperty(AUTOCENTER),
               (_ "px", this, dwMode));

    hres = hresFullValidFl(dwMode, DIPROPAUTOCENTER_VALID, 2);
    if(SUCCEEDED(hres))
    {
        if(this->didcFF & DIDC_FORCEFEEDBACK)
        {
            /*
             *  We need to create the effect driver if disabling
             *  autocenter so that CDIDev_FFAcquire will set the feedback
             *  mode properly.
             */
            if(fLimpFF(dwMode == DIPROPAUTOCENTER_OFF,
                       SUCCEEDED(hres = CDIDev_CreateEffectDriver(this))))
            {
                this->dwAutoCenter = dwMode;
                hres = S_OK;
            }
        } else
        {
            hres = E_NOTIMPL;
        }
    }

    ExitOleProc();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CDIDev | SetGlobalAxisProp |
 *
 *          Default implementation of <mf IDirectInputDevice::SetProperty>
 *          to handle properties which can be applied globally to all
 *          absolute axes.
 *
 *  @parm   IN LPDIPROPINFO | ppropi |
 *
 *          Information describing the property being set.
 *          We edit it to avoid reallocating memory all the time.
 *
 *  @parm   IN LPCDIPROPHEADER | pdiph |
 *
 *          The property itself.
 *
 *  @returns
 *
 *          We consider the property-set a success if all candidates
 *          succeeded.  <c E_NOTIMPL> counts as success, on the assumption
 *          that the property is not meaningful on the candidate.
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_SetGlobalAxisProp(PDD this, LPDIPROPINFO ppropi, LPCDIPROPHEADER pdiph)
{
    HRESULT hres;

    for(ppropi->iobj = 0; ppropi->iobj < this->df.dwNumObjs; ppropi->iobj++)
    {
        DWORD dwType = this->df.rgodf[ppropi->iobj].dwType;
        if(dwType & DIDFT_ABSAXIS)
        {
            ppropi->dwDevType = this->df.rgodf[ppropi->iobj].dwType;

            hres = this->pdcb->lpVtbl->SetProperty(this->pdcb, ppropi, pdiph);
            if(FAILED(hres) && hres != E_NOTIMPL)
            {
                goto done;
            }
        }
    }
    hres = S_OK;

    done:;
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CDIDev | DefSetProperty |
 *
 *          Default implementation of <mf IDirectInputDevice::SetProperty>
 *          to handle properties which the device decides not to implement.
 *
 *  @parm   IN LPDIPROPINFO | ppropi |
 *
 *          Information describing the property being set.
 *          We edit it to avoid reallocating memory all the time.
 *
 *  @parm   OUT LPCDIPROPHEADER | pdiph |
 *
 *          Where to put the property value.
 *
 *  @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 DI_POLLEDDEVICE>: The device is polled, so the result
 *          might not be meaningful.  (This return code is used when
 *          you attempt to set the buffer size property.)
 *
 *          <c DIERR_INVALIDPARAM> = <c E_INVALIDARG>:  The
 *          <p pdiph> parameter is not a valid pointer.
 *
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_DefSetProperty(PDD this, LPDIPROPINFO ppropi, LPCDIPROPHEADER pdiph)
{
    HRESULT hres;
    EnterProc(CDIDev_DefSetProperty,
              (_ "pGx", this, ppropi->pguid, ppropi->dwDevType));

    AssertF(CDIDev_InCrit(this));

    /*
     * Note: The indentation here is historical; I left it this way
     * to keep the diff size down.
     */

    hres = CDIDev_hresValidDefProp(this, ppropi, DIPVIFL_READONLY);
    if(SUCCEEDED(hres))
    {
        LPDIPROPDWORD pdipdw = (PV)pdiph;
        LPDIPROPRANGE pdiprg = (PV)pdiph;
        LPDIPROPPOINTER pdipptr = (PV)pdiph;
        VXDDWORDDATA vdd;

        switch((DWORD)(UINT_PTR)ppropi->pguid)
        {

        case (DWORD)(UINT_PTR)DIPROP_BUFFERSIZE:
            AssertF(this->pvi);     /* Validation should've caught this */
            vdd.pvi = this->pvi;
            if( pdipdw->dwData > this->celtBufMax )
            {
                RPF( "DIPROP_BUFFERSIZE: requested size %d is larger than maximum %d, using %d", 
                    pdipdw->dwData, this->celtBufMax, this->celtBufMax );
                vdd.dw = this->celtBufMax;
            }
            else
            {
                vdd.dw = pdipdw->dwData;
            }
            hres = Hel_SetBufferSize(&vdd);
#ifdef DEBUG_STICKY
            {
                TCHAR tszDbg[80];
                wsprintf( tszDbg, TEXT("SetBufferSize(0x%08x) returned 0x%08x\r\n"), vdd.dw, hres );
                OutputDebugString( tszDbg );
            }
#endif /* DEBUG_STICKY */
            if(SUCCEEDED(hres))
            {
                this->celtBuf = pdipdw->dwData;
                hres = this->hresPolled;
            }
            break;

        case (DWORD)(UINT_PTR)DIPROP_AXISMODE:
            hres = CDIDev_SetAxisMode(this, pdipdw->dwData);
            break;

            /*
             *  We will handle these global properties
             *  if the callback doesn't want to.
             */
        case (DWORD)(UINT_PTR)DIPROP_RANGE:
        case (DWORD)(UINT_PTR)DIPROP_DEADZONE:
        case (DWORD)(UINT_PTR)DIPROP_SATURATION:
        case (DWORD)(UINT_PTR)DIPROP_CALIBRATIONMODE:
        case (DWORD)(UINT_PTR)DIPROP_CALIBRATION:
            if(ppropi->dwDevType == 0)
            {           /* For device */
                hres = CDIDev_SetGlobalAxisProp(this, ppropi, pdiph);
            } else
            {
                goto _default;
            }
            break;

        case (DWORD)(UINT_PTR)DIPROP_MAXBUFFERSIZE:
            this->celtBufMax = pdipdw->dwData;
            hres = S_OK;
            break;

        case (DWORD)(UINT_PTR)DIPROP_APPDATA:
            if( ( this->df.rgodf[ppropi->iobj].dwType & DIDFT_CONTROLOBJS )
             &&!( this->df.rgodf[ppropi->iobj].dwType & DIDFT_NODATA ) )
            {
                if( this->pdix )
                {
                    if( this->pdix[ppropi->iobj].dwOfs != 0xFFFFFFFF )
                    {
                        this->pdix[ppropi->iobj].uAppData = pdipptr->uData;
                    }
                    else
                    {
                        hres = DIERR_OBJECTNOTFOUND;
                    }
                }
                else
                {
                    RPF("SetProperty: Need data format/semantic map applied to change app data");
                    hres = DIERR_NOTINITIALIZED;
                }
            }
            else
            {
                RPF("SetProperty: app data only valid for input controls");
                hres = E_NOTIMPL;
            }
            break;

        case (DWORD)(UINT_PTR)DIPROP_FFGAIN:
            if(ISVALIDGAIN(pdipdw->dwData))
            {
                this->dwGain = pdipdw->dwData;
                CDIDev_RefreshGain(this);
                hres = S_OK;
            } else
            {
                RPF("ERROR: SetProperty(DIPROP_FFGAIN): Gain out of range");
                hres = E_INVALIDARG;
            }
            break;

        case (DWORD)(UINT_PTR)DIPROP_AUTOCENTER:
            hres = CDIDev_SetAutoCenter(this, pdipdw->dwData);
            break;

            _default:;
        default:
            /*
             * The validation filter already failed invalid properties.
             * So what's left is that the property is valid but cannot
             * be set, because it doesn't exist on the device (e.g.,
             * dead zone) or because it is read-only.
             */
            SquirtSqflPtszV(sqfl | sqflBenign, 
                            TEXT("SetProperty: Property 0x%08x not supported on device"),
                            (DWORD)(UINT_PTR)ppropi->pguid );
            hres = E_NOTIMPL;
            break;

        }

    }

    ExitOleProc();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | IDirectInputDevice | RealSetProperty |
 *
 *          The function that does the real work.
 *
 *          <mf IDirectInputDevice::SetDataFormat> will internally
 *          set the axis mode property, so it needs this backdoor
 *          entry point.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *  @parm   IN REFGUID | rguidProp |
 *
 *          The identity of the property to be set.
 *
 *  @parm   IN LPDIPROPHEADER | pdiph |
 *
 *          Points to the <t DIPROPHEADER> portion of a structure
 *          which depends on the property.
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_RealSetProperty(PDD this, REFGUID rguid, LPCDIPROPHEADER pdiph)
{
    HRESULT hres;
    DIPROPINFO propi;
    EnterProcR(IDirectInputDevice8::SetProperty, (_ "pxp", this, rguid, pdiph));

    /*
     *  Must protect with the critical section to prevent somebody
     *  acquiring or changing the property we are reading.  We need
     *  to do this before validating, to prevent an acquisition.
     */
    CDIDev_EnterCrit(this);

    propi.pguid = rguid;
    if(SUCCEEDED(hres = CDIDev_hresValidProp(this, rguid, pdiph,
                                             0, &propi)))
    {

        hres = this->pdcb->lpVtbl->SetProperty(this->pdcb, &propi, pdiph);

        if(hres == E_NOTIMPL)
        {
            hres = CDIDev_DefSetProperty(this, &propi, pdiph);
        }
    }

    CDIDev_LeaveCrit(this);

    ExitOleProc();

    return hres;
}

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputDevice | SetProperty |
 *
 *          Set information about a device or object in a device.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *  @parm   IN REFGUID | rguidProp |
 *
 *          The identity of the property to be set.  This can be
 *          one of the predefined <c DIPROP_*> values, or it may
 *          be a pointer to a private GUID.
 *
 *  @parm   IN LPDIPROPHEADER | pdiph |
 *
 *          Points to the <t DIPROPHEADER> portion of a structure
 *          which depends on the property.
 *
 *  @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 DI_PROPNOEFFECT> <c S_FALSE>: The operation completed
 *          successfully but
 *          had no effect.  For example, changing the axis mode
 *          on a device with no axes will return this value.
 *
 *          <c DIERR_INVALIDPARAM> = <c E_INVALIDARG>:  The
 *          <p pdiph> parameter is not a valid pointer, or the
 *          <p dwHow> field is invalid, or the <p dwObj> field
 *          is not zero when <p dwHow> is set to <c DIPH_DEVICE>.
 *
 *          <c DIERR_OBJECTNOTFOUND>:  The specified object does not
 *          exist.
 *
 *          <c DIERR_UNSUPPORTED> = <c E_NOTIMPL>:  The property
 *          is not supported by the device or object.
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_SetProperty(PV pdd, REFGUID rguid, LPCDIPROPHEADER pdiph _THAT)
{
    HRESULT hres;
    EnterProcR(IDirectInputDevice8::SetProperty, (_ "pxp", pdd, rguid, pdiph));

    if(SUCCEEDED(hres = hresPvT(pdd)))
    {
        PDD this = _thisPv(pdd);
        hres = CDIDev_RealSetProperty(this, rguid, pdiph);
    }

    ExitOleProcR();
    return hres;
}

#ifdef XDEBUG

CSET_STUBS(SetProperty, (PV pdm, REFGUID rguid, LPCDIPROPHEADER pdiph),
           (pdm, rguid, pdiph THAT_))

#else

    #define CDIDev_SetPropertyA             CDIDev_SetProperty
    #define CDIDev_SetPropertyW             CDIDev_SetProperty

#endif

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputDevice | SetCooperativeLevel |
 *
 *          Establish the cooperativity level for the instance of
 *          the device.
 *
 *          The cooperativity level determines how the instance of
 *          the device interacts with other instances of the device
 *          and the rest of the system.
 *
 *          Note that if the system mouse is acquired in exclusive
 *          mode, then the mouse cursor will be removed from the screen
 *          until the device is unacquired.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *  @parm   HWND | hwnd |
 *
 *          The window associated with the device.
 *          The window must be a top-level window.
 *
 *          It is an error to destroy the window while it is still
 *          active in a DirectInput device.
 *
 *  @parm   DWORD | dwFlags |
 *
 *          Flags which describe the cooperativity level associated
 *          with the device.
 *
 *          It consists of <c DISCL_*> flags, documented separately.
 *
 *  @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.
 *
 *****************************************************************************/

HRESULT INLINE
    CDIDev_SetCooperativeLevel_IsValidFl(DWORD dwFlags)
{
    HRESULT hres;
    RD(static char s_szProc[] = "IDirectInputDevice::SetCooperativeLevel");

    if(!(dwFlags & ~DISCL_VALID))
    {
        if((dwFlags & DISCL_EXCLMASK) == DISCL_EXCLUSIVE ||
           (dwFlags & DISCL_EXCLMASK) == DISCL_NONEXCLUSIVE)
        {
            if((dwFlags & DISCL_GROUNDMASK) == DISCL_FOREGROUND ||
               (dwFlags & DISCL_GROUNDMASK) == DISCL_BACKGROUND)
            {
                hres = S_OK;
            } else
            {
                RPF("ERROR %s: arg %d: Must set exactly one of "
                    "DISCL_FOREGROUND or DISCL_BACKGROUND", s_szProc, 2);
                hres = E_INVALIDARG;
            }
        } else
        {
            RPF("ERROR %s: arg %d: Must set exactly one of "
                "DISCL_EXCLUSIVE or DISCL_NONEXCLUSIVE", s_szProc, 2);
            hres = E_INVALIDARG;
        }
    } else
    {
        RPF("ERROR %s: arg %d: invalid flags", s_szProc, 2);
        hres = E_INVALIDARG;

    }
    return hres;
}


HRESULT INLINE
    CDIDev_SetCooperativeLevel_IsValidHwnd(HWND hwnd, DWORD dwFlags)
{
    HRESULT hres;
    RD(static char s_szProc[] = "IDirectInputDevice::SetCooperativeLevel");

    /*
     *  If a window handle is passed, it must be valid.
     *
     *  The window must be a top-level window to be activated.
     *
     *  The window must belong to the calling process so we can
     *  hook it.
     */
    if(hwnd)
    {
        hres = hresFullValidHwnd(hwnd, 1);
        if(SUCCEEDED(hres))
        {
            if(!(GetWindowLong(hwnd, GWL_STYLE) & WS_CHILD))
            {
                if(GetWindowPid(hwnd) == GetCurrentProcessId())
                {
                } else
                {
                    RPF("ERROR %s: window must belong to current process",
                        s_szProc);
                    hres = E_HANDLE;
                }

            } else
            {
                RPF("ERROR %s: window may not be a child window", s_szProc);
                hres = E_HANDLE;
                goto done;
            }
        } else
        {
            goto done;
        }
    }

    /*
     *  Foreground mode or exclusive mode both require a window handle.
     */
    if(dwFlags & (DISCL_FOREGROUND | DISCL_EXCLUSIVE))
    {
        if(hwnd)
        {
        } else
        {
            RPF("ERROR %s: window handle required "
                "if DISCL_EXCLUSIVE or DISCL_FOREGROUND", s_szProc);
            hres = E_HANDLE;
            goto done;
        }
    }

    hres = S_OK;
    done:;
    return hres;
}


STDMETHODIMP
    CDIDev_SetCooperativeLevel(PV pdd, HWND hwnd, DWORD dwFlags _THAT)
{
    HRESULT hres;
    EnterProcR(IDirectInputDevice8::SetCooperativeLevel,
               (_ "pxx", pdd, hwnd, dwFlags));

    if(SUCCEEDED(hres = hresPvT(pdd)))
    {
        PDD this = _thisPv(pdd);

        /*
         *  Must protect with the critical section to prevent somebody
         *  acquiring or Reset()ing behind our back.
         */
        CDIDev_EnterCrit(this);

        if(SUCCEEDED(hres = IDirectInputDevice_NotAcquired(this)) &&
           SUCCEEDED(hres = CDIDev_SetCooperativeLevel_IsValidFl(dwFlags)) &&
           SUCCEEDED(hres = CDIDev_SetCooperativeLevel_IsValidHwnd(hwnd, dwFlags)))
        {

            AssertF(CDIDev_IsConsistent(this));

            if( SUCCEEDED( hres ) )
            {
                hres = this->pdcb->lpVtbl->SetCooperativeLevel(
                                                              this->pdcb, hwnd, dwFlags);
                if(SUCCEEDED(hres))
                {
                    this->discl = dwFlags;
                    this->hwnd = hwnd;
                    if(this->pvi)
                    {
                        this->pvi->hwnd = hwnd;
                    }
                }
            }
            else
            {
                AssertF( hres == E_INVALIDARG );
                RPF("ERROR %s: arg %d: invalid flags", s_szProc, 2);
            }


            AssertF(CDIDev_IsConsistent(this));

        }
        CDIDev_LeaveCrit(this);

    }

    ExitOleProcR();
    return hres;
}

#ifdef XDEBUG

CSET_STUBS(SetCooperativeLevel, (PV pdm, HWND hwnd, DWORD fl),
           (pdm, hwnd, fl THAT_))

#else

    #define CDIDev_SetCooperativeLevelA         CDIDev_SetCooperativeLevel
    #define CDIDev_SetCooperativeLevelW         CDIDev_SetCooperativeLevel

#endif

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputDevice | RunControlPanel |
 *
 *          Run the DirectInput control panel for the device.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *  @parm   IN HWND | hwndOwner |
 *
 *          Identifies the window handle that will be used as the
 *          parent window for subsequent UI.  NULL is a valid parameter,
 *          indicating that there is no parent window.
 *
 *  @parm   DWORD | dwFlags |
 *
 *          No flags are currently defined.  This parameter "must" 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 device is attached.
 *
 *  @devnote
 *
 *          The <p dwFlags> is eventually going to allow
 *          <c DIRCP_MODAL> to request a modal control panel.
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_RunControlPanel(PV pdd, HWND hwndOwner, DWORD fl _THAT)
{
    HRESULT hres;
    EnterProcR(IDirectInputDevice8::RunControlPanel,
               (_ "pxx", pdd, hwndOwner, fl));

    if(SUCCEEDED(hres = hresPvT(pdd)) &&
       SUCCEEDED(hres = hresFullValidHwnd0(hwndOwner, 1)) &&
       SUCCEEDED(hres = hresFullValidFl(fl, DIRCP_VALID, 2)))
    {

        PDD this = _thisPv(pdd);
        IDirectInputDeviceCallback *pdcb;

        /*
         *  Must protect with the critical section to prevent somebody
         *  Reset()ing behind our back.  However, we cannot hold the
         *  critical section during the control panel callback, because
         *  that will yield.
         *
         *  So we copy/addref the pdcb inside the critical section,
         *  then run the control panel outside the critical section,
         *  then release the pdcb when we're finally done.
         */
        CDIDev_EnterCrit(this);

        pdcb = this->pdcb;
        OLE_AddRef(pdcb);

        CDIDev_LeaveCrit(this);

        hres = pdcb->lpVtbl->RunControlPanel(pdcb, hwndOwner, fl);

        OLE_Release(pdcb);
    }

    ExitOleProc();
    return hres;
}

#ifdef XDEBUG

CSET_STUBS(RunControlPanel, (PV pdd, HWND hwndOwner, DWORD fl),
           (pdd, hwndOwner, fl THAT_))

#else

    #define CDIDev_RunControlPanelA         CDIDev_RunControlPanel
    #define CDIDev_RunControlPanelW         CDIDev_RunControlPanel

#endif

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputDevice | Initialize |
 *
 *          Initialize a DirectInputDevice object.
 *
 *          Note that if this method fails, the underlying object should
 *          be considered to be an an indeterminate state and needs to
 *          be reinitialized before it can be subsequently used.
 *
 *          The <mf IDirectInput::CreateDevice> method automatically
 *          initializes the device after creating it.  Applications
 *          normally do not need to call this function.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *  @parm   IN HINSTANCE | hinst |
 *
 *          Instance handle of the application or DLL that is creating
 *          the DirectInput object.
 *
 *          See the section titled "Initialization and Versions"
 *          for more information.
 *
 *  @parm   DWORD | dwVersion |
 *
 *          Version number of the dinput.h header file that was used.
 *
 *          See the section titled "Initialization and Versions"
 *          for more information.
 *
 *  @parm   IN REFGUID | rguid |
 *
 *          Identifies the instance of the device for which the interface
 *          should be associated.
 *          The <mf IDirectInput::EnumDevices> method
 *          can be used to determine which instance GUIDs are supported by
 *          the system.
 *
 *  @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 device had already been initialized with
 *          the instance GUID passed in <p lpGUID>.
 *
 *          <c DIERR_ACQUIRED>: The device cannot be initialized while
 *          it is acquired.
 *
 *          <c DIERR_DEVICENOTREG>: The instance GUID does not exist
 *          on the current machine.
 *
 *          <c DIERR_HASEFFECTS>:
 *          The device cannot be reinitialized because there are
 *          still effects attached to it.
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_Initialize(PV pdd, HINSTANCE hinst, DWORD dwVersion, REFGUID rguid _THAT)
{
    HRESULT hres;
    EnterProcR(IDirectInputDevice8::Initialize,
               (_ "pxxG", pdd, hinst, dwVersion, rguid));

    if(SUCCEEDED(hres = hresPvT(pdd)) &&
       SUCCEEDED(hres = hresFullValidGuid(rguid, 1)))
    {
        PDD this = _thisPv(pdd);
        CREATEDCB CreateDcb;
        IDirectInputDeviceCallback *pdcb;

        /*
         *  Must take the critical section to avoid Reset()ing
         *  the device (or generally speaking, modifying the
         *  internal state variables) while somebody else is
         *  messing with it.
         */
        CDIDev_EnterCrit(this);

        if(SUCCEEDED(hres = hresValidInstanceVer(hinst, dwVersion)) &&
           SUCCEEDED(hres = hresFindInstanceGUID(rguid, &CreateDcb, 1)) &&
           SUCCEEDED(hres = CDIDev_Reset(this)))
        {
            hres = CreateDcb(0, rguid, &IID_IDirectInputDeviceCallback,
                             (PPV)&pdcb);
            if(SUCCEEDED(hres))
            {
                this->pdcb = pdcb;
                AssertF(this->pvi == 0);
                if(SUCCEEDED(hres = CDIDev_GetDataFormat(this)) &&
                   SUCCEEDED(hres = CDIDev_GetPolled(this)) &&
                   SUCCEEDED(hres = this->pdcb->lpVtbl->GetInstance(
                                                                   this->pdcb, &this->pvi)) &&
                   SUCCEEDED(hres = CMapShep_New(NULL, &IID_IDirectInputMapShepherd, &this->pMS)) &&
                   SUCCEEDED(hres = CMap_InitializeCRCTable()) &&
                   SUCCEEDED(hres = CDIDev_GetCapabilitiesHelper(this)) )
                {
                    this->dwVersion = dwVersion;

                    /*
                     *  Take a copy of the global app hacks here rather than 
                     *  using the globals everywhere to make it easier to get 
                     *  rid of the globals some fine day.
                     */
                    this->diHacks = g_AppHacks;

                    this->pdcb->lpVtbl->SetDIData(this->pdcb, dwVersion, &this->diHacks);

                    this->guid = *rguid;
                    if(this->pvi && (this->pvi->fl & VIFL_EMULATED))
                    {
                        this->pvi->pdd = this;
                    }

                    hres = this->pdcb->lpVtbl->CookDeviceData(this->pdcb, 0, 0 );
                    if(SUCCEEDED(hres))
                    {
                        this->fCook = 1;
                    }

                    CDIDev_InitFF(this);

                    hres = S_OK;
                } else
                {
                    RPF("Device driver didn't provide a data format");
                }
            } else
            {
#ifdef NOISY
                RPF("Cannot create device");
#endif
            }
        }
        CDIDev_LeaveCrit(this);
    }

    ExitOleProc();
    return hres;
}

#ifdef XDEBUG

CSET_STUBS(Initialize,
           (PV pdd, HINSTANCE hinst, DWORD dwVersion, REFGUID rguid),
           (pdd, hinst, dwVersion, rguid THAT_))

#else

    #define CDIDev_InitializeA              CDIDev_Initialize
    #define CDIDev_InitializeW              CDIDev_Initialize

#endif

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method void | IDirectInputDevice | Init |
 *
 *          Initialize the internal parts of the DirectInputDevice object.
 *
 *****************************************************************************/

void INLINE
    CDIDev_Init(PDD this)
{
    /*
     *  The critical section must be the very first thing we do,
     *  because only Finalize checks for its existence.
     *
     *  (We might be finalized without being initialized if the user
     *  passed a bogus interface to CDIDev_New.)
     */
    this->fCritInited = fInitializeCriticalSection(&this->crst);

    if( this->fCritInited )
    {
        this->celtBufMax = DEVICE_MAXBUFFERSIZE;    /* Default maximum buffer size */

        this->pdcb = c_pdcbNil;

        GPA_InitFromZero(&this->gpaEff);
    }
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | IDirectInputDevice | New |
 *
 *          Create a new DirectInputDevice object, uninitialized.
 *
 *  @parm   IN PUNK | punkOuter |
 *
 *          Controlling unknown for aggregation.
 *
 *  @parm   IN RIID | riid |
 *
 *          Desired interface to new object.
 *
 *  @parm   OUT PPV | ppvObj |
 *
 *          Output pointer for new object.
 *
 *****************************************************************************/

STDMETHODIMP
    CDIDev_New(PUNK punkOuter, RIID riid, PPV ppvObj)
{
    HRESULT hres;
    EnterProcR(IDirectInputDevice8::<constructor>, (_ "Gp", riid, punkOuter));

    if (SUCCEEDED(hres = hresFullValidPcbOut(ppvObj, cbX(*ppvObj), 3)))
    {
        hres = Excl_Init();
        if(SUCCEEDED(hres))
        {
            LPVOID pvTry = NULL;
            hres = Common_NewRiid(CDIDev, punkOuter, riid, &pvTry);

            if(SUCCEEDED(hres))
            {
                PDD this = _thisPv(pvTry);
                CDIDev_Init(this);
                if( this->fCritInited )
                {
                    *ppvObj = pvTry;
                }
                else
                {
                    Common_Unhold(this);
                    *ppvObj = NULL;
                    hres = E_OUTOFMEMORY;
                }
            }

        }
    }

    ExitOleProcPpvR(ppvObj);
    return hres;
}


/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CDIDev | CDIDev_ModifyEffectParams |
 *
 *         Modifies parameters of DIEFFECT structure to fit the current FF device
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *
 *  @parm   IN OUT LPDIEFFECT | peff |
 *  
 *          Pointer to the effect structure
 *
 *  @parm   IN GUID | effGUID |
 *
 *         GUID for the effect
 *
 *  @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_UNSUPPORTED>: The effect can't be supported by the current device
 *          (e.g. the number of FF axes on the device is 0)
 *
 *          <c DIERR_INVALIDPARAM>: Can't create the effect even with the modified parameters
 *
 *****************************************************************************/

HRESULT CDIDev_ModifyEffectParams
    (
    PV pdd,
    LPDIEFFECT peff,
    GUID effGUID
    )
{
    HRESULT hres = S_OK;
    HRESULT hresCreate = S_OK;
    LPDIRECTINPUTEFFECT pdeff;
    PDD this = _thisPv(pdd);

    EnterProcR(CDIDev_ModifyEffectParams, (_ "p", pdd));

    /* 
     *  To make sure that effects we enumerate will actually get 
     *  created on the device, try creating the effect.
     *
     *  PREFIX warns (Win:171786) that pdeff is uninitialized when 
     *  CDIDev_CreateEffect tests that the pointer to it is writable.
     */
#ifdef XDEBUG       
    hresCreate = CDIDev_CreateEffect(this, &effGUID, peff, &pdeff, NULL, ((LPUNKNOWN)this)->lpVtbl);
#else
    hresCreate = CDIDev_CreateEffect(this, &effGUID, peff, &pdeff, NULL);
#endif
                
    if(SUCCEEDED(hresCreate))
    {
        Invoke_Release(&pdeff);    
    }
    else
    {
        if (hresCreate == DIERR_INVALIDPARAM)
            {
                //two things can give DIERR_INVALIDPARAM:
                //invalid axes and invalid trigger button
                //check the axes first, then the trigger buttons
                //try to eliminate all DIERR_INVALIDPARAMS
                LPDIOBJECTDATAFORMAT lpObjDat = this->df.rgodf;
                DWORD dwNum = this->df.dwNumObjs;
                DWORD nCount;
                LPDWORD lpAxes;
                LPDWORD lpThisAxis;
                LPDWORD lpEffAxis;
                DWORD nAxes = 0;
                DWORD dwTrigger = DIJOFS_BUTTON(0);
                BOOL bTriggerCorrect = FALSE;
                AllocCbPpv(sizeof(DWORD)*dwNum, &lpAxes);
                lpThisAxis = lpAxes;
                for (nCount = 0; nCount < dwNum; nCount ++)
                {
                    AssertF(lpObjDat != NULL);

                    //check the axes
                    if ((lpObjDat->dwType & (DIDFT_AXIS | DIDFT_FFACTUATOR) & DIDFT_TYPEMASK) &&
                        (fHasAllBitsFlFl(lpObjDat->dwType, (DIDFT_AXIS | DIDFT_FFACTUATOR) & DIDFT_ATTRMASK)))
                    {
                        *lpAxes = lpObjDat->dwOfs;
                        nAxes++;
                        lpAxes++;
                    }
                    else
                    {
                        //check the trigger button, if there's one
                        if ((peff->dwTriggerButton != DIEB_NOTRIGGER) && 
                            (lpObjDat->dwType & DIDFT_FFEFFECTTRIGGER & DIDFT_TYPEMASK) &&
                            (fHasAllBitsFlFl(lpObjDat->dwType, DIDFT_FFEFFECTTRIGGER & DIDFT_ATTRMASK)))
            
                        {
                            if (lpObjDat->dwOfs == peff->dwTriggerButton)
                            {
                                //the trigger is valid
                                bTriggerCorrect = TRUE;
                            }
                            else
                            {
                                //remember the trigger offset for the future
                                dwTrigger = lpObjDat->dwOfs;
                            }
                        }
                    }

                    lpObjDat++;
                }

                //first, chack if there are any FF axes
                if (nAxes == 0)
                {
                        //return an error if no FF axes on device
                        hres = DIERR_UNSUPPORTED;
                }
                else
                {

                    
                    //trigger buttons are checked for validity before axes,
                    //so set the trigger button, if needed,
                    //because if it is invalid, this is what caused the error
                    if ((peff->dwTriggerButton != DIEB_NOTRIGGER) && (bTriggerCorrect == FALSE))
                    {
                        peff->dwTriggerButton = dwTrigger;

                        // and try creating again
#ifdef XDEBUG
                        hresCreate = CDIDev_CreateEffect(this, &effGUID, peff, &pdeff, NULL, ((LPUNKNOWN)this)->lpVtbl);
#else
                        hresCreate = CDIDev_CreateEffect(this, &effGUID, peff, &pdeff, NULL);
#endif
                        if(SUCCEEDED(hresCreate))
                        {
                            Invoke_Release(&pdeff);
                        }

                    }
                    
                
                    if (hresCreate == DIERR_INVALIDPARAM)
                    {
                                
                        HRESULT hresInfo = S_OK;
                        EFFECTMAPINFO emi;

                        //this time, set the axes
                        if (peff->cAxes > nAxes)
                        {
                            //change the number of axes
                            peff->cAxes = nAxes;

                            //change the flags
                            if ((nAxes < 3)  && (peff->dwFlags & DIEFF_SPHERICAL))
                            {
                                peff->dwFlags &= ~DIEFF_SPHERICAL;
                                peff->dwFlags |= DIEFF_POLAR;
                            }
                            else
                            {
                                if ((nAxes < 2) && (peff->dwFlags & DIEFF_POLAR))
                                {
                                    peff->dwFlags &= ~DIEFF_POLAR;
                                    peff->dwFlags |= DIEFF_CARTESIAN;
                                }
                            }

                        }


                        //check if size of type-specific param structures is not bigger then number of axes,
                        //since this can also give us invalid params in type-specific .

                        //need to do this only for conditions
                        if (SUCCEEDED(hresInfo = CDIDev_FindEffectGUID(this, &effGUID, &emi, 2))) 
                        {
                            //do the conditions
                            if (emi.attr.dwEffType & DIEFT_CONDITION)
                            {
                                if (peff->cbTypeSpecificParams/(sizeof(DICONDITION)) > peff->cAxes)
                                {
                                    peff->cbTypeSpecificParams = peff->cAxes*(sizeof(DICONDITION));
                                }
                            }

                            //don't need to do anything for custom forces,
                            //since DInput doesn't check number of channels against number of axes anyway
                        }


                        //write over the axes
                        lpEffAxis = peff->rgdwAxes;
                        for (nCount = 0; nCount < nAxes, nCount < peff->cAxes; nCount ++)
                        {
                            *(lpEffAxis) = *(lpThisAxis);
                            lpThisAxis ++;
                            lpEffAxis++;
                        }


                        // and try creating again
#ifdef XDEBUG
                        hresCreate = CDIDev_CreateEffect(this, &effGUID, peff, &pdeff, NULL, ((LPUNKNOWN)this)->lpVtbl);
#else
                        hresCreate = CDIDev_CreateEffect(this, &effGUID, peff, &pdeff, NULL);
#endif
                        if(SUCCEEDED(hresCreate))
                        {
                                Invoke_Release(&pdeff);    
                        }
                        
                    }
                }

                //free the axes array
                FreePpv(&lpAxes);
            }
        }

    if ((SUCCEEDED(hres)) && (hresCreate == DIERR_INVALIDPARAM))
    {
        hres = hresCreate;
    }


    ExitOleProc();
    return hres;

}



/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method BOOL | CDIDev | CDIDev_IsStandardEffect |
 *
 *         Checks if the effect GUID belongs to a standard DI effect
 *
 *  @parm   IN GUID | effGUID |
 *
 *         GUID for the effect
 *
 *  @returns BOOL
 *
 *      TRUE if it is a standard DI effect;
 *      FALSE otherwise.
 *
 *
 *****************************************************************************/

BOOL CDIDev_IsStandardEffect
    (GUID effGUID)
{
    BOOL bStandard = TRUE;


    //check all the standard DX7 GUIDs
    if ((IsEqualGUID(&effGUID, &GUID_Sine))         ||
        (IsEqualGUID(&effGUID, &GUID_Triangle))     ||
        (IsEqualGUID(&effGUID, &GUID_ConstantForce)) ||
        (IsEqualGUID(&effGUID, &GUID_RampForce))        ||
        (IsEqualGUID(&effGUID, &GUID_Square))       ||
        (IsEqualGUID(&effGUID, &GUID_SawtoothUp))   ||
        (IsEqualGUID(&effGUID, &GUID_SawtoothDown)) ||
        (IsEqualGUID(&effGUID, &GUID_Spring))       ||
        (IsEqualGUID(&effGUID, &GUID_Damper))       ||
        (IsEqualGUID(&effGUID, &GUID_Inertia))      ||
        (IsEqualGUID(&effGUID, &GUID_Friction))     ||
        (IsEqualGUID(&effGUID, &GUID_CustomForce)))
    {
        bStandard = TRUE;
    }

    else
    {
        bStandard = FALSE;
    }

    return bStandard;

}



/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputDevice | EnumEffectsInFile |
 *
 *          Enumerates DIEFFECT struct(s) and effect GUID from file. 
 *          An application can use this in order to create pre-authored
 *          force effects.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *  @parm  LPCSTR | lpszFileName | 
 *
 *          Name of the RIFF file that contains collection of effects. 
 *
 *  @parm   IN OUT LPENUMEFFECTSCALLBACK | pec |
 *  
 *          The callback function.
 *
 *  @parm   IN OUT LPVOID | pvRef |
 *          Specifies the application-defined value given in the
 *          <mf IDirectInputDevice::EnumObjects> function.
 *
 *  @parm   IN DWORD | dwFlags |
 *
 *          Flags which control the enumeration.
 *
 *          It consists of <c DIFEF_*> flags, documented separately.
 *
 *  @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 lpDirectInputDevice> or
 *          <p lpdc> parameter is invalid.
 *
 *  @cb     BOOL CALLBACK | DIEnumEffectsCallback |
 *
 *          An application-defined callback function that receives
 *          effect GUID, DIEFFECT and repeat count as a result of a call to the
 *          <om IDirectInputDevice::EnumEffectsInFile> method.
 *
 *  @parm   OUT LPCDIFILEEFFECT | lpDiFileEf |
 *          
 *          Pointer to a DIFILEEFFECT structure. 
 *
 *
 *  @parm   IN OUT LPVOID | pvRef |
 *          Specifies the application-defined value given in the
 *          <mf IDirectInputDevice::EnumObjects> function.
 *
 *  @returns
 *
 *          Returns <c DIENUM_CONTINUE> to continue the enumeration
 *          or <c DIENUM_STOP> to stop the enumeration.
 *
 *****************************************************************************/

HRESULT CDIDev_EnumEffectsInFileA
    (
    PV pddA,
    LPCSTR lpszFileName, 
    LPDIENUMEFFECTSINFILECALLBACK pec, 
    LPVOID pvRef,
    DWORD dwFlags
    )
{
    HRESULT hres = E_FAIL;

    
    EnterProcR(IDirectInputDevice8::EnumEffectsInFile, (_ "s", lpszFileName));

    /* Validate incoming parameters */
    if(SUCCEEDED(hres = hresPvA(pddA)) &&
       SUCCEEDED(hres = hresFullValidReadStrA(lpszFileName, MAX_JOYSTRING,1)) &&
       SUCCEEDED(hres = hresFullValidPfn(pec, 2)) &&
       SUCCEEDED(hres = hresFullValidFl(dwFlags, DIFEF_ENUMVALID, 3)) )
    {
        PDD this = _thisPvNm(pddA, ddA);
        HMMIO       hmmio;        
        MMCKINFO    mmck;
        DWORD       dwEffectSz;

        hres = RIFF_Open(lpszFileName, MMIO_READ | MMIO_ALLOCBUF , &hmmio, &mmck, &dwEffectSz);

        if(SUCCEEDED(hres))
        {
            HRESULT hresRead;
            DIEFFECT        effect;
            DIFILEEFFECT    DiFileEf;
            DIENVELOPE      diEnvelope;
            DWORD           rgdwAxes[DIEFFECT_MAXAXES];
            LONG            rglDirection[DIEFFECT_MAXAXES];
            
            effect.rgdwAxes     = rgdwAxes;
            effect.rglDirection = rglDirection;
            effect.lpEnvelope   = &diEnvelope;

            DiFileEf.dwSize     = cbX(DiFileEf);
            DiFileEf.lpDiEffect = &effect;

            while ((SUCCEEDED(hres)) && (SUCCEEDED(hresRead = RIFF_ReadEffect(hmmio, &DiFileEf))))
            {
                BOOL fRc = DIENUM_CONTINUE; 
                BOOL bInclude = TRUE;
                HRESULT hresModify = DI_OK;

                //modify if needed
                if (dwFlags & DIFEF_MODIFYIFNEEDED)
                {
                    hresModify = CDIDev_ModifyEffectParams(this, &effect, DiFileEf.GuidEffect); 
                }

                //if necessary, check whether effect is standard
                if (!(dwFlags & DIFEF_INCLUDENONSTANDARD))
                {
                    bInclude = CDIDev_IsStandardEffect(DiFileEf.GuidEffect);
                }

                //call back only if all the conditions posed by the flags are satisfied 
                if ((SUCCEEDED(hresModify)) && (bInclude == TRUE))
                {
                    fRc = Callback(pec, &DiFileEf, pvRef);
                }

                //free type-specific only if allocated
                if(effect.cbTypeSpecificParams > 0)
                {
                    FreePv(effect.lpvTypeSpecificParams);
                    effect.cbTypeSpecificParams = 0x0;
                }

                if(fRc == DIENUM_STOP)
                {
                    break;
                } else if(fRc == DIENUM_CONTINUE)
                {
                    continue;
                } else
                {
                    RPF("IDirectInputDevice::EnumEffectsInFile: Invalid return value from enumeration callback");
                    ValidationException();
                    break;
                }
            }
            RIFF_Close(hmmio, 0);
            //if hresRead failed because couldn't descend into the chunk, it means the end of file,
            //so everything is OK;
            //else return this error
            if (SUCCEEDED(hres))
            {   
                if (hresRead == hresLe(ERROR_SECTOR_NOT_FOUND))
                {
                    hres = S_OK;
                }
                else
                {
                    hres = hresRead;
                }
            }
        }
    }

    ExitOleProc();
    return hres;
}


HRESULT CDIDev_EnumEffectsInFileW
    (
    PV pddW,
    LPCWSTR lpszFileName, 
    LPDIENUMEFFECTSINFILECALLBACK pec, 
    LPVOID pvRef,
    DWORD dwFlags
    )
{

    HRESULT hres = E_FAIL;

    EnterProcR(IDirectInputDevice8::EnumEffectsInFileW, (_ "s", lpszFileName));

    /* Validate incoming parameters */
    if(SUCCEEDED(hres = hresPvW(pddW)) &&
       SUCCEEDED(hres = hresFullValidReadStrW(lpszFileName, MAX_JOYSTRING,1)) &&
       SUCCEEDED(hres = hresFullValidPfn(pec, 2)) &&
       SUCCEEDED(hres = hresFullValidFl(dwFlags, DIFEF_ENUMVALID, 3)) )
    {
        CHAR szFileName[MAX_PATH];

        PDD this = _thisPvNm(pddW, ddW);
        
        UToA(szFileName, MAX_PATH, lpszFileName);

        hres = CDIDev_EnumEffectsInFileA(&this->ddA, szFileName, pec, pvRef, dwFlags);
    }

    return hres;
}

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @method HRESULT | IDirectInputDevice | WriteEffectToFile |
 *
 *          Writes DIEFFECT struct(s) and effect GUID to a file. 
 *          An application can use this in order to create pre-authored
 *          force effects.
 *
 *  @cwrap  LPDIRECTINPUTDEVICE | lpDirectInputDevice
 *
 *  @parm   LPCSTR | lpszFileName | 
 *
 *          Name of the RIFF file that contains collection of effects. 
 *
 *  @parm   IN DWORD  | dwEntries |
 *
 *          Number of <t DIFILEEFFECT> structures in the array.
 *
 *  @parm   IN LPCDIFILEEFFECT | rgDiFileEft |
 *
 *          Array of <t DIFILEEFFECT> structure.
 *
 *
 *  @parm   IN DWORD | dwFlags |
 *
 *          Flags which control how the effect should be written.
 *
 *          It consists of <c DIFEF_*> flags, documented separately.
 *
 *  @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 lpDirectInputDevice> or
 *          <p lpdc> parameter is invalid.
 *
 *****************************************************************************/


HRESULT CDIDev_WriteEffectToFileA
    (
    PV          pddA,
    LPCSTR      lpszFileName,
    DWORD       dwEntries,
    LPDIFILEEFFECT rgDiFileEffect,
    DWORD       dwFlags
    )
{
    HRESULT hres = E_NOTIMPL;

    EnterProcR(IDirectInputDevice8::WriteEffectToFileA, (_ "s", lpszFileName));


    /* Validate incoming parameters */
    if(SUCCEEDED(hres = hresPvA(pddA)) &&
       SUCCEEDED(hres = hresFullValidReadStrA(lpszFileName, MAX_JOYSTRING,1))&&
       SUCCEEDED(hres = hresFullValidFl(dwFlags, DIFEF_ENUMVALID, 3))  &&
       SUCCEEDED(hres = (IsBadReadPtr(rgDiFileEffect, cbX(*rgDiFileEffect))) ? E_POINTER : S_OK))

    {
        PDD this = _thisPvNm(pddA, ddA);
        HMMIO       hmmio;        
        MMCKINFO    mmck;
        DWORD       dwEffectSz;

        hres = RIFF_Open(lpszFileName, MMIO_CREATE | MMIO_WRITE | MMIO_ALLOCBUF , &hmmio, &mmck, &dwEffectSz);

        if(SUCCEEDED(hres))
        {
            UINT nCount;
            LPDIFILEEFFECT  lpDiFileEf = rgDiFileEffect;

            //write out the effects
            for(nCount = 0; nCount < dwEntries; nCount++)
            {
                BOOL bInclude = TRUE;

                hres = (IsBadReadPtr(lpDiFileEf, cbX(*lpDiFileEf))) ? E_POINTER : S_OK;

                if (FAILED(hres))
                {
                    break;
                }
                

                //if necessary, check whether the effect is standard
                if (!(dwFlags & DIFEF_INCLUDENONSTANDARD))
                {
                    bInclude = CDIDev_IsStandardEffect(lpDiFileEf->GuidEffect);
                }

                if ((SUCCEEDED(hres)) && (bInclude == TRUE))
                {
                    hres = RIFF_WriteEffect(hmmio, lpDiFileEf);
                }

                if(FAILED(hres))
                {
                    break;
                }

                lpDiFileEf++;
                
            }
            RIFF_Close(hmmio, 0);
        }
    }

    ExitOleProc();
    return hres;
}


HRESULT CDIDev_WriteEffectToFileW
    (
    PV          pddW,
    LPCWSTR      lpszFileName,
    DWORD       dwEntries,
    LPDIFILEEFFECT lpDiFileEffect,
    DWORD       dwFlags
    )
{

    HRESULT hres = E_FAIL;

    EnterProcR(IDirectInputDevice8::WriteEffectToFile, (_ "s", lpszFileName));

    /* Validate incoming parameters */
    if(SUCCEEDED(hres = hresPvW(pddW)) &&
       SUCCEEDED(hres = hresFullValidReadStrW(lpszFileName, MAX_JOYSTRING,1)))
    {
        CHAR szFileName[MAX_PATH];

        PDD this = _thisPvNm(pddW, ddW);
        
        UToA(szFileName, MAX_PATH, lpszFileName);

        hres = CDIDev_WriteEffectToFileA(&this->ddA, szFileName, dwEntries, lpDiFileEffect, dwFlags);
    }

    return hres;
}



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

#pragma BEGIN_CONST_DATA

#define CDIDev_Signature        0x20564544      /* "DEV " */

Primary_Interface_Begin(CDIDev, TFORM(ThisInterfaceT))
TFORM(CDIDev_GetCapabilities),
TFORM(CDIDev_EnumObjects),
TFORM(CDIDev_GetProperty),
TFORM(CDIDev_SetProperty),
TFORM(CDIDev_Acquire),
TFORM(CDIDev_Unacquire),
TFORM(CDIDev_GetDeviceState),
TFORM(CDIDev_GetDeviceData),
TFORM(CDIDev_SetDataFormat),
TFORM(CDIDev_SetEventNotification),
TFORM(CDIDev_SetCooperativeLevel),
TFORM(CDIDev_GetObjectInfo),
TFORM(CDIDev_GetDeviceInfo),
TFORM(CDIDev_RunControlPanel),
TFORM(CDIDev_Initialize),
TFORM(CDIDev_CreateEffect),
TFORM(CDIDev_EnumEffects),
TFORM(CDIDev_GetEffectInfo),
TFORM(CDIDev_GetForceFeedbackState),
TFORM(CDIDev_SendForceFeedbackCommand),
TFORM(CDIDev_EnumCreatedEffectObjects),
TFORM(CDIDev_Escape),
TFORM(CDIDev_Poll),
TFORM(CDIDev_SendDeviceData),
TFORM(CDIDev_EnumEffectsInFile),
TFORM(CDIDev_WriteEffectToFile),
TFORM(CDIDev_BuildActionMap),
TFORM(CDIDev_SetActionMap),
TFORM(CDIDev_GetImageInfo),

    Primary_Interface_End(CDIDev, TFORM(ThisInterfaceT))

Secondary_Interface_Begin(CDIDev, SFORM(ThisInterfaceT), SFORM(dd))
SFORM(CDIDev_GetCapabilities),
SFORM(CDIDev_EnumObjects),
SFORM(CDIDev_GetProperty),
SFORM(CDIDev_SetProperty),
SFORM(CDIDev_Acquire),
SFORM(CDIDev_Unacquire),
SFORM(CDIDev_GetDeviceState),
SFORM(CDIDev_GetDeviceData),
SFORM(CDIDev_SetDataFormat),
SFORM(CDIDev_SetEventNotification),
SFORM(CDIDev_SetCooperativeLevel),
SFORM(CDIDev_GetObjectInfo),
SFORM(CDIDev_GetDeviceInfo),
SFORM(CDIDev_RunControlPanel),
SFORM(CDIDev_Initialize),
SFORM(CDIDev_CreateEffect),
SFORM(CDIDev_EnumEffects),
SFORM(CDIDev_GetEffectInfo),
SFORM(CDIDev_GetForceFeedbackState),
SFORM(CDIDev_SendForceFeedbackCommand),
SFORM(CDIDev_EnumCreatedEffectObjects),
TFORM(CDIDev_Escape),
SFORM(CDIDev_Poll),
SFORM(CDIDev_SendDeviceData),
SFORM(CDIDev_EnumEffectsInFile),
SFORM(CDIDev_WriteEffectToFile),
SFORM(CDIDev_BuildActionMap),
SFORM(CDIDev_SetActionMap),
SFORM(CDIDev_GetImageInfo),
    Secondary_Interface_End(CDIDev, SFORM(ThisInterfaceT), SFORM(dd))