/*  LilyPad - Pad plugin for PS2 Emulator
 *  Copyright (C) 2002-2014  PCSX2 Dev Team/ChickenLiver
 *
 *  PCSX2 is free software: you can redistribute it and/or modify it under the
 *  terms of the GNU Lesser General Public License as published by the Free
 *  Software Found- ation, either version 3 of the License, or (at your option)
 *  any later version.
 *
 *  PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY
 *  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 *  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with PCSX2.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "Global.h"
#define DIRECTINPUT_VERSION 0x0800
#include <dinput.h>
#include "InputManager.h"
#include "Config.h"

#include "VKey.h"
#include "DirectInput.h"
#include "DeviceEnumerator.h"
#include "PS2Etypes.h"

// All for getting GUIDs of XInput devices....
#include <wbemidl.h>
#include <oleauto.h>
// MS's code imports wmsstd.h, thus requiring the entire windows
// media SDK also be installed for a simple macro.  This is
// simpler and less silly.
#ifndef SAFE_RELEASE
#define SAFE_RELEASE(p)     \
    {                       \
        if (p) {            \
            (p)->Release(); \
            (p) = NULL;     \
        }                   \
    }
#endif

// Aka htons, without the winsock dependency.
inline static u16 flipShort(u16 s)
{
    return (s >> 8) | (s << 8);
}

// Aka htonl, without the winsock dependency.
inline static u32 flipLong(u32 l)
{
    return (((u32)flipShort((u16)l)) << 16) | flipShort((u16)(l >> 16));
}

static void GUIDtoString(wchar_t *data, const GUID *pg)
{
    wsprintfW(data, L"%08X-%04X-%04X-%04X-%04X%08X",
              pg->Data1, (u32)pg->Data2, (u32)pg->Data3,
              flipShort(((u16 *)pg->Data4)[0]),
              flipShort(((u16 *)pg->Data4)[1]),
              flipLong(((u32 *)pg->Data4)[1]));
}

struct DirectInput8Data
{
    IDirectInput8 *lpDI8;
    int refCount;
    int deviceCount;
};

DirectInput8Data di8d = {0, 0, 0};

IDirectInput8 *GetDirectInput()
{
    if (!di8d.lpDI8) {
        if (FAILED(DirectInput8Create(hInst, 0x800, IID_IDirectInput8, (void **)&di8d.lpDI8, 0)))
            return 0;
    }
    di8d.refCount++;
    return di8d.lpDI8;
}
void ReleaseDirectInput()
{
    if (di8d.refCount) {
        di8d.refCount--;
        if (!di8d.refCount) {
            di8d.lpDI8->Release();
            di8d.lpDI8 = 0;
        }
    }
}

static int StringToGUID(GUID *pg, wchar_t *dataw)
{
    char data[100];
    if (wcslen(dataw) > 50)
        return 0;
    int w = 0;
    while (dataw[w]) {
        data[w] = (char)dataw[w];
        w++;
    }
    data[w] = 0;
    u32 temp[5];
    sscanf(data, "%08X-%04X-%04X-%04X-%04X%08X",
           &pg->Data1, temp, temp + 1,
           temp + 2, temp + 3, temp + 4);
    pg->Data2 = (u16)temp[0];
    pg->Data3 = (u16)temp[1];
    ((u16 *)pg->Data4)[0] = flipShort((u16)temp[2]);
    ((u16 *)pg->Data4)[1] = flipShort((u16)temp[3]);
    ((u32 *)pg->Data4)[1] = flipLong(temp[4]);
    return 1;
}


struct DI8Effect
{
    IDirectInputEffect *die;
    int scale;
};

BOOL CALLBACK EnumDeviceObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef);
BOOL CALLBACK EnumEffectsCallback(LPCDIEFFECTINFOW pdei, LPVOID pvRef);
class DirectInputDevice : public Device
{
public:
    DI8Effect *diEffects;

    IDirectInputDevice8 *did;
    GUID guidInstance;
    DirectInputDevice(DeviceType type, IDirectInputDevice8 *did, wchar_t *displayName, wchar_t *instanceID, wchar_t *productID, GUID guid)
        : Device(DI, type, displayName, instanceID, productID)
    {
        diEffects = 0;
        guidInstance = guid;
        this->did = 0;
        did->EnumEffects(EnumEffectsCallback, this, DIEFT_ALL);
        did->EnumObjects(EnumDeviceObjectsCallback, this, DIDFT_ALL);
        did->Release();
    }

    void SetEffect(ForceFeedbackBinding *binding, unsigned char force)
    {
        int index = 0;
        if (!diEffects) {
            return;
        }
        for (int port = 0; port < 2; port++) {
            for (int slot = 0; slot < 4; slot++) {
                int padtype = config.padConfigs[port][slot].type;
                unsigned int diff = binding - pads[port][slot][padtype].ffBindings;
                if (diff < (unsigned int)pads[port][slot][padtype].numFFBindings) {
                    index += diff;
                    port = 2;
                    break;
                }
                index += pads[port][slot][padtype].numFFBindings;
            }
        }
        IDirectInputEffect *die = diEffects[index].die;
        if (die) {
            DIEFFECT dieffect;
            memset(&dieffect, 0, sizeof(dieffect));
            union
            {
                DIPERIODIC periodic;
                DIRAMPFORCE ramp;
                DICONSTANTFORCE constant;
            };

            dieffect.dwSize = sizeof(dieffect);
            dieffect.lpvTypeSpecificParams = &periodic;
            int magnitude = abs((int)((force * 10000 * (__int64)diEffects[index].scale) / BASE_SENSITIVITY / 255));
            if (magnitude > 10000)
                magnitude = 10000;
            int type = ffEffectTypes[binding->effectIndex].type;
            if (type == EFFECT_CONSTANT) {
                dieffect.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
                constant.lMagnitude = magnitude;
            } else if (type == EFFECT_PERIODIC) {
                dieffect.cbTypeSpecificParams = sizeof(DIPERIODIC);
                periodic.dwMagnitude = 0;
                periodic.lOffset = magnitude;
                periodic.dwPhase = 0;
                periodic.dwPeriod = 2000000;
            } else if (type == EFFECT_RAMP) {
                dieffect.cbTypeSpecificParams = sizeof(DIRAMPFORCE);
                ramp.lEnd = ramp.lStart = magnitude;
            }
            dieffect.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTIDS;
            dieffect.dwDuration = 2000000;
            die->SetParameters(&dieffect, DIEP_TYPESPECIFICPARAMS | DIEP_START);
        }
    }

    int Activate(InitInfo *initInfo)
    {
        int i;
        IDirectInput8 *di8 = GetDirectInput();
        Deactivate();
        if (!di8)
            return 0;
        if (DI_OK != di8->CreateDevice(guidInstance, &did, 0)) {
            ReleaseDirectInput();
            did = 0;
            return 0;
        }

        {
            DIOBJECTDATAFORMAT *formats = (DIOBJECTDATAFORMAT *)calloc(numPhysicalControls, sizeof(DIOBJECTDATAFORMAT));
            for (i = 0; i < numPhysicalControls; i++) {
                formats[i].dwType = physicalControls[i].type | DIDFT_MAKEINSTANCE(physicalControls[i].id);
                formats[i].dwOfs = 4 * i;
            }
            DIDATAFORMAT format;
            format.dwSize = sizeof(format);
            format.dwDataSize = 4 * numPhysicalControls;
            format.dwObjSize = sizeof(DIOBJECTDATAFORMAT);
            format.dwFlags = 0;
            format.dwNumObjs = numPhysicalControls;
            format.rgodf = formats;
            int res = did->SetDataFormat(&format);
            for (i = 0; i < numPhysicalControls; i++) {
                if (physicalControls[i].type == ABSAXIS) {
                    DIPROPRANGE prop;
                    prop.diph.dwHeaderSize = sizeof(DIPROPHEADER);
                    prop.diph.dwSize = sizeof(DIPROPRANGE);
                    prop.diph.dwObj = formats[i].dwType;
                    prop.diph.dwHow = DIPH_BYID;
                    prop.lMin = -FULLY_DOWN;
                    prop.lMax = FULLY_DOWN;
                    did->SetProperty(DIPROP_RANGE, &prop.diph);

                    // May do something like this again, if there's any need.
                    /*
					if (FAILED(DI->did->SetProperty(DIPROP_RANGE, &prop.diph))) {
						if (FAILED(DI->did->GetProperty(DIPROP_RANGE, &prop.diph))) {
							// ????
							DI->objects[i].min = prop.lMin;
							DI->objects[i].max = prop.lMax;
							continue;
						}
					}
					DI->objects[i].min = prop.lMin;
					DI->objects[i].max = prop.lMax;
					//*/
                }
            }
            free(formats);
        }
        // Note:  Have to use hWndTop to properly hide cursor for mouse device.
        if (type == OTHER) {
            did->SetCooperativeLevel(initInfo->hWndTop, DISCL_BACKGROUND | DISCL_EXCLUSIVE);
        } else if (type == KEYBOARD) {
            did->SetCooperativeLevel(initInfo->hWndTop, DISCL_FOREGROUND);
        } else {
            did->SetCooperativeLevel(initInfo->hWndTop, DISCL_FOREGROUND | DISCL_EXCLUSIVE);
        }
        if (did->Acquire() != DI_OK) {
            did->Release();
            did = 0;
            ReleaseDirectInput();
            return 0;
        }
        AllocState();
        int count = GetFFBindingCount();
        diEffects = (DI8Effect *)calloc(count, sizeof(DI8Effect));
        i = 0;
        for (int port = 0; port < 2; port++) {
            for (int slot = 0; slot < 4; slot++) {
                int padtype = config.padConfigs[port][slot].type;
                int subIndex = i;
                for (int j = 0; j < pads[port][slot][padtype].numFFBindings; j++) {
                    ForceFeedbackBinding *b = 0;
                    b = &pads[port][slot][padtype].ffBindings[i - subIndex];
                    ForceFeedbackEffectType *eff = ffEffectTypes + b->effectIndex;
                    GUID guid;
                    if (!StringToGUID(&guid, eff->effectID))
                        continue;

                    DIEFFECT dieffect;
                    memset(&dieffect, 0, sizeof(dieffect));
                    dieffect.dwSize = sizeof(dieffect);
                    dieffect.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTIDS;
                    dieffect.dwDuration = 1000000;
                    dieffect.dwGain = 10000;
                    dieffect.dwTriggerButton = DIEB_NOTRIGGER;
                    union
                    {
                        DIPERIODIC pediodic;
                        DIRAMPFORCE ramp;
                        DICONSTANTFORCE constant;
                    } stuff = {0, 0, 0, 0};

                    if (eff->type == EFFECT_CONSTANT) {
                        dieffect.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
                    } else if (eff->type == EFFECT_PERIODIC) {
                        dieffect.cbTypeSpecificParams = sizeof(DIPERIODIC);
                    } else if (eff->type == EFFECT_RAMP) {
                        dieffect.cbTypeSpecificParams = sizeof(DIRAMPFORCE);
                    }
                    dieffect.lpvTypeSpecificParams = &stuff;

                    int maxForce = 0;
                    int numAxes = 0;
                    int *axes = (int *)malloc(sizeof(int) * 3 * numFFAxes);
                    DWORD *axisIDs = (DWORD *)(axes + numFFAxes);
                    LONG *dirList = (LONG *)(axisIDs + numFFAxes);
                    dieffect.rgdwAxes = axisIDs;
                    dieffect.rglDirection = dirList;
                    for (int k = 0; k < numFFAxes; k++) {
                        if (b->axes[k].force) {
                            int force = abs(b->axes[k].force);
                            if (force > maxForce) {
                                maxForce = force;
                            }
                            axes[numAxes] = k;
                            axisIDs[numAxes] = ffAxes[k].id;
                            dirList[numAxes] = b->axes[k].force;
                            numAxes++;
                        }
                    }
                    if (!numAxes) {
                        free(axes);
                        continue;
                    }
                    dieffect.cAxes = numAxes;
                    diEffects[i].scale = maxForce;
                    if (!SUCCEEDED(did->CreateEffect(guid, &dieffect, &diEffects[i].die, 0))) {
                        diEffects[i].die = 0;
                        diEffects[i].scale = 0;
                    }

                    free(axes);
                    axes = 0;
                    i++;
                }
            }
        }
        active = 1;
        return 1;
    }

    int Update()
    {
        if (!active)
            return 0;
        if (numPhysicalControls) {
            HRESULT res = did->Poll();
            // ??
            if ((res != DI_OK && res != DI_NOEFFECT) ||
                DI_OK != did->GetDeviceState(4 * numPhysicalControls, physicalControlState)) {
                Deactivate();
                return 0;
            }
            for (int i = 0; i < numPhysicalControls; i++) {
                if (physicalControls[i].type & RELAXIS) {
                    physicalControlState[i] *= (FULLY_DOWN / 3);
                } else if (physicalControls[i].type & BUTTON) {
                    physicalControlState[i] = (physicalControlState[i] & 0x80) * FULLY_DOWN / 128;
                }
            }
        }
        return 1;
    }

    int GetFFBindingCount()
    {
        int count = 0;
        for (int port = 0; port < 2; port++) {
            for (int slot = 0; slot < 4; slot++) {
                int padtype = config.padConfigs[port][slot].type;
                count += pads[port][slot][padtype].numFFBindings;
            }
        }
        return count;
    }

    void Deactivate()
    {
        FreeState();
        if (diEffects) {
            int count = GetFFBindingCount();
            for (int i = 0; i < count; i++) {
                if (diEffects[i].die) {
                    diEffects[i].die->Stop();
                    diEffects[i].die->Release();
                }
            }
            free(diEffects);
            diEffects = 0;
        }
        if (active) {
            did->Unacquire();
            did->Release();
            ReleaseDirectInput();
            did = 0;
            active = 0;
        }
    }

    ~DirectInputDevice()
    {
    }
};

