// libusb -> WinUSB bridge (c) 2010 JJS
//
// Licensed unter the BSD license.
//
// The purpose of this dll is to redirect calls to libusb to a
// WinUSB driver. This is usefull for systems where the driver signature
// check enforcement cannot be turned off in a convenient way (Vista/7 64 Bit).
// 
// By no means is the implementation of libusb calls complete. In fact, it
// is just the bare minimum to get PSPLINK or RemoteJoy running. Error
// codes are (with the exception of a timeout) not translated, but only a 
// generic (-1) error gets returned, so do not use this library when 
// developing or debugging libusb software as this will lead you nowhere.
//
// Note: The Microsoft C compiler is not fully C99 compliant, keep that
// in mind when doing changes.

#include <windows.h>
#include <stdio.h>
// This code needs the WDK and the respective directories must be known to VC

// Include libusb header usb.h
#include "usb.h"

// winusb.h includes usb.h from the WDK, which also defines __USB_H__
// Also, libusb defines some symbols that are defined in the WDK, fortunately they match
// and the "redefinition" warnings can be ignored
#undef __USB_H__
#pragma warning(disable: 4005)
#include <winusb.h>
#pragma warning(default: 4005)
#pragma comment(lib, "winusb.lib")
// Include setup api used for finding the device
#include <setupapi.h>
#pragma comment(lib, "setupapi.lib")


// Forward declaration
BOOL usbWinGetDevicePath(char* guidString, char* devicePath);


#define LIBUSB_ETIMEDOUT (-116)

#define USB_VENDOR_ID 0x54C
#define USB_PRODUCT_ID_TYPE_B 0x1C9
#define USB_PRODUCT_ID_TYPE_C 0x1CA

// GUID of the WinUSB PSP Type C driver, this driver is also included in PSPdisp v0.4+
char g_guidBuffer[256] = "{9ef8fcc2-f024-4fbd-ad39-50fcdbb81a95}";

// Device and bus structs in case the WinUSB PSP is connected
struct usb_device g_dummyUsbDevice = { NULL, NULL, "", NULL, { 0, 0, 0, 0, 0, 0, 0, USB_VENDOR_ID, USB_PRODUCT_ID_TYPE_B, 0, 0, 0, 0, 0 }, NULL, NULL, 1, 0, NULL };
struct usb_bus g_dummyUsbBus = { NULL, NULL, "bus-0", &g_dummyUsbDevice, 0, NULL };

// Empty device and bus structs if no PSP is connected
struct usb_device g_dummyUsbDeviceNoDevice = { NULL, NULL, "", NULL, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, NULL, NULL, 1, 0, NULL };
struct usb_bus g_dummyUsbBusNoDevice = { NULL, NULL, "bus-0", NULL, 0, NULL };

// Device handle reported back to the application, just a dummy with no relevance
int g_dummyDevHandle = 1;

// Global handles for the WinUSB device
HANDLE g_winusbDeviceHandle;
WINUSB_INTERFACE_HANDLE g_winusbInterfaceHandle;


// Checks for the device presence, then opens the device
usb_dev_handle *bridge_usb_open(struct usb_device *dev)
{
  char pathBuffer[256] = "";

  if (usbWinGetDevicePath(&g_guidBuffer[0], &pathBuffer[0]))
  {
    g_winusbDeviceHandle = CreateFile(pathBuffer, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, 0);

    if (g_winusbDeviceHandle != INVALID_HANDLE_VALUE)
    {
      if (WinUsb_Initialize(g_winusbDeviceHandle, &g_winusbInterfaceHandle))
      {
        return (usb_dev_handle*)&g_dummyDevHandle;
      }
      else
      {
        CloseHandle(g_winusbDeviceHandle);
      }
    }
  }

  // Error
  return NULL;
}



// Close the device handles
int bridge_usb_close(usb_dev_handle *dev)
{
  WinUsb_Free(g_winusbInterfaceHandle);
  g_winusbInterfaceHandle = 0;
  CloseHandle(g_winusbDeviceHandle);
  g_winusbDeviceHandle = NULL;
  return 0;
}


// Write data to the specified endpoint
int bridge_usb_bulk_write(usb_dev_handle *dev, int ep, char *bytes, int size, int timeout)
{
  ULONG bytesTransferred = 0;

  WinUsb_SetPipePolicy(g_winusbInterfaceHandle, ep, PIPE_TRANSFER_TIMEOUT, sizeof(int), &timeout);
  if (WinUsb_WritePipe(g_winusbInterfaceHandle, ep, (PUCHAR)&bytes[0], (ULONG)size, &bytesTransferred, NULL))
  {
    return bytesTransferred;
  }
  else
  {
    DWORD error = GetLastError();

    if (error == ERROR_SEM_TIMEOUT)
      return LIBUSB_ETIMEDOUT;
    else
      return -1;
  }
}



