Symptoms:
There is a command "Share Folder..." on VirtualPC. It creates a network drive on a virtualized Windows, the default drive letter is Z:. This network drive links to a folder on the host machine.
On virtualized Windows, after displaying contents of such a shared folder or after copying files from/to that folder:
- Total Commander crashes with message like "Fatal error in copy thread, aborting!" or "Access violation at address 7C910A19. Read of address 00000000.",
- Total Commander disappears rapidly without any error message.
This problem doesn't occur on Windows 9x family: 95, 98 and ME.
Cause and solution:
The cause of this problem is a buggy driver mrxvpc.sys (Virtual Machine Folder Sharing Driver) on virtualized Windows. It is a part of Virtual Machine Additions (menu "Action" -> "Install or Update Virtual Machine Additions" on VirtualPC):
- mrxvpc.sys (ver. 13.306.0.0) that comes with VirtualPC 2004 SP1 is buggy,
- mrxvpc.sys (ver. 13.803.0.0) that comes with VirtualPC 2007 is buggy,
- mrxvpc.sys (ver. 13.820.0.0) that comes with VirtualPC 2007 SP1 is OK.
Default location on virtualized Windows: C:\Windows\System32\Drivers\mrxvpc.sys
If you don't use Virtual Machine Additions, you are safe. But if you use:
1) Migrate from VirtualPC 2004, 2004 SP1 or 2007 to VirtualPC 2007 SP1.
2) After migrating, upgrade Virtual Machine Additions on all your Windows virtual machines (not required for Windows 95, 98 and ME virtual machines).
3) If you migrated to VirtualPC 2007 SP1 some time ago, you might not upgraded Virtual Machine Additions on all your Windows virtual machines after the migration. So do it now.
4) If you for any reason can't upgrade to VirtualPC 2007 SP1, find version 13.820.0.0 of mrxvpc.sys (on some other virtualized Windows machine) and copy it to your own virtualized Windows machines. And reboot them.
Long story:
On my virtualized Windows, during copying files from a folder shared by VirtualPC, Total Commander sometimes disappeared or crashed with errors like this:
Code: Select all
---------------------------
Total Commander 8.01
---------------------------
Fatal error in copy thread, aborting!
Access violation at address 7C911689. Read of address 00000018
Windows XP SP3 5.1 (Build 2600)
Please report this error to the Author, with a description
of what you were doing when this error occurred!
Windows exception: C0000005
Stack trace:
7C911689
421CDD 421D43 6753E0 67B540 67CB9C 67E12D
>417CB8 40362C
Raw:
421CDD 6D6BFB 5DCF24 41E21B 41E224 4020A2
4020A2 402226 402249 6D6B95 6D69F0 416DB0
416D7C 6820BE 68213B 421D43 6753E0 669CF7
6D6BFB 6D6CB7 67B540 67CB9C 6D6E13 6CFB90
6D6FA7 6CFCCD 67E12D 4023C7 4023EF 417CB8
40362C
Press Ctrl+C to copy this report!
---------------------------
OK
---------------------------
Code: Select all
---------------------------
Total Commander 8.01
---------------------------
Access violation at address 7C910A19. Read of address 00000000.
Access violation at address 7C910A19. Read of address 00000000
Windows XP SP3 5.1 (Build 2600)
Please report this error to the Author, with a description
of what you were doing when this error occurred!
Windows exception: C0000005
Stack trace:
7C910A19
4460A5 446A22 >423F38 445FE3 423F38 402D65
667AF5 444AD1 446A22 423F38 445FE3 423F38
429556 42969C 6D9F34
Raw:
445F51 423F38 446D9B 447A70 444AD1 446CF1
446CF1 4349F5 4460A5 446A22 423F38 445FE3
423F38 44670D 42673A 425234 402D65 668B34
667AF5 4460A5 446A22 446A45 423F38 445F51
423F38 402E4A 444AD1 446CF1 447AA1 425C14
4460A5 446A22 423F38 445FE3 423F38 429556
42969C 429856 6D9F34
Press Ctrl+C to copy this report!
Continue execution?
---------------------------
Tak Nie
---------------------------
1) I launched the IDA disassembler/debugger, launched Total Commander under IDA and checked addresses from the crash reports: 7C911689 and 7C910A19. Both of them are inside ntdll.dll, inside the same function.
2) I downloaded symbols for the debugger (a set of *.pdb files with an installer) from Microsoft. After loading ntdll.pdb to IDA I realized, that both 7C910A19 and 7C911689 addresses are inside ntdll.dll!RtlpCoalesceFreeBlocks function (used - among others - by ntdll.dll!RtlFreeHeap). I found the following information about RtlpCoalesceFreeBlocks in a great "Heap Corruption: A Case Study" article:
"is called when a block of heap memory is freed and the heap manager detects that there are adjacent blocks that are also free. In this case, the 2 or 3 adjacent blocks are merged into one, larger free block, so as to reduce heap fragmentation. The access violation occurring in ntdll.dll!RtlpCoalesceFreeBlocks() therefore indicates that, while manipulating the heap data structures to merge bocks, we ran into a bad address. This in turn is an indication that some of those data structures became corrupted some time earlier."
So... we have a problem with a heap corruption. How can we generate such a problem? If we get 128 bytes of heap (using kernel32.dll!LocalAlloc API) and write more than 128 bytes there, we will overwrite the next block of heap memory (with its header). This causes a heap corruption.
3) Under the debugger we can observe debugging messages (sent by EXE/DLLs that use kernel32.dll!OutputDebugString API). Luckily, ntdll.dll - which manages the heap - sends debugging messages in case of heap corruption. So I configured IDA to stop on debugger messages, launched Total Commander under IDA and I started to reproduce the crash (by entering into a problematic shared folder). IDA stopped with the following debugging message:
Code: Select all
Debugged application message: HEAP[TOTALCMD.EXE]: .
Debugged application message: Heap block at 00120E98 modified at 00121EA0 past requested size of 1000
4) As Microsoft states, "If [...] the corruption happens in the small fill pattern at end of buffer, for alignment reasons the corruption will be detected only when the block is freed."
So: we detected a heap corruption during freeing a block of heap memory - inside FindClose API call. FindClose finalizes folder reading, so this block of heap memory should have been allocated earlier, inside FindFirstFileW or FindNextFileW API call (Total Commander reads folder contents by calling FindFirstFileW, then FindNextFileW in a loop, and finalizes searching with FindClose). Where exactly was this block allocated? I put a breakpoint at the beginning of kernel32.dll!FindFirstFileW and also at the beginning of kernel32.dll!FindNextFileW and I went into the shared folder in Total Commander again. Debugger stopped at the beginning of FindFirstFileW call - so I put an additional breakpoint inside ntdll.dll!RtlAllocateHeap. Bingo! During first call of FindNextFileW, it allocates 0x1000 bytes of heap (as in our debugging message: "...past requested size of 1000"). So I executed RtlAllocateHeap and I received a pointer to the newly allocated block of heap memory, 0x001013E0:
There is an 8-byte header before this block (see picture in "Heap Corruption: A Case Study"):
Code: Select all
001013D8 03 02 58 00 8F 07 18 00 ..X.Ć...
= 8-byte header + 0x1000 requested bytes + 16-byte footer
And our 0x1000 of bytes:
Code: Select all
001013E0 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA .ş¦.ş¦.ş¦.ş¦
001013F0 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA .ş¦.ş¦.ş¦.ş¦
00101400 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA .ş¦.ş¦.ş¦.ş¦
00101410 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA .ş¦.ş¦.ş¦.ş¦
...
001023A0 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA .ş¦.ş¦.ş¦.ş¦
001023B0 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA .ş¦.ş¦.ş¦.ş¦
001023C0 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA .ş¦.ş¦.ş¦.ş¦
001023D0 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA .ş¦.ş¦.ş¦.ş¦
And there is also a 16-byte footer at the end of our block of memory:
Code: Select all
001023E0 AB AB AB AB AB AB AB AB 00 00 00 00 00 00 00 00 źźźźźźźź........
5) Hardware breakpoints don't work well under virtualized Windows, so I couldn't use them to detect writing to our 0x1000-byte block of heap memory. So I traced program manually and I noticed that the block is being used in the call to ntdll.dll!NtQueryDirectoryFile. This function reads a portion of a folder contents and fills our 0x1000-byte buffer with FILE_FULL_DIR_INFORMATION records - one record for each file in the folder. Records have variable length (which depends on the length of a file name). FindNextFileW calls NtQueryDirectoryFile internally to receive file names existing inside the folder. When consecutive calls to FindNextFileW will return all names from our 0x1000-byte buffer, FindNextFileW calls NtQueryDirectoryFile again to receive the next portion of folder contents.
After few calls to NtQueryDirectoryFile I noticed that the footer of our 0x1000-byte block of memory has been overwritten with four zeroes:
Code: Select all
001023E0 00 00 00 00 AB AB AB AB 00 00 00 00 00 00 00 00 ....źźźź........
And, after calling FindClose, the debugger returned a message again:
Code: Select all
Debugged application message: HEAP[TOTALCMD.EXE]: .
Debugged application message: Heap block at 001013D8 modified at 001023E0 past requested size of 1000
So the cause of the problem is inside the NtQueryDirectoryFile call. NtQueryDirectoryFile simply makes a kernel mode call, so I couldn't trace this call using IDA. This kernel mode call calls a filesystem driver.
6) I was able to reproduce this bug only when listing contents of a folder that has been shared with the host machine by VirtualPC. I copied this folder to the local disk of virtualized Windows and I was unable to reproduce this bug. When I mapped original folder as a network drive manually (I shared this folder on the host machine and I mapped it as a network drive on the virtualized Windows), I was also unable to reproduce the bug. So the conclusion is: the bug is related only to command "Share Folder..." on VirtualPC.
I noticed accidentally, that there are differences in drive properties for a network drive mapped manually and a network drive created by VirtualPC. When I right-click a manually mapped network drive in "My Computer" and choose Properties command, I can see: "Filesystem: NTFS" (NTFS is a filesystem on a host computer). When I check properties of a network drive created by VirtualPC, there is: "Filesystem: Host Filesystem".
So I searched all files inside virtualized Windows folder for string "Host Filesystem" (in unicode) and I found a driver mrxvpc.sys. After replacing it with a version from VirtualPC 2007 SP1 the problem disappeared, what I verified using IDA: NtQueryDirectoryFile returns now identical results, but without these four zeroes at the end of the FILE_FULL_DIR_INFORMATION record chain.
And this is the end of the story. Please note that the buggy driver is called not only by NtQueryDirectoryFile API, but also by other APIs. This may overwrite some memory areas other than a footer of a heap block - for example some local stack variables. So using buggy mrxvpc.sys driver may lead to unpredictable results, including data losses. It's worth to update it.
Checking for existence of buggy driver:
Total Commander x32 could warn the user if he uses a buggy version of the driver. This could prevent the user from blaming Total Commander for crashes and prevent him from data losses. This can be done on Total Commander start or only on accessing a drive shared by VirtualPC. Total Commander x64 will never suffer from a buggy driver - it can run only on 64-bit systems, and VirtualPC doesn't currently support 64-bit guest systems. The code below works from Delphi 2 to Delphi XE2, on all versions of Windows (starting from Windows 95).
Function IsBuggyVirtualPCDriverLoaded checks if the mrxvpc.sys driver is loaded. If so, it checks its version resources.
Function IsDriveSharedOnNTByVirtualPC queries existing devices. When drive Z: is being shared, there is a device "\Device\MicrosoftVMFolderSharing\;Z:\Z\Z". We can't check its existence by opening it using CreateFile API, because it may be inaccessible if drive was mounted, but hasn't been accessed yet. So we query devices using NtOpenSymbolicLinkObject and NtQuerySymbolicLinkObject APIs. Existing devices can be viewed using WinObj tool.
Checking for existence of buggy driver:
Code: Select all
uses
Windows, SysUtils, PSAPI;
{$IFNDEF WIN32}
function IsBuggyVirtualPCDriverLoaded : Boolean;
begin
Result:=False;
end;
{$ELSE}
function IsBuggyVirtualPCDriverLoaded : Boolean;
function GetFileVersion(const FileName : AnsiString) : Cardinal;
var
FileNameCopy : AnsiString;
VersionInfoSize : Cardinal;
Temp : UINT;
Buffer : Pointer;
VSFixedFileInfo : PVSFixedFileInfo;
begin
Result:=High(Result);
{GetFileVersionInfoA/W may modify the filename parameter,
so we create a local, writable copy of FileName}
FileNameCopy:=FileName;
UniqueString(FileNameCopy);
VersionInfoSize:=GetFileVersionInfoSizeA(PAnsiChar(FileNameCopy),Temp);
if VersionInfoSize > 0 then
begin
GetMem(Buffer,VersionInfoSize);
try
if GetFileVersionInfoA(PAnsiChar(FileNameCopy),0,VersionInfoSize,Buffer) then
if VerQueryValueA(Buffer,'\',Pointer(VSFixedFileInfo),Temp) then
if VSFixedFileInfo <> nil then
Result:=VSFixedFileInfo^.dwFileVersionMS;
finally
FreeMem(Buffer);
end;
end;
end;
function Min(A : Cardinal; B : Cardinal) : Cardinal;
begin
if A < B then
Result:=A
else
Result:=B;
end;
{$IFDEF VER90} {Delphi 2}
{In all places of the AnsiCompareFileName usage:
1) one of arguments is a non-MBCS string and
2) we don't need to know an exact result, we must only know if it is zero or not.
So we can exceptionally use AnsiCompareText instead of AnsiCompareFileName here.}
function AnsiCompareFileName(const S1 : AnsiString; const S2 : AnsiString) : Integer;
begin
Result:=AnsiCompareText(S1,S2);
end;
{In all places of the IncludeTrailingBackslash usage:
1) input string comes from GetSystemDirectoryA call or
2) input string comes from GetSystemWindowsDirectoryA or GetWindowsDirectoryA call.
These functions always return path without a backslash at the end, unless the result
points to the root directory. So we can omit MBCS problems by checking the string
length instead of checking if the last char is a backslash.}
function IncludeTrailingBackslash(const S : AnsiString) : AnsiString;
begin
if Length(S) > 3 then
Result:=S+'\'
else
Result:=S;
end;
{$ENDIF}
type
PPointerArray = ^TPointerArray;
TPointerArray = packed array[0..High(Integer) div SizeOf(Pointer)-1] of Pointer;
const
STR_QUESTIONMARKS = '\??\';
STR_SYSTEMROOT = '\SystemRoot\';
STR_BACKSLASH = '\';
var
GetSystemWindowsDirectoryA : function(lpBuffer : PAnsiChar; uSize : UINT) : UINT; stdcall;
Len : UINT;
SystemDirectory : AnsiString;
WindowsDirectory : AnsiString;
WindowsDrive : AnsiString;
PSAPIHandle : HINST;
Buffer : packed array[0..$8000-1] of AnsiChar;
Needed : UINT;
Needed2 : UINT;
ImageBaseArray : PPointerArray;
I : Integer;
S : AnsiString;
begin
Result:=False;
GetSystemWindowsDirectoryA:=GetProcAddress(GetModuleHandle(kernel32),PAnsiChar('GetSystemWindowsDirectoryA'));
if not Assigned(GetSystemWindowsDirectoryA) then
begin
GetSystemWindowsDirectoryA:=GetProcAddress(GetModuleHandle(kernel32),PAnsiChar('GetWindowsDirectoryA'));
if not Assigned(GetSystemWindowsDirectoryA) then
Exit;
end;
SetLength(SystemDirectory,GetSystemDirectoryA(nil,0));
Len:=GetSystemDirectoryA(PAnsiChar(SystemDirectory),Length(SystemDirectory));
if (Len = 0) or (Len > UINT(Length(SystemDirectory))) then
Exit;
SetLength(SystemDirectory,Len);
SystemDirectory:=AnsiString(IncludeTrailingBackslash(string(SystemDirectory)));
SetLength(WindowsDirectory,GetSystemWindowsDirectoryA(nil,0));
Len:=GetSystemWindowsDirectoryA(PAnsiChar(WindowsDirectory),Length(WindowsDirectory));
if (Len = 0) or (Len > UINT(Length(WindowsDirectory))) then
Exit;
SetLength(WindowsDirectory,Len);
WindowsDirectory:=AnsiString(IncludeTrailingBackslash(string(WindowsDirectory)));
WindowsDrive:=AnsiString(ExtractFileDrive(string(WindowsDirectory)));
{Windows NT4 doesn't have PSAPI.dll installed by default, but we try}
PSAPIHandle:=LoadLibrary('PSAPI.dll');
if PSAPIHandle = 0 then
begin
{Virtual Machine Additions has its own copy of PSAPI.dll. It is always located
in the VMADD subdirectory of Windows directory, user has no possibility to
change this location during installation of Virtual Machine Additions}
PSAPIHandle:=LoadLibraryA(PAnsiChar(WindowsDirectory+'VMADD\PSAPI.dll'));
if PSAPIHandle = 0 then
Exit;
end;
{Now LoadLibrary('PSAPI.dll') call inside PSAPI.pas will always be successful}
try
if EnumDeviceDrivers(nil,0,Needed) then
if Needed > 0 then
begin
GetMem(ImageBaseArray,Needed);
try
if EnumDeviceDrivers(PPointer(ImageBaseArray),Needed,Needed2) then
for I:=0 to (Min(Needed,Needed2) div SizeOf(ImageBaseArray^[0]))-1 do
begin
GetDeviceDriverBaseNameA(ImageBaseArray^[I],PAnsiChar(@Buffer),SizeOf(Buffer) div SizeOf(Buffer[0]));
if AnsiCompareFileName(string(Buffer),'mrxvpc.sys') = 0 then
begin
GetDeviceDriverFileNameA(ImageBaseArray^[I],PAnsiChar(@Buffer),SizeOf(Buffer) div SizeOf(Buffer[0]));
S:=Buffer;
if AnsiCompareFileName(string(Copy(S,1,Length(STR_QUESTIONMARKS))),STR_QUESTIONMARKS) = 0 then
S:=Copy(S,1+Length(STR_QUESTIONMARKS),High(Integer));
if AnsiCompareFileName(string(Copy(S,1,Length(STR_SYSTEMROOT))),STR_SYSTEMROOT) = 0 then
S:=WindowsDirectory+Copy(S,1+Length(STR_SYSTEMROOT),High(Integer));
if AnsiCompareFileName(string(Copy(S,1,Length(STR_BACKSLASH))),STR_BACKSLASH) = 0 then
S:=WindowsDrive+S;
if Length(ExtractFileDir(string(S))) = 0 then
S:=SystemDirectory+'Drivers\'+S; {"Drivers" subdirectory name is hardcoded in setupapi.dll (in some
Windows versions) and in ntoskrnl.exe (in all Windows versions,
including 64-bit - ntoskrnl.exe loads drivers during booting)}
Result:=GetFileVersion(S) < $000D0334;
Break;
end;
end;
finally
FreeMem(ImageBaseArray);
end;
end;
finally
FreeLibrary(PSAPIHandle);
end;
end;
{$ENDIF}
Code: Select all
uses
Windows;
{$IFNDEF WIN32}
function IsDriveSharedOnNTByVirtualPC(Drive : Cardinal) : LongBool;
begin
Result:=False;
end;
{$ELSE}
{Don't place Boolean as a result type, its size is not 4 bytes, so it may
cause stack misalignment and - as a result - NTDLL functions to fail}
function IsDriveSharedOnNTByVirtualPC(Drive : Cardinal) : LongBool;
const
FILE_SHARE_DELETE = $00000004;
var
NtOpenSymbolicLinkObject : function(LinkHandle : Pointer; DesiredAccess : Integer; ObjectAttributes : Pointer) : Cardinal; stdcall;
NtQuerySymbolicLinkObject : function(LinkHandle : THandle; LinkTarget : Pointer; ReturnedLength : Pointer) : Cardinal; stdcall;
UnicodeString : packed record {UNICODE_STRING}
Length : Word;
MaximumLength : Word;
Buffer : PWideChar;
end;
ObjectAttributes : packed record {OBJECT_ATTRIBUTES}
Length : Cardinal;
RootDirectory : THandle;
ObjectName : Pointer;
Attributes : Cardinal;
SecurityDescriptor : Pointer;
SecurityQualityOfService : Pointer;
end;
UnicodeBuffer : array[0..63] of WideChar;
Module : HINST;
Handle : THandle;
S : AnsiString;
I : Integer;
begin
Result:=False;
if Drive > Ord('Z')-Ord('A') then
Exit;
Module:=GetModuleHandle('ntdll.dll');
if Module = 0 then
Exit;
NtOpenSymbolicLinkObject:=GetProcAddress(Module,PAnsiChar('NtOpenSymbolicLinkObject'));
if not Assigned(NtOpenSymbolicLinkObject) then
Exit;
NtQuerySymbolicLinkObject:=GetProcAddress(Module,PAnsiChar('NtQuerySymbolicLinkObject'));
if not Assigned(NtQuerySymbolicLinkObject) then
Exit;
S:=AnsiString('\??\')+AnsiChar(Ord('A')+Drive)+AnsiString(':');
StringToWideChar(string(S),UnicodeBuffer,SizeOf(UnicodeBuffer) div SizeOf(UnicodeBuffer[0]));
UnicodeString.Length:=Length(S)*SizeOf(UnicodeBuffer[0]);
UnicodeString.MaximumLength:=SizeOf(UnicodeBuffer);
UnicodeString.Buffer:=@UnicodeBuffer;
ObjectAttributes.Length:=SizeOf(ObjectAttributes);
ObjectAttributes.RootDirectory:=0;
ObjectAttributes.ObjectName:=@UnicodeString;
ObjectAttributes.Attributes:=0;
ObjectAttributes.SecurityDescriptor:=nil;
ObjectAttributes.SecurityQualityOfService:=nil;
if NtOpenSymbolicLinkObject(@Handle,-2147352575{0x80020001 = GENERIC_READ or READ_CONTROL or FILE_READ_DATA},@ObjectAttributes) = 0 then
try
S:=AnsiString('\Device\MicrosoftVMFolderSharing\;')+AnsiChar(Ord('A')+Drive)+':\'+AnsiChar(Ord('A')+Drive)+'\'+AnsiChar(Ord('A')+Drive);
UnicodeString.Length:=0;
if NtQuerySymbolicLinkObject(Handle,@UnicodeString,nil) = 0 then
if UnicodeString.Length = Length(S)*SizeOf(UnicodeBuffer[0]) then
begin
Result:=True;
for I:=1 to Length(S) do
if WideChar(S[I]) <> UnicodeBuffer[I-1] then
begin
Result:=False;
Break;
end;
end;
finally
CloseHandle(Handle);
end;
end;
{$ENDIF}
Code: Select all
unit PSAPI;
interface
uses
Windows;
type
PPointer = ^Pointer;
function EnumDeviceDrivers(lpImageBase: PPointer; cb: DWORD; var lpcbNeeded: DWORD): BOOL;
function GetDeviceDriverBaseNameA(ImageBase: Pointer; lpBaseName: PAnsiChar; nSize: DWORD): DWORD;
function GetDeviceDriverFileNameA(ImageBase: Pointer; lpFileName: PAnsiChar; nSize: DWORD): DWORD;
implementation
var
hPSAPI: HINST;
_EnumDeviceDrivers: function (lpImageBase: PPointer; cb: DWORD; var lpcbNeeded: DWORD): BOOL; stdcall;
_GetDeviceDriverBaseNameA: function (ImageBase: Pointer; lpBaseName: PAnsiChar; nSize: DWORD): DWORD; stdcall;
_GetDeviceDriverFileNameA: function (ImageBase: Pointer; lpFileName: PAnsiChar; nSize: DWORD): DWORD; stdcall;
function CheckPSAPILoaded: Boolean;
begin
if hPSAPI = 0 then
begin
hPSAPI := LoadLibrary('PSAPI.dll');
if hPSAPI = 0 then
begin
Result := False;
Exit;
end;
@_EnumDeviceDrivers := GetProcAddress(hPSAPI, PAnsiChar('EnumDeviceDrivers'));
@_GetDeviceDriverBaseNameA := GetProcAddress(hPSAPI, PAnsiChar('GetDeviceDriverBaseNameA'));
@_GetDeviceDriverFileNameA := GetProcAddress(hPSAPI, PAnsiChar('GetDeviceDriverFileNameA'));
end;
Result := True;
end;
function EnumDeviceDrivers(lpImageBase: PPointer; cb: DWORD; var lpcbNeeded: DWORD): BOOL;
begin
if CheckPSAPILoaded then
Result := _EnumDeviceDrivers(lpImageBase, cb, lpcbNeeded)
else Result := False;
end;
function GetDeviceDriverBaseNameA(ImageBase: Pointer; lpBaseName: PAnsiChar; nSize: DWORD): DWORD;
begin
if CheckPSAPILoaded then
Result := _GetDeviceDriverBasenameA(ImageBase, lpBaseName, nSize)
else Result := 0;
end;
function GetDeviceDriverFileNameA(ImageBase: Pointer; lpFileName: PAnsiChar; nSize: DWORD): DWORD;
begin
if CheckPSAPILoaded then
Result := _GetDeviceDriverFileNameA(ImageBase, lpFileName, nSize)
else Result := 0;
end;
end.