==============================

NEXCOPY CID READER

==============================

The following code is all that is needed to use the DLL from C#:

public static class APISD
{
     [DllImport("cidsdm.dll", SetLastError = true)]
     private static extern bool GetCID(
         int nDrive,
         [MarshalAs(UnmanagedType.LPArray, SizeConst = 16)]
         byte[] pbtCIDBuffer,
         uint ulBufferSize
     );

     public static byte[] ReadCID(int nDrive)
     {
         byte[] CIDBuffer = new byte[16];
         // Call the DLL to read CID
         bool ret = GetCID(nDrive, CIDBuffer, (uint)CIDBuffer.Length);
         if (ret)
             return CIDBuffer;
         else
             return null;
     }
}

Just send them the cidsdm.dll file that is currently installed with DriveManager and that's all they should need to get the CID of a specific drive. It actually requires the physical drive number for the card reader. Every disk drive in the system receives a physical drive number that can be used to open a handle directly to the drive. Here's an example of what's happening in

cidsdm.dll:

char DriveAddress[20];
sprintf(DriveAddress, "\\\\.\\PhysicalDrive%d", nDrive);

HANDLE pDrive = CreateFile(DriveAddress, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);

Basically, I'm using the physical drive number to form an address that looks like "\\.\PhysicalDrive1" which allows me to open the handle to the drive. 

If they're not sure how to get this physical drive number let me know and I might be able to share some additional code to demonstrate how it can be done. It's not very hard, but not straightforward either unfortunately.

==============================

FIGURING OUT PORT MAPPING

==============================