// Read data from the specified endpoint
int bridge_usb_bulk_read(usb_dev_handle *dev, int ep, char *bytes, int size, int timeout)
{
  ULONG bytesTransferred = 0;

  WinUsb_SetPipePolicy(g_winusbInterfaceHandle, ep, PIPE_TRANSFER_TIMEOUT, sizeof(int), &timeout);
  if (WinUsb_ReadPipe(g_winusbInterfaceHandle, ep, (PUCHAR)&bytes[0], (ULONG)size, &bytesTransferred, NULL))
  {
    return bytesTransferred;
  }
  else
  {
    DWORD error = GetLastError();

    if (error == ERROR_SEM_TIMEOUT)
      return LIBUSB_ETIMEDOUT;
    else
      return -1;
  }
}


// Enumerate the available devices,
// here only implemented to check for the WinUSB device and return
// either the dummy usb device struct on success or the no device struct
struct usb_device *bridge_usb_device(usb_dev_handle *dev)
{
  char pathBuffer[256] = "";

  if (usbWinGetDevicePath(&g_guidBuffer[0], &pathBuffer[0]))
  {
    // Device is connected, return dummy usb_device with device entry
    return &g_dummyUsbDevice;
  }
  else
  {
    return &g_dummyUsbDeviceNoDevice;
  }

  return NULL;
}


// Enumerate the available busses,
// here only implemented to check for the WinUSB device and return
// either the dummy usb bus struct on success or the no device bus struct
struct usb_bus *bridge_usb_get_busses(void)
{
  char pathBuffer[256] = "";

  if (usbWinGetDevicePath(&g_guidBuffer[0], &pathBuffer[0]))
  {
    // Device is connected, return dummy usb_bus with device entry
    return &g_dummyUsbBus;
  }
  else
  {
    return &g_dummyUsbBusNoDevice;
  }

  return NULL;
}


// There is no equivalent of usb_reset in the WinUSB api.
// While this might be a problem because the function gets
// called in usbhostfs_pc it is usually working fine.
int bridge_usb_reset(usb_dev_handle *dev)
{
  return 0;
}



// The following functions are not needed and only implemented to match the
// required exports of the libusb0 dll

// Dummy
int bridge_usb_set_configuration(usb_dev_handle *dev, int configuration)
{
  return 0;
}

// Dummy
int bridge_usb_claim_interface(usb_dev_handle *dev, int interface)
{
  return 0;
}

// Dummy
int bridge_usb_release_interface(usb_dev_handle *dev, int interface)
{
  return 0;
}

// Dummy
int bridge_usb_set_altinterface(usb_dev_handle *dev, int alternate)
{
  return 0;
}

// Dummy
void bridge_usb_init(void)
{
  return;
}

// Dummy
int bridge_usb_find_busses(void)
{
  return 0;
}

// Dummy
int bridge_usb_find_devices(void)
{
  return 0;
}





// Get the full path for the device specified by the guid
BOOL usbWinGetDevicePath(char* guidString, char* devicePath)
{
  GUID guid;
  SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
  HDEVINFO devInfo;
  DWORD requiredSize;
  SP_DEVINFO_DATA devInfoData;
  PSP_INTERFACE_DEVICE_DETAIL_DATA deviceDetail;

  unsigned long guidBytes[8];
  int i;

  // Extract GUID
  if (11 != sscanf(guidString, "{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}", &guid.Data1, &guid.Data2, &guid.Data3, &guidBytes[0], &guidBytes[1], &guidBytes[2], &guidBytes[3], &guidBytes[4], &guidBytes[5], &guidBytes[6], &guidBytes[7]))
    return FALSE;

  for (i = 0; i < 8; i++)
    guid.Data4[i] = (unsigned char)guidBytes[i];

  devInfo = SetupDiGetClassDevs(&guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
  if (devInfo == INVALID_HANDLE_VALUE) 
    return FALSE;

  // Get first present device
  deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);

  if (TRUE != SetupDiEnumDeviceInterfaces(devInfo, NULL, &guid, 0, &deviceInterfaceData))
  {
    SetupDiDestroyDeviceInfoList(devInfo);
    return FALSE;
  }

  // Get buffer size for interface data
  if (TRUE != SetupDiGetDeviceInterfaceDetail(devInfo, &deviceInterfaceData, NULL, 0, &requiredSize, NULL))
  {
    if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
      return FALSE;
  }

  // Get interface data
  deviceDetail = (PSP_INTERFACE_DEVICE_DETAIL_DATA)malloc(requiredSize);
  
  deviceDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
  devInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
  
  if (TRUE != SetupDiGetDeviceInterfaceDetail(devInfo, &deviceInterfaceData, deviceDetail, requiredSize, NULL, &devInfoData))
  {
    free(deviceDetail);
    return FALSE;
  }

  strcpy(devicePath, deviceDetail->DevicePath);
  
  free(deviceDetail);
  
  return TRUE;
}