BOOL CALLBACK EnumEffectsCallback(LPCDIEFFECTINFOW pdei, LPVOID pvRef)
{
    DirectInputDevice *did = (DirectInputDevice *)pvRef;
    EffectType type;
    int diType = DIEFT_GETTYPE(pdei->dwEffType);
    if (diType == DIEFT_CONSTANTFORCE) {
        type = EFFECT_CONSTANT;
    } else if (diType == DIEFT_RAMPFORCE) {
        type = EFFECT_RAMP;
    } else if (diType == DIEFT_PERIODIC) {
        type = EFFECT_PERIODIC;
    } else {
        return DIENUM_CONTINUE;
    }
    wchar_t guidString[50];
    GUIDtoString(guidString, &pdei->guid);
    did->AddFFEffectType(pdei->tszName, guidString, type);

    return DIENUM_CONTINUE;
}

BOOL CALLBACK EnumDeviceObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef)
{
    DirectInputDevice *did = (DirectInputDevice *)pvRef;
    if (lpddoi->dwType & DIDFT_FFACTUATOR) {
        did->AddFFAxis(lpddoi->tszName, lpddoi->dwType);
    }

    ControlType type;

    if (lpddoi->dwType & DIDFT_POV)
        type = POV;
    else if (lpddoi->dwType & DIDFT_ABSAXIS)
        type = ABSAXIS;
    else if (lpddoi->dwType & DIDFT_RELAXIS)
        type = RELAXIS;
    else if (lpddoi->dwType & DIDFT_PSHBUTTON)
        type = PSHBTN;
    else if (lpddoi->dwType & DIDFT_TGLBUTTON)
        type = TGLBTN;
    else
        return DIENUM_CONTINUE;
    // If too many objects, ignore extra buttons.
    if ((did->numPhysicalControls > 255 && DIDFT_GETINSTANCE(lpddoi->dwType) > 255) && (type & (DIDFT_PSHBUTTON | DIDFT_TGLBUTTON))) {
        int i;
        for (i = did->numPhysicalControls - 1; i > did->numPhysicalControls - 4; i--) {
            if (!lpddoi->tszName[0])
                break;
            const wchar_t *s1 = lpddoi->tszName;
            const wchar_t *s2 = did->physicalControls[i].name;
            while (*s1 && *s1 == *s2) {
                s1++;
                s2++;
            }
            // If perfect match with one of last 4 names, break.
            if (!*s1 && !*s2)
                break;

            while (s1 != lpddoi->tszName && (s1[-1] >= '0' && s1[-1] <= '9'))
                s1--;
            int check = 0;
            while (*s1 >= '0' && *s1 <= '9') {
                check = check * 10 + *s1 - '0';
                s1++;
            }
            while (*s2 >= '0' && *s2 <= '9') {
                s2++;
            }
            // If perfect match other than final number > 30, then break.
            // takes care of "button xx" case without causing issues with F keys.
            if (!*s1 && !*s2 && check > 30)
                break;
        }
        if (i != did->numPhysicalControls - 4) {
            return DIENUM_CONTINUE;
        }
    }

    int vkey = 0;
    if (lpddoi->tszName[0] && did->type == KEYBOARD) {
        for (u32 i = 0; i < 256; i++) {
            wchar_t *t = GetVKStringW((u8)i);
            if (!wcsicmp(lpddoi->tszName, t)) {
                vkey = i;
                break;
            }
        }
    }
    did->AddPhysicalControl(type, DIDFT_GETINSTANCE(lpddoi->dwType), vkey, lpddoi->tszName);
    return DIENUM_CONTINUE;
}

