Source file: /~heha/hs/inpout32-hs.zip/LptGetAddr.cpp

#include <windows.h>
#include <setupapi.h>
#include <cfgmgr32.h>
#include <devguid.h>
#include <stdio.h>
#include <tchar.h>
#include "redir.h"

// Gets base address from PnP service, LPT1 -> n=0, etc.
// LOWORD = SPP address, HIWORD = ECP address
// Into an array of size n if a given
// Surprisingly, it works for NT4 too!
// It should work on Win95 too, but SetupDiEnumDeviceInfo() returns FALSE
// and GetLastError() returns 0x103 == ERROR_NO_MORE_ITEMS. (Win95C)
static DWORD LptGetAddrPnp(DWORD a[], int n) {
 DWORD Count=0;
 HDEVINFO Devs=SetupDiGetClassDevs(&GUID_DEVCLASS_PORTS,NULL,0,DIGCF_PRESENT);
 if (Devs!=INVALID_HANDLE_VALUE) {
  SP_DEVINFO_DATA devInfo;
  int Index,j;
  devInfo.cbSize=sizeof(devInfo);
  for (j=0; SetupDiEnumDeviceInfo(Devs,j,&devInfo); j++) {
   HKEY hKey;
   TCHAR val[16];
   DWORD valsize=sizeof(val);
   LOG_CONF Config;
   RES_DES resDes=0;
   IO_RESOURCE ior;
   DWORD addr=0;
   if (CM_Open_DevNode_Key(devInfo.DevInst,KEY_QUERY_VALUE,0,RegDisposition_OpenExisting,&hKey,CM_REGISTRY_HARDWARE)) continue;
   if (!RegQueryValueEx(hKey,T("PortName"),NULL,NULL,(LPBYTE)val,&valsize)
   && (_stscanf(val,T("LPT%d"),&Index)==1)
   && (unsigned)--Index<256
   && (a || Index==n)
   && !CM_Get_First_Log_Conf(&Config,devInfo.DevInst,ALLOC_LOG_CONF)) {	// Returns 0x34 on Win8
    while (!CM_Get_Next_Res_Des(&resDes,Config,ResType_IO,NULL,0)) {
     CM_Get_Res_Des_Data(resDes,&ior,sizeof(ior),0);
     if (addr) {
      addr |= (WORD)ior.IO_Header.IOD_Alloc_Base<<16;	// ECP address (second entry)
      break;
     }else addr = (WORD)ior.IO_Header.IOD_Alloc_Base;	// EPP address (first entry)
     Config = resDes;
    }
    CM_Free_Res_Des_Handle(resDes);
   }
   RegCloseKey(hKey);
   if (addr) {		// found an address?
    if (a) {		// array mode?
     if (Index<n) a[Index]=addr;	// write to array
     Count++;		// count LPT ports
    }else{		// single mode
     Count=addr;	// return address
     break;		// exit loop
    }
   }
  }/*for*/
  SetupDiDestroyDeviceInfoList(Devs);
 }
 return Count;
}

