1263 lines
38 KiB
C++
1263 lines
38 KiB
C++
/*++
|
|
|
|
Copyright (c) 2000 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
EmulatePrinter.cpp
|
|
|
|
Abstract:
|
|
|
|
This is a general purpose shim to fix all problems we have seen
|
|
that are remotely connected with printers. The shim fixes the
|
|
following:
|
|
|
|
1) Apps call EnumPrinters passing only PRINTER_ENUM_LOCAL but expect to see
|
|
network printers as well. For some reason Win9x enumerates network
|
|
printers as well when this API is called with only PRINTER_ENUM_LOCAL set.
|
|
|
|
2) Apps call EnumPrinters passing only PRINTER_ENUM_DEFAULT. This works
|
|
properly in win98, however the option does not exist in w2k. This
|
|
API performs the equivalent.
|
|
|
|
3) EnumPrinters Level 5 is not really supported on NT. This API calls
|
|
Level 2 and munges the data into a level 5 structure.
|
|
|
|
4) Win9x ignores pDefault parameter for OpenPrinter. Some native Win9x apps
|
|
are unaware about this and assume it is safe to use PRINTER_ALL_ACCESS
|
|
value for DesiredAccess flag, member of pDefault parameter, to open either
|
|
local printer or remote printer server. But Windows NT requires
|
|
SERVER_ALL_ACCESS set for this flag to access remote printer server.
|
|
To emulate Win9x behavior, we override pDefault with NULL value.
|
|
|
|
5) If an app calls one of several print APIs with a NULL printer name,
|
|
looks up and supplies the default printer name, or derives it from other params.
|
|
|
|
6) Verifies a correct handle was passed to SetPrinter. Win98 does this
|
|
at the start and if its a bad handle never uses the passed Information,
|
|
however w2k does not check the handle till after looking at the information.
|
|
This can cause an error if Level is 2 and the print buffer is null due to
|
|
a missing check in SetPrinterA. (note: this was fixed in whistler).
|
|
|
|
7) Verifies that the stack is correct after the proc set in SetAbortProc is
|
|
called.
|
|
|
|
8) Verifies that an initialized DEVMODEA has been passed to ResetDCA.
|
|
|
|
9) Checks GetProfileStringA for a WINDOWS DEVICE (i.e. printer). If one is
|
|
requested then make sure the string is not being truncated, if it is then
|
|
save the full printer name for later use.
|
|
|
|
10) Checks for a -1 in the nFromPage for PrintDlgA and corrects it to a zero.
|
|
Note: the OS should handle this as per the manual, however print team no-fixed it
|
|
as too risky to change since -1 is a special value in their code.
|
|
|
|
Notes:
|
|
|
|
This is a general purpose shim. This code from this shim was originally
|
|
in two seperate shims enumnetworkprinters and handlenullprintername.
|
|
|
|
Also added another SHIM EmulateStartPage to this.
|
|
|
|
History:
|
|
|
|
11/08/00 mnikkel created
|
|
12/07/00 prashkud Added StartPage to this.
|
|
01/25/01 mnikkel Removed W routines, they were causing problems
|
|
and were not needed.
|
|
02/07/01 mnikkel Added check for too long a string, removed fixed printer
|
|
name sizes.
|
|
02/27/2001 robkenny Converted to use tcs.h
|
|
05/21/2001 mnikkel Added PrintDlgA check
|
|
09/13/2001 mnikkel Changed so that level 5 data being created from Level 2
|
|
data is only done on win2k. Level 5 data was fixed for XP.
|
|
Also added check so shim works with printers shared out on
|
|
win9X while running on XP.
|
|
12/15/2001 mnikkel Corrected bug in shim where default printer flag was not
|
|
being set in enumprintersa.
|
|
|
|
--*/
|
|
|
|
#include "precomp.h"
|
|
#include <commdlg.h>
|
|
|
|
// This file and its assumptions need to be verified on an Intl version
|
|
#include "LegalStr.h" // BUGBUG
|
|
|
|
IMPLEMENT_SHIM_BEGIN(EmulatePrinter)
|
|
#include "ShimHookMacro.h"
|
|
|
|
#define MAX_PRINTER_NAME 221
|
|
#define MAX_DRIVERPORT_NAME 50
|
|
|
|
APIHOOK_ENUM_BEGIN
|
|
APIHOOK_ENUM_ENTRY(DocumentPropertiesA)
|
|
APIHOOK_ENUM_ENTRY(OpenPrinterA)
|
|
APIHOOK_ENUM_ENTRY(SetPrinterA)
|
|
APIHOOK_ENUM_ENTRY(CreateDCA)
|
|
APIHOOK_ENUM_ENTRY(ResetDCA)
|
|
APIHOOK_ENUM_ENTRY(EnumPrintersA)
|
|
APIHOOK_ENUM_ENTRY(GetProfileStringA)
|
|
APIHOOK_ENUM_ENTRY(SetAbortProc)
|
|
APIHOOK_ENUM_ENTRY(StartPage)
|
|
APIHOOK_ENUM_ENTRY(DeviceCapabilitiesA)
|
|
APIHOOK_ENUM_ENTRY(AddPrinterConnectionA)
|
|
APIHOOK_ENUM_ENTRY(DeletePrinterConnectionA)
|
|
APIHOOK_ENUM_ENTRY(PrintDlgA)
|
|
APIHOOK_ENUM_END
|
|
|
|
typedef int (WINAPI *_pfn_SetAbortProc)( HDC hdc, ABORTPROC lpAbortProc );
|
|
|
|
LPSTR g_pszFullPrinterName = NULL;
|
|
LPSTR g_pszPartialPrinterName = NULL;
|
|
CRITICAL_SECTION g_critSec;
|
|
BOOL g_bWin2k = FALSE;
|
|
|
|
|
|
/*++
|
|
These functions munge data from a Level 2 Information structure
|
|
into a Level 5 information structure.
|
|
--*/
|
|
|
|
BOOL
|
|
MungeInfo2TOInfo5_A(
|
|
PRINTER_INFO_2A* pInfo2,
|
|
DWORD cbBuf,
|
|
DWORD dwInfo2Returned,
|
|
PRINTER_INFO_5A* pInfo5,
|
|
LPDWORD pcbNeeded,
|
|
LPDWORD pcbReturned)
|
|
{
|
|
DWORD dwStringBufferSize = 0;
|
|
LPSTR lpStringBuffer = NULL;
|
|
|
|
// First calculate buffer size needed
|
|
for (DWORD i = 0; i < dwInfo2Returned; i++)
|
|
{
|
|
if (pInfo2[i].Attributes & PRINTER_ATTRIBUTE_NETWORK &&
|
|
!(pInfo2[i].Attributes & PRINTER_ATTRIBUTE_LOCAL) &&
|
|
pInfo2[i].pServerName != NULL &&
|
|
pInfo2[i].pShareName != NULL)
|
|
{
|
|
if (pInfo2[i].pServerName)
|
|
{
|
|
dwStringBufferSize += strlen(pInfo2[i].pServerName) + 1;
|
|
}
|
|
|
|
if (pInfo2[i].pPrinterName)
|
|
{
|
|
dwStringBufferSize += strlen(pInfo2[i].pPrinterName) + 1;
|
|
}
|
|
|
|
if (pInfo2[i].pShareName)
|
|
{
|
|
dwStringBufferSize += strlen(pInfo2[i].pShareName) + 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pInfo2[i].pPrinterName)
|
|
{
|
|
dwStringBufferSize += strlen(pInfo2[i].pPrinterName) + 1;
|
|
}
|
|
|
|
if (pInfo2[i].pPortName)
|
|
{
|
|
dwStringBufferSize += strlen(pInfo2[i].pPortName) + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// set the buffer size needed
|
|
*pcbNeeded = dwInfo2Returned * sizeof(PRINTER_INFO_5A)
|
|
+ dwStringBufferSize;
|
|
|
|
// verify that buffer passed in is big enough.
|
|
if (cbBuf < *pcbNeeded)
|
|
{
|
|
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
|
return FALSE;
|
|
}
|
|
|
|
// Allocate the Level 5 information structure
|
|
lpStringBuffer = ((LPSTR) pInfo5)
|
|
+ dwInfo2Returned * sizeof(PRINTER_INFO_5A);
|
|
|
|
// Munge the Level 2 information into the Level 5 structure
|
|
for (i = 0; i < dwInfo2Returned; i++)
|
|
{
|
|
if (pInfo2[i].Attributes & PRINTER_ATTRIBUTE_NETWORK &&
|
|
!(pInfo2[i].Attributes & PRINTER_ATTRIBUTE_LOCAL) &&
|
|
pInfo2[i].pServerName != NULL &&
|
|
pInfo2[i].pShareName != NULL)
|
|
{
|
|
// Copy over strings
|
|
strcpy( lpStringBuffer, pInfo2[i].pPrinterName );
|
|
pInfo5[i].pPrinterName = lpStringBuffer;
|
|
lpStringBuffer += strlen(pInfo2[i].pPrinterName) + 1;
|
|
|
|
strcpy( lpStringBuffer, pInfo2[i].pServerName );
|
|
strcat( lpStringBuffer, "\\" );
|
|
strcat( lpStringBuffer, pInfo2[i].pShareName );
|
|
|
|
pInfo5[i].pPortName = lpStringBuffer;
|
|
lpStringBuffer += strlen(pInfo2[i].pServerName) + strlen(pInfo2[i].pShareName) + 2;
|
|
|
|
pInfo5[i].Attributes = pInfo2[i].Attributes;
|
|
pInfo5[i].DeviceNotSelectedTimeout = 15000; // Use defaults here
|
|
pInfo5[i].TransmissionRetryTimeout = 45000; // Use defaults here
|
|
}
|
|
else
|
|
{
|
|
// Copy over strings
|
|
strcpy( lpStringBuffer, pInfo2[i].pPrinterName );
|
|
pInfo5[i].pPrinterName = lpStringBuffer;
|
|
lpStringBuffer += strlen(pInfo2[i].pPrinterName) + 1;
|
|
|
|
strcpy( lpStringBuffer, pInfo2[i].pPortName );
|
|
|
|
pInfo5[i].pPortName = lpStringBuffer;
|
|
lpStringBuffer += strlen(pInfo2[i].pPortName) + 1;
|
|
|
|
pInfo5[i].Attributes = pInfo2[i].Attributes;
|
|
pInfo5[i].DeviceNotSelectedTimeout = 15000; // Use defaults here
|
|
pInfo5[i].TransmissionRetryTimeout = 45000; // Use defaults here
|
|
}
|
|
}
|
|
|
|
// Set the number of structures munged
|
|
*pcbReturned = dwInfo2Returned;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
Our Callback routine for SetAbortProc, this routine
|
|
verifies that the stack is correct.
|
|
|
|
--*/
|
|
DWORD g_dwGuardNum = 0xABCD8765;
|
|
DWORD g_dwFailed = 0;
|
|
|
|
BOOL CALLBACK
|
|
AbortProcHook(
|
|
ABORTPROC pfnOld, // address of old ABORTPROC
|
|
HDC hdc, // handle to DC
|
|
int iError // error value
|
|
)
|
|
{
|
|
DWORD dwRet= 0;
|
|
|
|
|
|
// Flag to track whether the stack was corrected.
|
|
g_dwFailed = 0;
|
|
|
|
// Push a Guard number on the stack, call their
|
|
// abort procedure, then pop the stack till we
|
|
// find our guard number
|
|
__asm
|
|
{
|
|
push ebx
|
|
push ecx
|
|
|
|
push g_dwGuardNum
|
|
push iError
|
|
push hdc
|
|
|
|
call pfnOld ; make call to their abort proc
|
|
|
|
mov ecx,16
|
|
loc1:
|
|
dec ecx
|
|
pop ebx
|
|
cmp ebx, g_dwGuardNum
|
|
jne loc1
|
|
|
|
cmp ecx, 15
|
|
jz loc2
|
|
mov g_dwFailed, 1
|
|
loc2:
|
|
|
|
pop ecx
|
|
pop ebx
|
|
|
|
mov dwRet, eax
|
|
}
|
|
|
|
if (g_dwFailed)
|
|
{
|
|
LOGN( eDbgLevelError, "[AbortProcHook] Fixing incorrect calling convention for AbortProc");
|
|
}
|
|
|
|
return (BOOL) dwRet;
|
|
}
|
|
|
|
/*++
|
|
|
|
This stub function looks up the device name if pDeviceName is NULL
|
|
|
|
--*/
|
|
|
|
LONG
|
|
APIHOOK(DocumentPropertiesA)(
|
|
HWND hWnd,
|
|
HANDLE hPrinter,
|
|
LPSTR pDeviceName,
|
|
PDEVMODEA pDevModeOutput,
|
|
PDEVMODEA pDevModeInput,
|
|
DWORD fMode
|
|
)
|
|
{
|
|
LONG lRet = -1;
|
|
PRINTER_INFO_2A *pPrinterInfo2A = NULL;
|
|
|
|
// if they didn't supply a device name, we need to supply it.
|
|
if (!pDeviceName) {
|
|
LOGN( eDbgLevelError, "[DocumentPropertiesW] App passed NULL for pDeviceName.");
|
|
|
|
if (hPrinter) {
|
|
DWORD dwSizeNeeded = 0;
|
|
DWORD dwSizeUsed = 0;
|
|
|
|
// get the size
|
|
GetPrinterA(hPrinter, 2, NULL, 0, &dwSizeNeeded);
|
|
|
|
if (dwSizeNeeded != 0) {
|
|
|
|
// allocate memory for the info
|
|
pPrinterInfo2A = (PRINTER_INFO_2A*) malloc(dwSizeNeeded);
|
|
if (pPrinterInfo2A) {
|
|
|
|
// get the info
|
|
if (GetPrinterA(hPrinter, 2, (LPBYTE)pPrinterInfo2A, dwSizeNeeded, &dwSizeUsed)) {
|
|
pDeviceName = pPrinterInfo2A->pPrinterName;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!pDeviceName) {
|
|
DPFN( eDbgLevelError, "[DocumentPropertiesA] Unable to gather correct pDeviceName."
|
|
"Problem not fixed.\n");
|
|
}
|
|
|
|
lRet = ORIGINAL_API(DocumentPropertiesA)(
|
|
hWnd,
|
|
hPrinter,
|
|
pDeviceName,
|
|
pDevModeOutput,
|
|
pDevModeInput,
|
|
fMode
|
|
);
|
|
|
|
if (pPrinterInfo2A) {
|
|
free(pPrinterInfo2A);
|
|
}
|
|
|
|
return lRet;
|
|
}
|
|
|
|
|
|
/*++
|
|
These functions handle the case of EnumPrinters being called with the
|
|
PRINTER_ENUM_DEFAULT flag.
|
|
--*/
|
|
|
|
BOOL
|
|
EnumDefaultPrinterA(
|
|
PRINTER_INFO_2A* pInfo2,
|
|
LPBYTE pPrinterEnum,
|
|
DWORD cbBuf,
|
|
DWORD Level,
|
|
PRINTER_INFO_5A* pInfo5,
|
|
LPDWORD pcbNeeded,
|
|
LPDWORD pcbReturned
|
|
)
|
|
{
|
|
LPSTR pszName = NULL;
|
|
DWORD dwSize = 0;
|
|
HANDLE hPrinter = NULL;
|
|
BOOL bRet= FALSE;
|
|
DWORD dwInfo2Needed = 0;
|
|
DWORD dwDummy;
|
|
BOOL bDefaultFail = TRUE;
|
|
|
|
*pcbNeeded = 0;
|
|
*pcbReturned = 0;
|
|
|
|
// get the default printer name
|
|
if (GetDefaultPrinterA(NULL, &dwSize) < 1)
|
|
{
|
|
// Now that we have the right size, allocate a buffer
|
|
if ( GetLastError() == ERROR_INSUFFICIENT_BUFFER )
|
|
{
|
|
pszName = (LPSTR) ShimMalloc( dwSize );
|
|
if (pszName)
|
|
{
|
|
// Now get the default printer with the right buffer size.
|
|
if (GetDefaultPrinterA( pszName, &dwSize ) > 0 )
|
|
{
|
|
if ( OpenPrinterA( pszName, &hPrinter, NULL ) )
|
|
{
|
|
bDefaultFail = FALSE;
|
|
}
|
|
}
|
|
ShimFree(pszName);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( bDefaultFail )
|
|
{
|
|
SetLastError(ERROR_INVALID_NAME);
|
|
return FALSE;
|
|
}
|
|
|
|
// Printer Level 5 is not really supported on win2k.
|
|
// We'll call Level 2 and munge the data into a level 5 structure.
|
|
if ( g_bWin2k &&
|
|
Level == 5 &&
|
|
pcbNeeded != NULL &&
|
|
pcbReturned != NULL)
|
|
{
|
|
|
|
LOGN( eDbgLevelError, "[EnumPrintersA] EnumPrintersA called with Level 5 set."
|
|
" Fixing up Level 5 information.");
|
|
|
|
// get the size needed for the info2 data
|
|
if (GetPrinterA(hPrinter, 2, NULL, 0, &dwInfo2Needed) == 0 &&
|
|
GetLastError() == ERROR_INSUFFICIENT_BUFFER )
|
|
{
|
|
pInfo2 = (PRINTER_INFO_2A *) ShimMalloc( dwInfo2Needed );
|
|
|
|
// get the info2 data and munge into level 5 structure
|
|
if (pInfo2 &&
|
|
GetPrinterA(hPrinter, 2, (LPBYTE)pInfo2, dwInfo2Needed, &dwDummy))
|
|
{
|
|
bRet= MungeInfo2TOInfo5_A( pInfo2, cbBuf, 1, pInfo5, pcbNeeded, pcbReturned);
|
|
}
|
|
|
|
if (pInfo2)
|
|
ShimFree(pInfo2);
|
|
}
|
|
}
|
|
|
|
// Not win2k or not Level 5 so just get info
|
|
else
|
|
{
|
|
*pcbReturned = 1;
|
|
bRet = GetPrinterA(hPrinter, Level, pPrinterEnum, cbBuf, pcbNeeded);
|
|
}
|
|
|
|
// Close the printer
|
|
ClosePrinter(hPrinter);
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
These stub functions check for PRINTER_ENUM_DEFAULT, PRINTER_ENUM_LOCAL
|
|
and Level 5 information structures.
|
|
|
|
--*/
|
|
|
|
BOOL
|
|
APIHOOK(EnumPrintersA)(
|
|
DWORD Flags,
|
|
LPSTR Name,
|
|
DWORD Level,
|
|
LPBYTE pPrinterEnum,
|
|
DWORD cbBuf,
|
|
LPDWORD pcbNeeded,
|
|
LPDWORD pcbReturned
|
|
)
|
|
{
|
|
BOOL bRet = FALSE;
|
|
|
|
DWORD dwInfo2Needed = 0;
|
|
DWORD dwInfo2Returned = 0;
|
|
DWORD dwDummy;
|
|
|
|
PRINTER_INFO_2A* pInfo2 = NULL;
|
|
PRINTER_INFO_5A* pInfo5 = (PRINTER_INFO_5A *) pPrinterEnum;
|
|
|
|
// Win2k doesn't handle DEFAULT case like win98 did, so we get
|
|
// to do it for them.
|
|
if (Flags == PRINTER_ENUM_DEFAULT )
|
|
{
|
|
LOGN( eDbgLevelError, "[EnumPrintersA] Called with PRINTER_ENUM_DEFAULT flag."
|
|
" Providing Default printer.");
|
|
|
|
bRet = EnumDefaultPrinterA(
|
|
pInfo2,
|
|
pPrinterEnum,
|
|
cbBuf,
|
|
Level,
|
|
pInfo5,
|
|
pcbNeeded,
|
|
pcbReturned);
|
|
|
|
return bRet;
|
|
}
|
|
|
|
// For LOCAL also add in CONNECTIONS
|
|
if (Flags == PRINTER_ENUM_LOCAL)
|
|
{
|
|
LOGN( eDbgLevelInfo, "[EnumPrintersA] Called only for "
|
|
"PRINTER_ENUM_LOCAL. Adding PRINTER_ENUM_CONNECTIONS\n");
|
|
|
|
Flags = (PRINTER_ENUM_CONNECTIONS | PRINTER_ENUM_LOCAL);
|
|
}
|
|
|
|
// Printer Level 5 is not really supported on win2k.
|
|
// We'll call Level 2 and munge the data into a level 5 structure.
|
|
if ( g_bWin2k &&
|
|
Level == 5 &&
|
|
pcbNeeded != NULL &&
|
|
pcbReturned != NULL)
|
|
{
|
|
// get the size needed for the info2 data
|
|
ORIGINAL_API(EnumPrintersA)(Flags,
|
|
Name,
|
|
2,
|
|
NULL,
|
|
0,
|
|
&dwInfo2Needed,
|
|
&dwInfo2Returned);
|
|
|
|
if (dwInfo2Needed > 0)
|
|
{
|
|
// Printers found, get the info2 data and convert it to info5
|
|
pInfo2 = (PRINTER_INFO_2A *) ShimMalloc( dwInfo2Needed );
|
|
|
|
if (pInfo2 &&
|
|
ORIGINAL_API(EnumPrintersA)(Flags,
|
|
Name,
|
|
2,
|
|
(LPBYTE) pInfo2,
|
|
dwInfo2Needed,
|
|
&dwDummy,
|
|
&dwInfo2Returned) )
|
|
{
|
|
bRet = MungeInfo2TOInfo5_A( pInfo2,
|
|
cbBuf,
|
|
dwInfo2Returned,
|
|
pInfo5,
|
|
pcbNeeded,
|
|
pcbReturned);
|
|
}
|
|
|
|
|
|
if(pInfo2)
|
|
ShimFree( pInfo2 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bRet = ORIGINAL_API(EnumPrintersA)(Flags,
|
|
Name,
|
|
Level,
|
|
pPrinterEnum,
|
|
cbBuf,
|
|
pcbNeeded,
|
|
pcbReturned);
|
|
}
|
|
|
|
// For level 2 and level 5 there are some win95 only attributes
|
|
// that need to be emulated.
|
|
if ( (Level == 2 || Level == 5) &&
|
|
bRet &&
|
|
pPrinterEnum != NULL )
|
|
{
|
|
DWORD dwSize;
|
|
|
|
GetDefaultPrinterA(NULL, &dwSize);
|
|
|
|
if ( GetLastError() == ERROR_INSUFFICIENT_BUFFER )
|
|
{
|
|
// Now that we have the right size, allocate a buffer
|
|
LPSTR pszName = (LPSTR) ShimMalloc( dwSize );
|
|
|
|
if (pszName)
|
|
{
|
|
// Now get the default printer with the right buffer size.
|
|
if (GetDefaultPrinterA( pszName, &dwSize ) > 0 )
|
|
{
|
|
if (Level == 2)
|
|
{
|
|
if (strcmp( pszName, ((PRINTER_INFO_2A*)pPrinterEnum)->pPrinterName) == 0)
|
|
{
|
|
((PRINTER_INFO_2A*)pPrinterEnum)->Attributes |= PRINTER_ATTRIBUTE_DEFAULT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (strcmp( pszName, ((PRINTER_INFO_5A*)pPrinterEnum)->pPrinterName) == 0)
|
|
((PRINTER_INFO_5A*)pPrinterEnum)->Attributes |= PRINTER_ATTRIBUTE_DEFAULT;
|
|
}
|
|
}
|
|
|
|
ShimFree(pszName);
|
|
}
|
|
}
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
/*++
|
|
These stub functions substitute the default printer if the pPrinterName is NULL,
|
|
also they set pDefault to NULL to emulate win9x behavior
|
|
--*/
|
|
|
|
BOOL
|
|
APIHOOK(OpenPrinterA)(
|
|
LPSTR pPrinterName,
|
|
LPHANDLE phPrinter,
|
|
LPPRINTER_DEFAULTSA pDefault
|
|
)
|
|
{
|
|
LPSTR pszName = NULL;
|
|
DWORD dwSize;
|
|
BOOL bDefaultFail = TRUE;
|
|
BOOL bRet;
|
|
|
|
if (!pPrinterName)
|
|
{
|
|
LOGN( eDbgLevelError, "[OpenPrinterA] App passed NULL for pPrinterName, using default printer.");
|
|
|
|
// get the default printer name
|
|
if (GetDefaultPrinterA(NULL, &dwSize) < 1)
|
|
{
|
|
// Now that we have the right size, allocate a buffer
|
|
if ( GetLastError() == ERROR_INSUFFICIENT_BUFFER )
|
|
{
|
|
pszName = (LPSTR) ShimMalloc( dwSize );
|
|
if (pszName)
|
|
{
|
|
// Now get the default printer with the right buffer size.
|
|
if (GetDefaultPrinterA( pszName, &dwSize ) > 0 )
|
|
{
|
|
pPrinterName = pszName;
|
|
bDefaultFail = FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( bDefaultFail )
|
|
{
|
|
DPFN( eDbgLevelError, "[OpenPrinterA] Unable to gather default pPrinterName.\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( pPrinterName && g_pszPartialPrinterName && g_pszFullPrinterName &&
|
|
0 == strcmp(g_pszPartialPrinterName, pPrinterName) )
|
|
{
|
|
pPrinterName = g_pszFullPrinterName;
|
|
}
|
|
}
|
|
|
|
if ( pPrinterName )
|
|
{
|
|
DPFN( eDbgLevelInfo, "[OpenPrinterA] APIHOOK(OpenPrinterA: pPrinterName: %s\n", pPrinterName);
|
|
}
|
|
DPFN( eDbgLevelInfo, "[OpenPrinterA] APIHOOK(OpenPrinterA: pDefault: %x\n", pDefault);
|
|
DPFN( eDbgLevelInfo, "[OpenPrinterA] APIHOOK(OpenPrinterA: overriding pDefault with NULL value\n");
|
|
|
|
bRet = ORIGINAL_API(OpenPrinterA)(
|
|
pPrinterName,
|
|
phPrinter,
|
|
NULL);
|
|
|
|
ShimFree(pszName);
|
|
|
|
return bRet;
|
|
}
|
|
|
|
/*++
|
|
This stub function checks to see if the app is asking for the default printer
|
|
string. If it is it will be returned as follows:
|
|
|
|
PrinterName, Driver, Port
|
|
|
|
On Win9x, if the printer is a network printer, Port is \\server\share and
|
|
local printers are Port: (ex. LPT1:).
|
|
On Win2k, if the printer is a network printer, Port is NeXX: and local printers
|
|
are Port: .
|
|
We must query EnumPrinters in order to emulate Win9x. Note: If the printer
|
|
name is to large for the input buffer we trim it and keep track of the full
|
|
name for later us in other printer APIs.
|
|
--*/
|
|
DWORD
|
|
APIHOOK(GetProfileStringA)(
|
|
LPCSTR lpAppName, // section name
|
|
LPCSTR lpKeyName, // key name
|
|
LPCSTR lpDefault, // default string
|
|
LPSTR lpReturnedString, // destination buffer
|
|
DWORD nSize // size of destination buffer
|
|
)
|
|
{
|
|
if ( lpAppName &&
|
|
lpKeyName &&
|
|
0 == _stricmp(lpAppName, "windows") &&
|
|
0 == _stricmp(lpKeyName, "device" ) )
|
|
{
|
|
LPSTR pszProfileString = NULL;
|
|
DWORD dwSize = 0;
|
|
BOOL bDefaultFail = TRUE;
|
|
DWORD dwProfileStringLen;
|
|
LPSTR pszPrinterDriver = NULL;
|
|
LPSTR pszPrinterPort = NULL;
|
|
LPSTR pszName = NULL;
|
|
DWORD dwPrinterNameSize = 0;
|
|
DWORD dwDelimiterCount = 0;
|
|
LPSTR pszPtr;
|
|
|
|
// get the default printer name
|
|
if (GetDefaultPrinterA(NULL, &dwSize) <1)
|
|
{
|
|
// Now that we have the right size, allocate a buffer
|
|
if ( GetLastError() == ERROR_INSUFFICIENT_BUFFER )
|
|
{
|
|
pszName = (LPSTR) ShimMalloc( dwSize );
|
|
if (pszName)
|
|
{
|
|
// Now get the default printer with the right buffer size.
|
|
if (GetDefaultPrinterA( pszName, &dwSize ) > 0 )
|
|
{
|
|
bDefaultFail = FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ShimFree(pszName);
|
|
|
|
if ( bDefaultFail )
|
|
{
|
|
// Unable to get the default printername. Fall back to the max
|
|
// size printer name that the UI allows plus a pad for the driver
|
|
// and port names.
|
|
dwSize = MAX_PRINTER_NAME + MAX_DRIVERPORT_NAME;
|
|
}
|
|
else
|
|
{
|
|
// We have the printer name size add in a pad for the driver and
|
|
// port names.
|
|
dwSize += MAX_DRIVERPORT_NAME;
|
|
}
|
|
|
|
// Allocate the string
|
|
pszProfileString = (LPSTR) ShimMalloc(dwSize);
|
|
|
|
// Retrieve the profile string
|
|
dwProfileStringLen = ORIGINAL_API(GetProfileStringA)( lpAppName,
|
|
lpKeyName,
|
|
lpDefault,
|
|
pszProfileString,
|
|
dwSize );
|
|
|
|
if (dwProfileStringLen != NULL)
|
|
{
|
|
// Find the first and second commas.
|
|
pszPtr = pszProfileString;
|
|
while (*pszPtr)
|
|
{
|
|
if (*pszPtr == ',')
|
|
{
|
|
dwDelimiterCount++;
|
|
|
|
if (dwDelimiterCount == 1 && *(pszPtr+1))
|
|
{
|
|
pszPrinterDriver= pszPtr;
|
|
}
|
|
else if (dwDelimiterCount == 2 && *(pszPtr+1))
|
|
{
|
|
pszPrinterPort = pszPtr + 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
pszPtr++;
|
|
}
|
|
|
|
DPFN( eDbgLevelError, "[GetProfileStringA] Profilestring <%s>\n Driver <%s>\n Port <%s>", pszProfileString, pszPrinterDriver,pszPrinterPort);
|
|
|
|
// Check to see if this is a network printer
|
|
if ( strstr(pszProfileString, ",Ne") )
|
|
{
|
|
PRINTER_INFO_2A* pInfo2 = NULL;
|
|
DWORD dwInfo2Needed = 0;
|
|
DWORD dwInfo2Returned = 0;
|
|
DWORD dwDummy = 0;
|
|
DWORD i;
|
|
BOOL bEnumPrintersSuccess;
|
|
BOOL bDefaultFound = FALSE;
|
|
|
|
// Get the size of the Level 2 structure needed.
|
|
bEnumPrintersSuccess = EnumPrintersA( PRINTER_ENUM_CONNECTIONS | PRINTER_ENUM_LOCAL,
|
|
NULL,
|
|
2,
|
|
NULL,
|
|
0,
|
|
&dwInfo2Needed,
|
|
&dwInfo2Returned );
|
|
|
|
// Get the Level 2 Info structure for the printer.
|
|
pInfo2 = (PRINTER_INFO_2A *) ShimMalloc( dwInfo2Needed );
|
|
|
|
bEnumPrintersSuccess = EnumPrintersA( PRINTER_ENUM_CONNECTIONS | PRINTER_ENUM_LOCAL,
|
|
NULL,
|
|
2,
|
|
(LPBYTE) pInfo2,
|
|
dwInfo2Needed,
|
|
&dwDummy,
|
|
&dwInfo2Returned );
|
|
|
|
if (bEnumPrintersSuccess)
|
|
{
|
|
// Search for default printer in PRINTER_INFO_2 array
|
|
for (i = 0; i < dwInfo2Returned; i++)
|
|
{
|
|
if (0 != strstr(pszProfileString, pInfo2[i].pPrinterName))
|
|
{
|
|
bDefaultFound = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( bDefaultFound )
|
|
{
|
|
// Double check that this is a network printer and does not have
|
|
// local attribute
|
|
if (pInfo2[i].Attributes & PRINTER_ATTRIBUTE_NETWORK &&
|
|
!(pInfo2[i].Attributes & PRINTER_ATTRIBUTE_LOCAL) &&
|
|
pInfo2[i].pServerName != NULL &&
|
|
pInfo2[i].pShareName != NULL)
|
|
{
|
|
// Modify the Port to conform with Win9x standards.
|
|
LOGN( eDbgLevelInfo, "[GetProfileStringA] Altering default printer string returned by GetProfileStringA.\n");
|
|
DPFN( eDbgLevelInfo, "[GetProfileStringA] Old: %s\n", pszProfileString);
|
|
|
|
strcpy(pszPrinterPort, pInfo2[i].pServerName);
|
|
strcat(pszPrinterPort, "\\");
|
|
strcat(pszPrinterPort, pInfo2[i].pShareName);
|
|
}
|
|
else
|
|
{
|
|
// Just copy in the port
|
|
strcpy( pszPrinterPort, pInfo2[i].pPortName );
|
|
}
|
|
dwProfileStringLen = strlen(pszProfileString);
|
|
}
|
|
}
|
|
|
|
ShimFree(pInfo2);
|
|
}
|
|
|
|
// If the size they give is big enough, then return.
|
|
dwPrinterNameSize = pszPrinterDriver - pszProfileString;
|
|
if ( dwProfileStringLen < nSize )
|
|
{
|
|
strcpy( lpReturnedString, pszProfileString );
|
|
DPFN( eDbgLevelInfo, "[GetProfileStringA] Default Printer: %s Size: %d\n",
|
|
lpReturnedString, strlen(lpReturnedString));
|
|
return strlen(lpReturnedString);
|
|
}
|
|
|
|
// Modify the printer name and keep a global of the original if the printer
|
|
// name causes the profile string output buffer to overflow.
|
|
// If the size we need to reduce it by is greater than the size of
|
|
// the printer name we're screwed, pass through.
|
|
dwPrinterNameSize -= (dwProfileStringLen - nSize);
|
|
if ( dwPrinterNameSize > 0 )
|
|
{
|
|
DPFN( eDbgLevelInfo, "[GetProfileStringA] Reducing printer name by %d characters.\n",
|
|
dwProfileStringLen - nSize );
|
|
LOGN( eDbgLevelInfo, "[GetProfileStringA] Reducing printer name by %d characters.\n",
|
|
dwProfileStringLen - nSize );
|
|
|
|
// copy in the truncated printer name.
|
|
strncpy( lpReturnedString, pszProfileString, dwPrinterNameSize );
|
|
strcat( lpReturnedString, pszPrinterDriver );
|
|
|
|
EnterCriticalSection(&g_critSec);
|
|
|
|
// Release any previous allocations.
|
|
if (g_pszPartialPrinterName && g_pszFullPrinterName)
|
|
{
|
|
ShimFree(g_pszPartialPrinterName);
|
|
ShimFree(g_pszFullPrinterName);
|
|
}
|
|
|
|
// save the partial and full printer names for later use.
|
|
g_pszPartialPrinterName = (LPSTR)ShimMalloc(dwPrinterNameSize+1);
|
|
strncpy( g_pszPartialPrinterName, pszProfileString, dwPrinterNameSize );
|
|
g_pszPartialPrinterName[dwPrinterNameSize] = '\0';
|
|
|
|
g_pszFullPrinterName = (LPSTR)ShimMalloc(pszPrinterDriver-pszProfileString+1);
|
|
strncpy( g_pszFullPrinterName, pszProfileString, (pszPrinterDriver - pszProfileString) );
|
|
g_pszFullPrinterName[pszPrinterDriver - pszProfileString] = '\0';
|
|
|
|
LeaveCriticalSection(&g_critSec);
|
|
|
|
DPFN( eDbgLevelInfo, "[GetProfileStringA] Partial: %s Full: %s\n",
|
|
g_pszPartialPrinterName, g_pszFullPrinterName );
|
|
DPFN( eDbgLevelInfo, "[GetProfileStringA] New: %s Size: %d\n",
|
|
lpReturnedString, strlen(lpReturnedString));
|
|
|
|
// return the modified string size.
|
|
return strlen(lpReturnedString);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Either an error occurred or its not asking for default printer.
|
|
// pass through.
|
|
return ORIGINAL_API(GetProfileStringA)( lpAppName,
|
|
lpKeyName,
|
|
lpDefault,
|
|
lpReturnedString,
|
|
nSize);
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
This stub function pulls the device name from the DEVMODE if pszDevice is NULL
|
|
and the DC is not for DISPLAY
|
|
|
|
--*/
|
|
|
|
|
|
HDC
|
|
APIHOOK(CreateDCA)(
|
|
LPCSTR pszDriver,
|
|
LPCSTR pszDevice,
|
|
LPCSTR pszPort,
|
|
CONST DEVMODEA *pdm
|
|
)
|
|
{
|
|
// if they've used a NULL device, but included a printer devmode,
|
|
// fill in the device name from the printer devmode
|
|
if (!pszDevice && pdm && (!pszDriver || _stricmp(pszDriver, "DISPLAY") != 0)) {
|
|
LOGN( eDbgLevelError, "[CreateDCA] App passed NULL for pszDevice. Fixing.");
|
|
pszDevice = (LPCSTR)pdm->dmDeviceName;
|
|
}
|
|
|
|
return ORIGINAL_API(CreateDCA)(
|
|
pszDriver,
|
|
pszDevice,
|
|
pszPort,
|
|
pdm
|
|
);
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
This stub function verifies that ResetDCA hasn't been handed an
|
|
uninitialized InitData.
|
|
|
|
--*/
|
|
HDC
|
|
APIHOOK(ResetDCA)(
|
|
HDC hdc,
|
|
CONST DEVMODEA *lpInitData
|
|
)
|
|
{
|
|
// Sanity checks to make sure we aren't getting garbage
|
|
// or bad values.
|
|
if ( lpInitData &&
|
|
lpInitData->dmSize > sizeof( DEVMODEA ) ||
|
|
( lpInitData->dmSpecVersion != 0x401 &&
|
|
lpInitData->dmSpecVersion != 0x400 &&
|
|
lpInitData->dmSpecVersion != 0x320 ) )
|
|
{
|
|
LOGN( eDbgLevelError, "[ResetDCA] App passed bad DEVMODE structure, nulling.");
|
|
return ORIGINAL_API(ResetDCA)( hdc, NULL );
|
|
}
|
|
|
|
return ORIGINAL_API(ResetDCA)( hdc, lpInitData );
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
These stub functions verify that SetPrinter has a valid handle
|
|
before proceeding.
|
|
|
|
--*/
|
|
BOOL
|
|
APIHOOK(SetPrinterA)(
|
|
HANDLE hPrinter, // handle to printer object
|
|
DWORD Level, // information level
|
|
LPBYTE pPrinter, // printer data buffer
|
|
DWORD Command // printer-state command
|
|
)
|
|
{
|
|
BOOL bRet;
|
|
|
|
if (hPrinter == NULL)
|
|
{
|
|
LOGN( eDbgLevelError, "[SetPrinterA] Called with null handle.");
|
|
if (pPrinter == NULL)
|
|
LOGN( eDbgLevelError, "[SetPrinterA] Called with null printer data buffer.");
|
|
return FALSE;
|
|
}
|
|
else if (pPrinter == NULL)
|
|
{
|
|
LOGN( eDbgLevelError, "[SetPrinterA] Called with null printer data buffer.");
|
|
return FALSE;
|
|
}
|
|
|
|
bRet= ORIGINAL_API(SetPrinterA)(
|
|
hPrinter,
|
|
Level,
|
|
pPrinter,
|
|
Command);
|
|
|
|
DPFN( eDbgLevelSpew, "[SetPrinterA] Level= %d Command= %d Ret= %d\n",
|
|
Level, Command, bRet );
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
This routine hooks the SetAbortProc and replaces their
|
|
callback with ours.
|
|
--*/
|
|
|
|
int
|
|
APIHOOK(SetAbortProc)(
|
|
HDC hdc, // handle to DC
|
|
ABORTPROC lpAbortProc // abort function
|
|
)
|
|
{
|
|
lpAbortProc = (ABORTPROC) HookCallback(lpAbortProc, AbortProcHook);
|
|
|
|
return ORIGINAL_API(SetAbortProc)( hdc, lpAbortProc );
|
|
}
|
|
|
|
/*++
|
|
When apps start printing, they set a viewport
|
|
on the printDC. They then call StartPage which has a different behaviour
|
|
on 9x and WinNT. On 9x, a next call to StartPage resets the DC attributes
|
|
to the default values.However on NT, the next call to StartPage does not
|
|
reset the DC attributes.
|
|
So, on 9x all subsequent output setup and drawing calls are carried
|
|
out with a (0,0) viewport but on NT the viewport is leftover from its
|
|
initial call. Since some apps(eg. Quicken 2000 and 2001) expect the API
|
|
setting the (0,0) viewport,the result will be that the text and the
|
|
lines are clipped on the left and top of the page.
|
|
|
|
Here we hook StartPage and call SetViewportOrgEx(hdc, 0, 0, NULL) to
|
|
set the viewport to (0,0) on every call to StartPage to emulate
|
|
the 9x behaviour.
|
|
|
|
--*/
|
|
|
|
BOOL
|
|
APIHOOK(StartPage)(
|
|
HDC hdc
|
|
)
|
|
{
|
|
|
|
if (SetViewportOrgEx(hdc, 0, 0, NULL))
|
|
{
|
|
// We have now made the device point(viewport) map to (0, 0).
|
|
LOGN( eDbgLevelInfo, "[StartPage] Setting the device point map to (0,0).");
|
|
}
|
|
else
|
|
{
|
|
LOGN( eDbgLevelError, "[StartPage] Unable to set device point map to (0,0)."
|
|
"Failed in a call to SetViewportOrgEx");
|
|
}
|
|
|
|
return ORIGINAL_API(StartPage)(hdc);
|
|
|
|
}
|
|
|
|
/*++
|
|
This stub function verifies that DeviceCapabilities is using a correct
|
|
printer name.
|
|
--*/
|
|
DWORD
|
|
APIHOOK(DeviceCapabilitiesA)(
|
|
LPCSTR pDevice,
|
|
LPCSTR pPort,
|
|
WORD fwCapability,
|
|
LPSTR pOutput,
|
|
CONST DEVMODE *pDevMode
|
|
)
|
|
{
|
|
DWORD dwRet;
|
|
|
|
if ( pDevice && g_pszPartialPrinterName && g_pszFullPrinterName &&
|
|
0 == strcmp(g_pszPartialPrinterName, pDevice) )
|
|
{
|
|
pDevice = g_pszFullPrinterName;
|
|
}
|
|
|
|
dwRet= ORIGINAL_API(DeviceCapabilitiesA)( pDevice,
|
|
pPort,
|
|
fwCapability,
|
|
pOutput,
|
|
pDevMode );
|
|
|
|
if ( pDevice && pPort )
|
|
{
|
|
DPFN( eDbgLevelSpew, "[DeviceCapabilitiesA] pDevice= %s pPort= %s fwC= %d Out= %x RC= %d\n",
|
|
pDevice, pPort, fwCapability, pOutput, dwRet );
|
|
}
|
|
else
|
|
{
|
|
DPFN( eDbgLevelSpew, "[DeviceCapabilitiesA] fwC= %d Out= %x RC= %d\n",
|
|
fwCapability, pOutput, dwRet );
|
|
}
|
|
|
|
return dwRet;
|
|
}
|
|
|
|
/*++
|
|
This stub function verifies that AddPrinterConnection is using a correct
|
|
printer name.
|
|
--*/
|
|
BOOL
|
|
APIHOOK(AddPrinterConnectionA)(
|
|
LPSTR pName
|
|
)
|
|
{
|
|
if ( pName && g_pszPartialPrinterName && g_pszFullPrinterName &&
|
|
0 == strcmp(g_pszPartialPrinterName, pName) )
|
|
{
|
|
pName = g_pszFullPrinterName;
|
|
}
|
|
|
|
return ORIGINAL_API(AddPrinterConnectionA)( pName );
|
|
}
|
|
|
|
/*++
|
|
This stub function verifies that DeletePrinterConnection is using a correct
|
|
printer name.
|
|
--*/
|
|
BOOL
|
|
APIHOOK(DeletePrinterConnectionA)(
|
|
LPSTR pName
|
|
)
|
|
{
|
|
if ( pName && g_pszPartialPrinterName && g_pszFullPrinterName &&
|
|
0 == strcmp(g_pszPartialPrinterName, pName) )
|
|
{
|
|
pName = g_pszFullPrinterName;
|
|
}
|
|
|
|
return ORIGINAL_API(DeletePrinterConnectionA)( pName );
|
|
}
|
|
|
|
/*++
|
|
This stub function verifies that PrintDlgA is using a correct
|
|
nFromPage and nToPage.
|
|
--*/
|
|
|
|
BOOL
|
|
APIHOOK(PrintDlgA)(
|
|
LPPRINTDLG lppd
|
|
)
|
|
{
|
|
// check nFromPage and nToPage for legal values.
|
|
if ( lppd )
|
|
{
|
|
DPFN( eDbgLevelSpew, "[PrintDlgA] nFromPage = %d nToPage = %d",
|
|
lppd->nFromPage, lppd->nToPage);
|
|
|
|
if ( lppd->nFromPage == 0xffff )
|
|
{
|
|
lppd->nFromPage = 0;
|
|
DPFN( eDbgLevelInfo, "[PrintDlgA] Setting nFromPage to 0." );
|
|
}
|
|
|
|
if ( lppd->nToPage == 0xffff)
|
|
{
|
|
lppd->nToPage = lppd->nFromPage;
|
|
DPFN( eDbgLevelInfo, "[PrintDlgA] Setting nToPage to %d.", lppd->nFromPage );
|
|
}
|
|
}
|
|
|
|
return ORIGINAL_API(PrintDlgA)(lppd);
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
Register hooked functions
|
|
|
|
--*/
|
|
BOOL
|
|
NOTIFY_FUNCTION(
|
|
DWORD fdwReason)
|
|
{
|
|
|
|
if (fdwReason == DLL_PROCESS_ATTACH)
|
|
{
|
|
OSVERSIONINFOEX osvi;
|
|
BOOL bOsVersionInfoEx;
|
|
|
|
InitializeCriticalSection(&g_critSec);
|
|
|
|
// Check to see if we are under win2k
|
|
ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
|
|
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
|
|
bOsVersionInfoEx = GetVersionEx ((OSVERSIONINFO *) &osvi);
|
|
|
|
if(bOsVersionInfoEx)
|
|
{
|
|
if ( osvi.dwPlatformId == VER_PLATFORM_WIN32_NT &&
|
|
osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0 )
|
|
{
|
|
g_bWin2k = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
HOOK_BEGIN
|
|
|
|
CALL_NOTIFY_FUNCTION
|
|
|
|
APIHOOK_ENTRY(WINSPOOL.DRV, DocumentPropertiesA);
|
|
APIHOOK_ENTRY(WINSPOOL.DRV, OpenPrinterA);
|
|
APIHOOK_ENTRY(WINSPOOL.DRV, SetPrinterA);
|
|
APIHOOK_ENTRY(WINSPOOL.DRV, EnumPrintersA);
|
|
APIHOOK_ENTRY(WINSPOOL.DRV, OpenPrinterA);
|
|
APIHOOK_ENTRY(WINSPOOL.DRV, DeviceCapabilitiesA);
|
|
APIHOOK_ENTRY(WINSPOOL.DRV, AddPrinterConnectionA);
|
|
APIHOOK_ENTRY(WINSPOOL.DRV, DeletePrinterConnectionA);
|
|
|
|
APIHOOK_ENTRY(COMDLG32.DLL, PrintDlgA);
|
|
|
|
APIHOOK_ENTRY(KERNEL32.DLL,GetProfileStringA);
|
|
|
|
APIHOOK_ENTRY(GDI32.DLL, CreateDCA);
|
|
APIHOOK_ENTRY(GDI32.DLL, ResetDCA);
|
|
APIHOOK_ENTRY(GDI32.DLL, SetAbortProc);
|
|
APIHOOK_ENTRY(GDI32.DLL, StartPage);
|
|
|
|
HOOK_END
|
|
|
|
|
|
IMPLEMENT_SHIM_END
|
|
|