// Evil code from MS's site.  If only they'd just made a way to get
// an XInput device's GUID directly in the first place...
BOOL IsXInputDevice(const GUID *pGuidProductFromDirectInput)
{
    IWbemLocator *pIWbemLocator = NULL;
    IEnumWbemClassObject *pEnumDevices = NULL;
    IWbemClassObject *pDevices[20] = {0};
    IWbemServices *pIWbemServices = NULL;
    BSTR bstrNamespace = NULL;
    BSTR bstrDeviceID = NULL;
    BSTR bstrClassName = NULL;
    DWORD uReturned = 0;
    bool bIsXinputDevice = false;
    UINT iDevice = 0;
    VARIANT var;
    HRESULT hr;

    // CoInit if needed
    hr = CoInitialize(NULL);
    bool bCleanupCOM = SUCCEEDED(hr);

    // Create WMI
    hr = CoCreateInstance(__uuidof(WbemLocator),
                          NULL,
                          CLSCTX_INPROC_SERVER,
                          __uuidof(IWbemLocator),
                          (LPVOID *)&pIWbemLocator);
    if (FAILED(hr) || pIWbemLocator == NULL)
        goto LCleanup;

    bstrNamespace = SysAllocString(L"\\\\.\\root\\cimv2");
    if (bstrNamespace == NULL)
        goto LCleanup;
    bstrClassName = SysAllocString(L"Win32_PNPEntity");
    if (bstrClassName == NULL)
        goto LCleanup;
    bstrDeviceID = SysAllocString(L"DeviceID");
    if (bstrDeviceID == NULL)
        goto LCleanup;

    // Connect to WMI
    hr = pIWbemLocator->ConnectServer(bstrNamespace, NULL, NULL, 0L,
                                      0L, NULL, NULL, &pIWbemServices);
    if (FAILED(hr) || pIWbemServices == NULL)
        goto LCleanup;

    // Switch security level to IMPERSONATE.
    CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
                      RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);

    hr = pIWbemServices->CreateInstanceEnum(bstrClassName, 0, NULL, &pEnumDevices);
    if (FAILED(hr) || pEnumDevices == NULL)
        goto LCleanup;

    // Loop over all devices
    for (;;) {
        // Get 20 at a time
        hr = pEnumDevices->Next(10000, 20, pDevices, &uReturned);
        if (FAILED(hr))
            goto LCleanup;
        if (uReturned == 0)
            break;

        for (iDevice = 0; iDevice < uReturned; iDevice++) {
            // For each device, get its device ID
            hr = pDevices[iDevice]->Get(bstrDeviceID, 0L, &var, NULL, NULL);
            if (SUCCEEDED(hr) && var.vt == VT_BSTR && var.bstrVal != NULL) {
                // Check if the device ID contains "IG_".  If it does, then it's an XInput device
                // This information can not be found from DirectInput
                if (wcsstr(var.bstrVal, L"IG_")) {
                    // If it does, then get the VID/PID from var.bstrVal
                    DWORD dwPid = 0, dwVid = 0;
                    WCHAR *strVid = wcsstr(var.bstrVal, L"VID_");
                    if (strVid) {
                        dwVid = wcstoul(strVid + 4, 0, 16);
                    }
                    WCHAR *strPid = wcsstr(var.bstrVal, L"PID_");
                    if (strPid) {
                        dwPid = wcstoul(strPid + 4, 0, 16);
                    }

                    // Compare the VID/PID to the DInput device
                    DWORD dwVidPid = MAKELONG(dwVid, dwPid);
                    if (dwVidPid == pGuidProductFromDirectInput->Data1) {
                        bIsXinputDevice = true;
                        goto LCleanup;
                    }
                }
            }
            SAFE_RELEASE(pDevices[iDevice]);
        }
    }

