5993 lines
139 KiB
C
5993 lines
139 KiB
C
/*++
|
|
|
|
Copyright (c) 1996 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
hwcomp.c
|
|
|
|
Abstract:
|
|
|
|
Win95 to NT hardware device comparison routines.
|
|
|
|
Author:
|
|
|
|
Jim Schmidt (jimschm) 8-Jul-1996
|
|
|
|
Revision History:
|
|
|
|
marcw 28-Jun-1999 Added HwComp_MakeLocalSourceDeviceExists
|
|
jimschm 04-Dec-1998 Fixed checksum problem caused by date rounding
|
|
jimschm 29-Sep-1998 Fixed incompatible hardware message to use proper roots
|
|
jimschm 28-Apr-1998 Support for description-less hardware
|
|
jimschm 01-Apr-1998 Added %1 to (not currently present) message
|
|
jimschm 27-Feb-1998 Added suppression of (not currently present) devices
|
|
marcw 11-Nov-1997 Minor change to ensure that we can find the dial-up adapter.
|
|
jimschm 03-Nov-1997 Revised to use GROWBUFs and project's reg api
|
|
jimschm 08-Oct-1997 Added legacy keyboard support
|
|
marcw 18-Sep-1997 Added some fields to netcard enumerator
|
|
jimschm 24-Jun-1997 Added net card enumerator
|
|
marcw 05-May-1997 Fixed problem with looking for usable hdd. Need to
|
|
look for "diskdrive" class instead of "hdc" class.
|
|
marcw 18-Apr-1997 Added ability to determine if a CdRom and Hdd
|
|
compatible with Windows Nt is online.
|
|
marcw 14-Apr-1997 Retrofitted new progress bar handling code in.
|
|
jimschm 2-Jan-1997 Added INF verification to hwcomp.dat
|
|
to automatically detect when an OEM
|
|
changes one or more INFs
|
|
|
|
--*/
|
|
|
|
#include "pch.h"
|
|
#include "hwcompp.h"
|
|
#include <cfgmgr32.h>
|
|
|
|
|
|
#define DBG_HWCOMP "HwComp"
|
|
|
|
#ifdef UNICODE
|
|
#error "hwcomp.c cannot be compiled as UNICODE"
|
|
#endif
|
|
|
|
#define S_HWCOMP_DAT TEXT("hwcomp.dat")
|
|
#define S_HKLM_ENUM TEXT("HKLM\\Enum")
|
|
#define S_HARDWAREID TEXT("HardwareID")
|
|
#define S_COMPATIBLEIDS TEXT("CompatibleIDs")
|
|
#define S_CLASS TEXT("Class")
|
|
#define S_MANUFACTURER TEXT("Manufacturer")
|
|
#define S_IGNORE_THIS_FILE TEXT("*")
|
|
|
|
#define PNPID_FIELD 2
|
|
|
|
|
|
#define DECLARE(varname,text) text,
|
|
|
|
PCTSTR g_DeviceFields[] = {
|
|
DEVICE_FIELDS /* , */
|
|
NULL
|
|
};
|
|
|
|
#undef DECLARE
|
|
|
|
|
|
GROWBUFFER g_FileNames = GROWBUF_INIT;
|
|
GROWBUFFER g_DecompFileNames = GROWBUF_INIT;
|
|
HASHTABLE g_PnpIdTable;
|
|
HASHTABLE g_UnsupPnpIdTable;
|
|
HASHTABLE g_ForceBadIdTable;
|
|
HASHTABLE g_InfFileTable;
|
|
HASHTABLE g_NeededHardwareIds;
|
|
HASHTABLE g_UiSuppliedIds;
|
|
BOOL g_ValidWinDir;
|
|
BOOL g_ValidSysDrive;
|
|
BOOL g_IncompatibleScsiDevice = FALSE;
|
|
BOOL g_ValidCdRom;
|
|
BOOL g_FoundPnp8387;
|
|
PPARSEDPATTERN g_PatternCompatibleIDsTable;
|
|
|
|
typedef enum {
|
|
HW_INCOMPATIBLE,
|
|
HW_REINSTALL,
|
|
HW_UNSUPPORTED
|
|
} HWTYPES;
|
|
|
|
static PCTSTR g_ExcludeTable[] = {
|
|
TEXT("wkstamig.inf"),
|
|
TEXT("desktop.inf"),
|
|
TEXT("usermig.inf"),
|
|
TEXT("dosnet.inf"),
|
|
TEXT("pad.inf"),
|
|
TEXT("msmail.inf"),
|
|
TEXT("wordpad.inf"),
|
|
TEXT("syssetup.inf"),
|
|
TEXT("pinball.inf"),
|
|
TEXT("perms.inf"),
|
|
TEXT("optional.inf"),
|
|
TEXT("multimed.inf"),
|
|
TEXT("mmopt.inf"),
|
|
TEXT("layout.inf"),
|
|
TEXT("kbd.inf"),
|
|
TEXT("iexplore.inf"),
|
|
TEXT("intl.inf"),
|
|
TEXT("imagevue.inf"),
|
|
TEXT("games.inf"),
|
|
TEXT("font.inf"),
|
|
TEXT("communic.inf"),
|
|
TEXT("apps.inf"),
|
|
TEXT("accessor.inf"),
|
|
TEXT("mailnews.inf"),
|
|
TEXT("cchat.inf"),
|
|
TEXT("iermv2.inf"),
|
|
TEXT("default.inf"),
|
|
TEXT("setup16.inf"),
|
|
TEXT("")
|
|
};
|
|
|
|
BOOL
|
|
GetFileNames (
|
|
IN PCTSTR *InfDirs,
|
|
IN UINT InfDirCount,
|
|
IN BOOL QueryFlag,
|
|
IN OUT PGROWBUFFER FileNames,
|
|
IN OUT PGROWBUFFER DecompFileNames
|
|
);
|
|
|
|
|
|
VOID
|
|
FreeFileNames (
|
|
IN PGROWBUFFER FileNames,
|
|
IN PGROWBUFFER DecompFileNames,
|
|
IN BOOL QueryFlag
|
|
);
|
|
|
|
VOID
|
|
pFreeHwCompDatName (
|
|
PCTSTR Name
|
|
);
|
|
|
|
BOOL
|
|
pIsInfFileExcluded (
|
|
PCTSTR FileNamePtr
|
|
);
|
|
|
|
BOOL
|
|
pGetFileNamesWorker (
|
|
IN OUT PGROWBUFFER FileNames,
|
|
IN OUT PGROWBUFFER DecompFileNames,
|
|
IN PCTSTR InfDir,
|
|
IN BOOL QueryFlag
|
|
);
|
|
|
|
BOOL
|
|
pIsDeviceConsideredCompatible (
|
|
PCTSTR DevIds
|
|
);
|
|
|
|
BOOL
|
|
pFindForcedBadHardwareId (
|
|
IN PCTSTR PnpIdList,
|
|
OUT PTSTR InfFileName OPTIONAL
|
|
);
|
|
|
|
//
|
|
// Implementation
|
|
//
|
|
|
|
BOOL
|
|
WINAPI
|
|
HwComp_Entry (
|
|
IN HINSTANCE hinstDLL,
|
|
IN DWORD dwReason,
|
|
IN LPVOID lpv
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
HwComp_Entry initializes the hwcomp library. It does what would normally
|
|
happen if this were a standalone dll, as opposed to a library.
|
|
|
|
|
|
At process detach, the device buffer is freed if necessary.
|
|
|
|
Arguments:
|
|
|
|
hinstDLL - (OS-supplied) instance handle for the DLL
|
|
dwReason - (OS-supplied) indicates attach or detatch from process or
|
|
thread
|
|
lpv - unused
|
|
|
|
Return Value:
|
|
|
|
Return value is always TRUE (indicating successful init).
|
|
|
|
--*/
|
|
|
|
{
|
|
switch (dwReason)
|
|
{
|
|
case DLL_PROCESS_ATTACH:
|
|
g_UiSuppliedIds = HtAlloc();
|
|
|
|
if (!g_UiSuppliedIds) {
|
|
DEBUGMSG ((DBG_ERROR, "HwComp_Entry: Can't create g_UiSuppliedIds"));
|
|
return FALSE;
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
case DLL_PROCESS_DETACH:
|
|
DEBUGMSG_IF ((
|
|
g_EnumsActive,
|
|
DBG_ERROR,
|
|
"%u hardware enumerations still active",
|
|
g_EnumsActive
|
|
));
|
|
|
|
DEBUGMSG_IF ((
|
|
g_NetEnumsActive,
|
|
DBG_ERROR,
|
|
"%u network hardware enumerations still active",
|
|
g_NetEnumsActive
|
|
));
|
|
|
|
FreeNtHardwareList();
|
|
|
|
if (g_NeededHardwareIds) {
|
|
HtFree (g_NeededHardwareIds);
|
|
g_NeededHardwareIds = NULL;
|
|
}
|
|
|
|
if (g_UiSuppliedIds) {
|
|
HtFree (g_UiSuppliedIds);
|
|
g_UiSuppliedIds = NULL;
|
|
}
|
|
|
|
if (g_PatternCompatibleIDsTable) {
|
|
DestroyParsedPattern (g_PatternCompatibleIDsTable);
|
|
g_PatternCompatibleIDsTable = NULL;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
PCTSTR
|
|
pGetHwCompDat (
|
|
IN PCTSTR SourceDir,
|
|
IN BOOL MustExist
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
GetHwCompDat builds e:\i386\hwcomp.dat, where e:\i386 is specified in
|
|
SourceDir. The caller must call pFreeHwCompDatName to clean up the
|
|
memory allocation.
|
|
|
|
Arguments:
|
|
|
|
SourceDir - The directory holding hwcomp.dat
|
|
|
|
MustExist - Specifies TRUE if hwcomp.dat must exist as specified,
|
|
or FALSE if hwcomp.dat does not necessarily exist.
|
|
|
|
Return Value:
|
|
|
|
MustExist = TRUE:
|
|
A pointer to a string, or NULL hwcomp.dat does not exist as
|
|
specified, or if allocation failed.
|
|
|
|
MustExist = FALSE:
|
|
A pointer to a string, or NULL if a memory allocation failed.
|
|
|
|
Caller must free the string via pFreeHwCompDatName.
|
|
|
|
--*/
|
|
|
|
{
|
|
PTSTR FileName;
|
|
|
|
if (SourceDir) {
|
|
FileName = JoinPaths (SourceDir, S_HWCOMP_DAT);
|
|
} else {
|
|
FileName = DuplicatePathString (S_HWCOMP_DAT, 0);
|
|
}
|
|
|
|
if (MustExist && GetFileAttributes (FileName) == 0xffffffff) {
|
|
pFreeHwCompDatName (FileName);
|
|
return NULL;
|
|
}
|
|
|
|
return FileName;
|
|
}
|
|
|
|
|
|
VOID
|
|
pFreeHwCompDatName (
|
|
PCTSTR Name
|
|
)
|
|
{
|
|
if (Name) {
|
|
FreePathString (Name);
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Routines for accessing the registry
|
|
//
|
|
|
|
PVOID
|
|
pPrivateRegValAllocator (
|
|
DWORD Size
|
|
)
|
|
{
|
|
return AllocText (Size);
|
|
}
|
|
|
|
VOID
|
|
pPrivateRegValDeallocator (
|
|
PCVOID Mem
|
|
)
|
|
{
|
|
FreeText (Mem);
|
|
}
|
|
|
|
|
|
PCTSTR
|
|
pGetAltDeviceDesc (
|
|
PCTSTR DriverSubKey
|
|
)
|
|
{
|
|
TCHAR DriverKey[MAX_REGISTRY_KEY];
|
|
HKEY Key;
|
|
PCTSTR Data;
|
|
PTSTR ReturnText = NULL;
|
|
|
|
if (!DriverSubKey) {
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Get driver key
|
|
//
|
|
|
|
wsprintf (DriverKey, TEXT("HKLM\\System\\CurrentControlSet\\Services\\Class\\%s"), DriverSubKey);
|
|
|
|
Key = OpenRegKeyStr (DriverKey);
|
|
if (!Key) {
|
|
return NULL;
|
|
}
|
|
|
|
Data = GetRegValueString (Key, TEXT("DriverDesc"));
|
|
|
|
CloseRegKey (Key);
|
|
|
|
if (Data) {
|
|
ReturnText = pPrivateRegValAllocator (SizeOfString (Data));
|
|
if (ReturnText) {
|
|
StringCopy (ReturnText, Data);
|
|
}
|
|
MemFree (g_hHeap, 0, Data);
|
|
}
|
|
|
|
return ReturnText;
|
|
}
|
|
|
|
|
|
VOID
|
|
pGetRegValText (
|
|
HKEY Key,
|
|
PCTSTR VarText,
|
|
PCTSTR *RetPtr
|
|
)
|
|
{
|
|
MYASSERT (!(*RetPtr));
|
|
*RetPtr = (PCTSTR) GetRegValueDataOfType2 (
|
|
Key,
|
|
VarText,
|
|
REG_SZ,
|
|
pPrivateRegValAllocator,
|
|
pPrivateRegValDeallocator
|
|
);
|
|
}
|
|
|
|
|
|
VOID
|
|
pFreeRegValText (
|
|
PHARDWARE_ENUM EnumPtr
|
|
)
|
|
{
|
|
//
|
|
// Free all device field text
|
|
//
|
|
|
|
#define DECLARE(varname,text) pPrivateRegValDeallocator((PVOID) EnumPtr->varname); EnumPtr->varname = NULL;
|
|
DEVICE_FIELDS
|
|
#undef DECLARE
|
|
|
|
}
|
|
|
|
|
|
VOID
|
|
pGetAllRegVals (
|
|
PHARDWARE_ENUM EnumPtr
|
|
)
|
|
{
|
|
PCTSTR AltDesc;
|
|
INFSTRUCT is = INITINFSTRUCT_GROWBUFFER;
|
|
PTSTR BetterDesc = NULL;
|
|
TCHAR PnpId[MAX_PNP_ID];
|
|
PCTSTR PnpIdList;
|
|
PCTSTR OldDesc;
|
|
PCTSTR p;
|
|
PCTSTR end;
|
|
PCTSTR start;
|
|
PTSTR newBuf;
|
|
PTSTR ptr;
|
|
CHARTYPE ch;
|
|
|
|
#define DECLARE(varname,text) if(!EnumPtr->varname) { \
|
|
pGetRegValText ( \
|
|
EnumPtr->ek.CurrentKey->KeyHandle, \
|
|
text, \
|
|
&EnumPtr->varname \
|
|
); \
|
|
} \
|
|
|
|
DEVICE_FIELDS
|
|
|
|
#undef DECLARE
|
|
|
|
//
|
|
// If there is a better name for this device, use it
|
|
//
|
|
|
|
PnpIdList = EnumPtr->HardwareID;
|
|
|
|
while (PnpIdList && *PnpIdList) {
|
|
PnpIdList = ExtractPnpId (PnpIdList, PnpId);
|
|
|
|
if (*PnpId) {
|
|
|
|
if (InfFindFirstLine (g_Win95UpgInf, S_PNP_DESCRIPTIONS, PnpId, &is)) {
|
|
AltDesc = InfGetStringField (&is, 1);
|
|
|
|
if (AltDesc) {
|
|
OldDesc = EnumPtr->DeviceDesc;
|
|
|
|
BetterDesc = (PTSTR) pPrivateRegValAllocator (SizeOfString (AltDesc));
|
|
StringCopy (BetterDesc, AltDesc);
|
|
EnumPtr->DeviceDesc = BetterDesc;
|
|
|
|
DEBUGMSG ((
|
|
DBG_HWCOMP,
|
|
"Using %s for description (instead of %s)",
|
|
BetterDesc,
|
|
OldDesc
|
|
));
|
|
|
|
pPrivateRegValDeallocator ((PVOID) OldDesc);
|
|
break;
|
|
}
|
|
ELSE_DEBUGMSG ((DBG_WHOOPS, "Better description could not be retrieved"));
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Workaround: if the device description is bad, use the driver
|
|
// description if it is available
|
|
//
|
|
|
|
if (!BetterDesc) {
|
|
|
|
if (!EnumPtr->DeviceDesc || CharCount (EnumPtr->DeviceDesc) < 5) {
|
|
|
|
AltDesc = pGetAltDeviceDesc (EnumPtr->Driver);
|
|
if (AltDesc) {
|
|
pPrivateRegValDeallocator ((PVOID) EnumPtr->DeviceDesc);
|
|
EnumPtr->DeviceDesc = AltDesc;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Fix leading/trail space problems
|
|
//
|
|
|
|
if (EnumPtr->DeviceDesc) {
|
|
|
|
start = SkipSpace (EnumPtr->DeviceDesc);
|
|
|
|
end = GetEndOfString (start);
|
|
p = SkipSpaceR (start, end);
|
|
|
|
if (p && (p != end || start != EnumPtr->DeviceDesc)) {
|
|
|
|
p = _tcsinc (p);
|
|
|
|
newBuf = pPrivateRegValAllocator (
|
|
(PBYTE) (p + 1) - (PBYTE) start
|
|
);
|
|
StringCopyAB (newBuf, start, p);
|
|
|
|
pPrivateRegValDeallocator ((PVOID) EnumPtr->DeviceDesc);
|
|
EnumPtr->DeviceDesc = newBuf;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Eliminate newline chars inside description; replace \r or \n with a space
|
|
// the replacement occurs inplace, so chars will be replaced inplace
|
|
//
|
|
|
|
if (EnumPtr->DeviceDesc) {
|
|
|
|
for (ptr = (PTSTR)EnumPtr->DeviceDesc; *ptr; ptr = _tcsinc (ptr)) {
|
|
ch = _tcsnextc (ptr);
|
|
if (!_istprint (ch)) {
|
|
//
|
|
// in this case it's a single TCHAR, just replace it inplace
|
|
//
|
|
MYASSERT (*ptr == (TCHAR)ch);
|
|
*ptr = TEXT(' ');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
InfCleanUpInfStruct (&is);
|
|
}
|
|
|
|
|
|
BOOL
|
|
pGetPnpIdList (
|
|
IN PINFSTRUCT is,
|
|
OUT PTSTR Buffer,
|
|
IN UINT Size
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pGetPnpIdList is similar to SetupGetMultiSzField, except it supports
|
|
skipping of blank fields.
|
|
|
|
Arguments:
|
|
|
|
is - Specifies the INFSTRUCT that indicates the line being processed.
|
|
Buffer - Receives the multi-sz ID list.
|
|
Size - Specifies the size of Buffer.
|
|
|
|
Return Value:
|
|
|
|
TRUE if one or more PNP ID fields exist, or FALSE if none exist.
|
|
|
|
--*/
|
|
|
|
{
|
|
UINT FieldCount;
|
|
UINT Field;
|
|
PTSTR p;
|
|
PTSTR End;
|
|
PCTSTR FieldStr;
|
|
|
|
p = Buffer;
|
|
End = (PTSTR) ((PBYTE) Buffer + Size);
|
|
End--;
|
|
|
|
FieldCount = InfGetFieldCount (is);
|
|
|
|
if (FieldCount < PNPID_FIELD) {
|
|
return FALSE;
|
|
}
|
|
|
|
for (Field = PNPID_FIELD ; Field <= FieldCount ; Field++) {
|
|
FieldStr = InfGetStringField (is, Field);
|
|
if (FieldStr && *FieldStr) {
|
|
if (SizeOfString (FieldStr) > (UINT) (End - p)) {
|
|
DEBUGMSG ((DBG_WHOOPS, "PNP ID list is bigger than %u bytes", Size));
|
|
break;
|
|
}
|
|
|
|
StringCopy (p, FieldStr);
|
|
p = GetEndOfString (p) + 1;
|
|
}
|
|
}
|
|
|
|
*p = 0;
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
BeginHardwareEnum initializes a structure for enumeration of all hardware
|
|
configuration registry values. Call BeginHardwareEnum, followed by
|
|
either NextHardwareEnum or AbortHardwareEnum.
|
|
|
|
Arguments:
|
|
|
|
EnumPtr - Receives the next enumerated item
|
|
|
|
Return Value:
|
|
|
|
TRUE if the supplied enumeration structure was filled, or FALSE if
|
|
there are no hardware items (unlikely) or an error occurred.
|
|
GetLastError() will provide the failure reason or ERROR_SUCCESS.
|
|
|
|
--*/
|
|
|
|
BOOL
|
|
RealEnumFirstHardware (
|
|
OUT PHARDWARE_ENUM EnumPtr,
|
|
IN TYPE_OF_ENUM TypeOfEnum,
|
|
IN DWORD EnumFlags
|
|
)
|
|
{
|
|
//
|
|
// If string tables have not been created, create them
|
|
// before enumerating.
|
|
//
|
|
|
|
if (TypeOfEnum != ENUM_ALL_DEVICES) {
|
|
if (!g_PnpIdTable || !g_UnsupPnpIdTable || !g_InfFileTable || !g_ForceBadIdTable) {
|
|
if (!CreateNtHardwareList (
|
|
SOURCEDIRECTORYARRAY(),
|
|
SOURCEDIRECTORYCOUNT(),
|
|
NULL,
|
|
REGULAR_OUTPUT
|
|
)) {
|
|
|
|
LOG ((
|
|
LOG_ERROR,
|
|
"Unable to create NT hardware list "
|
|
"(required for hardware enumeration)"
|
|
));
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
START_ENUM;
|
|
|
|
//
|
|
// Init enum struct
|
|
//
|
|
ZeroMemory (EnumPtr, sizeof (HARDWARE_ENUM));
|
|
EnumPtr->State = STATE_ENUM_FIRST_KEY;
|
|
EnumPtr->TypeOfEnum = TypeOfEnum;
|
|
EnumPtr->EnumFlags = EnumFlags;
|
|
|
|
//
|
|
// Call NextHardwareEnum to fill in rest of struct
|
|
//
|
|
|
|
return RealEnumNextHardware (EnumPtr);
|
|
}
|
|
|
|
|
|
VOID
|
|
pGenerateTapeIds (
|
|
IN OUT PGROWBUFFER HackBuf,
|
|
IN PCTSTR PnpIdList
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pGenerateTapeIds creates two IDs based on the IDs given by the caller. The
|
|
first created ID is the caller's ID prefixed with Sequential. The second
|
|
created ID is the caller's ID prefixed with Sequential and without the
|
|
revision character. These new IDs match the tape IDs that NT supports.
|
|
|
|
Arguments:
|
|
|
|
HackBuf - Specifies a buffer that holds the new IDs, in a multi-sz. It
|
|
may have some initial IDs in it. Receives additional IDs.
|
|
PnpIdList - Specifies the ID list (either a hardware ID list or compatible
|
|
ID list).
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
TCHAR PnpId[MAX_PNP_ID];
|
|
TCHAR HackedPnpIdBuf[MAX_PNP_ID + 32];
|
|
PTSTR HackedPnpId;
|
|
PTSTR p;
|
|
|
|
if (PnpIdList) {
|
|
|
|
while (*PnpIdList) {
|
|
PnpIdList = ExtractPnpId (PnpIdList, PnpId);
|
|
|
|
//
|
|
// Ignore PNP IDs that specify the root enumerator, or that
|
|
// begin with Gen and don't have an underscore (such as GenDisk)
|
|
//
|
|
|
|
*HackedPnpIdBuf = 0;
|
|
|
|
if (StringIMatchCharCount (TEXT("SCSI\\"), PnpId, 5)) {
|
|
MoveMemory (PnpId, PnpId + 5, SizeOfString (PnpId + 5));
|
|
HackedPnpId = _tcsappend (HackedPnpIdBuf, TEXT("SCSI\\"));
|
|
} else {
|
|
HackedPnpId = HackedPnpIdBuf;
|
|
}
|
|
|
|
if (_tcschr (PnpId, TEXT('\\'))) {
|
|
continue;
|
|
}
|
|
|
|
if (StringIMatchCharCount (PnpId, TEXT("Gen"), 3) &&
|
|
!_tcschr (PnpId, TEXT('_'))
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Add another ID with Sequential
|
|
//
|
|
|
|
wsprintf (HackedPnpId, TEXT("Sequential%s"), PnpId);
|
|
MultiSzAppend (HackBuf, HackedPnpIdBuf);
|
|
|
|
//
|
|
// Add another ID with Sequential and without the single
|
|
// character revision
|
|
//
|
|
|
|
p = GetEndOfString (HackedPnpId);
|
|
p = _tcsdec (HackedPnpId, p);
|
|
*p = 0;
|
|
MultiSzAppend (HackBuf, HackedPnpIdBuf);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
BOOL
|
|
pIsMultiFunctionDevice (
|
|
IN PCTSTR PnpIdList OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pIsMultiFunctionDevice scans the caller-supplied list of PNP IDs for one
|
|
that starts with MF\. This prefix indicates the multi-function enumerator
|
|
root.
|
|
|
|
Arguments:
|
|
|
|
PnpIdList - Specifies the comma-separated list of PNP IDs
|
|
|
|
Return Value:
|
|
|
|
TRUE if a multi-function ID is in the list, FALSE otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
TCHAR PnpId[MAX_PNP_ID];
|
|
BOOL b = FALSE;
|
|
|
|
if (PnpIdList) {
|
|
while (*PnpIdList) {
|
|
PnpIdList = ExtractPnpId (PnpIdList, PnpId);
|
|
if (StringIMatchCharCount (TEXT("MF\\"), PnpId, 3)) {
|
|
b = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return b;
|
|
}
|
|
|
|
|
|
BOOL
|
|
pGenerateMultiFunctionIDs (
|
|
IN OUT PGROWBUFFER IdList,
|
|
IN PCTSTR EncodedDevicePath
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pGenerateMultiFunctionIDs locates the device node for the device related to
|
|
the multi-function node. If a device node is found, all of its IDs (both
|
|
hardware IDs and compatible IDs) are added to the multi-function device as
|
|
compatible IDs.
|
|
|
|
Arguments:
|
|
|
|
IdList - Specifies an initialized grow buffer that has zero or
|
|
more multi-sz strings. Receives additional multi-sz
|
|
entries.
|
|
EncodedDevicePath - Specifies a device path in the form of
|
|
Root&Device&Instance, as obtained from the
|
|
multi-function dev node key name.
|
|
|
|
Return Value:
|
|
|
|
TRUE if the multi-function device has a master device, FALSE otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
HKEY Parent;
|
|
HKEY Key;
|
|
BOOL b = FALSE;
|
|
TCHAR DevicePathCopy[MAX_REGISTRY_KEY];
|
|
PCTSTR Start;
|
|
PTSTR End;
|
|
TCHAR c;
|
|
PCTSTR Data;
|
|
PTSTR q;
|
|
|
|
//
|
|
// Multifunction devices have IDs in the form of:
|
|
//
|
|
// MF\CHILDxxxx\Root&Device&Instance
|
|
//
|
|
// Find the original device by parsing this string.
|
|
//
|
|
|
|
Parent = OpenRegKeyStr (S_HKLM_ENUM);
|
|
if (!Parent) {
|
|
return FALSE;
|
|
}
|
|
|
|
StringCopy (DevicePathCopy, EncodedDevicePath);
|
|
Start = DevicePathCopy;
|
|
|
|
End = _tcschr (Start, TEXT('&'));
|
|
|
|
for (;;) {
|
|
|
|
if (!End) {
|
|
c = 0;
|
|
} else {
|
|
c = *End;
|
|
*End = 0;
|
|
}
|
|
|
|
Key = OpenRegKey (Parent, Start);
|
|
|
|
if (Key) {
|
|
|
|
//
|
|
// Key exists. Close the parent, and begin
|
|
// using the current key as the parent. Then
|
|
// continue parsing if necessary.
|
|
//
|
|
|
|
CloseRegKey (Parent);
|
|
Parent = Key;
|
|
|
|
if (!c) {
|
|
b = TRUE;
|
|
break;
|
|
}
|
|
|
|
*End = TEXT('\\'); // turns DevicePathCopy into a subkey path
|
|
|
|
Start = End + 1;
|
|
End = _tcschr (Start, TEXT('&'));
|
|
|
|
} else if (c) {
|
|
|
|
//
|
|
// Key does not exist, try breaking at the
|
|
// next ampersand.
|
|
//
|
|
|
|
*End = c;
|
|
End = _tcschr (End + 1, TEXT('&'));
|
|
|
|
} else {
|
|
|
|
//
|
|
// Nothing left, key was not found
|
|
//
|
|
|
|
MYASSERT (!End);
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
if (b) {
|
|
|
|
DEBUGMSG ((DBG_HWCOMP, "Parsed MF device node is %s", DevicePathCopy));
|
|
|
|
//
|
|
// Now get all the IDs for this device
|
|
//
|
|
|
|
q = (PTSTR) IdList->Buf;
|
|
|
|
Data = GetRegValueString (Parent, S_HARDWAREID);
|
|
if (Data) {
|
|
MultiSzAppend (IdList, Data);
|
|
MemFree (g_hHeap, 0, Data);
|
|
}
|
|
|
|
Data = GetRegValueString (Parent, S_COMPATIBLEIDS);
|
|
if (Data) {
|
|
MultiSzAppend (IdList, Data);
|
|
MemFree (g_hHeap, 0, Data);
|
|
}
|
|
|
|
//
|
|
// Convert commas into nuls, because we are passing
|
|
// back a multi-sz.
|
|
//
|
|
|
|
if (!q) {
|
|
q = (PTSTR) IdList->Buf;
|
|
}
|
|
|
|
End = (PTSTR) (IdList->Buf + IdList->End);
|
|
|
|
while (q < End) {
|
|
if (_tcsnextc (q) == TEXT(',')) {
|
|
*q = 0;
|
|
}
|
|
q = _tcsinc (q);
|
|
}
|
|
|
|
//
|
|
// Do not double-terminate the multi-sz yet. The caller
|
|
// may want to append more IDs to the list.
|
|
//
|
|
}
|
|
|
|
CloseRegKey (Parent);
|
|
|
|
return b;
|
|
}
|
|
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
NextHardwareEnum returns the next registry value related to hardware
|
|
configuration.
|
|
|
|
Arguments:
|
|
|
|
EnumPtr - Specifies the current enumeration structure
|
|
|
|
Return Value:
|
|
|
|
TRUE if the supplied enumeration structure was filled, or FALSE if
|
|
there are no hardware items (unlikely) or an error occurred.
|
|
GetLastError() will provide the failure reason or ERROR_SUCCESS.
|
|
|
|
--*/
|
|
|
|
BOOL
|
|
RealEnumNextHardware (
|
|
IN OUT PHARDWARE_ENUM EnumPtr
|
|
)
|
|
{
|
|
TCHAR InstanceBuf[MAX_REGISTRY_KEY];
|
|
PTSTR p;
|
|
GROWBUFFER HackBuf = GROWBUF_INIT;
|
|
MULTISZ_ENUM e;
|
|
PTSTR NewBuf;
|
|
BOOL TapeDevice;
|
|
INFSTRUCT is = INITINFSTRUCT_GROWBUFFER;
|
|
PCTSTR pattern;
|
|
|
|
for (;;) {
|
|
switch (EnumPtr->State) {
|
|
|
|
case STATE_ENUM_FIRST_KEY:
|
|
EnumPtr->State = STATE_ENUM_CHECK_KEY;
|
|
|
|
if (!EnumFirstRegKeyInTree (&EnumPtr->ek, S_HKLM_ENUM)) {
|
|
END_ENUM;
|
|
return FALSE;
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
case STATE_ENUM_NEXT_KEY:
|
|
EnumPtr->State = STATE_ENUM_CHECK_KEY;
|
|
|
|
if (!EnumNextRegKeyInTree (&EnumPtr->ek)) {
|
|
END_ENUM;
|
|
return FALSE;
|
|
}
|
|
|
|
break;
|
|
|
|
case STATE_ENUM_CHECK_KEY:
|
|
EnumPtr->State = STATE_ENUM_FIRST_VALUE;
|
|
|
|
if (InfFindFirstLine (g_Win95UpgInf, S_IGNORE_REG_KEY, NULL, &is)) {
|
|
do {
|
|
pattern = InfGetStringField (&is, 1);
|
|
|
|
if (pattern && IsPatternMatch (pattern, EnumPtr->ek.FullKeyName)) {
|
|
DEBUGMSG ((DBG_WARNING, "Hardware key %s is excluded", EnumPtr->ek.FullKeyName));
|
|
|
|
EnumPtr->State = STATE_ENUM_NEXT_KEY;
|
|
break;
|
|
}
|
|
} while (InfFindNextLine (&is));
|
|
|
|
InfCleanUpInfStruct (&is);
|
|
}
|
|
break;
|
|
|
|
case STATE_ENUM_FIRST_VALUE:
|
|
if (!EnumFirstRegValue (
|
|
&EnumPtr->ev,
|
|
EnumPtr->ek.CurrentKey->KeyHandle
|
|
)) {
|
|
EnumPtr->State = STATE_ENUM_NEXT_KEY;
|
|
} else {
|
|
EnumPtr->State = STATE_EVALUATE_VALUE;
|
|
}
|
|
|
|
break;
|
|
|
|
case STATE_ENUM_NEXT_VALUE:
|
|
if (!EnumNextRegValue (&EnumPtr->ev)) {
|
|
EnumPtr->State = STATE_ENUM_NEXT_KEY;
|
|
} else {
|
|
EnumPtr->State = STATE_EVALUATE_VALUE;
|
|
}
|
|
|
|
break;
|
|
|
|
case STATE_EVALUATE_VALUE:
|
|
if (StringIMatch (EnumPtr->ev.ValueName, S_CLASS)) {
|
|
//
|
|
// Match found: fill struct with data
|
|
//
|
|
|
|
EnumPtr->State = STATE_VALUE_CLEANUP;
|
|
|
|
//
|
|
// Get HardwareID and CompatibleIDs
|
|
//
|
|
|
|
pGetRegValText (
|
|
EnumPtr->ek.CurrentKey->KeyHandle,
|
|
S_HARDWAREID,
|
|
&EnumPtr->HardwareID
|
|
);
|
|
|
|
pGetRegValText (
|
|
EnumPtr->ek.CurrentKey->KeyHandle,
|
|
S_COMPATIBLEIDS,
|
|
&EnumPtr->CompatibleIDs
|
|
);
|
|
|
|
//
|
|
// Special case: flip hardware ID and compatible IDs
|
|
// if we don't have a hardware ID but we do have a
|
|
// compatible ID
|
|
//
|
|
|
|
if (!EnumPtr->HardwareID && EnumPtr->CompatibleIDs) {
|
|
DEBUGMSG ((
|
|
DBG_WARNING,
|
|
"Reversing hardware and compatible IDs for %s",
|
|
EnumPtr->CompatibleIDs
|
|
));
|
|
|
|
EnumPtr->HardwareID = EnumPtr->CompatibleIDs;
|
|
EnumPtr->CompatibleIDs = NULL;
|
|
}
|
|
|
|
//
|
|
// Multifunction device special case
|
|
//
|
|
|
|
if (pIsMultiFunctionDevice (EnumPtr->HardwareID)) {
|
|
//
|
|
// Multifunction devices have IDs in the form of:
|
|
//
|
|
// MF\CHILDxxxx\Root&Device&Instance
|
|
//
|
|
// Find the original device by parsing this string.
|
|
//
|
|
|
|
pGenerateMultiFunctionIDs (
|
|
&HackBuf,
|
|
EnumPtr->ek.CurrentKey->KeyName
|
|
);
|
|
|
|
}
|
|
|
|
//
|
|
// Tape device special case
|
|
//
|
|
|
|
else if (_tcsistr (EnumPtr->ek.FullKeyName, TEXT("SCSI"))) {
|
|
|
|
pGetRegValText (
|
|
EnumPtr->ek.CurrentKey->KeyHandle,
|
|
S_CLASS,
|
|
&EnumPtr->Class
|
|
);
|
|
|
|
TapeDevice = FALSE;
|
|
|
|
if (!EnumPtr->Class) {
|
|
TapeDevice = TRUE;
|
|
} else if (_tcsistr (EnumPtr->Class, TEXT("tape"))) {
|
|
TapeDevice = TRUE;
|
|
}
|
|
ELSE_DEBUGMSG ((
|
|
DBG_VERBOSE,
|
|
"SCSI device class %s is not a tape device",
|
|
EnumPtr->Class
|
|
));
|
|
|
|
if (TapeDevice) {
|
|
|
|
//
|
|
// For tape devices in the SCSI enumerator, we must create
|
|
// extra compatible IDs. For each ID, we add two more,
|
|
// both prefixed with Sequential, and one with its revision
|
|
// number stripped off.
|
|
//
|
|
|
|
pGenerateTapeIds (&HackBuf, EnumPtr->HardwareID);
|
|
pGenerateTapeIds (&HackBuf, EnumPtr->CompatibleIDs);
|
|
|
|
DEBUGMSG_IF ((
|
|
HackBuf.End,
|
|
DBG_HWCOMP,
|
|
"Tape Device detected, fixing PNP IDs."
|
|
));
|
|
|
|
DEBUGMSG_IF ((
|
|
!HackBuf.End,
|
|
DBG_VERBOSE,
|
|
"Tape Device detected, but no IDs to fix.\n"
|
|
"Hardware ID: %s\n"
|
|
"Compatible IDs: %s",
|
|
EnumPtr->HardwareID,
|
|
EnumPtr->CompatibleIDs
|
|
));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Add all IDs in HackBuf (a multi-sz) to the compatible
|
|
// ID list.
|
|
//
|
|
|
|
if (HackBuf.End) {
|
|
MultiSzAppend (&HackBuf, S_EMPTY);
|
|
|
|
if (EnumPtr->CompatibleIDs) {
|
|
|
|
NewBuf = pPrivateRegValAllocator (
|
|
ByteCount (EnumPtr->CompatibleIDs) +
|
|
HackBuf.End
|
|
);
|
|
|
|
StringCopy (NewBuf, EnumPtr->CompatibleIDs);
|
|
|
|
} else {
|
|
NewBuf = pPrivateRegValAllocator (HackBuf.End);
|
|
*NewBuf = 0;
|
|
}
|
|
|
|
p = GetEndOfString (NewBuf);
|
|
|
|
if (EnumFirstMultiSz (&e, (PCTSTR) HackBuf.Buf)) {
|
|
do {
|
|
|
|
if (p != NewBuf) {
|
|
p = _tcsappend (p, TEXT(","));
|
|
}
|
|
|
|
p = _tcsappend (p, e.CurrentString);
|
|
|
|
} while (EnumNextMultiSz (&e));
|
|
}
|
|
|
|
DEBUGMSG ((
|
|
DBG_HWCOMP,
|
|
"Hardware ID: %s\n"
|
|
"Old compatible ID list: %s\n"
|
|
"New compatible ID list: %s",
|
|
EnumPtr->HardwareID,
|
|
EnumPtr->CompatibleIDs,
|
|
NewBuf
|
|
));
|
|
|
|
if (EnumPtr->CompatibleIDs) {
|
|
pPrivateRegValDeallocator ((PVOID) EnumPtr->CompatibleIDs);
|
|
}
|
|
|
|
EnumPtr->CompatibleIDs = NewBuf;
|
|
|
|
FreeGrowBuffer (&HackBuf);
|
|
}
|
|
|
|
//
|
|
// Unless the user specified that the hardware ID was not required, break if it does not
|
|
// exist.
|
|
//
|
|
if (!EnumPtr->HardwareID && !(EnumPtr->EnumFlags & ENUM_DONT_REQUIRE_HARDWAREID)) {
|
|
break;
|
|
}
|
|
|
|
|
|
//
|
|
// Process enumeration filter
|
|
//
|
|
|
|
if ((EnumPtr->EnumFlags & ENUM_WANT_COMPATIBLE_FLAG) ||
|
|
(EnumPtr->TypeOfEnum != ENUM_ALL_DEVICES)
|
|
) {
|
|
|
|
EnumPtr->HardwareIdCompatible = FindHardwareId (EnumPtr->HardwareID, NULL);
|
|
EnumPtr->CompatibleIdCompatible = FindHardwareId (EnumPtr->CompatibleIDs, NULL);
|
|
EnumPtr->HardwareIdUnsupported = FindUnsupportedHardwareId (EnumPtr->HardwareID, NULL);
|
|
EnumPtr->CompatibleIdUnsupported = FindUnsupportedHardwareId (EnumPtr->CompatibleIDs, NULL);
|
|
|
|
//
|
|
// Process UI-based IDs and unsupported IDs
|
|
//
|
|
|
|
if (EnumPtr->EnumFlags & ENUM_USER_SUPPLIED_FLAG_NEEDED) {
|
|
EnumPtr->SuppliedByUi = FindUserSuppliedDriver (EnumPtr->HardwareID, EnumPtr->CompatibleIDs);
|
|
}
|
|
|
|
if (EnumPtr->EnumFlags & ENUM_DONT_WANT_USER_SUPPLIED) {
|
|
if (EnumPtr->SuppliedByUi) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (EnumPtr->EnumFlags & ENUM_WANT_USER_SUPPLIED_ONLY) {
|
|
if (!EnumPtr->SuppliedByUi) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
EnumPtr->Compatible = EnumPtr->HardwareIdCompatible ||
|
|
EnumPtr->CompatibleIdCompatible;
|
|
|
|
EnumPtr->Unsupported = EnumPtr->HardwareIdUnsupported ||
|
|
EnumPtr->CompatibleIdUnsupported;
|
|
|
|
//
|
|
// This logic is broken for a USB device that has both
|
|
// unsupported and compatible IDs in its hardware ID list.
|
|
//
|
|
// Removing this if statement causes that device to be
|
|
// reported as unsupported.
|
|
//
|
|
//if (EnumPtr->HardwareIdCompatible) {
|
|
// EnumPtr->Unsupported = FALSE;
|
|
//}
|
|
|
|
if (EnumPtr->Unsupported) {
|
|
EnumPtr->Compatible = FALSE;
|
|
}
|
|
|
|
//
|
|
// Special case: force incompatible? If so, we indicate
|
|
// this only by modifying the abstract Compatible flag.
|
|
//
|
|
|
|
if (pFindForcedBadHardwareId (EnumPtr->HardwareID, NULL) ||
|
|
pFindForcedBadHardwareId (EnumPtr->CompatibleIDs, NULL)
|
|
) {
|
|
|
|
EnumPtr->Compatible = FALSE;
|
|
}
|
|
|
|
//
|
|
// Continue enumerating if this device does not fit the
|
|
// caller's request.
|
|
//
|
|
|
|
if (EnumPtr->TypeOfEnum == ENUM_COMPATIBLE_DEVICES) {
|
|
if (!EnumPtr->Compatible) {
|
|
break;
|
|
}
|
|
} else if (EnumPtr->TypeOfEnum == ENUM_INCOMPATIBLE_DEVICES) {
|
|
if (EnumPtr->Compatible || EnumPtr->Unsupported) {
|
|
break;
|
|
}
|
|
} else if (EnumPtr->TypeOfEnum == ENUM_UNSUPPORTED_DEVICES) {
|
|
if (!EnumPtr->Unsupported) {
|
|
break;
|
|
}
|
|
} else if (EnumPtr->TypeOfEnum == ENUM_NON_FUNCTIONAL_DEVICES) {
|
|
if (EnumPtr->Compatible) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Copy reg key to struct
|
|
//
|
|
|
|
StringCopy (InstanceBuf, EnumPtr->ek.FullKeyName);
|
|
p = _tcschr (InstanceBuf, TEXT('\\'));
|
|
MYASSERT(p);
|
|
if (p) {
|
|
p = _tcschr (_tcsinc (p), TEXT('\\'));
|
|
MYASSERT(p);
|
|
}
|
|
if (p) {
|
|
p = _tcschr (_tcsinc (p), TEXT('\\'));
|
|
MYASSERT(p);
|
|
}
|
|
if (p) {
|
|
p = _tcsinc (p);
|
|
}
|
|
|
|
EnumPtr->InstanceId = DuplicateText (p);
|
|
EnumPtr->FullKey = EnumPtr->ek.FullKeyName;
|
|
EnumPtr->KeyHandle = EnumPtr->ek.CurrentKey->KeyHandle;
|
|
|
|
//
|
|
// Get all fields; require Class field
|
|
//
|
|
|
|
if (!(EnumPtr->EnumFlags & ENUM_DONT_WANT_DEV_FIELDS)) {
|
|
pGetAllRegVals (EnumPtr);
|
|
if (!EnumPtr->Class) {
|
|
|
|
DEBUGMSG ((
|
|
DBG_HWCOMP,
|
|
"Device %s does not have a Class field",
|
|
EnumPtr->InstanceId
|
|
));
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Determine if device is online
|
|
//
|
|
|
|
if (EnumPtr->EnumFlags & ENUM_WANT_ONLINE_FLAG) {
|
|
EnumPtr->Online = IsPnpIdOnline (EnumPtr->InstanceId, EnumPtr->Class);
|
|
}
|
|
|
|
return TRUE;
|
|
} else {
|
|
EnumPtr->State = STATE_ENUM_NEXT_VALUE;
|
|
}
|
|
|
|
break;
|
|
|
|
case STATE_VALUE_CLEANUP:
|
|
|
|
//
|
|
// Free all device field text
|
|
//
|
|
|
|
pFreeRegValText (EnumPtr);
|
|
|
|
FreeText (EnumPtr->InstanceId);
|
|
EnumPtr->InstanceId = NULL;
|
|
|
|
EnumPtr->State = STATE_ENUM_NEXT_VALUE;
|
|
break;
|
|
|
|
default:
|
|
MYASSERT(FALSE);
|
|
END_ENUM;
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
AbortHardwareEnum cleans up all resources in use by an enumeration. Call
|
|
this function with the EnumPtr value of BeginHardwareEnum or NextHardwareEnum.
|
|
|
|
Arguments:
|
|
|
|
EnumPtr - Specifies the enumeration to abort.
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
|
|
VOID
|
|
AbortHardwareEnum (
|
|
IN OUT PHARDWARE_ENUM EnumPtr
|
|
)
|
|
{
|
|
PushError();
|
|
|
|
END_ENUM;
|
|
|
|
if (EnumPtr->State == STATE_VALUE_CLEANUP) {
|
|
pFreeRegValText (EnumPtr);
|
|
FreeText (EnumPtr->InstanceId);
|
|
}
|
|
|
|
AbortRegKeyTreeEnum (&EnumPtr->ek);
|
|
|
|
ZeroMemory (EnumPtr, sizeof (HARDWARE_ENUM));
|
|
|
|
PopError();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// NT5 INF database
|
|
//
|
|
|
|
|
|
|
|
BOOL
|
|
FindHardwareId (
|
|
IN PCTSTR PnpIdList,
|
|
OUT PTSTR InfFileName OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
FindHardwareId parses an ID string that may contain zero or more
|
|
plug and play device IDs, separated by commas. The function then
|
|
searches for each ID in the device ID table, copying the INF file
|
|
name to a supplied buffer when a match is found.
|
|
|
|
|
|
Arguments:
|
|
|
|
PnpIdList - An ID string that contains zero or more plug and play
|
|
device IDs, separated by commas.
|
|
InfFileName - A buffer (big enough to hold MAX_PATH characters)
|
|
that receives the INF file name upon successful
|
|
match. If a match is not found, InfFileName is
|
|
set to an empty string.
|
|
|
|
Return Value:
|
|
|
|
TRUE if a match was found, or FALSE if a match was not found.
|
|
|
|
--*/
|
|
|
|
|
|
{
|
|
return FindHardwareIdInHashTable (PnpIdList, InfFileName, g_PnpIdTable, TRUE);
|
|
}
|
|
|
|
|
|
BOOL
|
|
FindUnsupportedHardwareId (
|
|
IN PCTSTR PnpIdList,
|
|
OUT PTSTR InfFileName OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
FindUnsupportedHardwareId parses an ID string that may contain zero
|
|
or more plug and play device IDs, separated by commas. The function
|
|
then searches for each ID in the device ID table, copying the INF file
|
|
name to a supplied buffer when a match is found.
|
|
|
|
Arguments:
|
|
|
|
PnpIdList - An ID string that contains zero or more plug and play
|
|
device IDs, separated by commas.
|
|
InfFileName - A buffer (big enough to hold MAX_PATH characters)
|
|
that receives the INF file name upon successful
|
|
match. If a match is not found, InfFileName is
|
|
set to an empty string.
|
|
|
|
Return Value:
|
|
|
|
TRUE if a match was found, or FALSE if a match was not found.
|
|
|
|
--*/
|
|
|
|
|
|
{
|
|
return FindHardwareIdInHashTable (PnpIdList, InfFileName, g_UnsupPnpIdTable, FALSE);
|
|
}
|
|
|
|
|
|
BOOL
|
|
pFindForcedBadHardwareId (
|
|
IN PCTSTR PnpIdList,
|
|
OUT PTSTR InfFileName OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pFindForcedBadHardwareId parses an ID string that may contain zero or more
|
|
plug and play device IDs, separated by commas. The function then searches
|
|
for each ID in the force bad device ID table, copying the INF file name to a
|
|
supplied buffer when a match is found.
|
|
|
|
Arguments:
|
|
|
|
PnpIdList - An ID string that contains zero or more plug and play
|
|
device IDs, separated by commas.
|
|
InfFileName - A buffer (big enough to hold MAX_PATH characters)
|
|
that receives the INF file name upon successful
|
|
match. If a match is not found, InfFileName is
|
|
set to an empty string.
|
|
|
|
Return Value:
|
|
|
|
TRUE if a match was found, or FALSE if a match was not found.
|
|
|
|
--*/
|
|
|
|
|
|
{
|
|
return FindHardwareIdInHashTable (PnpIdList, InfFileName, g_ForceBadIdTable, FALSE);
|
|
}
|
|
|
|
|
|
BOOL
|
|
FindUserSuppliedDriver (
|
|
IN PCTSTR HardwareIdList, OPTIONAL
|
|
IN PCTSTR CompatibleIdList OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
FindUserSuppliedDriver parses hardware and compatible hardware ID
|
|
strings that may contain zero or more plug and play device IDs,
|
|
separated by commas. The function then searches for each ID in
|
|
g_UiSuppliedIds table.
|
|
|
|
Arguments:
|
|
|
|
HardwareIdList - An ID string that contains zero or more plug and play
|
|
device IDs, separated by commas.
|
|
|
|
CompatibleIdList - An ID string that contains zero or more plug and play
|
|
device IDs, separated by commas.
|
|
|
|
Return Value:
|
|
|
|
TRUE if a match was found, or FALSE if a match was not found.
|
|
|
|
--*/
|
|
|
|
|
|
{
|
|
BOOL b = FALSE;
|
|
|
|
if (HardwareIdList) {
|
|
b = FindHardwareIdInHashTable (HardwareIdList, NULL, g_UiSuppliedIds, FALSE);
|
|
}
|
|
|
|
if (!b && CompatibleIdList) {
|
|
b = FindHardwareIdInHashTable (CompatibleIdList, NULL, g_UiSuppliedIds, FALSE);
|
|
}
|
|
|
|
return b;
|
|
}
|
|
|
|
|
|
BOOL
|
|
FindHardwareIdInHashTable (
|
|
IN PCTSTR PnpIdList,
|
|
OUT PTSTR InfFileName, OPTIONAL
|
|
IN HASHTABLE StrTable,
|
|
IN BOOL UseOverrideList
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
FindHardwareIdInHashTable queries a string table for each PNP ID in
|
|
the specified list. If one is found, the routine optionally copies the
|
|
INF file it was found in. The caller can also choose to scan the win95upg.inf
|
|
override list.
|
|
|
|
Arguments:
|
|
|
|
PnpIdList - Specifies zero or more PNP IDs, separated by commas.
|
|
|
|
InfFileName - Receives the file name of the INF containing the PNP IDs.
|
|
|
|
StrTable - Specifies the string table to query. If InfFileName is not NULL,
|
|
the string table must have an extra data value of the offset in
|
|
g_InfFileTable.
|
|
|
|
UseOverrideList - Specifies TRUE if the win95upg.inf file is to be queried for
|
|
the PNP ID. This query is performed after it has been
|
|
determined that all IDs in PnpIdList are not in StrTable.
|
|
|
|
Return Value:
|
|
|
|
TRUE if at least one PNP ID in PnpIdList was found, or FALSE if none of the
|
|
IDs were found.
|
|
|
|
--*/
|
|
|
|
{
|
|
HASHITEM InfName;
|
|
TCHAR PnpId[MAX_PNP_ID];
|
|
PCSTR p;
|
|
TCHAR FixedEisaId[MAX_PNP_ID];
|
|
|
|
//
|
|
// Extract a PNP ID from PnpIdList, then look for it in string table
|
|
//
|
|
|
|
if (!PnpIdList) {
|
|
return FALSE;
|
|
}
|
|
|
|
MYASSERT (StrTable);
|
|
if (!StrTable) {
|
|
return FALSE;
|
|
}
|
|
|
|
p = PnpIdList;
|
|
|
|
while (*p) {
|
|
p = ExtractPnpId (p, PnpId);
|
|
if (*PnpId == 0) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Locate ID in PNP ID table
|
|
//
|
|
|
|
if (HtFindStringAndData (StrTable, PnpId, (PVOID) &InfName)) {
|
|
|
|
//
|
|
// Found PNP ID. Get INF file and return.
|
|
//
|
|
|
|
if (InfFileName) {
|
|
if (StrTable != g_PnpIdTable && StrTable != g_UnsupPnpIdTable && StrTable != g_ForceBadIdTable) {
|
|
DEBUGMSG ((DBG_WHOOPS, "Caller wants InfFileName from private string table"));
|
|
} else {
|
|
_tcssafecpy (
|
|
InfFileName,
|
|
HtGetStringFromItem (InfName),
|
|
MAX_TCHAR_PATH
|
|
);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// This is a fix for the EISA roots. On Win9x, we have an EISA
|
|
// enumerator, but on NT, the ISA enumerator handles EISA too.
|
|
//
|
|
|
|
if (StringIMatchCharCount (TEXT("EISA\\"), PnpId, 5)) {
|
|
StringCopy (FixedEisaId, TEXT("EISA&"));
|
|
StringCat (FixedEisaId, PnpId + 5);
|
|
|
|
if (HtFindStringAndData (StrTable, FixedEisaId, (PVOID) &InfName)) {
|
|
|
|
//
|
|
// Found PNP ID. Get INF file and return.
|
|
//
|
|
|
|
if (InfFileName) {
|
|
if (StrTable != g_PnpIdTable && StrTable != g_UnsupPnpIdTable && StrTable != g_ForceBadIdTable) {
|
|
DEBUGMSG ((DBG_WHOOPS, "Caller wants InfFileName from private string table (2)"));
|
|
} else {
|
|
_tcssafecpy (
|
|
InfFileName,
|
|
HtGetStringFromItem (InfName),
|
|
MAX_TCHAR_PATH
|
|
);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Locate ID in override table
|
|
//
|
|
|
|
if (UseOverrideList) {
|
|
if (pIsDeviceConsideredCompatible (PnpIdList)) {
|
|
|
|
DEBUGMSG ((
|
|
DBG_WARNING,
|
|
"%s is considered compatible but actually does not have PNP support in NT.",
|
|
PnpIdList
|
|
));
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
pProcessNtInfFile (
|
|
IN PCTSTR InfFile,
|
|
IN INT UiMode,
|
|
IN OUT HASHTABLE InfFileTable,
|
|
IN OUT HASHTABLE PnpIdTable,
|
|
IN OUT HASHTABLE UnsupPnpIdTable
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pProcessNtInfFile scans an NT INF and places all hardware device
|
|
IDs in the PNP string table. All entries of the string table
|
|
have extra data that points to the INF file (added to the INF
|
|
file name string table).
|
|
|
|
Arguments:
|
|
|
|
InfFile - The path to an INF file to be examined
|
|
|
|
UiMode - Specifies VERBOSE_OUTPUT or PNPREPT_OUTPUT when the PNP
|
|
IDs are to be dumped tvia the progress bar output
|
|
routines. If REGULAR_OUTPUT, no output is generated.
|
|
|
|
Return Value:
|
|
|
|
TRUE if the function completes successfully, or FALSE if it fails.
|
|
Call GetLastError for additional failure information.
|
|
|
|
--*/
|
|
|
|
{
|
|
HINF hInf;
|
|
INFSTRUCT is = INITINFSTRUCT_GROWBUFFER;
|
|
INFSTRUCT isMfg = INITINFSTRUCT_GROWBUFFER;
|
|
INFSTRUCT isDev = INITINFSTRUCT_GROWBUFFER;
|
|
BOOL UnsupportedDevice;
|
|
PCTSTR DevSection;
|
|
PCTSTR Manufacturer;
|
|
TCHAR PnpId[MAX_PNPID_LENGTH * 4];
|
|
PTSTR CurrentDev;
|
|
TCHAR TrimmedId[MAX_PNP_ID];
|
|
PCTSTR FileName;
|
|
PCTSTR p;
|
|
CHARTYPE ch;
|
|
HASHITEM InfOffset = NULL;
|
|
LONG rc;
|
|
LONG DontCare = 0;
|
|
BOOL Result = FALSE;
|
|
PCTSTR RealDevSection = NULL;
|
|
BOOL b;
|
|
PCTSTR TempStr;
|
|
HASHITEM hashItem;
|
|
|
|
//
|
|
// Get a pointer to the inf file excluding the path
|
|
//
|
|
|
|
FileName = NULL;
|
|
for (p = InfFile ; *p ; p = _tcsinc (p)) {
|
|
ch = _tcsnextc (p);
|
|
if (ch == TEXT('\\')) {
|
|
FileName = _tcsinc (p);
|
|
} else if (!FileName && ch == TEXT(':')) {
|
|
FileName = _tcsinc (p);
|
|
}
|
|
}
|
|
|
|
MYASSERT (*FileName);
|
|
|
|
//
|
|
// Open INF file with Setup APIs
|
|
//
|
|
|
|
hInf = InfOpenInfFile (InfFile);
|
|
|
|
if (hInf == INVALID_HANDLE_VALUE) {
|
|
LOG ((LOG_ERROR, "Failed to open %s while processing hardware INFs.", InfFile));
|
|
return FALSE;
|
|
}
|
|
|
|
__try {
|
|
//
|
|
// Enumerate [Manufacturer] section
|
|
//
|
|
|
|
if (!InfFindFirstLine (hInf, S_MANUFACTURER, NULL, &is)) {
|
|
rc = GetLastError();
|
|
|
|
// If section not found, return success
|
|
if (rc == ERROR_SECTION_NOT_FOUND || rc == ERROR_LINE_NOT_FOUND) {
|
|
SetLastError (ERROR_SUCCESS);
|
|
Result = TRUE;
|
|
__leave;
|
|
}
|
|
|
|
SetLastError (rc);
|
|
LOG ((LOG_ERROR, "Error trying to find %s in %s", S_MANUFACTURER, InfFile));
|
|
__leave;
|
|
}
|
|
|
|
do {
|
|
//
|
|
// Get the manufacturer name
|
|
//
|
|
Manufacturer = InfGetLineText (&is);
|
|
if (!Manufacturer) {
|
|
LOG ((LOG_ERROR, "Error getting line text of enumerated line"));
|
|
__leave;
|
|
}
|
|
|
|
//
|
|
// Enumerate the devices listed in the manufacturer's section,
|
|
// looking for PnpId
|
|
//
|
|
|
|
if (!InfFindFirstLine (hInf, Manufacturer, NULL, &isMfg)) {
|
|
rc = GetLastError();
|
|
|
|
// if section not found, move on to next manufacturer
|
|
if (rc == ERROR_SECTION_NOT_FOUND || rc == ERROR_LINE_NOT_FOUND) {
|
|
DEBUGMSG ((
|
|
DBG_HWCOMP,
|
|
"Manufacturer %s section does not exist in %s",
|
|
Manufacturer,
|
|
InfFile
|
|
));
|
|
|
|
continue;
|
|
}
|
|
|
|
LOG((LOG_ERROR, "Error while searching for %s in %s.", Manufacturer, InfFile));
|
|
__leave;
|
|
}
|
|
|
|
do {
|
|
//
|
|
// Is this an unsupported device?
|
|
//
|
|
|
|
DevSection = InfGetStringField (&isMfg, 1);
|
|
if (!DevSection) {
|
|
// There is no field 1
|
|
continue;
|
|
}
|
|
|
|
UnsupportedDevice = FALSE;
|
|
|
|
//
|
|
// Try section.NTx86 first, then section.NT, then section
|
|
//
|
|
|
|
RealDevSection = JoinText (DevSection, TEXT(".NTx86"));
|
|
b = InfFindFirstLine (hInf, RealDevSection, NULL, &isDev);
|
|
|
|
if (!b) {
|
|
FreeText (RealDevSection);
|
|
RealDevSection = JoinText (DevSection, TEXT(".NT"));
|
|
b = InfFindFirstLine (hInf, RealDevSection, NULL, &isDev);
|
|
}
|
|
|
|
if (!b) {
|
|
FreeText (RealDevSection);
|
|
RealDevSection = DuplicateText (DevSection);
|
|
b = InfFindFirstLine (hInf, RealDevSection, NULL, &isDev);
|
|
}
|
|
|
|
if (!b) {
|
|
DEBUGMSG ((
|
|
DBG_HWCOMP,
|
|
"Device section for %s does not exist in %s of %s",
|
|
RealDevSection,
|
|
Manufacturer,
|
|
InfFile
|
|
));
|
|
} else {
|
|
|
|
if (InfFindFirstLine (hInf, RealDevSection, TEXT("DeviceUpgradeUnsupported"), &isDev)) {
|
|
TempStr = InfGetStringField (&isDev, 1);
|
|
|
|
if (TempStr && _ttoi (TempStr)) {
|
|
UnsupportedDevice = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
FreeText (RealDevSection);
|
|
|
|
//
|
|
// Get the device id
|
|
//
|
|
|
|
if (!pGetPnpIdList (&isMfg, PnpId, sizeof (PnpId))) {
|
|
// There is no field 2
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Add each device id to the id tree
|
|
//
|
|
|
|
CurrentDev = PnpId;
|
|
while (*CurrentDev) {
|
|
//
|
|
// First time through add the INF file name to string table
|
|
//
|
|
|
|
if (!InfOffset) {
|
|
if (InfFileTable) {
|
|
InfOffset = HtAddString (InfFileTable, FileName);
|
|
|
|
if (!InfOffset) {
|
|
LOG ((LOG_ERROR, "Cannot add %s to table of INFs.", FileName));
|
|
__leave;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Add PNP ID to string table
|
|
//
|
|
|
|
StringCopy (TrimmedId, SkipSpace (CurrentDev));
|
|
TruncateTrailingSpace (TrimmedId);
|
|
|
|
if (UnsupportedDevice) {
|
|
hashItem = HtAddStringAndData (UnsupPnpIdTable, TrimmedId, &InfOffset);
|
|
} else {
|
|
hashItem = HtAddStringAndData (PnpIdTable, TrimmedId, &InfOffset);
|
|
}
|
|
|
|
if (!hashItem) {
|
|
LOG ((LOG_ERROR, "Cannot add %s to table of PNP IDs.", CurrentDev));
|
|
__leave;
|
|
}
|
|
|
|
MYASSERT (
|
|
UnsupportedDevice ?
|
|
hashItem == HtFindString (UnsupPnpIdTable, TrimmedId) :
|
|
hashItem == HtFindString (PnpIdTable, TrimmedId)
|
|
);
|
|
|
|
//
|
|
// UI options
|
|
//
|
|
|
|
if (UiMode == VERBOSE_OUTPUT || UiMode == PNPREPT_OUTPUT) {
|
|
TCHAR Msg[MAX_ENCODED_PNPID_LENGTH + MAX_INF_DESCRIPTION + 16];
|
|
TCHAR Desc[MAX_INF_DESCRIPTION];
|
|
TCHAR EncPnpId[MAX_ENCODED_PNPID_LENGTH * 4];
|
|
TCHAR EncDesc[MAX_INF_DESCRIPTION * 2];
|
|
|
|
if (SetupGetStringField (
|
|
&isMfg.Context,
|
|
0,
|
|
Desc,
|
|
MAX_INF_DESCRIPTION,
|
|
NULL
|
|
)) {
|
|
if (UiMode == VERBOSE_OUTPUT) {
|
|
wsprintf (Msg, TEXT(" PNP ID: %s, Desc: %s"), PnpId, Desc);
|
|
} else {
|
|
StringCopy (EncPnpId, PnpId);
|
|
StringCopy (EncDesc, Desc);
|
|
|
|
EncodePnpId (EncPnpId);
|
|
EncodePnpId (EncDesc);
|
|
|
|
wsprintf (Msg, TEXT("%s\\%s\\%s"), EncPnpId, EncDesc, FileName);
|
|
}
|
|
ProgressBar_SetSubComponent (Msg);
|
|
}
|
|
}
|
|
|
|
CurrentDev = GetEndOfString (CurrentDev) + 1;
|
|
}
|
|
|
|
} while (InfFindNextLine (&isMfg));
|
|
|
|
} while (InfFindNextLine (&is));
|
|
|
|
InfCloseInfFile (hInf);
|
|
SetLastError (ERROR_SUCCESS);
|
|
|
|
Result = TRUE;
|
|
}
|
|
__finally {
|
|
PushError();
|
|
InfCleanUpInfStruct (&is);
|
|
InfCleanUpInfStruct (&isMfg);
|
|
InfCleanUpInfStruct (&isDev);
|
|
InfCloseInfFile (hInf);
|
|
PopError();
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
|
|
PCTSTR
|
|
ExtractPnpId (
|
|
IN PCTSTR PnpIdList,
|
|
OUT PTSTR PnpIdBuf
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
ExtractPnpId removes the next PNP ID from a list of zero or more
|
|
PNP IDs (separated by commas). Upon return, PnpIdBuf contains the
|
|
PNP ID (or empty string if none found), and the return value points
|
|
to the next PNP ID in the list.
|
|
|
|
This routine is designed to be called in a loop until the return
|
|
value points to the nul terminated of PnpIdList.
|
|
|
|
Arguments:
|
|
|
|
PnpIdList - Specifies a pointer to the next string in the PNP ID list.
|
|
|
|
PnpIdBuf - Receives the PNP ID with spaces trimmed on both sides of the
|
|
ID.
|
|
|
|
|
|
Return Value:
|
|
|
|
A pointer to the next item in the list, or a pointer to the nul at the
|
|
end of the list. If the pointer points to a non-nul character, call
|
|
ExtractPnpId again, using the return value for the PnpIdList param.
|
|
|
|
--*/
|
|
|
|
{
|
|
PCTSTR p, q;
|
|
|
|
PnpIdList = SkipSpace (PnpIdList);
|
|
|
|
q = _tcschr (PnpIdList, TEXT(','));
|
|
if (!q) {
|
|
q = GetEndOfString (PnpIdList);
|
|
}
|
|
|
|
p = q;
|
|
if (p > (PnpIdList + MAX_PNP_ID - 1)) {
|
|
p = PnpIdList + MAX_PNP_ID - 1;
|
|
}
|
|
|
|
StringCopyAB (PnpIdBuf, PnpIdList, p);
|
|
TruncateTrailingSpace (PnpIdBuf);
|
|
|
|
if (*q) {
|
|
q = _tcsinc (q);
|
|
}
|
|
|
|
return q;
|
|
}
|
|
|
|
|
|
BOOL
|
|
AddPnpIdsToHashTable (
|
|
IN OUT HASHTABLE Table,
|
|
IN PCTSTR PnpIdList
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
AddPnpIdsToHashTable extracts all PNP IDs from a comma-separated
|
|
list of PNP IDs and places each one in the specified string table.
|
|
|
|
PNP IDs are added to the string table as case-insensitive.
|
|
|
|
Arguments:
|
|
|
|
Table - Specifies the table to add each PNP ID to
|
|
|
|
PnpIdList - Specifies a comma-separated list of zero or more PNP
|
|
IDs to add to Table.
|
|
|
|
|
|
Return Value:
|
|
|
|
TRUE if all IDs were processed successfully, or FALSE if an error
|
|
occurred adding to the string table.
|
|
|
|
--*/
|
|
|
|
{
|
|
TCHAR PnpId[MAX_PNP_ID];
|
|
PCTSTR p;
|
|
|
|
p = PnpIdList;
|
|
if (!p) {
|
|
return TRUE;
|
|
}
|
|
|
|
while (*p) {
|
|
p = ExtractPnpId (p, PnpId);
|
|
|
|
if (*PnpId) {
|
|
|
|
if (!HtAddString (Table, PnpId)) {
|
|
LOG ((LOG_ERROR, "Can't add %s to table of PNP ids.", PnpId));
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
AddPnpIdsToGrowList (
|
|
IN OUT PGROWLIST GrowList,
|
|
IN PCTSTR PnpIdList
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
AddPnpIdsToHashTable extracts all PNP IDs from a comma-separated
|
|
list of PNP IDs and places each one in the specified grow list.
|
|
|
|
Arguments:
|
|
|
|
GrowList - Specifies the list to add each PNP ID to
|
|
|
|
PnpIdList - Specifies a comma-separated list of zero or more PNP
|
|
IDs to add to GrowList.
|
|
|
|
Return Value:
|
|
|
|
TRUE if all IDs were processed successfully, or FALSE if an error
|
|
occurred adding to the grow list.
|
|
|
|
--*/
|
|
|
|
{
|
|
TCHAR PnpId[MAX_PNP_ID];
|
|
PCTSTR p;
|
|
|
|
p = PnpIdList;
|
|
|
|
while (*p) {
|
|
p = ExtractPnpId (p, PnpId);
|
|
|
|
if (*PnpId) {
|
|
|
|
if (!GrowListAppendString (GrowList, PnpId)) {
|
|
DEBUGMSG ((DBG_ERROR, "AddPnpIdsToGrowList: Can't add %s", PnpId));
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
PCTSTR
|
|
AddPnpIdsToGrowBuf (
|
|
IN OUT PGROWBUFFER GrowBuffer,
|
|
IN PCTSTR PnpIdList
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
AddPnpIdsToGrowBuf extracts all PNP IDs from a comma-separated
|
|
list of PNP IDs and places each one in the specified grow buffer.
|
|
|
|
Arguments:
|
|
|
|
GrowBuffer - Specifies the buffer to add each PNP ID to
|
|
|
|
PnpIdList - Specifies a comma-separated list of zero or more PNP
|
|
IDs to add to GrowBuffer.
|
|
|
|
Return Value:
|
|
|
|
A pointer to the beginning of the multisz buffer
|
|
|
|
--*/
|
|
|
|
{
|
|
TCHAR PnpId[MAX_PNP_ID];
|
|
PCTSTR p;
|
|
|
|
p = PnpIdList;
|
|
|
|
while (*p) {
|
|
p = ExtractPnpId (p, PnpId);
|
|
|
|
if (*PnpId) {
|
|
|
|
if (!MultiSzAppend (GrowBuffer, PnpId)) {
|
|
DEBUGMSG ((DBG_ERROR, "AddPnpIdsToGrowBuf: Can't add %s", PnpId));
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return GrowBuffer->Buf;
|
|
}
|
|
|
|
|
|
BOOL
|
|
pIsFileOnCD (
|
|
PCTSTR File
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pIsFileOnCd checks the drive letter at the head of File to see if
|
|
it is a CD-ROM.
|
|
|
|
This function also emulates the CD-ROM behavior for the report tool.
|
|
|
|
Arguments:
|
|
|
|
File - Specifies the full path of the file to compare
|
|
|
|
Return Value:
|
|
|
|
TRUE if the file is on a CD-ROM, or FALSE if it is not.
|
|
|
|
--*/
|
|
|
|
{
|
|
TCHAR RootDir[4];
|
|
|
|
//
|
|
// If report tool, or private stress option, always return TRUE.
|
|
//
|
|
|
|
if (REPORTONLY()) {
|
|
return TRUE;
|
|
}
|
|
|
|
#ifdef PRERELEASE
|
|
if (g_Stress) {
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// A CD drive cannot be a UNC path
|
|
//
|
|
|
|
if (File[0] && File[1] != TEXT(':')) {
|
|
return FALSE;
|
|
}
|
|
|
|
RootDir[0] = File[0];
|
|
RootDir[1] = File[1];
|
|
RootDir[2] = TEXT('\\');
|
|
RootDir[3] = 0;
|
|
|
|
return DRIVE_CDROM == GetDriveType (RootDir);
|
|
}
|
|
|
|
|
|
DWORD
|
|
pComputeInfChecksum (
|
|
IN PCTSTR HwCompDat, OPTIONAL
|
|
OUT PBOOL Rebuild OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pComputeInfChecksum calculates a checksum for all INFs in the
|
|
source directories. This routine scans all directories in the
|
|
SOURCEDIRECTORYARRAY() global string array.
|
|
|
|
Arguments:
|
|
|
|
HwCompDat - Specifies path to hwcomp.dat, required if Rebuild is
|
|
specified.
|
|
|
|
Rebuild - Receives TRUE if an INF file was found with a greater
|
|
date than hwcomp.dat.
|
|
|
|
Return Value:
|
|
|
|
The checksum.
|
|
|
|
--*/
|
|
|
|
{
|
|
HANDLE hFind;
|
|
WIN32_FIND_DATA fd;
|
|
DWORD Checksum = 0;
|
|
PTSTR p;
|
|
TCHAR InfPattern[MAX_TCHAR_PATH];
|
|
UINT u, v;
|
|
FILETIME HwCompDatTime;
|
|
|
|
MYASSERT ((!HwCompDat && !Rebuild) || (HwCompDat && Rebuild));
|
|
|
|
if (Rebuild) {
|
|
if (DoesFileExistEx (HwCompDat, &fd)) {
|
|
*Rebuild = FALSE;
|
|
HwCompDatTime = fd.ftLastWriteTime;
|
|
} else {
|
|
*Rebuild = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// NTRAID#NTBUG9-379084-2001/04/27-jimschm disable this until a better solution is found
|
|
//
|
|
|
|
#if 0
|
|
|
|
for (u = 0 ; u < SOURCEDIRECTORYCOUNT() ; u++) {
|
|
|
|
//
|
|
// Have we already processed this source dir?
|
|
//
|
|
|
|
for (v = 0 ; v < u ; v++) {
|
|
if (StringIMatch (SOURCEDIRECTORY(u),SOURCEDIRECTORY(v))) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (v != u) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Process this directory
|
|
//
|
|
|
|
StringCopy (InfPattern, SOURCEDIRECTORY(u));
|
|
AppendWack (InfPattern);
|
|
StringCat (InfPattern, TEXT("*.in?"));
|
|
|
|
hFind = FindFirstFile (InfPattern, &fd);
|
|
if (hFind != INVALID_HANDLE_VALUE) {
|
|
do {
|
|
//
|
|
// Make sure file name ends in underscore or f
|
|
//
|
|
// We cheat... because we know that if the file was DBCS,
|
|
// it couldn't end in .INF
|
|
//
|
|
p = GetEndOfString (fd.cFileName);
|
|
MYASSERT (p != fd.cFileName);
|
|
p = _tcsdec2 (fd.cFileName, p);
|
|
|
|
if (*p == TEXT('_')) {
|
|
if (_istlower (*(p -1)))
|
|
*p = TEXT('f');
|
|
else
|
|
*p = TEXT('F');
|
|
} else if (tolower (*p) != TEXT('f')) {
|
|
continue;
|
|
}
|
|
|
|
// Make sure the file is not excluded
|
|
if (pIsInfFileExcluded (fd.cFileName)) {
|
|
continue;
|
|
}
|
|
|
|
if (Rebuild) {
|
|
// Check file times
|
|
if (CompareFileTime (&fd.ftLastWriteTime, &HwCompDatTime) > 0) {
|
|
*Rebuild = TRUE;
|
|
// abandon computation
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Add the file size to the checksum
|
|
Checksum = _rotl (Checksum, 1) ^ fd.nFileSizeLow;
|
|
|
|
// Add file name
|
|
for (p = fd.cFileName ; *p ; p++) {
|
|
// preserve character and order
|
|
Checksum += (DWORD) (*p) * (DWORD) (1 + fd.cFileName - p);
|
|
}
|
|
|
|
} while (FindNextFile (hFind, &fd));
|
|
|
|
FindClose (hFind);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
return Checksum;
|
|
}
|
|
|
|
|
|
BOOL
|
|
LoadDeviceList (
|
|
IN LOADOP Operation,
|
|
IN PCTSTR HwCompDatPath
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
LoadDeviceList attempts to load hwcomp.dat from the path specified
|
|
in the HwCompDat parameter. If it is able to load this file, all
|
|
PNP IDs for all INFs are valid. If it is not able to load this file,
|
|
the file does not exist, or the file does not match the INFs.
|
|
|
|
Arguments:
|
|
|
|
Operation - QUERY: the validity of hwcomp.dat is to be checked
|
|
LOAD: load the data into memory.
|
|
DUMP: dump the file to stdout
|
|
|
|
HwCompDatPath - The path of hwcomp.dat, the data file holding a
|
|
pre-compiled compatible PNP ID list.
|
|
|
|
Return Value:
|
|
|
|
TRUE if the function completes successfully, or FALSE if it fails.
|
|
Call GetLastError for additional failure information.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD StoredChecksum;
|
|
BOOL b = FALSE;
|
|
HASHTABLE InfFileTable = NULL;
|
|
HASHTABLE PnpIdTable = NULL;
|
|
BOOL Rebuild;
|
|
DWORD CurrentChecksum;
|
|
DWORD HwCompDatId = 0;
|
|
|
|
//
|
|
// !!! IMPORTANT !!!
|
|
//
|
|
// hwcomp.dat is used by other parts of NT. *DO NOT* change it without first e-mailing
|
|
// the NT group. Also, be sure to keep code in lib.c in sync with changes.
|
|
//
|
|
|
|
if (Operation == DUMP) {
|
|
DumpHwCompDat (HwCompDatPath, TRUE);
|
|
return TRUE;
|
|
}
|
|
|
|
__try {
|
|
//
|
|
// Open the hardware compatibility database
|
|
//
|
|
|
|
HwCompDatId = OpenHwCompDat (HwCompDatPath);
|
|
|
|
if (!HwCompDatId) {
|
|
__leave;
|
|
}
|
|
|
|
#if 0
|
|
//
|
|
// Get the checksum
|
|
//
|
|
|
|
StoredChecksum = GetHwCompDatChecksum (HwCompDatId);
|
|
|
|
//
|
|
// Verify the checksum
|
|
//
|
|
|
|
CurrentChecksum = pComputeInfChecksum (HwCompDatPath, &Rebuild);
|
|
|
|
if (CurrentChecksum != StoredChecksum || Rebuild) {
|
|
|
|
if (!pIsFileOnCD (HwCompDatPath)) {
|
|
DEBUGMSG ((DBG_WARNING, "PNP dat file's internal checksum does not match"));
|
|
__leave;
|
|
}
|
|
|
|
DEBUGMSG ((
|
|
DBG_WARNING,
|
|
"PNP dat file's internal checksum does not match. Error "
|
|
"ignored because %s is on a CD.",
|
|
HwCompDatPath
|
|
));
|
|
}
|
|
|
|
#endif
|
|
|
|
//
|
|
// Load the rest of hwcomp.dat
|
|
//
|
|
|
|
if (!LoadHwCompDat (HwCompDatId)) {
|
|
DEBUGMSG ((DBG_ERROR, "Can't load hwcomp.dat"));
|
|
__leave;
|
|
}
|
|
|
|
//
|
|
// If a load operation, put the hash tables into globals for use by
|
|
// the rest of hwcomp.c.
|
|
//
|
|
|
|
if (Operation == LOAD) {
|
|
|
|
//
|
|
// Take ownership of the hash tables
|
|
//
|
|
|
|
if (g_InfFileTable) {
|
|
HtFree (g_InfFileTable);
|
|
}
|
|
|
|
if (g_PnpIdTable) {
|
|
HtFree (g_PnpIdTable);
|
|
}
|
|
|
|
if (g_UnsupPnpIdTable) {
|
|
HtFree (g_UnsupPnpIdTable);
|
|
}
|
|
|
|
TakeHwCompHashTables (
|
|
HwCompDatId,
|
|
(PVOID *) (&g_PnpIdTable),
|
|
(PVOID *) (&g_UnsupPnpIdTable),
|
|
(PVOID *) (&g_InfFileTable)
|
|
);
|
|
|
|
}
|
|
|
|
b = TRUE;
|
|
|
|
}
|
|
__finally {
|
|
|
|
CloseHwCompDat (HwCompDatId);
|
|
|
|
}
|
|
|
|
return b;
|
|
}
|
|
|
|
|
|
BOOL
|
|
pWriteDword (
|
|
IN HANDLE File,
|
|
IN DWORD Val
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pWriteDword writes the specified DWORD value to File.
|
|
|
|
Arguments:
|
|
|
|
File - Specifies file to write to
|
|
|
|
Val - Specifies value to write
|
|
|
|
Return Value:
|
|
|
|
TRUE if the function completes successfully, or FALSE if it fails.
|
|
Call GetLastError for additional failure information.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD BytesWritten;
|
|
|
|
return WriteFile (File, &Val, sizeof (Val), &BytesWritten, NULL);
|
|
}
|
|
|
|
|
|
BOOL
|
|
pWriteWord (
|
|
IN HANDLE File,
|
|
IN WORD Val
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pWriteWord writes the specified WORD vlue to File.
|
|
|
|
Arguments:
|
|
|
|
File - Specifies file to write to
|
|
|
|
Val - Specifies value to write
|
|
|
|
Return Value:
|
|
|
|
TRUE if the function completes successfully, or FALSE if it fails.
|
|
Call GetLastError for additional failure information.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD BytesWritten;
|
|
|
|
return WriteFile (File, &Val, sizeof (Val), &BytesWritten, NULL);
|
|
}
|
|
|
|
|
|
BOOL
|
|
pWriteStringWithLength (
|
|
IN HANDLE File,
|
|
IN PCTSTR String
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pWriteStringWithLength writes the length of String as a WORD,
|
|
and then writes String (excluding the nul terminator).
|
|
|
|
Arguments:
|
|
|
|
File - Specifies file to write to
|
|
|
|
String - Specifies string to write
|
|
|
|
Return Value:
|
|
|
|
TRUE if the function completes successfully, or FALSE if it fails.
|
|
Call GetLastError for additional failure information.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD BytesWritten;
|
|
WORD Length;
|
|
|
|
Length = (WORD) ByteCount (String);
|
|
if (!pWriteWord (File, Length)) {
|
|
DEBUGMSG ((DBG_ERROR, "pWriteStringWithLength: Can't write word"));
|
|
return FALSE;
|
|
}
|
|
|
|
if (Length) {
|
|
if (!WriteFile (File, String, Length, &BytesWritten, NULL)) {
|
|
DEBUGMSG ((DBG_ERROR, "pWriteStringWithLength: Can't write %s", String));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
pPnpIdEnum (
|
|
IN HASHTABLE Table,
|
|
IN HASHITEM StringId,
|
|
IN PCTSTR String,
|
|
IN PVOID ExtraData,
|
|
IN UINT ExtraDataSize,
|
|
IN LPARAM lParam
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pPnpIdEnum is a string table callback function that writes a PNP
|
|
ID to the file indicated in the Params struct (the lParam member).
|
|
|
|
This function only writes PNP IDs for a specific INF file (indicated
|
|
by the ExtraData arg).
|
|
|
|
Arguments:
|
|
|
|
Table - Specifies table being enumerated
|
|
|
|
StringId - Specifies offset of string in Table
|
|
|
|
String - Specifies string being enumerated
|
|
|
|
ExtraData - Specifies a pointer to a LONG that holds the INF ID
|
|
to enumerate. The PNP ID's INF ID must match this
|
|
parameter.
|
|
|
|
lParam - Specifies a pointer to a SAVE_ENUM_PARAMS struct
|
|
|
|
Return Value:
|
|
|
|
TRUE if the function completes successfully, or FALSE if it fails.
|
|
|
|
--*/
|
|
|
|
{
|
|
PSAVE_ENUM_PARAMS Params;
|
|
PCSTR BangString;
|
|
BOOL b = TRUE;
|
|
|
|
Params = (PSAVE_ENUM_PARAMS) lParam;
|
|
|
|
if (*((HASHITEM *) ExtraData) == Params->InfFileOffset) {
|
|
//
|
|
// Write this PNP ID to the file
|
|
//
|
|
|
|
if (Params->UnsupportedDevice) {
|
|
|
|
BangString = JoinTextExA (NULL, "!", String, NULL, 0, NULL);
|
|
b = pWriteStringWithLength (Params->File, BangString);
|
|
FreeTextA (BangString);
|
|
|
|
} else {
|
|
|
|
b = pWriteStringWithLength (Params->File, String);
|
|
|
|
}
|
|
}
|
|
|
|
return b;
|
|
}
|
|
|
|
|
|
BOOL
|
|
pInfFileEnum (
|
|
IN HASHTABLE Table,
|
|
IN HASHITEM StringId,
|
|
IN PCTSTR String,
|
|
IN HASHTABLE ExtraData,
|
|
IN UINT ExtraDataSize,
|
|
IN LPARAM lParam
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pInfFileEnum is a string table callback function and is called for
|
|
each INF in g_InfFileTable.
|
|
|
|
This routine writes the name of the INF to disk, and then enumerates
|
|
the PNP IDs for the INF, writing them to disk.
|
|
|
|
The PNP ID list is terminated with an empty string.
|
|
|
|
Arguments:
|
|
|
|
Table - Specifies g_InfFileTable
|
|
|
|
StringId - Specifies offset of String in g_InfFileTable
|
|
|
|
String - Specifies current INF file being enumerated
|
|
|
|
ExtraData - unused
|
|
|
|
ExtraDataSize - unused
|
|
|
|
lParam - Specifies a pointer to SAVE_ENUM_PARAMS struct.
|
|
|
|
Return Value:
|
|
|
|
TRUE if the function completes successfully, or FALSE if it fails.
|
|
|
|
--*/
|
|
|
|
{
|
|
PSAVE_ENUM_PARAMS Params;
|
|
|
|
Params = (PSAVE_ENUM_PARAMS) lParam;
|
|
Params->InfFileOffset = StringId;
|
|
|
|
//
|
|
// Save the file name
|
|
//
|
|
|
|
if (!pWriteStringWithLength (Params->File, String)) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Enumerate all PNP IDs
|
|
//
|
|
|
|
Params->UnsupportedDevice = FALSE;
|
|
|
|
if (!EnumHashTableWithCallback (g_PnpIdTable, pPnpIdEnum, lParam)) {
|
|
LOG ((LOG_ERROR, "Error while saving device list."));
|
|
return FALSE;
|
|
}
|
|
|
|
Params->UnsupportedDevice = TRUE;
|
|
|
|
if (!EnumHashTableWithCallback (g_UnsupPnpIdTable, pPnpIdEnum, lParam)) {
|
|
LOG ((LOG_ERROR, "Error while saving device list. (2)"));
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Terminate the PNP ID list
|
|
//
|
|
|
|
if (!pWriteStringWithLength (Params->File, S_EMPTY)) {
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
SaveDeviceList (
|
|
PCTSTR HwCompDatPath
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
SaveDeviceList writes all data stored in g_InfFileTable and g_PnpIdTable
|
|
to the file specified by HwCompDat. This file will therefore contain
|
|
all PNP IDs in all INFs of Windows NT.
|
|
|
|
Arguments:
|
|
|
|
HwCompDatPath - Specifies path of file to write
|
|
|
|
Return Value:
|
|
|
|
TRUE if the function completes successfully, or FALSE if it fails.
|
|
Call GetLastError for additional failure information.
|
|
|
|
--*/
|
|
|
|
{
|
|
HANDLE File;
|
|
DWORD BytesWritten;
|
|
BOOL b = FALSE;
|
|
SAVE_ENUM_PARAMS Params;
|
|
DWORD ChecksumToStore;
|
|
|
|
//
|
|
// !!! IMPORTANT !!!
|
|
//
|
|
// hwcomp.dat is used by other parts of NT. *DO NOT* change it without first e-mailing
|
|
// the NT group. Also, be sure to keep code in lib.c in sync with changes.
|
|
//
|
|
ChecksumToStore = pComputeInfChecksum (NULL, NULL);
|
|
|
|
File = CreateFile (
|
|
HwCompDatPath,
|
|
GENERIC_WRITE,
|
|
0, // open for exclusive access
|
|
NULL, // no security attribs
|
|
CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL // no template
|
|
);
|
|
|
|
if (File == INVALID_HANDLE_VALUE) {
|
|
LOG ((LOG_ERROR, "Cannot open %s for writing", HwCompDatPath));
|
|
return FALSE;
|
|
}
|
|
|
|
__try {
|
|
//
|
|
// Write version stamp
|
|
//
|
|
|
|
if (!WriteFile (File, HWCOMPDAT_SIGNATURE, ByteCount (HWCOMPDAT_SIGNATURE), &BytesWritten, NULL)) {
|
|
LOG ((LOG_ERROR, "Can't write signature file."));
|
|
__leave;
|
|
}
|
|
|
|
//
|
|
// Write checksum
|
|
//
|
|
|
|
if (!pWriteDword (File, ChecksumToStore)) {
|
|
LOG ((LOG_ERROR, "Can't write checksum"));
|
|
__leave;
|
|
}
|
|
|
|
//
|
|
// Enumerate the INF table, writing the INF file name and all PNP IDs
|
|
//
|
|
|
|
Params.File = File;
|
|
|
|
if (!EnumHashTableWithCallback (
|
|
g_InfFileTable,
|
|
pInfFileEnum,
|
|
(LPARAM) (&Params)
|
|
)) {
|
|
DEBUGMSG ((DBG_WARNING, "SaveDeviceList: EnumHashTableWithCallback returned FALSE"));
|
|
__leave;
|
|
}
|
|
|
|
//
|
|
// Terminate the INF file list
|
|
//
|
|
|
|
if (!pWriteStringWithLength (File, S_EMPTY)) {
|
|
DEBUGMSG ((DBG_WARNING, "SaveDeviceList: Can't write INF terminator"));
|
|
__leave;
|
|
}
|
|
|
|
b = TRUE;
|
|
}
|
|
__finally {
|
|
CloseHandle (File);
|
|
|
|
if (!b) {
|
|
DeleteFile (HwCompDatPath);
|
|
}
|
|
}
|
|
|
|
return b;
|
|
}
|
|
|
|
|
|
BOOL
|
|
pIsInfFileExcluded (
|
|
PCTSTR FileNamePtr
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
IsInfFileExcluded returns TRUE when the specified file name does
|
|
not contain PNP IDs.
|
|
|
|
Arguments:
|
|
|
|
FileNamePtr - The name of the uncompressed INF file, without any path info.
|
|
|
|
Return Value:
|
|
|
|
TRUE if the file should be ignored by the PNP parser, or FALSE if
|
|
the file may contain PNP IDs.
|
|
|
|
--*/
|
|
|
|
{
|
|
PCTSTR *p;
|
|
|
|
// Check for OEMN (old network INFs)
|
|
if (StringIMatchCharCount (FileNamePtr, TEXT("OEMN"), 4)) {
|
|
return TRUE;
|
|
}
|
|
|
|
// Make sure extension has INF
|
|
|
|
if (!StringIMatch (FileNamePtr + TcharCount (FileNamePtr) - 3 * sizeof (TCHAR), TEXT("INF"))) {
|
|
return TRUE;
|
|
}
|
|
|
|
// Check list of excluded files
|
|
|
|
for (p = g_ExcludeTable ; **p ; p++) {
|
|
if (StringIMatch (FileNamePtr, *p)) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
VOID
|
|
pGetNonExistingFile (
|
|
IN PCTSTR Path,
|
|
OUT PTSTR EndOfPath,
|
|
IN PCTSTR DefaultName
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pGetNonExistingFile generates a file name of a file that does
|
|
not exist. It creates an empty file with that name, to reserve it.
|
|
|
|
Arguments:
|
|
|
|
Path - Specifies the path where the file will exist. Path must
|
|
end in a backslash.
|
|
|
|
EndOfPath - Points to the nul at the end of Path and is used to
|
|
write the new file name.
|
|
|
|
DefaultName - Specifies the default file name to try to use. If such
|
|
a file already exists, numbers are appended to
|
|
DefaultName until a unique name is found.
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
|
|
{
|
|
UINT Count = 0;
|
|
|
|
StringCopy (EndOfPath, DefaultName);
|
|
|
|
while (GetFileAttributes (Path) != 0xffffffff) {
|
|
Count++;
|
|
wsprintf (EndOfPath, TEXT("%s.%03u"), DefaultName, Count);
|
|
}
|
|
}
|
|
|
|
|
|
BOOL
|
|
GetFileNames (
|
|
IN PCTSTR *InfDirs,
|
|
IN UINT InfDirCount,
|
|
IN BOOL QueryFlag,
|
|
IN OUT PGROWBUFFER FileNames,
|
|
IN OUT PGROWBUFFER DecompFileNames
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
GetFileNames searches InfDirs for any file that ends with .INF or .IN_.
|
|
It builds a MULTI_SZ list of file names that may contain PNP IDs. All
|
|
compressed INFs are decompressed into a temporary directory.
|
|
|
|
If the QueryFlag is set, the file name list is prepared but no files
|
|
are decompressed.
|
|
|
|
Arguments:
|
|
|
|
InfDirs - A list of paths to the directory containing INFs, either
|
|
compressed or non-compressed.
|
|
|
|
InfDirCount - Specifies the number of dirs in the InfDirs array.
|
|
|
|
QueryFlag - TRUE if the function should build the file list but
|
|
should not decompress; FALSE if the function
|
|
should build the file list and decompress as needed.
|
|
|
|
FileNames - Specifies an empty GROWBUFFER struct that is used to build
|
|
a multi-sz list of full paths to the INF files.
|
|
|
|
Return Value:
|
|
|
|
A pointer to the MUTLI_SZ list. The caller is responsible for freeing
|
|
this buffer via FreeFileNames.
|
|
|
|
The return value is NULL if an error occurred. Call GetLastError for
|
|
an error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
UINT u;
|
|
|
|
//
|
|
// Add list of files for each directory
|
|
//
|
|
|
|
for (u = 0 ; u < InfDirCount ; u++) {
|
|
if (!pGetFileNamesWorker (FileNames, DecompFileNames, InfDirs[u], QueryFlag)) {
|
|
FreeFileNames (FileNames, DecompFileNames, QueryFlag);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
MultiSzAppend (FileNames, S_EMPTY);
|
|
MultiSzAppend (DecompFileNames, S_EMPTY);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL
|
|
pGetFileNamesWorker (
|
|
IN OUT PGROWBUFFER FileNames,
|
|
IN OUT PGROWBUFFER DecompFileNames,
|
|
IN PCTSTR InfDir,
|
|
IN BOOL QueryFlag
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pGetFileNamesWorker gets the file names for a single directory.
|
|
See GetFileNames for more details.
|
|
|
|
Arguments:
|
|
|
|
FileNames - Specifies GROWBUFFER of file names. This routine
|
|
appends file names using MultiSzAppend but does not
|
|
append the final empty string.
|
|
|
|
InfDir - Specifies directory holding zero or more INFs (either
|
|
compressed or non-compressed).
|
|
|
|
QueryFlag - Specifies TRUE if INF list is to be queried, or
|
|
FALSE if the list is to be fully processed. When
|
|
QueryFlag is TRUE, files are not decompressed or
|
|
opened.
|
|
|
|
Return Value:
|
|
|
|
TRUE if the function completes successfully, or FALSE if it fails.
|
|
Call GetLastError for additional failure information.
|
|
|
|
--*/
|
|
|
|
{
|
|
PTSTR p;
|
|
TCHAR ActualFile[MAX_TCHAR_PATH];
|
|
CHAR AnsiFileName[MAX_MBCHAR_PATH];
|
|
PTSTR FileNameOnDisk;
|
|
HANDLE hFile;
|
|
DWORD BytesRead;
|
|
HANDLE hFind;
|
|
WIN32_FIND_DATA fd;
|
|
TCHAR Pattern[MAX_TCHAR_PATH];
|
|
TCHAR UncompressedFile[MAX_TCHAR_PATH];
|
|
TCHAR CompressedFile[MAX_TCHAR_PATH];
|
|
PTSTR FileNamePtr;
|
|
BOOL DecompressFlag;
|
|
DWORD rc;
|
|
BYTE BufForSp[2048];
|
|
PSP_INF_INFORMATION psp;
|
|
|
|
psp = (PSP_INF_INFORMATION) BufForSp;
|
|
|
|
//
|
|
// Get file names
|
|
//
|
|
|
|
StringCopy (Pattern, InfDir);
|
|
StringCopy (AppendWack (Pattern), TEXT("*.in?"));
|
|
|
|
hFind = FindFirstFile (Pattern, &fd);
|
|
if (hFind == INVALID_HANDLE_VALUE) {
|
|
if (GetLastError() == ERROR_NO_MORE_FILES) {
|
|
return TRUE;
|
|
}
|
|
|
|
LOG ((LOG_ERROR, "FindFirstFile failed for %s", Pattern));
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Determine if each matching file is actually an INF, and if so
|
|
// add it to the FileNames growbuf.
|
|
//
|
|
|
|
rc = ERROR_SUCCESS;
|
|
|
|
do {
|
|
if (*g_CancelFlagPtr) {
|
|
rc = ERROR_CANCELLED;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Make sure file has _ or f at the end.
|
|
//
|
|
|
|
p = GetEndOfString (fd.cFileName);
|
|
MYASSERT (p != fd.cFileName);
|
|
p = _tcsdec2 (fd.cFileName, p);
|
|
MYASSERT (p);
|
|
|
|
if (!p) {
|
|
continue;
|
|
}
|
|
|
|
if (*p != TEXT('_') && _totlower (*p) != TEXT('f')) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Default actual file to uncompressed name
|
|
//
|
|
|
|
StringCopy (ActualFile, fd.cFileName);
|
|
|
|
//
|
|
// Build source file (CompressedFile)
|
|
//
|
|
|
|
StringCopy (CompressedFile, InfDir);
|
|
StringCopy (AppendWack (CompressedFile), ActualFile);
|
|
|
|
//
|
|
// Build destination file (UncompressedFile) and detect collisions
|
|
//
|
|
/*
|
|
StringCopy (UncompressedFile, g_TempDir);
|
|
FileNamePtr = AppendWack (UncompressedFile);
|
|
pGetNonExistingFile (UncompressedFile, FileNamePtr, ActualFile);
|
|
*/
|
|
DecompressFlag = FALSE;
|
|
if (!GetTempFileName (g_TempDir, TEXT("inf"), 0, UncompressedFile)) {
|
|
rc = GetLastError ();
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Create uncompressed file path
|
|
//
|
|
|
|
if (*p == TEXT('_')) {
|
|
|
|
//
|
|
// Extract real name from INF file at offset 0x3c
|
|
//
|
|
|
|
ActualFile[0] = 0;
|
|
hFile = CreateFile (
|
|
CompressedFile,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL
|
|
);
|
|
|
|
if (hFile != INVALID_HANDLE_VALUE) {
|
|
|
|
if (0xffffffff != SetFilePointer (hFile, 0x3c, NULL, FILE_BEGIN)) {
|
|
|
|
if (ReadFile (
|
|
hFile,
|
|
AnsiFileName,
|
|
sizeof (AnsiFileName),
|
|
&BytesRead,
|
|
NULL
|
|
)) {
|
|
|
|
if (BytesRead >= SizeOfString (fd.cFileName)) {
|
|
FileNameOnDisk = ConvertAtoT (AnsiFileName);
|
|
|
|
if (StringIMatchCharCount (
|
|
fd.cFileName,
|
|
FileNameOnDisk,
|
|
CharCount (fd.cFileName) - 1
|
|
)) {
|
|
|
|
//
|
|
// Real name found -- use it as ActualFile
|
|
//
|
|
|
|
StringCopy (ActualFile, FileNameOnDisk);
|
|
|
|
//
|
|
// Also use real file name for decompression, but
|
|
// append numbers if collision.
|
|
//
|
|
/*
|
|
pGetNonExistingFile (
|
|
UncompressedFile,
|
|
FileNamePtr,
|
|
FileNameOnDisk
|
|
);
|
|
*/
|
|
}
|
|
|
|
FreeAtoT (FileNameOnDisk);
|
|
}
|
|
}
|
|
}
|
|
|
|
CloseHandle (hFile);
|
|
}
|
|
|
|
//
|
|
// If file name could not be found, discard this file
|
|
//
|
|
|
|
if (!ActualFile[0]) {
|
|
DEBUGMSG ((DBG_HWCOMP, "%s is not an INF file", fd.cFileName));
|
|
continue;
|
|
}
|
|
|
|
DecompressFlag = TRUE;
|
|
|
|
} else {
|
|
StringCopy (UncompressedFile, CompressedFile);
|
|
}
|
|
|
|
//
|
|
// Skip excluded files
|
|
//
|
|
|
|
if (pIsInfFileExcluded (ActualFile)) {
|
|
continue;
|
|
}
|
|
|
|
if (!QueryFlag) {
|
|
|
|
//
|
|
// Uncompress file if necessary
|
|
//
|
|
|
|
if (DecompressFlag) {
|
|
/*
|
|
SetFileAttributes (UncompressedFile, FILE_ATTRIBUTE_NORMAL);
|
|
DeleteFile (UncompressedFile);
|
|
*/
|
|
rc = SetupDecompressOrCopyFile (CompressedFile, UncompressedFile, 0);
|
|
|
|
if (rc != ERROR_SUCCESS) {
|
|
LOG ((LOG_ERROR, "Could not decompress %s to %s", CompressedFile, UncompressedFile));
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Determine if this is an NT 4 INF
|
|
//
|
|
|
|
if (!SetupGetInfInformation (
|
|
UncompressedFile,
|
|
INFINFO_INF_NAME_IS_ABSOLUTE,
|
|
psp,
|
|
sizeof (BufForSp),
|
|
NULL) ||
|
|
psp->InfStyle != INF_STYLE_WIN4
|
|
) {
|
|
|
|
DEBUGMSG ((DBG_HWCOMP, "%s is not a WIN4 INF file", UncompressedFile));
|
|
/*
|
|
if (DecompressFlag && !QueryFlag) {
|
|
DeleteFile (UncompressedFile);
|
|
}
|
|
*/
|
|
StringCopy (UncompressedFile, S_IGNORE_THIS_FILE);
|
|
}
|
|
|
|
TickProgressBar();
|
|
}
|
|
|
|
//
|
|
// Add file to grow buffer
|
|
//
|
|
|
|
MultiSzAppend (DecompressFlag ? DecompFileNames : FileNames, UncompressedFile);
|
|
|
|
} while (rc == ERROR_SUCCESS && FindNextFile (hFind, &fd));
|
|
|
|
FindClose (hFind);
|
|
|
|
if (rc != ERROR_SUCCESS) {
|
|
SetLastError (rc);
|
|
DEBUGMSG ((DBG_ERROR, "pGetFileNamesWorker: Error encountered in loop"));
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
VOID
|
|
FreeFileNames (
|
|
IN PGROWBUFFER FileNames,
|
|
IN OUT PGROWBUFFER DecompFileNames,
|
|
IN BOOL QueryFlag
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
FreeFileNames cleans up the list generated by GetFileNames. If
|
|
QueryFlag is set to FALSE, all temporary decompressed
|
|
files are deleted.
|
|
|
|
Arguments:
|
|
|
|
FileNames - The same grow buffer passed to GetFileNames
|
|
QueryFlag - The same flag passed to GetFileNames
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
|
|
{
|
|
PTSTR p;
|
|
|
|
p = (PTSTR) DecompFileNames->Buf;
|
|
if (!p) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Remove all files in temp dir (we created them when performing decompression)
|
|
//
|
|
|
|
if (!QueryFlag) {
|
|
while (*p) {
|
|
if (StringIMatchCharCount (p, g_TempDirWack, g_TempDirWackChars)) {
|
|
SetFileAttributes (p, FILE_ATTRIBUTE_NORMAL);
|
|
DeleteFile (p);
|
|
}
|
|
|
|
p = GetEndOfString (p) + 1;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Deallocate FileNames
|
|
//
|
|
|
|
FreeGrowBuffer (DecompFileNames);
|
|
FreeGrowBuffer (FileNames);
|
|
}
|
|
|
|
|
|
VOID
|
|
pBuildPatternCompatibleIDsTable (
|
|
VOID
|
|
)
|
|
{
|
|
INFSTRUCT is = INITINFSTRUCT_GROWBUFFER;
|
|
PCTSTR p;
|
|
GROWBUFFER joinedPattern = GROWBUF_INIT;
|
|
PPARSEDPATTERN test;
|
|
|
|
MYASSERT (g_Win95UpgInf != INVALID_HANDLE_VALUE);
|
|
if (InfFindFirstLine (g_Win95UpgInf, S_COMPATIBLE_PNP_IDS, NULL, &is)) {
|
|
do {
|
|
p = InfGetStringField (&is, 0);
|
|
if (*p) {
|
|
//
|
|
// first check if the pattern is correct
|
|
// if it isn't, we skip it
|
|
//
|
|
test = CreateParsedPattern (p);
|
|
if (test) {
|
|
DestroyParsedPattern (test);
|
|
GrowBufAppendString (&joinedPattern, TEXT("<"));
|
|
GrowBufAppendString (&joinedPattern, p);
|
|
GrowBufAppendString (&joinedPattern, TEXT(">"));
|
|
}
|
|
ELSE_DEBUGMSG ((DBG_WHOOPS, "Unable to parse pattern %s in [%s]", p, S_COMPATIBLE_PNP_IDS));
|
|
}
|
|
} while (InfFindNextLine (&is));
|
|
}
|
|
InfCleanUpInfStruct (&is);
|
|
|
|
if (joinedPattern.Buf) {
|
|
g_PatternCompatibleIDsTable = CreateParsedPattern (joinedPattern.Buf);
|
|
FreeGrowBuffer (&joinedPattern);
|
|
}
|
|
}
|
|
|
|
|
|
BOOL
|
|
CreateNtHardwareList (
|
|
IN PCTSTR * NtInfPaths,
|
|
IN UINT NtInfPathCount,
|
|
IN PCTSTR HwCompDatPath, OPTIONAL
|
|
IN INT UiMode
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
CreateNtHardwareList gets a list of all INF files and calls pProcessNtInfFile
|
|
to build the NT device list. This routine is called at initialization.
|
|
The resulting list is saved to disk as hwcomp.dat. If hwcomp.dat already
|
|
exists, the device list is read from disk.
|
|
|
|
Arguments:
|
|
|
|
NtInfPaths - Specifies an array of full paths to the NT INF files.
|
|
|
|
NtInfPathCount - Specifies the number of elements in NtInfPaths. Cannot
|
|
be zero.
|
|
|
|
HwCompDatPath - Specifies a full path spec where the new HWCOMP.DAT file
|
|
should be loaded from. This is used by the hwdatgen tool.
|
|
|
|
UiMode - Specifies the type of output (if any) to produce while building
|
|
the device lists. Values are zero, PNPREPT_OUTPUT, or
|
|
VERBOSE_OUTPUT.
|
|
|
|
Return Value:
|
|
|
|
Returns TRUE if successful, or FALSE if not. Call GetLastError for
|
|
failure code.
|
|
|
|
--*/
|
|
|
|
{
|
|
PCTSTR SourceFile;
|
|
PCTSTR DestFile;
|
|
BOOL FreeSourceAndDest;
|
|
UINT u;
|
|
PTSTR File;
|
|
DWORD rc;
|
|
BOOL bSaved = FALSE;
|
|
INFSTRUCT is = INITINFSTRUCT_GROWBUFFER;
|
|
PCTSTR p;
|
|
|
|
MYASSERT (NtInfPathCount > 0);
|
|
|
|
//
|
|
// If string tables already exist, then we do not have to build this list
|
|
// a second time.
|
|
//
|
|
|
|
if (!g_PatternCompatibleIDsTable) {
|
|
//
|
|
// table not set up; build it now
|
|
//
|
|
pBuildPatternCompatibleIDsTable ();
|
|
}
|
|
|
|
if (g_PnpIdTable && g_UnsupPnpIdTable && g_InfFileTable && g_ForceBadIdTable) {
|
|
return TRUE;
|
|
}
|
|
|
|
DEBUGMSG ((DBG_VERBOSE, "CreateNtHardwareList: building hardware list"));
|
|
|
|
MYASSERT (!g_PnpIdTable);
|
|
MYASSERT (!g_UnsupPnpIdTable);
|
|
MYASSERT (!g_InfFileTable);
|
|
MYASSERT (!g_ForceBadIdTable);
|
|
|
|
//
|
|
// Prepare file names. If HwCompDatPath is provided, use it only.
|
|
//
|
|
|
|
if (HwCompDatPath) {
|
|
//
|
|
// Use caller-supplied path; the caller is not necessarily NT Setup.
|
|
//
|
|
|
|
SourceFile = HwCompDatPath;
|
|
DestFile = HwCompDatPath;
|
|
FreeSourceAndDest = FALSE;
|
|
} else {
|
|
//
|
|
// Locate the source hwcomp.dat. If one does not exist,
|
|
// use the first source directory as Source.
|
|
//
|
|
|
|
SourceFile = NULL;
|
|
|
|
for (u = 0 ; !SourceFile && u < NtInfPathCount ; u++) {
|
|
SourceFile = pGetHwCompDat (NtInfPaths[u], TRUE);
|
|
}
|
|
|
|
if (!SourceFile) {
|
|
SourceFile = pGetHwCompDat (NtInfPaths[0], FALSE);
|
|
}
|
|
|
|
DestFile = pGetHwCompDat (g_TempDir, FALSE);
|
|
FreeSourceAndDest = TRUE;
|
|
}
|
|
|
|
//
|
|
// Build force table
|
|
//
|
|
|
|
if (g_ForceBadIdTable) {
|
|
HtFree (g_ForceBadIdTable);
|
|
}
|
|
|
|
g_ForceBadIdTable = HtAlloc();
|
|
|
|
if (InfFindFirstLine (g_Win95UpgInf, TEXT("Forced Incompatible IDs"), NULL, &is)) {
|
|
|
|
do {
|
|
|
|
p = InfGetStringField (&is, 0);
|
|
|
|
if (*p) {
|
|
HtAddString (g_ForceBadIdTable, p);
|
|
}
|
|
|
|
} while (InfFindNextLine (&is));
|
|
|
|
}
|
|
|
|
InfCleanUpInfStruct (&is);
|
|
|
|
__try {
|
|
//
|
|
// Try loading state from CD
|
|
//
|
|
|
|
if (UiMode != PNPREPT_OUTPUT) {
|
|
|
|
if (!LoadDeviceList (LOAD, SourceFile)) {
|
|
//
|
|
// Could not load from CD -- try loading from temporary storage
|
|
// location
|
|
//
|
|
|
|
if (!HwCompDatPath && LoadDeviceList (LOAD, DestFile)) {
|
|
return TRUE;
|
|
}
|
|
|
|
DEBUGMSG ((DBG_HWCOMP, "%s does not exist or needs to be rebuilt", SourceFile));
|
|
|
|
} else {
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Load the INF file names
|
|
//
|
|
|
|
ProgressBar_SetComponentById (MSG_DECOMPRESSING);
|
|
|
|
// Get file names
|
|
if (!g_FileNames.Buf && !g_DecompFileNames.Buf) {
|
|
if (!GetFileNames (NtInfPaths, NtInfPathCount, FALSE, &g_FileNames, &g_DecompFileNames)) {
|
|
DEBUGMSG ((DBG_WARNING, "HWCOMP: Can't get INF file names"));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
__try {
|
|
|
|
ProgressBar_SetComponentById (MSG_HWCOMP);
|
|
|
|
//
|
|
// Initialize string tables
|
|
//
|
|
|
|
g_PnpIdTable = HtAllocWithData (sizeof (HASHITEM));
|
|
g_UnsupPnpIdTable = HtAllocWithData (sizeof (HASHITEM));
|
|
g_InfFileTable = HtAlloc();
|
|
|
|
if (!g_PnpIdTable || !g_UnsupPnpIdTable || !g_InfFileTable) {
|
|
LOG ((LOG_ERROR, "HWCOMP: Can't allocate string tables"));
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Walk through list of INF files, and locate device names inside
|
|
// manufacturer sections. Add each name to the string table.
|
|
//
|
|
|
|
File = (PTSTR) g_FileNames.Buf;
|
|
while (*File) {
|
|
//
|
|
// Skip non-WIN4 INF files
|
|
//
|
|
|
|
if (StringMatch (File, S_IGNORE_THIS_FILE)) {
|
|
File = GetEndOfString (File) + 1;
|
|
if (!TickProgressBar()) {
|
|
break;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Process all WIN4 INF files
|
|
//
|
|
|
|
if (UiMode != PNPREPT_OUTPUT) {
|
|
ProgressBar_SetSubComponent (File);
|
|
}
|
|
|
|
if (!pProcessNtInfFile (File, UiMode, g_InfFileTable, g_PnpIdTable, g_UnsupPnpIdTable)) {
|
|
if ((GetLastError() & 0xe0000000) == 0xe0000000) {
|
|
DEBUGMSG ((DBG_WARNING, "pProcessNtInfFile failed to parse %s.", File));
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!TickProgressBar()) {
|
|
break;
|
|
}
|
|
|
|
File = GetEndOfString (File) + 1;
|
|
}
|
|
|
|
rc = GetLastError();
|
|
if (rc == ERROR_SUCCESS) {
|
|
File = (PTSTR) g_DecompFileNames.Buf;
|
|
while (*File) {
|
|
//
|
|
// Skip non-WIN4 INF files
|
|
//
|
|
|
|
if (StringMatch (File, S_IGNORE_THIS_FILE)) {
|
|
File = GetEndOfString (File) + 1;
|
|
if (!TickProgressBar()) {
|
|
break;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Process all WIN4 INF files
|
|
//
|
|
|
|
if (UiMode != PNPREPT_OUTPUT) {
|
|
ProgressBar_SetSubComponent (File);
|
|
}
|
|
|
|
if (!pProcessNtInfFile (File, UiMode, g_InfFileTable, g_PnpIdTable, g_UnsupPnpIdTable)) {
|
|
if ((GetLastError() & 0xe0000000) == 0xe0000000) {
|
|
DEBUGMSG ((DBG_WARNING, "pProcessNtInfFile failed to parse %s.", File));
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!TickProgressBar()) {
|
|
break;
|
|
}
|
|
|
|
File = GetEndOfString (File) + 1;
|
|
}
|
|
rc = GetLastError();
|
|
}
|
|
|
|
//
|
|
// Clean up UI
|
|
//
|
|
|
|
ProgressBar_SetComponent (NULL);
|
|
ProgressBar_SetSubComponent (NULL);
|
|
|
|
//
|
|
// Save string tables to hwcomp.dat
|
|
//
|
|
|
|
if (UiMode == PNPREPT_OUTPUT) {
|
|
bSaved = TRUE;
|
|
} else if (rc == ERROR_SUCCESS) {
|
|
bSaved = SaveDeviceList (DestFile);
|
|
|
|
//
|
|
// Try copying this file to the right place for future installs
|
|
//
|
|
|
|
if (bSaved && !HwCompDatPath) {
|
|
if (!StringIMatch (DestFile, SourceFile)) {
|
|
CopyFile (DestFile, SourceFile, FALSE);
|
|
}
|
|
}
|
|
|
|
if (!bSaved) {
|
|
rc = GetLastError();
|
|
}
|
|
}
|
|
}
|
|
|
|
__finally {
|
|
FreeFileNames (&g_FileNames, &g_DecompFileNames, FALSE);
|
|
}
|
|
}
|
|
|
|
__finally {
|
|
if (FreeSourceAndDest) {
|
|
pFreeHwCompDatName (SourceFile);
|
|
pFreeHwCompDatName (DestFile);
|
|
}
|
|
}
|
|
|
|
return bSaved;
|
|
}
|
|
|
|
|
|
VOID
|
|
FreeNtHardwareList (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
FreeNtHardwareList cleans up the string tables. This function is called by
|
|
DllMain when a process detaches.
|
|
|
|
Arguments:
|
|
|
|
none
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
|
|
{
|
|
if (g_InfFileTable) {
|
|
HtFree (g_InfFileTable);
|
|
g_InfFileTable = NULL;
|
|
}
|
|
|
|
if (g_PnpIdTable) {
|
|
HtFree (g_PnpIdTable);
|
|
g_PnpIdTable = NULL;
|
|
}
|
|
|
|
if (g_UnsupPnpIdTable) {
|
|
HtFree (g_UnsupPnpIdTable);
|
|
g_UnsupPnpIdTable = NULL;
|
|
}
|
|
|
|
if (g_ForceBadIdTable) {
|
|
HtFree (g_ForceBadIdTable);
|
|
g_ForceBadIdTable = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Routines that use the enumerators
|
|
//
|
|
|
|
BOOL
|
|
HwComp_ScanForCriticalDevices (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
HwComp_ScanForCriticalDevices is one of the first functions called in
|
|
the upgrade module. It enumerates the hardware and determines if
|
|
certain required devices are compatible.
|
|
|
|
Arguments:
|
|
|
|
none
|
|
|
|
Return Value:
|
|
|
|
TRUE if processing was successful, or FALSE if an error occurred.
|
|
Call GetLastError() for failure code.
|
|
|
|
--*/
|
|
|
|
{
|
|
HARDWARE_ENUM e;
|
|
|
|
//
|
|
// Reset flags for reentrancy
|
|
//
|
|
|
|
g_ValidWinDir = FALSE;
|
|
g_ValidSysDrive = FALSE;
|
|
g_ValidCdRom = FALSE;
|
|
g_FoundPnp8387 = FALSE;
|
|
|
|
if (g_NeededHardwareIds) {
|
|
HtFree (g_NeededHardwareIds);
|
|
g_NeededHardwareIds = NULL;
|
|
}
|
|
|
|
g_NeededHardwareIds = HtAlloc();
|
|
MYASSERT (g_NeededHardwareIds);
|
|
|
|
//
|
|
// Make sure hardware list is valid
|
|
//
|
|
|
|
if (!CreateNtHardwareList (
|
|
SOURCEDIRECTORYARRAY(),
|
|
SOURCEDIRECTORYCOUNT(),
|
|
NULL,
|
|
REGULAR_OUTPUT
|
|
)) {
|
|
|
|
DEBUGMSG_IF ((
|
|
GetLastError() != ERROR_CANCELLED,
|
|
DBG_ERROR,
|
|
"HwComp_ScanForCriticalDevices: CreateNtHardwareList failed!"
|
|
));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Scan all hardware
|
|
//
|
|
|
|
if (EnumFirstHardware (&e, ENUM_ALL_DEVICES, ENUM_WANT_COMPATIBLE_FLAG | ENUM_DONT_REQUIRE_HARDWAREID)) {
|
|
do {
|
|
//
|
|
// Fill g_NeededHardwareIDs with all PNP IDs of incompatible devices.
|
|
// Skip the devices that are deliberately unsupported.
|
|
//
|
|
|
|
if (!e.Compatible && !e.Unsupported) {
|
|
if (e.HardwareID) {
|
|
AddPnpIdsToHashTable (g_NeededHardwareIds, e.HardwareID);
|
|
}
|
|
|
|
if (e.CompatibleIDs) {
|
|
AddPnpIdsToHashTable (g_NeededHardwareIds, e.CompatibleIDs);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Test 1: Check to see if (A) g_WinDir is on supported device, and
|
|
// (B) g_BootDriveLetter is on a supported device.
|
|
//
|
|
|
|
if (e.Compatible && e.CurrentDriveLetter) {
|
|
if (_tcschr (e.CurrentDriveLetter, _tcsnextc (g_WinDir))) {
|
|
g_ValidWinDir = TRUE;
|
|
}
|
|
|
|
if (_tcschr (e.CurrentDriveLetter, g_BootDriveLetter)) {
|
|
g_ValidSysDrive = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Test 2: Check to see if the class is CDROM
|
|
//
|
|
|
|
if (e.Compatible && e.Class) {
|
|
if (StringIMatch (e.Class, TEXT("CDROM"))) {
|
|
g_ValidCdRom = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Test 3: Check to see if HardwareID or CompatibleIDs contains
|
|
// *PNP8387 (Dial-Up Adapter)
|
|
//
|
|
|
|
if (e.CompatibleIDs && _tcsistr (e.CompatibleIDs, TEXT("*PNP8387"))) {
|
|
g_FoundPnp8387 = TRUE;
|
|
}
|
|
|
|
if (e.HardwareID && _tcsistr (e.HardwareID, TEXT("*PNP8387"))) {
|
|
g_FoundPnp8387 = TRUE;
|
|
}
|
|
|
|
//
|
|
// Test 4: Test for an incompatible SCSI adapter
|
|
//
|
|
|
|
if (e.HardwareID && !e.Compatible && _tcsistr (e.Class, TEXT("SCSI"))) {
|
|
g_IncompatibleScsiDevice = TRUE;
|
|
}
|
|
|
|
} while (EnumNextHardware (&e));
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
HwComp_DialUpAdapterFound (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
HwComp_DialUpAdapterFound returns TRUE if *PNP8387 was found
|
|
during the HwComp_ScanForCriticalDevices routine.
|
|
|
|
Arguments:
|
|
|
|
none
|
|
|
|
Return Value:
|
|
|
|
TRUE if the Microsoft Dial-Up Adapter exists, or FALSE if it
|
|
does not.
|
|
|
|
--*/
|
|
|
|
{
|
|
return g_FoundPnp8387;
|
|
}
|
|
|
|
|
|
BOOL
|
|
HwComp_NtUsableHardDriveExists (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
HwComp_NtUsableHardDriveExists returns TRUE if a compatible
|
|
hard disk exists for the Windows directory and the boot
|
|
drive.
|
|
|
|
Arguments:
|
|
|
|
none
|
|
|
|
Return Value:
|
|
|
|
TRUE if a compatible hard disk exists, or FALSE if one does
|
|
not exist.
|
|
|
|
--*/
|
|
|
|
{
|
|
return g_ValidSysDrive && g_ValidWinDir;
|
|
}
|
|
|
|
|
|
BOOL
|
|
HwComp_ReportIncompatibleController (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
HwComp_ReportIncompatibleController adds a message when an incompatible
|
|
hard disk controller is found. If the boot drive or windir drive is
|
|
incompatible, then a strong warning is given. Otherwise, the message
|
|
is informational.
|
|
|
|
Arguments:
|
|
|
|
none
|
|
|
|
Return Value:
|
|
|
|
TRUE if an incompatible controller message was added, FALSE otherwise.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
HARDWARE_ENUM e;
|
|
BOOL MsgAdded = FALSE;
|
|
PCTSTR Group;
|
|
PCTSTR Message;
|
|
BOOL BadMainDev;
|
|
|
|
//
|
|
// Do this only if a bad controller exists
|
|
//
|
|
|
|
BadMainDev = HwComp_NtUsableHardDriveExists();
|
|
if (!BadMainDev && !g_IncompatibleScsiDevice) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Scan incompatible hardware
|
|
//
|
|
|
|
if (EnumFirstHardware (
|
|
&e,
|
|
ENUM_NON_FUNCTIONAL_DEVICES,
|
|
ENUM_WANT_COMPATIBLE_FLAG | ENUM_DONT_REQUIRE_HARDWAREID
|
|
)) {
|
|
|
|
do {
|
|
|
|
//
|
|
// this test is not reliable
|
|
// there are CDROMs that will falll into this category but they are not HD controllers
|
|
// and there are also real SCSI controllers that have an "Unknown" class because
|
|
// Win9x doesn't have (or need) a driver for them
|
|
//
|
|
#if 0
|
|
if (_tcsistr (e.Class, TEXT("SCSI"))) {
|
|
|
|
if (!MsgAdded) {
|
|
MsgAdded = TRUE;
|
|
|
|
Group = BuildMessageGroup (MSG_INCOMPATIBLE_HARDWARE_ROOT, MSG_INCOMPATIBLE_HARD_DISK_SUBGROUP, NULL);
|
|
|
|
Message = GetStringResource (
|
|
BadMainDev ? MSG_INCOMPATIBLE_HARD_DISK_WARNING :
|
|
MSG_INCOMPATIBLE_HARD_DISK_NOTIFICATION
|
|
);
|
|
|
|
MsgMgr_ContextMsg_Add (TEXT("*BadController"), Group, Message);
|
|
|
|
FreeText (Group);
|
|
FreeStringResource (Message);
|
|
}
|
|
|
|
MsgMgr_LinkObjectWithContext (TEXT("*BadController"), e.FullKey);
|
|
DEBUGMSG ((DBG_HWCOMP, "Bad controller key: %s", e.FullKey));
|
|
}
|
|
#endif
|
|
|
|
} while (EnumNextHardware (&e));
|
|
}
|
|
|
|
return MsgAdded;
|
|
}
|
|
|
|
|
|
BOOL
|
|
HwComp_NtUsableCdRomDriveExists (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
HwComp_NtUsableCdRomDriveExists returns TRUE if a compatible
|
|
CD-ROM drive exists.
|
|
|
|
Arguments:
|
|
|
|
none
|
|
|
|
Return Value:
|
|
|
|
TRUE if a compatible CD-ROM drive exists, or FALSE if it does not.
|
|
|
|
--*/
|
|
|
|
{
|
|
return g_ValidCdRom;
|
|
}
|
|
|
|
|
|
BOOL
|
|
HwComp_MakeLocalSourceDeviceExists (
|
|
VOID
|
|
)
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
MakeLocalSourceDevice scans the IDs found on the system for a match in a
|
|
special section in win95upg.inf. If one of these devices is found, the
|
|
function returns TRUE. This is used to catch cases where the user's CDrom
|
|
drive may be attached to a device that will not be available during
|
|
textmode.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
TRUE if such a device exists, FALSE otherwise.
|
|
|
|
--*/
|
|
|
|
|
|
{
|
|
INFSTRUCT is = INITINFSTRUCT_GROWBUFFER;
|
|
HASHTABLE table;
|
|
HARDWARE_ENUM e;
|
|
BOOL rDeviceExists = FALSE;
|
|
PTSTR p = NULL;
|
|
|
|
|
|
__try {
|
|
if (InfFindFirstLine (g_Win95UpgInf, S_MAKELOCALSOURCEDEVICES, NULL, &is)) {
|
|
|
|
table = HtAlloc ();
|
|
|
|
//
|
|
// Add all of the "bad" pnpids listed in win95upg.inf.
|
|
//
|
|
do {
|
|
|
|
p = InfGetStringField (&is, 0);
|
|
HtAddString (table, p);
|
|
|
|
} while (InfFindNextLine (&is));
|
|
|
|
//
|
|
// Now, enumerate the devices on the system and see if we have any matches.
|
|
//
|
|
if (EnumFirstHardware (&e, ENUM_ALL_DEVICES, ENUM_WANT_ONLINE_FLAG)) {
|
|
do {
|
|
|
|
if (HtFindString (table, e.HardwareID)) {
|
|
|
|
rDeviceExists = TRUE;
|
|
AbortHardwareEnum (&e);
|
|
DEBUGMSG ((DBG_WARNING, "Device %s requires us to turn on the local source flag.", e.HardwareID));
|
|
break;
|
|
}
|
|
|
|
|
|
} while (EnumNextHardware (&e));
|
|
}
|
|
|
|
|
|
HtFree (table);
|
|
}
|
|
}
|
|
__except (1) {
|
|
return FALSE;
|
|
}
|
|
|
|
return rDeviceExists;
|
|
|
|
}
|
|
|
|
|
|
BOOL
|
|
HwComp_DoesDatFileNeedRebuilding (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
HwComp_DoesDatFileNeedRebuilding locates hwcomp.dat in the source
|
|
directories and determines if it needs to be rebuilt by obtaining
|
|
the checksum in the file and comparing it against the one from
|
|
the current INF files.
|
|
|
|
Arguments:
|
|
|
|
none
|
|
|
|
Return Value:
|
|
|
|
TRUE if the hwcomp.dat file needs to be rebuilt, or FALSE if it
|
|
does not.
|
|
|
|
--*/
|
|
|
|
{
|
|
PCTSTR SourceFile = NULL;
|
|
UINT u;
|
|
BOOL b = FALSE;
|
|
|
|
for (u = 0 ; u < SOURCEDIRECTORYCOUNT() ; u++) {
|
|
SourceFile = pGetHwCompDat(SOURCEDIRECTORY(u), TRUE);
|
|
if (SourceFile) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (SourceFile) {
|
|
TurnOnWaitCursor();
|
|
b = LoadDeviceList (QUERY, SourceFile);
|
|
TurnOffWaitCursor();
|
|
|
|
pFreeHwCompDatName (SourceFile);
|
|
}
|
|
|
|
// b is TRUE when hwcomp.dat is valid, so return opposite
|
|
return !b;
|
|
}
|
|
|
|
|
|
INT
|
|
HwComp_GetProgressMax (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
HwComp_GetProgressMax calculates the number of INF files that would
|
|
need to be scanned if hwcomp.dat needs to be rebuilt. If it is
|
|
determined that hwcomp.dat does not need to be rebuilt, the function
|
|
returns zero.
|
|
|
|
Arguments:
|
|
|
|
none
|
|
|
|
Return Value:
|
|
|
|
The number of INF files that need to be processed, times two. (One
|
|
pass for decompression, another pass for parsing the INFs.)
|
|
|
|
--*/
|
|
|
|
{
|
|
INT FileCount = 0;
|
|
PCTSTR File;
|
|
PCTSTR SourceFile = NULL;
|
|
BOOL b = FALSE;
|
|
|
|
//
|
|
// Query validity of hwcomp.dat
|
|
//
|
|
|
|
if (!HwComp_DoesDatFileNeedRebuilding()) {
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// hwcomp.dat needs to be rebuilt, so return the number of files
|
|
// that need to be scanned times two.
|
|
//
|
|
|
|
// Count files
|
|
if (!g_FileNames.Buf && !g_DecompFileNames.Buf) {
|
|
if (!GetFileNames (
|
|
SOURCEDIRECTORYARRAY(),
|
|
SOURCEDIRECTORYCOUNT(),
|
|
TRUE,
|
|
&g_FileNames,
|
|
&g_DecompFileNames
|
|
)) {
|
|
LOG ((LOG_ERROR, "HWCOMP: Can't estimate number of INF files"));
|
|
return 850; // estimation, so there is some progress bar activity
|
|
}
|
|
}
|
|
|
|
for (File = (PCTSTR) g_FileNames.Buf ; *File ; File = GetEndOfString (File) + 1) {
|
|
FileCount++;
|
|
}
|
|
for (File = (PCTSTR) g_DecompFileNames.Buf ; *File ; File = GetEndOfString (File) + 1) {
|
|
FileCount++;
|
|
}
|
|
|
|
FreeFileNames (&g_FileNames, &g_DecompFileNames, TRUE);
|
|
MYASSERT (!g_FileNames.Buf);
|
|
MYASSERT (!g_DecompFileNames.Buf);
|
|
|
|
return FileCount*2;
|
|
}
|
|
|
|
|
|
BOOL
|
|
pIsDeviceConsideredCompatible (
|
|
PCTSTR DevIds
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pIsDeviceConsideredCompatible scans a list of comma-separated
|
|
PNP IDs against the list in win95upg.inf. If at least one
|
|
ID matches, TRUE is returned.
|
|
|
|
This function also implements a hack for the VIRTUAL root.
|
|
|
|
Arguments:
|
|
|
|
DevIds - Specifies a list of zero or more PNP IDs, separated
|
|
by commas.
|
|
|
|
Return Value:
|
|
|
|
TRUE if a PNP ID was found to be overridden as compatible, or
|
|
FALSE if none of the IDs are in win95upg.inf.
|
|
|
|
--*/
|
|
|
|
{
|
|
TCHAR Id[MAX_PNP_ID];
|
|
INFCONTEXT ic;
|
|
|
|
while (*DevIds) {
|
|
|
|
//
|
|
// Create Id string from comma-separated PNP ID list
|
|
//
|
|
|
|
DevIds = ExtractPnpId (DevIds, Id);
|
|
if (*Id == 0) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Search win95upg.inf for the PNP ID
|
|
//
|
|
|
|
if (SetupFindFirstLine (g_Win95UpgInf, S_STANDARD_PNP_IDS, Id, &ic)) {
|
|
DEBUGMSG ((DBG_HWCOMP, "%s is incompatible, but suppressed in win95upg.inf", Id));
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// This is a hack for the VIRTUAL enumerator, used by Turtle Beach.
|
|
//
|
|
|
|
if (StringIMatchCharCount (TEXT("VIRTUAL\\"), Id, 8)) {
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Test for pattern PNPIDs
|
|
//
|
|
if (g_PatternCompatibleIDsTable && TestParsedPattern (g_PatternCompatibleIDsTable, Id)) {
|
|
DEBUGMSG ((DBG_HWCOMP, "%s is incompatible, but suppressed in win95upg.inf", Id));
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
pIsDeviceInstalled (
|
|
IN PCTSTR DeviceDesc
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pIsDeviceInstalled scans the registry looking for a device that has the
|
|
specified description and is online.
|
|
|
|
Arguments:
|
|
|
|
DeviceDesc - Specifies the description of the duplicate device to find
|
|
(i.e., Dial-Up Adapter)
|
|
|
|
Return Value:
|
|
|
|
TRUE if an identical device was found and is online, or FALSE if not.
|
|
|
|
--*/
|
|
|
|
{
|
|
HARDWARE_ENUM e;
|
|
|
|
if (EnumFirstHardware (
|
|
&e,
|
|
ENUM_NON_FUNCTIONAL_DEVICES,
|
|
ENUM_WANT_ONLINE_FLAG|ENUM_DONT_WANT_USER_SUPPLIED
|
|
)) {
|
|
do {
|
|
if (e.Online) {
|
|
if (e.DeviceDesc) {
|
|
if (StringIMatch (e.DeviceDesc, DeviceDesc)) {
|
|
AbortHardwareEnum (&e);
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
} while (EnumNextHardware (&e));
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
VOID
|
|
pGetFriendlyClassName (
|
|
IN HKEY ClassKey,
|
|
IN PCTSTR Class,
|
|
OUT PTSTR Buffer
|
|
)
|
|
{
|
|
PCTSTR Data = NULL;
|
|
HKEY SubKey;
|
|
|
|
SubKey = OpenRegKey (ClassKey, Class);
|
|
|
|
if (SubKey) {
|
|
Data = GetRegValueString (SubKey, S_EMPTY);
|
|
CloseRegKey (SubKey);
|
|
|
|
if (!Data || !*Data) {
|
|
SubKey = NULL;
|
|
}
|
|
}
|
|
|
|
if (!SubKey) {
|
|
Data = GetStringResource (MSG_UNKNOWN_DEVICE_CLASS);
|
|
if (Data) {
|
|
_tcssafecpy (Buffer, Data, MAX_TCHAR_PATH);
|
|
FreeStringResource (Data);
|
|
}
|
|
ELSE_DEBUGMSG ((DBG_ERROR, "Unable to load string resource MSG_UNKNOWN_DEVICE_CLASS. Check localization."));
|
|
return;
|
|
}
|
|
|
|
_tcssafecpy (Buffer, Data, MAX_TCHAR_PATH);
|
|
MemFree (g_hHeap, 0, Data);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
BOOL
|
|
pStuffDeviceInReport (
|
|
PHARDWARE_ENUM e,
|
|
HKEY Key,
|
|
HWTYPES SupportedType
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pStuffDeviceInReport adds a device to the upgrade report. The device is
|
|
either incompatible or unsupported.
|
|
|
|
Arguments:
|
|
|
|
e - Specifies the current hardware enumerator struct.
|
|
Key - Specifies a handle to the Class key in HKLM\System\Services
|
|
SupportedType - Specifies one of the HWTYPES constants -- HW_INCOMPATIBLE,
|
|
HW_REINSTALL or HW_UNINSTALL, to indicate which category
|
|
to stuff the message into.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PCTSTR ModifiedDescription = NULL;
|
|
PCTSTR Group = NULL;
|
|
PCTSTR Message = NULL;
|
|
PCTSTR DeviceDesc = NULL;
|
|
PCTSTR Array[6];
|
|
BOOL UnknownClass = FALSE;
|
|
PCTSTR Mfg;
|
|
PCTSTR Class;
|
|
PCTSTR HardwareID;
|
|
PCTSTR CompatibleID;
|
|
PCTSTR ClassAndName;
|
|
TCHAR FriendlyClass[MAX_TCHAR_PATH];
|
|
UINT SubGroup;
|
|
PCTSTR SubGroupText; // for log output only, hard-coded text
|
|
BOOL b = FALSE;
|
|
|
|
//
|
|
// Determine which group this message belongs in. The order is determined
|
|
// by the alphanumeric order of SubGroup.
|
|
//
|
|
|
|
if (SupportedType == HW_INCOMPATIBLE) {
|
|
SubGroup = MSG_INCOMPATIBLE_HARDWARE_PNP_SUBGROUP;
|
|
SubGroupText = TEXT("Incompatible");
|
|
} else if (SupportedType == HW_REINSTALL) {
|
|
SubGroup = MSG_REINSTALL_HARDWARE_PNP_SUBGROUP;
|
|
SubGroupText = TEXT("Reinstall");
|
|
} else {
|
|
SubGroup = MSG_UNSUPPORTED_HARDWARE_PNP_SUBGROUP;
|
|
SubGroupText = TEXT("Unsupported");
|
|
}
|
|
|
|
//
|
|
// Is device suppressed?
|
|
//
|
|
|
|
if (IsReportObjectHandled (e->FullKey)) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Sometimes blank entries are found!!
|
|
//
|
|
|
|
__try {
|
|
DeviceDesc = e->DeviceDesc;
|
|
|
|
if (!DeviceDesc || (DeviceDesc && *DeviceDesc == 0)) {
|
|
|
|
LOG ((
|
|
LOG_ERROR,
|
|
"Skipping device because it lacks DriverDesc (%s,%s,%s)",
|
|
e->Mfg,
|
|
e->Class,
|
|
e->HardwareID
|
|
));
|
|
|
|
__leave;
|
|
}
|
|
|
|
Mfg = e->Mfg;
|
|
if (!Mfg) {
|
|
|
|
DEBUGMSG ((
|
|
DBG_WARNING,
|
|
"Device lacking manufacturer (%s,%s,%s)",
|
|
e->DeviceDesc,
|
|
e->Class,
|
|
e->HardwareID
|
|
));
|
|
|
|
Mfg = S_EMPTY;
|
|
}
|
|
|
|
Class = e->Class;
|
|
if (!Class) {
|
|
|
|
DEBUGMSG ((
|
|
DBG_WARNING,
|
|
"Device lacking class (%s,%s,%s)",
|
|
e->DeviceDesc,
|
|
e->Mfg,
|
|
e->HardwareID
|
|
));
|
|
|
|
Class = GetStringResource (MSG_UNKNOWN_DEVICE_CLASS);
|
|
MYASSERT (Class);
|
|
UnknownClass = TRUE;
|
|
}
|
|
|
|
HardwareID = e->HardwareID;
|
|
if (!HardwareID) {
|
|
|
|
DEBUGMSG ((
|
|
DBG_WARNING,
|
|
"Device lacking hardware ID (%s,%s,%s)",
|
|
e->DeviceDesc,
|
|
e->Mfg,
|
|
e->Class
|
|
));
|
|
|
|
HardwareID = S_EMPTY;
|
|
}
|
|
|
|
CompatibleID = e->CompatibleIDs;
|
|
if (!CompatibleID) {
|
|
CompatibleID = S_EMPTY;
|
|
}
|
|
|
|
//
|
|
// Add "(not currently present)" to offline devices
|
|
//
|
|
|
|
if (!e->Online) {
|
|
//
|
|
// Verify identical online device doesn't exist
|
|
//
|
|
|
|
if (pIsDeviceInstalled (DeviceDesc)) {
|
|
__leave;
|
|
}
|
|
|
|
Array[0] = DeviceDesc;
|
|
ModifiedDescription = ParseMessageID (MSG_OFFLINE_DEVICE, Array);
|
|
}
|
|
|
|
//
|
|
// Add hardware message to the incompatibility table
|
|
//
|
|
|
|
if (UnknownClass) {
|
|
StringCopy (FriendlyClass, Class);
|
|
} else {
|
|
pGetFriendlyClassName (Key, Class, FriendlyClass);
|
|
}
|
|
|
|
DEBUGMSG ((
|
|
DBG_HWCOMP,
|
|
"%s Device:\n"
|
|
" %s (%s)\n"
|
|
" %s\n"
|
|
" %s (%s)\n"
|
|
" %s\n",
|
|
SubGroupText,
|
|
HardwareID,
|
|
CompatibleID,
|
|
ModifiedDescription ? ModifiedDescription : DeviceDesc,
|
|
Class,
|
|
FriendlyClass,
|
|
Mfg
|
|
));
|
|
|
|
//
|
|
// Add the message via message manager
|
|
//
|
|
|
|
Array[0] = ModifiedDescription ? ModifiedDescription : DeviceDesc;
|
|
Array[1] = S_EMPTY; // formerly Enumerator Text
|
|
Array[2] = Class;
|
|
Array[3] = Mfg;
|
|
Array[4] = HardwareID;
|
|
Array[5] = FriendlyClass;
|
|
|
|
ClassAndName = JoinPaths (Array[5], Array[0]);
|
|
|
|
Group = BuildMessageGroup (
|
|
MSG_INCOMPATIBLE_HARDWARE_ROOT,
|
|
SubGroup,
|
|
ClassAndName
|
|
);
|
|
MYASSERT (Group);
|
|
|
|
FreePathString (ClassAndName);
|
|
|
|
Message = ParseMessageID (MSG_HARDWARE_MESSAGE, Array);
|
|
MYASSERT (Message);
|
|
|
|
MsgMgr_ObjectMsg_Add (e->FullKey, Group, Message);
|
|
|
|
LOG ((
|
|
LOG_INFORMATION,
|
|
"%s Device:\n"
|
|
" %s (%s)\n"
|
|
" %s\n"
|
|
" %s\n"
|
|
" %s\n",
|
|
SubGroupText,
|
|
HardwareID,
|
|
CompatibleID,
|
|
ModifiedDescription ? ModifiedDescription : DeviceDesc,
|
|
Class,
|
|
Mfg
|
|
));
|
|
|
|
b = TRUE;
|
|
|
|
}
|
|
__finally {
|
|
|
|
//
|
|
// Cleanup
|
|
//
|
|
|
|
FreeStringResource (ModifiedDescription);
|
|
FreeText (Group);
|
|
FreeStringResource (Message);
|
|
|
|
if (UnknownClass) {
|
|
FreeStringResource (Class);
|
|
}
|
|
}
|
|
|
|
return b;
|
|
}
|
|
|
|
|
|
LONG
|
|
HwComp_PrepareReport (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
HwComp_PrepareReport is called after the progress bar on the
|
|
Win9x side of the upgrade. It enumerates the hardware and adds
|
|
incompatibility messages for all incompatible hardware.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
A Win32 status code.
|
|
|
|
--*/
|
|
|
|
{
|
|
LONG rc;
|
|
HARDWARE_ENUM e;
|
|
HKEY Key;
|
|
HWTYPES msgType;
|
|
INFSTRUCT is = INITINFSTRUCT_GROWBUFFER;
|
|
PTSTR pnpIdList;
|
|
PTSTR p, q;
|
|
TCHAR ch;
|
|
|
|
Key = OpenRegKeyStr (TEXT("HKLM\\System\\CurrentControlSet\\Services\\Class"));
|
|
|
|
__try {
|
|
|
|
if (!CreateNtHardwareList (
|
|
SOURCEDIRECTORYARRAY(),
|
|
SOURCEDIRECTORYCOUNT(),
|
|
NULL,
|
|
REGULAR_OUTPUT
|
|
)) {
|
|
|
|
rc = GetLastError();
|
|
if (rc != ERROR_CANCELLED) {
|
|
LOG ((LOG_ERROR, "Could not create list of NT hardware."));
|
|
}
|
|
|
|
__leave;
|
|
}
|
|
|
|
if (EnumFirstHardware (
|
|
&e,
|
|
ENUM_INCOMPATIBLE_DEVICES,
|
|
ENUM_WANT_ONLINE_FLAG|ENUM_DONT_WANT_USER_SUPPLIED
|
|
)) {
|
|
|
|
do {
|
|
|
|
msgType = HW_INCOMPATIBLE;
|
|
|
|
if (e.HardwareID) {
|
|
pnpIdList = DuplicateText (e.HardwareID);
|
|
|
|
p = pnpIdList;
|
|
do {
|
|
q = _tcschr (p, TEXT(','));
|
|
if (!q) {
|
|
q = GetEndOfString (p);
|
|
}
|
|
|
|
ch = *q;
|
|
*q = 0;
|
|
|
|
if (InfFindFirstLine (g_Win95UpgInf, S_REINSTALL_PNP_IDS, p, &is)) {
|
|
msgType = HW_REINSTALL;
|
|
DEBUGMSG ((DBG_HWCOMP, "Found reinstall hardware ID %s", p));
|
|
break;
|
|
}
|
|
|
|
*q = ch;
|
|
p = q + 1;
|
|
} while (ch);
|
|
|
|
FreeText (pnpIdList);
|
|
}
|
|
|
|
if (msgType == HW_INCOMPATIBLE && e.CompatibleIDs) {
|
|
pnpIdList = DuplicateText (e.CompatibleIDs);
|
|
|
|
p = pnpIdList;
|
|
do {
|
|
q = _tcschr (p, TEXT(','));
|
|
if (!q) {
|
|
q = GetEndOfString (p);
|
|
}
|
|
|
|
ch = *q;
|
|
*q = 0;
|
|
|
|
if (InfFindFirstLine (g_Win95UpgInf, S_REINSTALL_PNP_IDS, p, &is)) {
|
|
msgType = HW_REINSTALL;
|
|
DEBUGMSG ((DBG_HWCOMP, "Found reinstall compatible ID %s", p));
|
|
break;
|
|
}
|
|
|
|
*q = ch;
|
|
p = q + 1;
|
|
} while (ch);
|
|
|
|
FreeText (pnpIdList);
|
|
}
|
|
|
|
if (pStuffDeviceInReport (&e, Key, msgType)) {
|
|
DEBUGMSG ((DBG_HWCOMP, "Found incompatible hardware %s", e.DeviceDesc));
|
|
}
|
|
|
|
} while (EnumNextHardware (&e));
|
|
}
|
|
|
|
if (EnumFirstHardware (
|
|
&e,
|
|
ENUM_UNSUPPORTED_DEVICES,
|
|
ENUM_WANT_ONLINE_FLAG|ENUM_DONT_WANT_USER_SUPPLIED
|
|
)) {
|
|
|
|
do {
|
|
|
|
if (pStuffDeviceInReport (&e, Key, HW_UNSUPPORTED)) {
|
|
DEBUGMSG ((DBG_HWCOMP, "Found incompatible hardware %s", e.DeviceDesc));
|
|
}
|
|
|
|
} while (EnumNextHardware (&e));
|
|
}
|
|
|
|
rc = ERROR_SUCCESS;
|
|
}
|
|
__finally {
|
|
if (Key) {
|
|
CloseRegKey (Key);
|
|
}
|
|
InfCleanUpInfStruct (&is);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
BUSTYPE
|
|
pGetBusType (
|
|
IN PHARDWARE_ENUM EnumPtr
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pGetBusType returns the type of bus that the current hardware
|
|
devices belongs to.
|
|
|
|
Arguments:
|
|
|
|
EnumPtr - Specifies hardware device to process as returned from
|
|
the hardware enum functions.
|
|
|
|
Return Value:
|
|
|
|
The enumerated bus type.
|
|
|
|
--*/
|
|
|
|
{
|
|
PCTSTR p, q;
|
|
TCHAR Bus[MAX_HARDWARE_STRING];
|
|
|
|
if (EnumPtr->BusType) {
|
|
StringCopy (Bus, EnumPtr->BusType);
|
|
} else {
|
|
|
|
p = EnumPtr->FullKey;
|
|
p = _tcschr (p, TEXT('\\'));
|
|
if (p) {
|
|
p = _tcschr (_tcsinc (p), TEXT('\\'));
|
|
}
|
|
if (!p) {
|
|
return BUSTYPE_UNKNOWN;
|
|
}
|
|
|
|
p++;
|
|
q = _tcschr (p, TEXT('\\'));
|
|
if (!q) {
|
|
q = GetEndOfString (p);
|
|
}
|
|
|
|
StringCopyAB (Bus, p, q);
|
|
}
|
|
|
|
if (StringIMatch (Bus, S_ISA)) {
|
|
return BUSTYPE_ISA;
|
|
}
|
|
if (StringIMatch (Bus, S_EISA)) {
|
|
return BUSTYPE_EISA;
|
|
}
|
|
if (StringIMatch (Bus, S_MCA)) {
|
|
return BUSTYPE_MCA;
|
|
}
|
|
if (StringIMatch (Bus, S_PCI)) {
|
|
return BUSTYPE_PCI;
|
|
}
|
|
if (StringIMatch (Bus, S_PNPISA)) {
|
|
return BUSTYPE_PNPISA;
|
|
}
|
|
if (StringIMatch (Bus, S_PCMCIA)) {
|
|
return BUSTYPE_PCMCIA;
|
|
}
|
|
if (StringIMatch (Bus, S_ROOT)) {
|
|
return BUSTYPE_ROOT;
|
|
}
|
|
if (ISPC98()) {
|
|
if (StringIMatch (Bus, S_C98PNP)) {
|
|
return BUSTYPE_PNPISA;
|
|
}
|
|
}
|
|
|
|
return BUSTYPE_UNKNOWN;
|
|
}
|
|
|
|
|
|
VOID
|
|
pGetIoAddrs (
|
|
IN PHARDWARE_ENUM EnumPtr,
|
|
OUT PTSTR Buf
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pGetIoAddrs returns a list of comma-separated IO address ranges.
|
|
For example:
|
|
|
|
0x310-0x31F,0x388-0x38F
|
|
|
|
Arguments:
|
|
|
|
EnumPtr - Specifies device to process
|
|
|
|
Buf - Receives zero or more comma-separated address ranges.
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
|
|
{
|
|
DEVNODERESOURCE_ENUM e;
|
|
PIO_RESOURCE_9X IoRes;
|
|
PTSTR p;
|
|
|
|
*Buf = 0;
|
|
p = Buf;
|
|
|
|
if (EnumFirstDevNodeResource (&e, EnumPtr->FullKey)) {
|
|
do {
|
|
|
|
if (e.Type == ResType_IO) {
|
|
if (p > Buf) {
|
|
p = _tcsappend (p, TEXT(","));
|
|
}
|
|
|
|
IoRes = (PIO_RESOURCE_9X) e.ResourceData;
|
|
wsprintf (
|
|
p,
|
|
TEXT("0x%X-0x%X"),
|
|
IoRes->IO_Header.IOD_Alloc_Base,
|
|
IoRes->IO_Header.IOD_Alloc_End
|
|
);
|
|
|
|
p = GetEndOfString (p);
|
|
}
|
|
} while (EnumNextDevNodeResource (&e));
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
pGetIrqs (
|
|
IN PHARDWARE_ENUM EnumPtr,
|
|
OUT PTSTR Buf
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pGetIrqs returns a list of comma-separated IRQs used by the device.
|
|
For example:
|
|
|
|
0x07,0x0F
|
|
|
|
Arguments:
|
|
|
|
EnumPtr - Specifies the device to process
|
|
|
|
Buf - Receives zero or more comma-separated IRQs
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
|
|
{
|
|
DEVNODERESOURCE_ENUM e;
|
|
PIRQ_RESOURCE_9X IrqRes;
|
|
PTSTR p;
|
|
|
|
*Buf = 0;
|
|
p = Buf;
|
|
|
|
if (EnumFirstDevNodeResource (&e, EnumPtr->FullKey)) {
|
|
do {
|
|
|
|
if (e.Type == ResType_IRQ) {
|
|
if (p > Buf) {
|
|
p = _tcsappend (p, TEXT(","));
|
|
}
|
|
|
|
IrqRes = (PIRQ_RESOURCE_9X) e.ResourceData;
|
|
wsprintf (
|
|
p,
|
|
TEXT("0x%02X"),
|
|
IrqRes->AllocNum
|
|
);
|
|
|
|
p = GetEndOfString (p);
|
|
}
|
|
} while (EnumNextDevNodeResource (&e));
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
pGetDma (
|
|
IN PHARDWARE_ENUM EnumPtr,
|
|
OUT PTSTR Buf
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pGetDma returns a list of comma-separated DMA channels used
|
|
by the device. For example:
|
|
|
|
1,4
|
|
|
|
An empty string means "auto"
|
|
|
|
Arguments:
|
|
|
|
EnumPtr - Specifies hardware device to process
|
|
|
|
Buf - Receives zero or more comma-separated DMA channel numbers
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
|
|
{
|
|
DEVNODERESOURCE_ENUM e;
|
|
PDMA_RESOURCE_9X DmaRes;
|
|
DWORD Bit, Channel;
|
|
PTSTR p;
|
|
|
|
*Buf = 0;
|
|
p = Buf;
|
|
|
|
if (EnumFirstDevNodeResource (&e, EnumPtr->FullKey)) {
|
|
do {
|
|
|
|
if (e.Type == ResType_DMA) {
|
|
if (p > Buf) {
|
|
p = _tcsappend (p, TEXT(","));
|
|
}
|
|
|
|
DmaRes = (PDMA_RESOURCE_9X) e.ResourceData;
|
|
Channel = 0;
|
|
|
|
for (Bit = 1 ; Bit ; Bit <<= 1) {
|
|
|
|
if (DmaRes->DMA_Bits & Bit) {
|
|
wsprintf (p, TEXT("%u"), Channel);
|
|
p = GetEndOfString (p);
|
|
}
|
|
|
|
Channel++;
|
|
}
|
|
|
|
p = GetEndOfString (p);
|
|
}
|
|
} while (EnumNextDevNodeResource (&e));
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
pGetMemRanges (
|
|
IN PHARDWARE_ENUM EnumPtr,
|
|
OUT PTSTR Buf
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pGetMemRanges returns a list of comma-separated memory addresses
|
|
used by the device. For example:
|
|
|
|
0x0000D800-0x0000D9FF,0x0000F000-0x0000FFFF
|
|
|
|
Arguments:
|
|
|
|
EnumPtr - Specifies hardware to process
|
|
|
|
Buf - Receives zero or more comma-separated address ranges
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
|
|
{
|
|
DEVNODERESOURCE_ENUM e;
|
|
PMEM_RESOURCE_9X MemRes;
|
|
PTSTR p;
|
|
|
|
*Buf = 0;
|
|
p = Buf;
|
|
|
|
if (EnumFirstDevNodeResource (&e, EnumPtr->FullKey)) {
|
|
do {
|
|
|
|
if (e.Type == ResType_Mem) {
|
|
if (p > Buf) {
|
|
p = _tcsappend (p, TEXT(","));
|
|
}
|
|
|
|
MemRes = (PMEM_RESOURCE_9X) e.ResourceData;
|
|
|
|
wsprintf (
|
|
p,
|
|
TEXT("0x%08X-0x%08X"),
|
|
MemRes->MEM_Header.MD_Alloc_Base,
|
|
MemRes->MEM_Header.MD_Alloc_End
|
|
);
|
|
|
|
p = GetEndOfString (p);
|
|
}
|
|
} while (EnumNextDevNodeResource (&e));
|
|
}
|
|
}
|
|
|
|
|
|
TRANSCIEVERTYPE
|
|
pGetTranscieverType (
|
|
IN PHARDWARE_ENUM EnumPtr
|
|
)
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pGetTranscieverType returns the transciever type for the specified
|
|
device (i.e. net card).
|
|
|
|
Arguments:
|
|
|
|
EnumPtr - Specifies hardware device to process
|
|
|
|
Return Value:
|
|
|
|
The devices's transciever type
|
|
|
|
--*/
|
|
|
|
|
|
{
|
|
return TRANSCIEVERTYPE_AUTO;
|
|
}
|
|
|
|
|
|
IOCHANNELREADY
|
|
pGetIoChannelReady (
|
|
IN PHARDWARE_ENUM EnumPtr
|
|
)
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pGetIoChannelReady returns the setting of the IoChannelReady
|
|
mode for a device.
|
|
|
|
Arguments:
|
|
|
|
EnumPtr - Specifies hardware device to process
|
|
|
|
Return Value:
|
|
|
|
The device's IO Channel Ready mode
|
|
|
|
--*/
|
|
|
|
|
|
{
|
|
return IOCHANNELREADY_AUTODETECT;
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
EnumFirstNetCard/EnumNextNetCard enumerate all installed network adapters
|
|
on a machine.
|
|
|
|
Arguments:
|
|
|
|
EnumPtr - An uninitiailzed structure that receives the state of enumeration.
|
|
|
|
Return Value:
|
|
|
|
TRUE if a net card was enumerated, or FALSE if no more net cards exist.
|
|
|
|
--*/
|
|
|
|
BOOL
|
|
EnumFirstNetCard (
|
|
OUT PNETCARD_ENUM EnumPtr
|
|
)
|
|
{
|
|
START_NET_ENUM;
|
|
|
|
ZeroMemory (EnumPtr, sizeof (NETCARD_ENUM));
|
|
EnumPtr->State = STATE_ENUM_FIRST_HARDWARE;
|
|
|
|
return EnumNextNetCard (EnumPtr);
|
|
}
|
|
|
|
BOOL
|
|
EnumNextNetCard (
|
|
IN OUT PNETCARD_ENUM EnumPtr
|
|
)
|
|
{
|
|
PHARDWARE_ENUM ep;
|
|
|
|
for (;;) {
|
|
|
|
switch (EnumPtr->State) {
|
|
|
|
case STATE_ENUM_FIRST_HARDWARE:
|
|
if (!EnumFirstHardware (&EnumPtr->HardwareEnum, ENUM_ALL_DEVICES,ENUM_DONT_REQUIRE_HARDWAREID)) {
|
|
END_NET_ENUM;
|
|
return FALSE;
|
|
}
|
|
|
|
EnumPtr->State = STATE_EVALUATE_HARDWARE;
|
|
break;
|
|
|
|
case STATE_ENUM_NEXT_HARDWARE:
|
|
if (!EnumNextHardware (&EnumPtr->HardwareEnum)) {
|
|
END_NET_ENUM;
|
|
return FALSE;
|
|
}
|
|
|
|
EnumPtr->State = STATE_EVALUATE_HARDWARE;
|
|
break;
|
|
|
|
case STATE_EVALUATE_HARDWARE:
|
|
ep = &EnumPtr->HardwareEnum;
|
|
|
|
EnumPtr->State = STATE_ENUM_NEXT_HARDWARE;
|
|
|
|
if (!StringIMatch (ep->Class, TEXT("Net"))) {
|
|
break;
|
|
}
|
|
|
|
if (ep -> HardwareID) {
|
|
_tcssafecpy (EnumPtr->HardwareId, ep->HardwareID, MAX_HARDWARE_STRING);
|
|
}
|
|
|
|
if (ep -> CompatibleIDs) {
|
|
_tcssafecpy (EnumPtr -> CompatibleIDs, ep -> CompatibleIDs, MAX_HARDWARE_STRING);
|
|
}
|
|
|
|
if (ep->DeviceDesc) {
|
|
_tcssafecpy (EnumPtr->Description, ep->DeviceDesc, MAX_HARDWARE_STRING);
|
|
} else {
|
|
EnumPtr->Description[0] = 0;
|
|
}
|
|
_tcssafecpy (EnumPtr->CurrentKey, ep->ek.FullKeyName, MAX_HARDWARE_STRING);
|
|
|
|
EnumPtr->BusType = pGetBusType (ep);
|
|
EnumPtr->TranscieverType = pGetTranscieverType (ep);
|
|
EnumPtr->IoChannelReady = pGetIoChannelReady (ep);
|
|
|
|
pGetIoAddrs (ep, EnumPtr->IoAddrs);
|
|
pGetIrqs (ep, EnumPtr->Irqs);
|
|
pGetDma (ep, EnumPtr->Dma);
|
|
pGetMemRanges (ep, EnumPtr->MemRanges);
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
VOID
|
|
EnumNetCardAbort (
|
|
IN PNETCARD_ENUM EnumPtr
|
|
)
|
|
{
|
|
PushError();
|
|
END_NET_ENUM;
|
|
AbortHardwareEnum (&EnumPtr->HardwareEnum);
|
|
PopError();
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
EncodePnpId (
|
|
IN OUT LPSTR Id
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is used for the pnprept tool to encode a PNP ID so it does not
|
|
have any backslashes.
|
|
|
|
Arguments:
|
|
|
|
Id - Specifies ID that may have backslashes. The buffer must be big enough
|
|
to hold strlen(Id) * 2 characters.
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
|
|
{
|
|
CHAR TempBuf[MAX_MBCHAR_PATH];
|
|
LPSTR s, d;
|
|
|
|
s = Id;
|
|
d = TempBuf;
|
|
while (*s) {
|
|
if (*s == '\\') {
|
|
*d = '~';
|
|
d++;
|
|
*d = '1';
|
|
} else if (*s == '*') {
|
|
*d = '~';
|
|
d++;
|
|
*d = '2';
|
|
} else if (*s == '~') {
|
|
*d = '~';
|
|
d++;
|
|
*d = '~';
|
|
}
|
|
else {
|
|
*d = *s;
|
|
}
|
|
|
|
d++;
|
|
s++;
|
|
}
|
|
|
|
*d = 0;
|
|
lstrcpy (Id, TempBuf);
|
|
}
|
|
|
|
VOID
|
|
DecodePnpId (
|
|
IN OUT LPSTR Id
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is used by pnprept to decode a PNP ID that was encoded by
|
|
EncodePnpId.
|
|
|
|
Arguments:
|
|
|
|
Id - Specifies the encoded string to process. The string is truncated if
|
|
any encoded characters are found.
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
|
|
{
|
|
LPSTR s, d;
|
|
|
|
s = Id;
|
|
d = Id;
|
|
while (*s) {
|
|
if (*s == '~') {
|
|
s++;
|
|
if (*s == '1') {
|
|
*d = '\\';
|
|
} else if (*s == '2') {
|
|
*d = '*';
|
|
} else {
|
|
*d = *s;
|
|
}
|
|
} else if (d < s) {
|
|
*d = *s;
|
|
}
|
|
|
|
d++;
|
|
s++;
|
|
}
|
|
|
|
*d = 0;
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
pFindFileInAnySourceDir (
|
|
IN PCTSTR File,
|
|
OUT PTSTR MatchPath
|
|
)
|
|
{
|
|
UINT Count;
|
|
UINT i;
|
|
PCTSTR Path;
|
|
|
|
Count = SOURCEDIRECTORYCOUNT();
|
|
|
|
for (i = 0 ; i < Count ; i++) {
|
|
Path = JoinPaths (SOURCEDIRECTORY(i), File);
|
|
|
|
__try {
|
|
if (DoesFileExist (Path)) {
|
|
_tcssafecpy (MatchPath, Path, MAX_TCHAR_PATH);
|
|
return TRUE;
|
|
}
|
|
}
|
|
__finally {
|
|
FreePathString (Path);
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
GetLegacyKeyboardId (
|
|
OUT PTSTR Buffer,
|
|
IN UINT BufferSize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
GetLegacyKeyboardId looks in NT's keyboard.inf for a legacy mapping.
|
|
If one is found, the legacy ID is returned to the caller so they
|
|
can write it to the answer file.
|
|
|
|
Arguments:
|
|
|
|
Buffer - Receives the legacy text corresponding to the installed
|
|
keyboard device.
|
|
|
|
BufferSize - Specifies the size of Buffer in bytes
|
|
|
|
Return Value:
|
|
|
|
TRUE if the legacy ID was found and copied, or FALSE if an error
|
|
occurred. If FALSE is returned, the caller should add an incompatibility
|
|
message and let NT decide which keyboard to use.
|
|
|
|
--*/
|
|
|
|
{
|
|
HINF Inf;
|
|
TCHAR KeyboardInfPath[MAX_TCHAR_PATH];
|
|
TCHAR TempDir[MAX_TCHAR_PATH];
|
|
PCTSTR TempKeyboardInfPath;
|
|
INFSTRUCT is = INITINFSTRUCT_POOLHANDLE;
|
|
PCTSTR PnpId, LegacyId;
|
|
LONG rc;
|
|
UINT keyboardType;
|
|
UINT keyboardSubtype;
|
|
PTSTR id = NULL;
|
|
HARDWARE_ENUM e;
|
|
TCHAR curId[MAX_PNP_ID];
|
|
PCTSTR devIds = NULL;
|
|
|
|
|
|
keyboardType = GetKeyboardType (0);
|
|
keyboardSubtype = GetKeyboardType (1);
|
|
|
|
//
|
|
// keyboard type : 7 - Japanese keyboard
|
|
//
|
|
if (keyboardType == 7) {
|
|
|
|
//
|
|
// sub type : 2 - 106 keyboard
|
|
//
|
|
if (keyboardSubtype == 2) {
|
|
|
|
id = TEXT("PCAT_106KEY");
|
|
|
|
//
|
|
// Ok, we have a japanese keyboard. Still, it may be USB which
|
|
// requires a different entry. Lets check.
|
|
//
|
|
if (EnumFirstHardware (&e, ENUM_COMPATIBLE_DEVICES, ENUM_WANT_DEV_FIELDS)) {
|
|
|
|
|
|
|
|
do {
|
|
devIds = e.HardwareID;
|
|
|
|
while (*devIds) {
|
|
|
|
devIds = ExtractPnpId (devIds, curId);
|
|
if (*curId == 0) {
|
|
continue;
|
|
}
|
|
|
|
|
|
if (InfFindFirstLine (g_Win95UpgInf, S_JPN_USB_KEYBOARDS, curId, &is)) {
|
|
|
|
id = TEXT("kbdhid");
|
|
AbortHardwareEnum (&e);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
} while (EnumNextHardware (&e) && !StringIMatch (id, TEXT("kbdhid")));
|
|
}
|
|
|
|
|
|
|
|
_tcssafecpy (Buffer, id, BufferSize / sizeof (TCHAR));
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// keyboard type : 8 - Korean keyboard
|
|
//
|
|
if (keyboardType == 8) {
|
|
|
|
switch (keyboardSubtype) {
|
|
|
|
case 3:
|
|
|
|
//
|
|
// 101a keyboard
|
|
//
|
|
id = TEXT("STANDARD");
|
|
break;
|
|
case 4:
|
|
|
|
//
|
|
// 101b keyboard
|
|
//
|
|
id = TEXT("101B TYPE");
|
|
break;
|
|
case 5:
|
|
|
|
//
|
|
// 101c keyboard
|
|
//
|
|
id = TEXT("101C TYPE");
|
|
break;
|
|
case 6:
|
|
|
|
//
|
|
// 103 keyboard
|
|
//
|
|
id = TEXT("103 TYPE");
|
|
break;
|
|
}
|
|
|
|
if (id) {
|
|
|
|
_tcssafecpy (Buffer, id, BufferSize);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Move keyboard.in_ (or keyboard.inf) from NT sources into
|
|
// temp dir.
|
|
//
|
|
|
|
if (!pFindFileInAnySourceDir (S_KEYBOARD_IN_, KeyboardInfPath)) {
|
|
if (!pFindFileInAnySourceDir (S_KEYBOARD_INF, KeyboardInfPath)) {
|
|
LOG ((LOG_ERROR,"GetLegacyKeyboardId: keyboard.inf not found in sources"));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
GetTempPath (sizeof (TempDir), TempDir);
|
|
TempKeyboardInfPath = JoinPaths (TempDir, S_KEYBOARD_INF);
|
|
|
|
__try {
|
|
rc = SetupDecompressOrCopyFile (
|
|
KeyboardInfPath,
|
|
TempKeyboardInfPath,
|
|
0
|
|
);
|
|
|
|
if (rc != ERROR_SUCCESS) {
|
|
LOG ((LOG_ERROR,"GetLegacyKeyboardId: keyboard.inf could not be copied to temp dir"));
|
|
return FALSE;
|
|
}
|
|
|
|
__try {
|
|
Inf = InfOpenInfFile (TempKeyboardInfPath);
|
|
|
|
if (Inf == INVALID_HANDLE_VALUE) {
|
|
LOG ((LOG_ERROR,"GetLegacyKeyboardId: %s could not be opened",TempKeyboardInfPath));
|
|
return FALSE;
|
|
}
|
|
|
|
__try {
|
|
//
|
|
// We now have keyboard.inf open. Let's enumerate each PNP
|
|
// ID in the [LegacyXlate.DevId] section and check if the
|
|
// hardware is available.
|
|
//
|
|
|
|
if (InfFindFirstLine (Inf, S_WIN9XUPGRADE, NULL, &is)) {
|
|
do {
|
|
PnpId = InfGetStringField (&is, 0);
|
|
if (PnpId) {
|
|
//
|
|
// Is PnpId online?
|
|
//
|
|
|
|
DEBUGMSG ((DBG_HWCOMP, "Searching for keyboard ID %s", PnpId));
|
|
|
|
if (IsPnpIdOnline (PnpId, S_KEYBOARD_CLASS)) {
|
|
//
|
|
// Yes - obtain PNP, copy to caller's buffer
|
|
// and get out
|
|
//
|
|
|
|
LegacyId = InfGetStringField (&is, 1);
|
|
if (LegacyId) {
|
|
DEBUGMSG ((DBG_HWCOMP, "Found match: %s = %s", LegacyId, PnpId));
|
|
_tcssafecpy (Buffer, LegacyId, BufferSize / sizeof (TCHAR));
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
InfResetInfStruct (&is);
|
|
|
|
} while (InfFindNextLine (&is));
|
|
}
|
|
|
|
}
|
|
__finally {
|
|
InfCleanUpInfStruct (&is);
|
|
InfCloseInfFile (Inf);
|
|
}
|
|
|
|
}
|
|
__finally {
|
|
DeleteFile (TempKeyboardInfPath);
|
|
}
|
|
|
|
}
|
|
__finally {
|
|
FreePathString (TempKeyboardInfPath);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
IsComputerOffline (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
IsComputerOffline checks if a network card exists on the machine and is
|
|
currently present.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
TRUE if the machine is known to be offline, or FALSE if the online state is
|
|
not known.
|
|
|
|
--*/
|
|
|
|
|
|
{
|
|
HARDWARE_ENUM e;
|
|
BOOL possiblyOnline = FALSE;
|
|
|
|
if (EnumFirstHardware (&e, ENUM_ALL_DEVICES, ENUM_WANT_ONLINE_FLAG|ENUM_WANT_COMPATIBLE_FLAG)) {
|
|
do {
|
|
//
|
|
// Enumerate all PNP devices of class Net
|
|
// or Modem
|
|
//
|
|
|
|
if (e.Class) {
|
|
if (StringIMatch (e.Class, TEXT("net")) ||
|
|
StringIMatch (e.Class, TEXT("modem"))
|
|
) {
|
|
//
|
|
// Determine if this is not a RAS adapter
|
|
//
|
|
|
|
if (e.CompatibleIDs && !_tcsistr (e.CompatibleIDs, TEXT("*PNP8387"))) {
|
|
possiblyOnline = TRUE;
|
|
}
|
|
|
|
if (e.HardwareID && !_tcsistr (e.HardwareID, TEXT("*PNP8387"))) {
|
|
possiblyOnline = TRUE;
|
|
}
|
|
|
|
//
|
|
// If this is not a RAS adapter, we assume it may be a LAN
|
|
// adapter. If the LAN adapter is present, then we assume
|
|
// that it might be online.
|
|
//
|
|
|
|
if (possiblyOnline) {
|
|
possiblyOnline = e.Online;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Other tests for online go here
|
|
//
|
|
|
|
if (possiblyOnline) {
|
|
AbortHardwareEnum (&e);
|
|
return FALSE;
|
|
}
|
|
|
|
} while (EnumNextHardware (&e));
|
|
}
|
|
|
|
//
|
|
// We have one of the following cases:
|
|
//
|
|
// - No device with the class of "Net"
|
|
// - Only the RAS adapter is installed
|
|
// - All "Net" class devices are not present
|
|
//
|
|
// That makes us say "this computer is offline"
|
|
//
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
HwComp_AnyNeededDrivers (
|
|
VOID
|
|
)
|
|
{
|
|
return !HtIsEmpty (g_NeededHardwareIds);
|
|
}
|
|
|
|
|
|
BOOL
|
|
AppendDynamicSuppliedDrivers (
|
|
IN PCTSTR DriversPath
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
AppendDynamicSuppliedDrivers scans a path and its subdirs for new drivers and places
|
|
all hardware device IDs in the global PNP string tables.
|
|
|
|
Arguments:
|
|
|
|
DriversPath - The root path to new drivers
|
|
|
|
Return Value:
|
|
|
|
TRUE if the function completes successfully, or FALSE if it fails for at least one driver.
|
|
|
|
--*/
|
|
|
|
{
|
|
TREE_ENUM te;
|
|
DWORD HwCompDatId;
|
|
BOOL b = TRUE;
|
|
|
|
if (EnumFirstFileInTree (&te, DriversPath, TEXT("hwcomp.dat"), TRUE)) {
|
|
do {
|
|
if (te.Directory) {
|
|
continue;
|
|
}
|
|
//
|
|
// Open the hardware compatibility database
|
|
//
|
|
|
|
HwCompDatId = OpenAndLoadHwCompDatEx (
|
|
te.FullPath,
|
|
(PVOID)g_PnpIdTable,
|
|
(PVOID)g_UnsupPnpIdTable,
|
|
(PVOID)g_InfFileTable
|
|
);
|
|
if (HwCompDatId) {
|
|
SetWorkingTables (HwCompDatId, NULL, NULL, NULL);
|
|
CloseHwCompDat (HwCompDatId);
|
|
} else {
|
|
LOG ((
|
|
LOG_WARNING,
|
|
"AppendDynamicSuppliedDrivers: OpenAndLoadHwCompDat failed for %s (rc=0x%x)",
|
|
te.FullPath,
|
|
GetLastError ()
|
|
));
|
|
//
|
|
// report failure, but keep going
|
|
//
|
|
b = FALSE;
|
|
}
|
|
} while (EnumNextFileInTree (&te));
|
|
}
|
|
|
|
return b;
|
|
}
|
|
|
|
|