// This is merely a workaround for the Windows 8 bug stated above.
// In newer documentation, Microsoft says this were a feature:
// CM_Get_First_Log_Conf() is supported for 64 bit processes only.
// This routine will work for all Windows from 2000 and newer.
// As you can see, I have a decent reason to indent by only one space.
// -141022: This new implementation doesn't rely on known service names anymore
//	As a disadvantage, it contains three nested loops for GUID+PortName scan
static DWORD LptGetAddrNT(DWORD a[], int n) {
 DWORD Count=0;		// possible return value (can be greater than n)
 TCHAR buf[MAX_PATH];	// handy stack-allocated multi-purpose buffer
 
 HKEY hEnum;		// Open the registry (first stage)
 if (!RegOpenKeyEx(HKEY_LOCAL_MACHINE,T("SYSTEM\\CurrentControlSet\\Enum"),0,
   KEY_QUERY_VALUE|KEY_ENUMERATE_SUB_KEYS,&hEnum)) {
// first-level enumeration
  DWORD size;
  for (DWORD i1=0; size=elemof(buf),
    !RegEnumKeyEx(hEnum,i1,buf,&size,NULL,NULL,NULL,NULL); i1++) {
   HKEY hK1;
   if (!RegOpenKeyEx(hEnum,buf,0,KEY_QUERY_VALUE|KEY_ENUMERATE_SUB_KEYS,&hK1)) {
// second-level enumeration
    for (DWORD i2=0; size=elemof(buf),
      !RegEnumKeyEx(hK1,i2,buf,&size,NULL,NULL,NULL,NULL); i2++) {
     HKEY hK2;
     if (!RegOpenKeyEx(hK1,buf,0,KEY_QUERY_VALUE|KEY_ENUMERATE_SUB_KEYS,&hK2)) {
// third-level enumeration
      for (DWORD i3=0; size=elemof(buf),
        !RegEnumKeyEx(hK2,i3,buf,&size,NULL,NULL,NULL,NULL); i3++) {
       DWORD addr=0;
       int Index;
       HKEY hPath;
       if (!RegOpenKeyEx(hK2,buf,0,KEY_QUERY_VALUE,&hPath)) {
// check for right ClassGUID
        size=sizeof buf;
#ifdef CHECKGUID
        if (!RegQueryValueEx(hPath,T("ClassGUID"),NULL,NULL,(PBYTE)buf,&size)
        && !_tcsicmp(buf,T("{4d36e978-e325-11ce-bfc1-08002be10318}")))
#else
        if (!RegQueryValueEx(hPath,T("Class"),NULL,NULL,(PBYTE)buf,&size)
        && !_tcsicmp(buf,T("Ports")))
#endif
	{
         HKEY hParams;
         if (!RegOpenKeyEx(hPath,T("Device Parameters"),0,KEY_QUERY_VALUE,&hParams)) {
          HKEY hControl;
          size=sizeof buf;
          if (!RegQueryValueEx(hParams,T("PortName"),NULL,NULL,(PBYTE)buf,&size)
          && _stscanf(buf,T("LPT%d"),&Index)==1
          && (unsigned)--Index<256	// Use zero-based index from here, avoid negative values
          && (a || Index==n)
          && !RegOpenKeyEx(hPath,T("Control"),0,KEY_QUERY_VALUE,&hControl)) {
           size=sizeof buf;
           if (!RegQueryValueEx(hControl,T("AllocConfig"),NULL,NULL,(PBYTE)buf,&size)) {
// This undocumented AllocConfig structure is checked against Win2k and Win8/64.
// In both cases, the first ResType entry was at byte offset 16.
            DWORD *p=(DWORD*)buf;
            DWORD marker=0;
            int k;
            for (k=0; k<(int)size/4-5; k++,p++) {
             if (marker) {
              if (p[0]==marker
              && !HIWORD(p[1])		// port address less than 64K
              && !p[2]			// no high DWORD part
              && p[3]<16) {		// length limited to 16
               if (addr) {
                addr|=p[1]<<16;		// ECP address
                break;
               }else addr=p[1];		// SPP address
               p+=3;
               k+=3;			// eat DWORDs
              }
             }else if (p[0]==0x00010001	// seems like ResType_IO
             && !HIWORD(p[1])		// some small number
             && p[2]			// marker DWORD available
             && !HIWORD(p[3])) {	// port address less than 64K
              marker=p[2];
             }
            }
           }
           RegCloseKey(hControl);
          }
          RegCloseKey(hParams);
         }
        }
        RegCloseKey(hPath);
       }
       if (addr) {
        if (a) {
         if (Index<n) a[Index]=addr;
         Count++;
        }else{
         RegCloseKey(hK2);
         RegCloseKey(hK1);
         RegCloseKey(hEnum);
         return addr;
        }
       }
      }
      RegCloseKey(hK2);
     }
    }
    RegCloseKey(hK1);
   }
  }
  RegCloseKey(hEnum);
 }
 return Count;
}

#ifdef _M_IX86