LCleanup:
    if (bstrNamespace)
        SysFreeString(bstrNamespace);
    if (bstrDeviceID)
        SysFreeString(bstrDeviceID);
    if (bstrClassName)
        SysFreeString(bstrClassName);
    for (iDevice = 0; iDevice < 20; iDevice++)
        SAFE_RELEASE(pDevices[iDevice]);
    SAFE_RELEASE(pEnumDevices);
    SAFE_RELEASE(pIWbemLocator);
    SAFE_RELEASE(pIWbemServices);

    if (bCleanupCOM)
        CoUninitialize();

    return bIsXinputDevice;
}


struct DeviceEnumInfo
{
    IDirectInput8 *di8;
    int ignoreXInput;
};

BOOL CALLBACK EnumCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef)
{
    IDirectInput8 *di8 = ((DeviceEnumInfo *)pvRef)->di8;
    const wchar_t *name;
    wchar_t temp[40];
    //if (((DeviceEnumInfo*)pvRef)->ignoreXInput && lpddi->
    if (lpddi->tszInstanceName[0]) {
        name = lpddi->tszInstanceName;
    } else if (lpddi->tszProductName[0]) {
        name = lpddi->tszProductName;
    } else {
        wsprintfW(temp, L"Device %i", di8d.deviceCount);
        name = temp;
    }
    di8d.deviceCount++;
    wchar_t *fullName = (wchar_t *)malloc((wcslen(name) + 4) * sizeof(wchar_t));
    wsprintf(fullName, L"DX %s", name);
    wchar_t instanceID[100];
    wchar_t productID[100];
    GUIDtoString(instanceID, &lpddi->guidInstance);
    GUIDtoString(productID, &lpddi->guidProduct);
    DeviceType type = OTHER;
    if ((lpddi->dwDevType & 0xFF) == DI8DEVTYPE_KEYBOARD) {
        type = KEYBOARD;
    } else if ((lpddi->dwDevType & 0xFF) == DI8DEVTYPE_MOUSE) {
        type = MOUSE;
    }
    IDirectInputDevice8 *did;
    if (DI_OK == di8->CreateDevice(lpddi->guidInstance, &did, 0)) {
        DirectInputDevice *dev = new DirectInputDevice(type, did, fullName, instanceID, productID, lpddi->guidInstance);
        if (dev->numPhysicalControls || dev->numFFAxes) {
            dm->AddDevice(dev);
        } else {
            delete dev;
        }
    }
    free(fullName);
    return DIENUM_CONTINUE;
}

void EnumDirectInputDevices(int ignoreXInput)
{
    DeviceEnumInfo enumInfo;
    enumInfo.di8 = GetDirectInput();
    if (!enumInfo.di8)
        return;
    enumInfo.ignoreXInput = ignoreXInput;
    di8d.deviceCount = 0;
    enumInfo.di8->EnumDevices(DI8DEVCLASS_ALL, EnumCallback, &enumInfo, DIEDFL_ATTACHEDONLY);
    ReleaseDirectInput();
}