To map a drive letter to a specific port within the Nexcopy duplicator is a fairly complicated process. Unfortunately, there is no simple way to do it, however the method we use of mapping a drive to a specific port is the only consistent way that I know of. The best place to start understanding the process is looking at the USBView sample which is included in the Windows Driver Kit (can download it from http://www.microsoft.com/en-us/download/details.aspx?id=11800). The USBView sample application creates a mapping of all the USB controllers, USB root hubs, USB hubs, USB ports, and USB drives on the system which it is run and displays them in a nice hierarchical fashion. This code is the basis of our port mapping because the order in which all USB hubs and ports that are part of the Nexcopy duplicator are discovered is always the same as long as the internal hub and port configuration of the duplicator remains the same.

So, to briefly explain the basics of what we are doing, we are using code exactly like the USBView sample to enumerate the USB controllers on the system. For each USB controller, we must use the code to find the root hub which contains all the ports for that USB controller. Within each root hub there are typically anywhere from 2 - 8 ports, depending on the specific controller. Therefore the next step is to enumerate the ports for the root hub and determine if a USB device or USB hub is connected to it. If a USB hub is found plugged into one of the ports then we have to determine if it is the first hub within the Nexcopy duplicator. To do this, we check the hub's DevicePath, which usually looks something similar to:

USB#VID_05E3&PID_0608#5&1eaa782a&0&3#{f18a0e88-c30c-11d0-8815-00a0c906bed8}

Within that path, you will notice the VID and PID designator, which is what we use to determine if this is one of the Nexcopy duplicators. Over the years, various different hardware and configurations have been used for the Nexcopy duplicators as their design has been improved. As a result, within our code we have defined the VID/PID combinations for the various Nexcopy hubs that have been used. Every time we find a hub during our enumeration, we check the hub's DevicePath to see if it contains one of these VID/PID combinations:

const string HUB_ORIGINAL = "VID_05E3&PID_0608"; const string HUB_NEW = "VID_1A40&PID_0101"; const string HUB_3 = "VID_1A40&PID_0201";

If it does, then this is the first hub within the Nexcopy duplicator, which I will call the primary hub. From this point, you start a port counter at 0 that is incremented every time a nested port is found (incremented every time EXCEPT when the port has a hub attached, in which case I don't increment the counter). Within each hub some of the ports are used to connect other hubs while some of the ports are obviously used to connect drives. If you find a port with a USB device connected, you can save an item in an array that correlates that device's InstanceID with the port counter that you have been keeping. If you find a port with a hub plugged in then you immediately start enumerating the ports of that hub and so on until we have fully enumerated the entire nested tree of hubs and ports below the primary hub. Once we're done with this enumeration, the counter that we started at 0 will be at somewhere like 32 if there were 7 nested hubs (8 total including the primary hub) with 4 ports each for example.

So now we have an array that looks kind of like this:

Index 0: { 2,
"USB\VID_0C76&PID_0005\0000000000000E7FF52DFFCC317ABC0F94" } Index 1: { 13, "USB\VID_0C76&PID_0005\0000000000000E7FF52DFFCC317ABC0F9A" } Index 2: { 21, "USB\VID_0BB4&PID_0C8D\HT05DHL06419" }

This would describe 3 different USB drives plugged somewhere into the Nexcopy duplicator. Next you can use the function I've included at the bottom of this email named FindInstanceID which is actually a modified version of the function FindDriveLetter that's included in the USBView sample. This will allow you to pass in a drive letter and it will return the InstanceID. From here, you can look through the array created earlier to find the InstanceID you just got for your specific drive letter. At this point, you now are able to correlate a drive letter AND a port number, however, you're not done yet. The problem is that the port number we have isn't the actual port number shown on the outside of the Nexcopy duplicator. However, since the ports are always enumerated in the same order we can create a mapping which may state that port 21 from our port counter is actually port 10 as shown on the outside of the duplicator. Here is an example of a mapping array that I'm using:

static int[] PortMap_New = { 4, 3, 2, 1, 8, 7, 6, 5, 0, 12, 11, 10, 9, 16, 15, 14, 13, 0, 20, 19, 18, 17, 0, 0, 0 };

What this mapping says is that when the port counter is at 0 (index 0 in the array) then the actual port number is 4. When the port counter is at 1, the actual port number is 3, and so on. The places where you see zeros are actually ports that aren't being used.

Hopefully this description gives you a good idea of how to get going with determining the port numbers. I suggest that you get very familiar with the USBView sample application and code because the majority of the code you need to actually do this is all there already.

Here is the FindInstanceID function I referenced earlier, in C# code. 
If you are writing in C++ then your code should be even simpler and you can probably just modify the FindDriveLetter function from the USBView
sample:

static public string FindInstanceID(string DriveLetter) {
     string InstanceID = "";

     // We start by getting the unique DeviceNumber of the given
     // DriveLetter.  We'll use this later to find a matching
     // DevicePath "symbolic name"
     int DevNum = GetDeviceNumber(@"\\.\" + DriveLetter.TrimEnd('\\'));
     if (DevNum < 0)
     {
         return null;
     }

     Guid DiskGUID = new Guid(GUID_DEVINTERFACE_DISK);

     // We start at the "root" of the device tree and look for all
     // devices that match the interface GUID of a disk
     IntPtr h = SetupDiGetClassDevs(ref DiskGUID, 0, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
     if (h.ToInt32() != INVALID_HANDLE_VALUE)
     {
         bool Success = true;
         int i = 0;
         do
         {
             // create a Device Interface Data structure
             SP_DEVICE_INTERFACE_DATA dia = new SP_DEVICE_INTERFACE_DATA();
             dia.cbSize = Marshal.SizeOf(dia);

             // start the enumeration
             Success = SetupDiEnumDeviceInterfaces(h, IntPtr.Zero, ref DiskGUID, i, ref dia);
             if (Success)
             {
                 // build a DevInfo Data structure
                 SP_DEVINFO_DATA da = new SP_DEVINFO_DATA();
                 da.cbSize = Marshal.SizeOf(da);

                 // build a Device Interface Detail Data structure
                 SP_DEVICE_INTERFACE_DETAIL_DATA didd = new SP_DEVICE_INTERFACE_DETAIL_DATA();
                 didd.cbSize = 4 + Marshal.SystemDefaultCharSize; // trust me :)

                 // now we can get some more detailed information
                 int nRequiredSize = 0;
                 int nBytes = BUFFER_SIZE;
                 if (SetupDiGetDeviceInterfaceDetail(h, ref dia, ref didd, nBytes, ref nRequiredSize, ref da))
                 {
                     // Now that we have a DevicePath... we can use it to
                     // generate another DeviceNumber to see if it matches
                     // the one we're looking for.
                     if (GetDeviceNumber(didd.DevicePath) == DevNum)
                     {
                         // current InstanceID is at the "USBSTOR" 
level, so we
                         // need up "move up" one level to get to the "USB" level
                         IntPtr ptrPrevious;
                         CM_Get_Parent(out ptrPrevious, da.DevInst, 0);

                         // Now we get the InstanceID of the USB level device
                         IntPtr ptrInstanceBuf = Marshal.AllocHGlobal(nBytes);
                         CM_Get_Device_ID(ptrPrevious, ptrInstanceBuf, nBytes, 0);
                         InstanceID =
Marshal.PtrToStringAuto(ptrInstanceBuf);

                         Marshal.FreeHGlobal(ptrInstanceBuf);
                         break;
                     }
                 }
             }
             i++;
         } while (Success);
         SetupDiDestroyDeviceInfoList(h);
     }
     return InstanceID;
}

private static int GetDeviceNumber(string DevicePath) {
     int ans = -1;

     IntPtr h = CreateFile(DevicePath.TrimEnd('\\'), 0, 0, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
     if (h.ToInt32() != INVALID_HANDLE_VALUE)
     {
         int requiredSize;
         STORAGE_DEVICE_NUMBER Sdn = new STORAGE_DEVICE_NUMBER();
         int nBytes = Marshal.SizeOf(Sdn);
         IntPtr ptrSdn = Marshal.AllocHGlobal(nBytes);

         if (DeviceIoControl(h, IOCTL_STORAGE_GET_DEVICE_NUMBER, IntPtr.Zero, 0, ptrSdn, nBytes, out requiredSize, IntPtr.Zero))
         {
             Sdn = (STORAGE_DEVICE_NUMBER)Marshal.PtrToStructure(ptrSdn,
typeof(STORAGE_DEVICE_NUMBER));
             // just my way of combining the relevant parts of the
             // STORAGE_DEVICE_NUMBER into a single number
             ans = (Sdn.DeviceType << 8) + Sdn.DeviceNumber;
         }
         Marshal.FreeHGlobal(ptrSdn);
         CloseHandle(h);
     }
     return ans;
}