#ifdef UNICODE
#define LptGetAddr95(a,n) 0
#else
// This routine will work for Windows 95, 98, and Me,
// but is used only for Windows 95 here, as Windows 98+ can use SetupDi functions
static DWORD LptGetAddr95(DWORD a[], int n) {
 char buf[MAX_PATH];		// multi-purpose buffer
 HKEY hEnumDD,hEnumLM;
 DWORD Count=0;
   // Open the registry
 if (!RegOpenKeyEx(HKEY_DYN_DATA,"Config Manager\\Enum",0,KEY_ENUMERATE_SUB_KEYS,&hEnumDD)) {
  if (!RegOpenKeyEx(HKEY_LOCAL_MACHINE,"Enum",0,KEY_QUERY_VALUE,&hEnumLM)) {
   FILETIME ft;
   DWORD Length;
   DWORD i;
   for (i=0; Length=elemof(buf),!RegEnumKeyEx(hEnumDD,i,buf,&Length,NULL,NULL,NULL,&ft); i++) {
    int Index;
    DWORD addr=0;
    HKEY hEntry;
    if (!RegOpenKeyEx(hEnumDD,buf,0,KEY_QUERY_VALUE,&hEntry)) {
     DWORD Problem=0;
     Length=sizeof Problem;
     RegQueryValueEx(hEntry,"Problem",NULL,NULL,(PBYTE)&Problem,&Length);
     if (!Problem) {
      HKEY hDevInfo;
      Length=sizeof buf;
      if (!RegQueryValueEx(hEntry,"HardwareKey", NULL,NULL,(PBYTE)buf,&Length)
      && !RegOpenKeyEx(hEnumLM,buf,0,KEY_QUERY_VALUE,&hDevInfo)) {
       Length=sizeof buf;
       if (!RegQueryValueEx(hDevInfo,"PortName",NULL,NULL,(PBYTE)buf,&Length)
       && sscanf(buf,"LPT%d",&Index)==1
       && (unsigned)--Index<256
       && (a || Index==n)
       && !(Length=sizeof(buf),RegQueryValueEx(hEntry,"Allocation",NULL,NULL,(PBYTE)buf,&Length))) {
        WORD *p=(WORD*)buf;	// Decode the Allocation data: the port address is present
        int k;			// directly after a 0x000C entry (which doesn't have 0x0000 after it).
        for (k=0; k<(int)Length/2-2; k++,p++) {
         if (p[0]==0x000C && p[1]) {	// there is an I/O address
          if (addr) {			// (which is possibly NOT correct because 0x0C may describe ISA!!)
           addr|=p[1]<<16;	// assume ECP address
           break;
          }else addr=p[1];	// SPP address
          p+=2;			// skip start and end address
          k+=2;
         }
        }
       }
       RegCloseKey(hDevInfo);
      }
     }
     RegCloseKey(hEntry);
    }
    if (addr) {
     if (a) {
      if (Index<n) a[Index]=addr;
      Count++;
     }else{
      Count=addr;
      break;
     }
    }
   }/*for*/
   RegCloseKey(hEnumLM);
  }
  RegCloseKey(hEnumDD);
 }
 return Count;
}
#endif

// This is the routine for NT4 and possibly NT3.51
static DWORD LptGetAddrNT4(DWORD a[], int n) {
 DWORD Count=0;
 HKEY hHdw;
 if (!RegOpenKeyEx(HKEY_LOCAL_MACHINE,T("HARDWARE"),0,KEY_ENUMERATE_SUB_KEYS|KEY_QUERY_VALUE,&hHdw)) {
  HKEY hPP;
  TCHAR key[128],buf[128];
  DWORD kLength,Length;
  if (!RegOpenKeyEx(hHdw,T("DEVICEMAP\\PARALLEL PORTS"),
    0,KEY_ENUMERATE_SUB_KEYS|KEY_QUERY_VALUE,&hPP)) {
   int i;
   for (i=0;
     kLength=elemof(key), Length=sizeof buf,
     !RegEnumValue(hPP,i,key,&kLength,NULL,NULL,(PBYTE)buf,&Length);
     i++) {
    int Index,i0;
    DWORD addr=0;
    if (_stscanf(buf,T("\\DosDevices\\LPT%d"),&Index)==1
    && (unsigned)--Index<256
    && _stscanf(key,T("\\Device\\Parallel%d"),&i0)) {
     HKEY hRes;
     if (!RegOpenKeyEx(hHdw,T("RESOURCEMAP\\LOADED PARALLEL DRIVER RESOURCES\\Parport"),
       0,KEY_QUERY_VALUE,&hRes)) {
      _sntprintf(key,elemof(key),T("\\Device\\ParallelPort%d.Raw"),i0);
      Length=sizeof buf;
      if (!RegQueryValueEx(hRes,key,NULL,NULL,(PBYTE)buf,&Length)
      && Length>=26) addr=((WORD*)buf)[12];
      RegCloseKey(hRes);
     }
    }
    if (addr) {
     if (a) {
      if (Index<n) a[Index]=addr;
      Count++;
     }else{
      Count=addr;
      break;
     }
    }
   }/*for*/
   RegCloseKey(hPP);
  }
  RegCloseKey(hHdw);
 }
 return Count;
}   

#endif

// This single entry tries to detect LPT port addresses
// at first using documented PnP dervices.
// On failure, it will use old-style, undocumented registry parsing.
// All four detechtion methods above share the same signature.
EXTERN_C DWORD LptGetAddr(DWORD a[], int n) {
 DWORD ret=LptGetAddrPnp(a,n);	// Works for Win98,Me,2k,XP,Vista,7,surely 2k3, 2k8
 if (!ret) {
#ifdef _M_IX86
  if (sysver<0) ret=LptGetAddr95(a,n);	// Windows 95 only (not suitable for Win32s)
  else if ((BYTE)sysver<5) ret=LptGetAddrNT4(a,n);	// (NT4), NT3.51
  else
#endif
       ret=LptGetAddrNT(a,n);		// Here: for Windows 8
 }
 return ret;
}
Detected encoding: ASCII (7 bit)2