mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1359 lines
34 KiB
1359 lines
34 KiB
/***************************************************************************
|
|
* This file contains functions that are specific for the shim mechanism
|
|
* implemented in Whistler
|
|
*
|
|
* Author: clupu (Feb 16, 2000)
|
|
*
|
|
* History:
|
|
*
|
|
* rparsons - 11/27/2000 - Fixed a bug that caused SDB name to be truncated.
|
|
* Example: Yoda's Challenege became Yoda's
|
|
*
|
|
\**************************************************************************/
|
|
|
|
#include "windows.h"
|
|
#include "commctrl.h"
|
|
#include "shlwapi.h"
|
|
#include <tchar.h>
|
|
|
|
#include "qfixapp.h"
|
|
#include "dbSupport.h"
|
|
#include "resource.h"
|
|
|
|
#define _WANT_TAG_INFO
|
|
|
|
extern "C" {
|
|
#include "shimdb.h"
|
|
|
|
BOOL
|
|
ShimdbcExecute(
|
|
LPCWSTR lpszCmdLine
|
|
);
|
|
}
|
|
|
|
|
|
extern HINSTANCE g_hInstance;
|
|
extern HWND g_hDlg;
|
|
extern TCHAR g_szSDBToDelete[MAX_PATH];
|
|
extern TCHAR g_szAppTitle[64];
|
|
|
|
#define MAX_CMD_LINE 1024
|
|
#define MAX_SHIM_DESCRIPTION 1024
|
|
#define MAX_SHIM_NAME 128
|
|
|
|
#define MAX_BUFFER_SIZE 1024
|
|
|
|
#define SHIM_FILE_LOG_NAME _T("QFixApp.log")
|
|
|
|
// Temp buffer to read UNICODE strings from the database.
|
|
TCHAR g_szData[MAX_BUFFER_SIZE];
|
|
|
|
#define MAX_XML_SIZE 1024 * 16
|
|
|
|
TCHAR g_szXML[MAX_XML_SIZE];
|
|
|
|
TCHAR g_szQFixAppLayerName[] = _T("!#RunLayer");
|
|
|
|
|
|
INT_PTR CALLBACK
|
|
ShowXMLDlgProc(
|
|
HWND hdlg,
|
|
UINT uMsg,
|
|
WPARAM wParam,
|
|
LPARAM lParam
|
|
)
|
|
/*++
|
|
ShowXMLDlgProc
|
|
|
|
Description: Show the dialog with the XML for the current selections.
|
|
|
|
--*/
|
|
{
|
|
int wCode = LOWORD(wParam);
|
|
int wNotifyCode = HIWORD(wParam);
|
|
|
|
switch (uMsg) {
|
|
case WM_INITDIALOG:
|
|
CenterWindow(hdlg);
|
|
SetDlgItemText(hdlg, IDC_XML, (LPTSTR)lParam);
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
switch (wCode) {
|
|
|
|
case IDCANCEL:
|
|
EndDialog(hdlg, TRUE);
|
|
break;
|
|
|
|
case IDC_SAVE_XML:
|
|
DoFileSave(hdlg);
|
|
EndDialog(hdlg, TRUE);
|
|
break;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
LPTSTR
|
|
ReadAndAllocateString(
|
|
PDB pdb,
|
|
TAGID tiString
|
|
)
|
|
{
|
|
TCHAR* psz;
|
|
|
|
g_szData[0] = 0;
|
|
|
|
SdbReadStringTag(pdb, tiString, g_szData, MAX_BUFFER_SIZE);
|
|
|
|
if (g_szData[0] == 0) {
|
|
LogMsg(_T("[ReadAndAllocateString] Couldn't read the string.\n"));
|
|
return NULL;
|
|
}
|
|
|
|
psz = (LPTSTR)HeapAlloc(GetProcessHeap(),
|
|
HEAP_ZERO_MEMORY,
|
|
sizeof(TCHAR) * (lstrlen(g_szData) + 1));
|
|
|
|
if (NULL == psz) {
|
|
return NULL;
|
|
} else {
|
|
lstrcpy(psz, g_szData);
|
|
}
|
|
|
|
return psz;
|
|
}
|
|
|
|
|
|
PFIX
|
|
ParseTagFlag(
|
|
PDB pdb,
|
|
TAGID tiFlag,
|
|
BOOL bAllFlags
|
|
)
|
|
/*++
|
|
ParseTagFlag
|
|
|
|
Description: Parse a Flag tag for the NAME, DESCRIPTION and MASK
|
|
|
|
--*/
|
|
{
|
|
TAGID tiFlagInfo;
|
|
TAG tWhichInfo;
|
|
PFIX pFix = NULL;
|
|
TCHAR* pszName = NULL;
|
|
TCHAR* pszDesc = NULL;
|
|
ULONGLONG ull;
|
|
BOOL bGeneral = (bAllFlags ? TRUE : FALSE);
|
|
|
|
tiFlagInfo = SdbGetFirstChild(pdb, tiFlag);
|
|
|
|
while (tiFlagInfo != 0) {
|
|
tWhichInfo = SdbGetTagFromTagID(pdb, tiFlagInfo);
|
|
|
|
switch (tWhichInfo) {
|
|
case TAG_GENERAL:
|
|
bGeneral = TRUE;
|
|
break;
|
|
|
|
case TAG_NAME:
|
|
pszName = ReadAndAllocateString(pdb, tiFlagInfo);
|
|
break;
|
|
|
|
case TAG_DESCRIPTION:
|
|
pszDesc = ReadAndAllocateString(pdb, tiFlagInfo);
|
|
break;
|
|
|
|
case TAG_FLAG_MASK_KERNEL:
|
|
case TAG_FLAG_MASK_USER:
|
|
//
|
|
// Read the mask even if we're not using it anywhere yet...
|
|
//
|
|
ull = SdbReadQWORDTag(pdb, tiFlagInfo, 0);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
tiFlagInfo = SdbGetNextChild(pdb, tiFlag, tiFlagInfo);
|
|
}
|
|
|
|
if (!bGeneral) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Done. Add the fix to the list.
|
|
//
|
|
pFix = (PFIX)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(FIX));
|
|
|
|
if (pFix == NULL || pszName == NULL) {
|
|
|
|
cleanup:
|
|
if (pFix != NULL) {
|
|
HeapFree(GetProcessHeap(), 0, pFix);
|
|
}
|
|
|
|
if (pszName != NULL) {
|
|
HeapFree(GetProcessHeap(), 0, pszName);
|
|
}
|
|
|
|
if (pszDesc != NULL) {
|
|
HeapFree(GetProcessHeap(), 0, pszDesc);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
pFix->pszName = pszName;
|
|
pFix->bLayer = FALSE;
|
|
pFix->bFlag = TRUE;
|
|
pFix->ullFlagMask = ull;
|
|
|
|
if (pszDesc != NULL) {
|
|
pFix->pszDesc = pszDesc;
|
|
} else {
|
|
TCHAR* pszNone;
|
|
|
|
pszNone = (TCHAR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PATH);
|
|
|
|
if (NULL == pszNone) {
|
|
return NULL;
|
|
}
|
|
|
|
*pszNone = 0;
|
|
|
|
LoadString(g_hInstance, IDS_NO_DESCR_AVAIL, pszNone, MAX_PATH);
|
|
pFix->pszDesc = pszNone;
|
|
}
|
|
|
|
return pFix;
|
|
}
|
|
|
|
PFIX
|
|
ParseTagShim(
|
|
PDB pdb,
|
|
TAGID tiShim,
|
|
BOOL bAllShims
|
|
)
|
|
/*++
|
|
ParseTagShim
|
|
|
|
Description: Parse a Shim tag for the NAME, SHORTNAME, DESCRIPTION ...
|
|
|
|
--*/
|
|
{
|
|
TAGID tiShimInfo;
|
|
TAG tWhichInfo;
|
|
PFIX pFix = NULL;
|
|
TCHAR* pszName = NULL;
|
|
TCHAR* pszDesc = NULL;
|
|
BOOL bGeneral = (bAllShims ? TRUE : FALSE);
|
|
|
|
tiShimInfo = SdbGetFirstChild(pdb, tiShim);
|
|
|
|
while (tiShimInfo != 0) {
|
|
tWhichInfo = SdbGetTagFromTagID(pdb, tiShimInfo);
|
|
|
|
switch (tWhichInfo) {
|
|
case TAG_GENERAL:
|
|
bGeneral = TRUE;
|
|
break;
|
|
|
|
case TAG_NAME:
|
|
pszName = ReadAndAllocateString(pdb, tiShimInfo);
|
|
break;
|
|
|
|
case TAG_DESCRIPTION:
|
|
pszDesc = ReadAndAllocateString(pdb, tiShimInfo);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
tiShimInfo = SdbGetNextChild(pdb, tiShim, tiShimInfo);
|
|
}
|
|
|
|
if (!bGeneral) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Done. Add the fix to the list.
|
|
//
|
|
pFix = (PFIX)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(FIX));
|
|
|
|
if (pFix == NULL || pszName == NULL) {
|
|
|
|
cleanup:
|
|
if (pFix != NULL) {
|
|
HeapFree(GetProcessHeap(), 0, pFix);
|
|
}
|
|
|
|
if (pszName != NULL) {
|
|
HeapFree(GetProcessHeap(), 0, pszName);
|
|
}
|
|
|
|
if (pszDesc != NULL) {
|
|
HeapFree(GetProcessHeap(), 0, pszDesc);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
pFix->pszName = pszName;
|
|
pFix->bLayer = FALSE;
|
|
pFix->bFlag = FALSE;
|
|
|
|
//
|
|
// If we didn't find a description, load it from the resource table.
|
|
//
|
|
if (pszDesc != NULL) {
|
|
pFix->pszDesc = pszDesc;
|
|
} else {
|
|
TCHAR* pszNone;
|
|
|
|
pszNone = (TCHAR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PATH);
|
|
|
|
if (NULL == pszNone) {
|
|
return NULL;
|
|
}
|
|
|
|
*pszNone = 0;
|
|
|
|
LoadString(g_hInstance, IDS_NO_DESCR_AVAIL, pszNone, MAX_PATH);
|
|
pFix->pszDesc = pszNone;
|
|
}
|
|
|
|
return pFix;
|
|
}
|
|
|
|
PFIX
|
|
ParseTagLayer(
|
|
PDB pdb,
|
|
TAGID tiLayer,
|
|
PFIX pFixHead
|
|
)
|
|
/*++
|
|
ParseTagLayer
|
|
|
|
Description: Parse a LAYER tag for the NAME and the SHIMs that it contains.
|
|
|
|
--*/
|
|
{
|
|
PFIX pFix = NULL;
|
|
TAGID tiName;
|
|
TAGID tiShim;
|
|
int nShimCount, nInd;
|
|
TCHAR* pszName = NULL;
|
|
PFIX* parrShim = NULL;
|
|
TCHAR** parrCmdLine = NULL;
|
|
|
|
tiName = SdbFindFirstTag(pdb, tiLayer, TAG_NAME);
|
|
|
|
if (tiName == TAGID_NULL) {
|
|
LogMsg(_T("[ParseTagLayer] Failed to get the name of the layer.\n"));
|
|
return NULL;
|
|
}
|
|
|
|
pszName = ReadAndAllocateString(pdb, tiName);
|
|
|
|
//
|
|
// Now loop through all the SHIMs that this LAYER consists of and
|
|
// allocate an array to keep all the pointers to the SHIMs' pFix
|
|
// structures. We do this in 2 passes. First we calculate how many
|
|
// SHIMs are in the layer, then we lookup their appropriate pFix-es.
|
|
//
|
|
tiShim = SdbFindFirstTag(pdb, tiLayer, TAG_SHIM_REF);
|
|
|
|
nShimCount = 0;
|
|
|
|
while (tiShim != TAGID_NULL) {
|
|
nShimCount++;
|
|
tiShim = SdbFindNextTag(pdb, tiLayer, tiShim);
|
|
}
|
|
|
|
parrShim = (PFIX*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PFIX) * (nShimCount + 1));
|
|
parrCmdLine = (TCHAR**)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(TCHAR*) * (nShimCount + 1));
|
|
|
|
//
|
|
// Done. Add the fix to the list.
|
|
//
|
|
pFix = (PFIX)HeapAlloc(GetProcessHeap(), 0, sizeof(FIX));
|
|
|
|
if (pFix == NULL || parrCmdLine == NULL || pszName == NULL || parrShim == NULL) {
|
|
|
|
cleanup:
|
|
if (pFix != NULL) {
|
|
HeapFree(GetProcessHeap(), 0, pFix);
|
|
}
|
|
|
|
if (parrCmdLine != NULL) {
|
|
HeapFree(GetProcessHeap(), 0, parrCmdLine);
|
|
}
|
|
|
|
if (parrShim != NULL) {
|
|
HeapFree(GetProcessHeap(), 0, parrShim);
|
|
}
|
|
|
|
if (pszName != NULL) {
|
|
HeapFree(GetProcessHeap(), 0, pszName);
|
|
}
|
|
|
|
LogMsg(_T("[ParseTagLayer] Memory allocation error.\n"));
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Now fill out the array of PFIX pointers and cmd lines.
|
|
//
|
|
tiShim = SdbFindFirstTag(pdb, tiLayer, TAG_SHIM_REF);
|
|
|
|
nInd = 0;
|
|
|
|
while (tiShim != TAGID_NULL) {
|
|
TCHAR szShimName[MAX_SHIM_NAME] = _T("");
|
|
PFIX pFixWalk;
|
|
|
|
tiName = SdbFindFirstTag(pdb, tiShim, TAG_NAME);
|
|
|
|
if (tiName == TAGID_NULL) {
|
|
LogMsg(_T("[ParseTagLayer] Failed to get the name of the Shim.\n"));
|
|
goto cleanup;
|
|
}
|
|
|
|
SdbReadStringTag(pdb, tiName, szShimName, MAX_SHIM_NAME);
|
|
|
|
if (szShimName[0] == 0) {
|
|
LogMsg(_T("[ParseTagLayer] Couldn't read the name of the Shim.\n"));
|
|
goto cleanup;
|
|
}
|
|
|
|
pFixWalk = pFixHead;
|
|
|
|
while (pFixWalk != NULL) {
|
|
if (!pFixWalk->bLayer) {
|
|
if (lstrcmpi(pFixWalk->pszName, szShimName) == 0) {
|
|
parrShim[nInd] = pFixWalk;
|
|
|
|
//
|
|
// Now get the command line for this Shim in the layer.
|
|
//
|
|
tiName = SdbFindFirstTag(pdb, tiShim, TAG_COMMAND_LINE);
|
|
|
|
if (tiName != TAGID_NULL) {
|
|
parrCmdLine[nInd] = ReadAndAllocateString(pdb, tiName);
|
|
}
|
|
|
|
nInd++;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
pFixWalk = pFixWalk->pNext;
|
|
}
|
|
|
|
tiShim = SdbFindNextTag(pdb, tiLayer, tiShim);
|
|
}
|
|
|
|
pFix->pszName = pszName;
|
|
pFix->bLayer = TRUE;
|
|
pFix->bFlag = FALSE;
|
|
pFix->parrShim = parrShim;
|
|
pFix->parrCmdLine = parrCmdLine;
|
|
|
|
return pFix;
|
|
}
|
|
|
|
BOOL
|
|
IsSDBFromSP2(
|
|
void
|
|
)
|
|
/*++
|
|
IsSDBFromSP2
|
|
|
|
Description: Determine if the SDB is from Service Pack 2.
|
|
|
|
--*/
|
|
{
|
|
BOOL fResult = FALSE;
|
|
PDB pdb;
|
|
TAGID tiDatabase;
|
|
TAGID tiLibrary;
|
|
TAGID tiChild;
|
|
PFIX pFix;
|
|
TCHAR szSDBPath[MAX_PATH];
|
|
|
|
if (!GetSystemWindowsDirectory(szSDBPath, MAX_PATH)) {
|
|
return FALSE;
|
|
}
|
|
|
|
_tcscat(szSDBPath, _T("\\apppatch\\sysmain.sdb"));
|
|
|
|
//
|
|
// Open the shim database.
|
|
//
|
|
pdb = SdbOpenDatabase(szSDBPath, DOS_PATH);
|
|
|
|
if (!pdb) {
|
|
LogMsg(_T("[IsSDBFromSP2] Cannot open shim DB '%s'\n"), szSDBPath);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Now browse the shim DB and look only for tags Shim within
|
|
// the LIBRARY list tag.
|
|
//
|
|
tiDatabase = SdbFindFirstTag(pdb, TAGID_ROOT, TAG_DATABASE);
|
|
|
|
if (tiDatabase == TAGID_NULL) {
|
|
LogMsg(_T("[IsSDBFromSP2] Cannot find TAG_DATABASE under the root tag\n"));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Get TAG_LIBRARY.
|
|
//
|
|
tiLibrary = SdbFindFirstTag(pdb, tiDatabase, TAG_LIBRARY);
|
|
|
|
if (tiLibrary == TAGID_NULL) {
|
|
LogMsg(_T("[IsSDBFromSP2] Cannot find TAG_LIBRARY under the TAG_DATABASE tag\n"));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Loop get the first shim in the library.
|
|
//
|
|
tiChild = SdbFindFirstTag(pdb, tiLibrary, TAG_SHIM);
|
|
|
|
if (tiChild == NULL) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Get information about the first shim listed.
|
|
//
|
|
pFix = ParseTagShim(pdb, tiChild, TRUE);
|
|
|
|
if (NULL == pFix) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// If the first shim listed is 2GbGetDiskFreeSpace, this is SP2.
|
|
//
|
|
if (!(_tcsicmp(pFix->pszName, _T("2GbGetDiskFreeSpace.dll")))) {
|
|
fResult = TRUE;
|
|
}
|
|
|
|
Cleanup:
|
|
SdbCloseDatabase(pdb);
|
|
|
|
return (fResult);
|
|
}
|
|
|
|
PFIX
|
|
ReadFixesFromSdb(
|
|
LPTSTR pszSdb,
|
|
BOOL bAllFixes
|
|
)
|
|
/*++
|
|
ReadFixesFromSdb
|
|
|
|
Description: Query the database and enumerate all available shims fixes.
|
|
|
|
--*/
|
|
{
|
|
int nLen = 0;
|
|
TCHAR* pszShimDB = NULL;
|
|
PDB pdb;
|
|
TAGID tiDatabase;
|
|
TAGID tiLibrary;
|
|
TAGID tiChild;
|
|
PFIX pFixHead = NULL;
|
|
PFIX pFix;
|
|
|
|
nLen = lstrlen(pszSdb);
|
|
|
|
if (0 == nLen) {
|
|
LogMsg(_T("[ReadFixesFromSdb] Invalid SDB path passed through pszSdb\n"));
|
|
return NULL;
|
|
}
|
|
|
|
pszShimDB = (TCHAR*)HeapAlloc(GetProcessHeap(),
|
|
HEAP_ZERO_MEMORY,
|
|
(nLen + MAX_PATH)*sizeof(TCHAR));
|
|
|
|
if (pszShimDB == NULL) {
|
|
LogMsg(_T("[ReadFixesFromSdb] Failed to allocate memory\n"));
|
|
return NULL;
|
|
}
|
|
|
|
GetSystemWindowsDirectory(pszShimDB, MAX_PATH);
|
|
lstrcat(pszShimDB, _T("\\AppPatch\\"));
|
|
lstrcat(pszShimDB, pszSdb);
|
|
|
|
//
|
|
// Open the shim database.
|
|
//
|
|
pdb = SdbOpenDatabase(pszShimDB, DOS_PATH);
|
|
|
|
if (!pdb) {
|
|
LogMsg(_T("[ReadFixesFromSdb] Cannot open shim DB '%s'\n"), pszShimDB);
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Now browse the shim DB and look only for tags Shim within
|
|
// the LIBRARY list tag.
|
|
//
|
|
tiDatabase = SdbFindFirstTag(pdb, TAGID_ROOT, TAG_DATABASE);
|
|
|
|
if (tiDatabase == TAGID_NULL) {
|
|
LogMsg(_T("[ReadFixesFromSdb] Cannot find TAG_DATABASE under the root tag\n"));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Get TAG_LIBRARY.
|
|
//
|
|
tiLibrary = SdbFindFirstTag(pdb, tiDatabase, TAG_LIBRARY);
|
|
|
|
if (tiLibrary == TAGID_NULL) {
|
|
LogMsg(_T("[ReadFixesFromSdb] Cannot find TAG_LIBRARY under the TAG_DATABASE tag\n"));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Loop through all TAG_SHIM tags within TAG_LIBRARY.
|
|
//
|
|
tiChild = SdbFindFirstTag(pdb, tiLibrary, TAG_SHIM);
|
|
|
|
while (tiChild != TAGID_NULL) {
|
|
pFix = ParseTagShim(pdb, tiChild, bAllFixes);
|
|
|
|
if (pFix != NULL) {
|
|
pFix->pNext = pFixHead;
|
|
pFixHead = pFix;
|
|
}
|
|
|
|
tiChild = SdbFindNextTag(pdb, tiLibrary, tiChild);
|
|
}
|
|
|
|
//
|
|
// Loop through all TAG_FLAG tags within TAG_LIBRARY.
|
|
//
|
|
tiChild = SdbFindFirstTag(pdb, tiLibrary, TAG_FLAG);
|
|
|
|
while (tiChild != TAGID_NULL) {
|
|
pFix = ParseTagFlag(pdb, tiChild, bAllFixes);
|
|
|
|
if (pFix != NULL) {
|
|
pFix->pNext = pFixHead;
|
|
pFixHead = pFix;
|
|
}
|
|
|
|
tiChild = SdbFindNextTag(pdb, tiLibrary, tiChild);
|
|
}
|
|
|
|
//
|
|
// Loop through all TAG_LAYER tags within TAG_DATABASE.
|
|
//
|
|
tiChild = SdbFindFirstTag(pdb, tiDatabase, TAG_LAYER);
|
|
|
|
while (tiChild != TAGID_NULL) {
|
|
|
|
pFix = ParseTagLayer(pdb, tiChild, pFixHead);
|
|
|
|
if (pFix != NULL) {
|
|
pFix->pNext = pFixHead;
|
|
pFixHead = pFix;
|
|
}
|
|
|
|
tiChild = SdbFindNextTag(pdb, tiDatabase, tiChild);
|
|
}
|
|
|
|
Cleanup:
|
|
SdbCloseDatabase(pdb);
|
|
|
|
HeapFree(GetProcessHeap(), 0, pszShimDB);
|
|
|
|
return pFixHead;
|
|
}
|
|
|
|
#define ADD_AND_CHECK(cbSizeX, cbCrtSizeX, pszDst) \
|
|
{ \
|
|
TCHAR* pszSrc = szBuffer; \
|
|
\
|
|
while (*pszSrc != 0) { \
|
|
\
|
|
if (cbSizeX - cbCrtSizeX <= 5) { \
|
|
LogMsg(_T("[ADD_AND_CHECK] Out of space.\n")); \
|
|
return FALSE; \
|
|
} \
|
|
\
|
|
if (*pszSrc == _T('&')) { \
|
|
lstrcpy(pszDst, _T("&")); \
|
|
pszDst += 5; \
|
|
cbCrtSizeX += 5; \
|
|
} else { \
|
|
*pszDst++ = *pszSrc; \
|
|
cbCrtSizeX++; \
|
|
} \
|
|
pszSrc++; \
|
|
} \
|
|
*pszDst = 0; \
|
|
cbCrtSizeX++; \
|
|
}
|
|
|
|
BOOL
|
|
CollectShims(
|
|
HWND hListShims,
|
|
LPTSTR pszXML,
|
|
LPCTSTR pszLayerName,
|
|
BOOL fAddW2K,
|
|
int cbSize
|
|
)
|
|
/*++
|
|
CollectShims
|
|
|
|
Description: Collects all the shims from the list view
|
|
and generates the XML in pszXML
|
|
|
|
--*/
|
|
{
|
|
int cShims = 0, nShimsApplied = 0, nIndex;
|
|
int cbCrtSize = 0;
|
|
BOOL fReturn = FALSE;
|
|
LVITEM lvi;
|
|
TCHAR szBuffer[1024];
|
|
|
|
//
|
|
// Build the header, then walk each of the shims and
|
|
// determine if they're selected.
|
|
//
|
|
wsprintf(szBuffer, _T(" <LIBRARY>\r\n <LAYER NAME=\"%s\">\r\n"), pszLayerName + 2);
|
|
ADD_AND_CHECK(cbSize, cbCrtSize, pszXML);
|
|
|
|
cShims = ListView_GetItemCount(hListShims);
|
|
|
|
for (nIndex = 0; nIndex < cShims; nIndex++) {
|
|
|
|
fReturn = ListView_GetCheckState(hListShims, nIndex);
|
|
|
|
if (fReturn) {
|
|
//
|
|
// This shim is selected - add it to the XML.
|
|
//
|
|
lvi.mask = LVIF_PARAM;
|
|
lvi.iItem = nIndex;
|
|
lvi.iSubItem = 0;
|
|
|
|
ListView_GetItem(hListShims, &lvi);
|
|
|
|
PFIX pFix = (PFIX)lvi.lParam;
|
|
PMODULE pModule = pFix->pModule;
|
|
|
|
if (pFix->bFlag) {
|
|
wsprintf(szBuffer, _T(" <FLAG NAME=\"%s\"/>\r\n"),
|
|
pFix->pszName);
|
|
|
|
} else {
|
|
|
|
//
|
|
// Check for module include/exclude so we know how to open/close the XML.
|
|
//
|
|
if (NULL != pModule) {
|
|
|
|
if (NULL != pFix->pszCmdLine) {
|
|
|
|
wsprintf(szBuffer, _T(" <SHIM NAME=\"%s\" COMMAND_LINE=\"%s\">\r\n"),
|
|
pFix->pszName,
|
|
pFix->pszCmdLine);
|
|
|
|
} else {
|
|
|
|
wsprintf(szBuffer, _T(" <SHIM NAME=\"%s\">\r\n"),
|
|
pFix->pszName);
|
|
|
|
}
|
|
|
|
ADD_AND_CHECK(cbSize, cbCrtSize, pszXML);
|
|
|
|
//
|
|
// Add the modules to the XML.
|
|
//
|
|
while (NULL != pModule) {
|
|
wsprintf(szBuffer, _T(" <%s MODULE=\"%s\"/>\r\n"),
|
|
pModule->fInclude ? _T("INCLUDE") : _T("EXCLUDE"),
|
|
pModule->pszName);
|
|
|
|
pModule = pModule->pNext;
|
|
|
|
ADD_AND_CHECK(cbSize, cbCrtSize, pszXML);
|
|
}
|
|
|
|
//
|
|
// Close the SHIM tag.
|
|
//
|
|
wsprintf(szBuffer, _T(" </SHIM>\r\n"),
|
|
pFix->pszName);
|
|
|
|
} else {
|
|
|
|
//
|
|
// No include/exclude was provided - just build the shim tag normally.
|
|
//
|
|
if (NULL != pFix->pszCmdLine) {
|
|
|
|
wsprintf(szBuffer, _T(" <SHIM NAME=\"%s\" COMMAND_LINE=\"%s\"/>\r\n"),
|
|
pFix->pszName,
|
|
pFix->pszCmdLine);
|
|
|
|
} else {
|
|
|
|
wsprintf(szBuffer, _T(" <SHIM NAME=\"%s\"/>\r\n"),
|
|
pFix->pszName);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
ADD_AND_CHECK(cbSize, cbCrtSize, pszXML);
|
|
|
|
nShimsApplied++;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If this is Windows 2000, add Win2kPropagateLayer.
|
|
//
|
|
if (fAddW2K) {
|
|
lstrcpy(szBuffer, _T(" <SHIM NAME=\"Win2kPropagateLayer\"/>\r\n"));
|
|
ADD_AND_CHECK(cbSize, cbCrtSize, pszXML);
|
|
}
|
|
|
|
LogMsg(_T("[CollectShims] %d shim(s) selected\n"), nShimsApplied);
|
|
|
|
//
|
|
// Close the open tags.
|
|
//
|
|
lstrcpy(szBuffer, _T(" </LAYER>\r\n </LIBRARY>\r\n"));
|
|
|
|
ADD_AND_CHECK(cbSize, cbCrtSize, pszXML);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL
|
|
CollectFileAttributes(
|
|
HWND hTreeFiles,
|
|
LPTSTR pszXML,
|
|
int cbSize,
|
|
LPCTSTR pszShortName,
|
|
DWORD binaryType
|
|
)
|
|
/*++
|
|
CollectFileAttributes
|
|
|
|
Description: Collects the attributes of all the files in the tree
|
|
and generates the XML in pszXML.
|
|
|
|
--*/
|
|
{
|
|
HTREEITEM hBinItem;
|
|
HTREEITEM hItem;
|
|
PATTRINFO pAttrInfo;
|
|
UINT State;
|
|
TVITEM item;
|
|
int cbCrtSize = 0;
|
|
TCHAR szItem[MAX_PATH];
|
|
TCHAR szBuffer[1024];
|
|
|
|
wsprintf(szBuffer,
|
|
_T(" <APP NAME=\"%s\" VENDOR=\"Unknown\">\r\n")
|
|
_T(" <%s NAME=\"%s\""),
|
|
pszShortName,
|
|
(binaryType == SCS_32BIT_BINARY ? _T("EXE") : _T("EXE - ERROR: 16 BIT BINARY")),
|
|
pszShortName);
|
|
|
|
ADD_AND_CHECK(cbSize, cbCrtSize, pszXML);
|
|
|
|
//
|
|
// First get the main EXE.
|
|
//
|
|
hBinItem = TreeView_GetChild(hTreeFiles, TVI_ROOT);
|
|
|
|
item.mask = TVIF_HANDLE | TVIF_PARAM | TVIF_TEXT;
|
|
item.hItem = hBinItem;
|
|
item.pszText = szItem;
|
|
item.cchTextMax = MAX_PATH;
|
|
|
|
TreeView_GetItem(hTreeFiles, &item);
|
|
|
|
pAttrInfo = (PATTRINFO)(item.lParam);
|
|
|
|
hItem = TreeView_GetChild(hTreeFiles, hBinItem);
|
|
|
|
while (hItem != NULL) {
|
|
item.mask = TVIF_HANDLE | TVIF_PARAM | TVIF_STATE | TVIF_TEXT;
|
|
item.hItem = hItem;
|
|
|
|
TreeView_GetItem(hTreeFiles, &item);
|
|
|
|
State = item.state & TVIS_STATEIMAGEMASK;
|
|
|
|
if (State) {
|
|
if (((State >> 12) & 0x03) == 2) {
|
|
|
|
wsprintf(szBuffer, _T(" %s"), (LPSTR)item.pszText);
|
|
|
|
ADD_AND_CHECK(cbSize, cbCrtSize, pszXML);
|
|
}
|
|
}
|
|
|
|
hItem = TreeView_GetNextSibling(hTreeFiles, hItem);
|
|
}
|
|
|
|
lstrcpy(szBuffer, _T(">\r\n"));
|
|
|
|
ADD_AND_CHECK(cbSize, cbCrtSize, pszXML);
|
|
|
|
//
|
|
// Done with the main binary. Now enumerate the matching files.
|
|
//
|
|
hBinItem = TreeView_GetNextSibling(hTreeFiles, hBinItem);
|
|
|
|
while (hBinItem != NULL) {
|
|
|
|
item.mask = TVIF_HANDLE | TVIF_PARAM | TVIF_STATE | TVIF_TEXT;
|
|
item.hItem = hBinItem;
|
|
item.pszText = szItem;
|
|
item.cchTextMax = MAX_PATH;
|
|
|
|
TreeView_GetItem(hTreeFiles, &item);
|
|
|
|
pAttrInfo = (PATTRINFO)(item.lParam);
|
|
|
|
wsprintf(szBuffer, _T(" <MATCHING_FILE NAME=\"%s\""), szItem);
|
|
|
|
ADD_AND_CHECK(cbSize, cbCrtSize, pszXML);
|
|
|
|
hItem = TreeView_GetChild(hTreeFiles, hBinItem);
|
|
|
|
while (hItem != NULL) {
|
|
item.mask = TVIF_HANDLE | TVIF_PARAM | TVIF_STATE | TVIF_TEXT;
|
|
item.hItem = hItem;
|
|
|
|
TreeView_GetItem(hTreeFiles, &item);
|
|
|
|
State = item.state & TVIS_STATEIMAGEMASK;
|
|
|
|
if (State) {
|
|
if (((State >> 12) & 0x03) == 2) {
|
|
|
|
wsprintf(szBuffer, _T(" %s"), (LPSTR)item.pszText);
|
|
|
|
ADD_AND_CHECK(cbSize, cbCrtSize, pszXML);
|
|
}
|
|
}
|
|
|
|
hItem = TreeView_GetNextSibling(hTreeFiles, hItem);
|
|
}
|
|
|
|
lstrcpy(szBuffer, _T("/>\r\n"));
|
|
|
|
ADD_AND_CHECK(cbSize, cbCrtSize, pszXML);
|
|
|
|
hBinItem = TreeView_GetNextSibling(hTreeFiles, hBinItem);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL
|
|
CreateSDBFile(
|
|
LPCTSTR pszShortName,
|
|
LPTSTR pszSDBName
|
|
)
|
|
/*++
|
|
CreateSDBFile
|
|
|
|
Description: Creates the XML file on the user's hard drive and
|
|
generates the SDB using shimdbc.
|
|
--*/
|
|
{
|
|
TCHAR* psz = NULL;
|
|
TCHAR* pszTemp = NULL;
|
|
TCHAR szXMLFile[128];
|
|
TCHAR szSDBFile[128];
|
|
TCHAR szAppPatchDir[MAX_PATH] = _T("");
|
|
WCHAR szCompiler[MAX_PATH];
|
|
HANDLE hFile;
|
|
DWORD dwBytesWritten = 0;
|
|
|
|
//
|
|
// Create the XML file.
|
|
//
|
|
GetSystemWindowsDirectory(szAppPatchDir, MAX_PATH);
|
|
lstrcat(szAppPatchDir, _T("\\AppPatch"));
|
|
|
|
SetCurrentDirectory(szAppPatchDir);
|
|
|
|
pszTemp = (TCHAR*)HeapAlloc(GetProcessHeap(),
|
|
HEAP_ZERO_MEMORY,
|
|
(lstrlen(pszShortName)+1)*sizeof(TCHAR));
|
|
|
|
if (NULL == pszTemp) {
|
|
LogMsg(_T("[CreateSDBFile] Failed to allocate memory\n"));
|
|
return FALSE;
|
|
}
|
|
|
|
lstrcpy(pszTemp, pszShortName);
|
|
|
|
psz = PathFindExtension(pszTemp);
|
|
|
|
if (NULL == psz) {
|
|
return FALSE;
|
|
} else {
|
|
*psz = '\0';
|
|
}
|
|
|
|
// Build the XML file path
|
|
wsprintf(szXMLFile, _T("%s\\%s.xml"), szAppPatchDir, pszTemp);
|
|
|
|
// Build the SDB file path
|
|
wsprintf(szSDBFile, _T("%s\\%s.sdb"), szAppPatchDir, pszTemp);
|
|
|
|
hFile = CreateFile(szXMLFile,
|
|
GENERIC_WRITE,
|
|
0,
|
|
NULL,
|
|
CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL);
|
|
|
|
if (hFile == INVALID_HANDLE_VALUE) {
|
|
LogMsg(_T("[CreateSDBFile] CreateFile '%s' failed 0x%X.\n"),
|
|
szXMLFile, GetLastError());
|
|
return FALSE;
|
|
}
|
|
|
|
if (!(WriteFile(hFile,
|
|
g_szXML,
|
|
lstrlen(g_szXML) * sizeof(TCHAR),
|
|
&dwBytesWritten,
|
|
NULL))) {
|
|
LogMsg(_T("[CreateSDBFile] WriteFile '%s' failed 0x%X.\n"),
|
|
szXMLFile, GetLastError());
|
|
CloseHandle(hFile);
|
|
return FALSE;
|
|
}
|
|
|
|
CloseHandle(hFile);
|
|
|
|
//
|
|
// Set up the command line for shimdbc and generate an SDB.
|
|
//
|
|
wsprintf(szCompiler,
|
|
_T("shimdbc.exe fix -q \"%s\" \"%s\""),
|
|
szXMLFile,
|
|
szSDBFile);
|
|
|
|
if (!ShimdbcExecute(szCompiler)) {
|
|
LogMsg(_T("[CreateSDBFile] CreateProcess \"%s\" failed 0x%X\n"),
|
|
szCompiler, GetLastError());
|
|
return FALSE;
|
|
}
|
|
|
|
// Give the SDB name back to the caller if they want it.
|
|
if (pszSDBName) {
|
|
lstrcpy(pszSDBName, szSDBFile);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL
|
|
CollectFix(
|
|
HWND hListLayers,
|
|
HWND hListShims,
|
|
HWND hTreeFiles,
|
|
LPCTSTR pszShortName,
|
|
LPCTSTR pszFullPath,
|
|
DWORD dwFlags,
|
|
LPTSTR pszFileCreated
|
|
)
|
|
/*++
|
|
CollectFix
|
|
|
|
Description: Adds the necessary support to apply shim(s) for
|
|
the specified app.
|
|
--*/
|
|
{
|
|
BOOL fAddW2K = FALSE;
|
|
TCHAR* pszXML;
|
|
TCHAR szError[MAX_PATH];
|
|
TCHAR szXmlFile[MAX_PATH];
|
|
TCHAR* pszLayerName = NULL;
|
|
TCHAR szBuffer[1024];
|
|
TCHAR szLayer[128] = _T("!#");
|
|
TCHAR szUnicodeHdr[2] = { 0xFEFF, 0 };
|
|
int cbCrtXmlSize = 0, cbLength;
|
|
int cbXmlSize = MAX_XML_SIZE;
|
|
DWORD dwBinaryType = SCS_32BIT_BINARY;
|
|
|
|
g_szXML[0] = 0;
|
|
pszXML = g_szXML;
|
|
|
|
//
|
|
// Create a layer to run all the apps under it.
|
|
//
|
|
if (dwFlags & CFF_USELAYERTAB) {
|
|
|
|
LRESULT lSel;
|
|
|
|
lSel = SendMessage(hListLayers, LB_GETCURSEL, 0, 0);
|
|
|
|
if (lSel == LB_ERR) {
|
|
LoadString(g_hInstance, IDS_LAYER_SELECT, szError, MAX_PATH);
|
|
MessageBox(g_hDlg, szError, g_szAppTitle, MB_OK | MB_ICONEXCLAMATION);
|
|
return TRUE;
|
|
}
|
|
|
|
SendMessage(hListLayers, LB_GETTEXT, lSel, (LPARAM)(szLayer + 2));
|
|
|
|
pszLayerName = szLayer;
|
|
} else {
|
|
pszLayerName = g_szQFixAppLayerName;
|
|
}
|
|
|
|
//
|
|
// Determine the binary type.
|
|
//
|
|
GetBinaryType(pszFullPath, &dwBinaryType);
|
|
|
|
//
|
|
// Build the header for the XML.
|
|
//
|
|
wsprintf(szBuffer,
|
|
_T("%s<?xml version=\"1.0\" encoding=\"UTF-16\"?>\r\n")
|
|
_T("<DATABASE NAME=\"%s custom database\">\r\n"),
|
|
szUnicodeHdr,
|
|
pszShortName);
|
|
|
|
|
|
ADD_AND_CHECK(cbXmlSize, cbCrtXmlSize, pszXML);
|
|
|
|
//
|
|
// If this is Windows 2000 and they're not using
|
|
// a predefined layer, add Win2kPropagateLayer.
|
|
//
|
|
if ((dwFlags & CFF_ADDW2KSUPPORT) && !(dwFlags & CFF_USELAYERTAB)) {
|
|
fAddW2K = TRUE;
|
|
}
|
|
|
|
//
|
|
// If we're not using a predefined layer, build one manually.
|
|
//
|
|
if (!(dwFlags & CFF_USELAYERTAB)) {
|
|
CollectShims(hListShims,
|
|
pszXML,
|
|
pszLayerName,
|
|
fAddW2K,
|
|
cbXmlSize - cbCrtXmlSize);
|
|
}
|
|
|
|
cbLength = lstrlen(pszXML);
|
|
pszXML += cbLength;
|
|
cbCrtXmlSize += cbLength + 1;
|
|
|
|
//
|
|
// Retrieve attributes and matching files from the tree.
|
|
// If we're displaying the XML, pass the real binary type.
|
|
// If we're not, which means we're creating fix support or
|
|
// running the application, let the callee think it's a 32-bit module.
|
|
//
|
|
if (dwFlags & CFF_SHOWXML) {
|
|
CollectFileAttributes(hTreeFiles,
|
|
pszXML,
|
|
cbXmlSize - cbCrtXmlSize,
|
|
pszShortName,
|
|
dwBinaryType);
|
|
} else {
|
|
CollectFileAttributes(hTreeFiles,
|
|
pszXML,
|
|
cbXmlSize - cbCrtXmlSize,
|
|
pszShortName,
|
|
SCS_32BIT_BINARY);
|
|
|
|
}
|
|
|
|
cbLength = lstrlen(pszXML);
|
|
pszXML += cbLength;
|
|
cbCrtXmlSize += cbLength + 1;
|
|
|
|
//
|
|
// Add our layer to this EXE.
|
|
//
|
|
wsprintf(szBuffer, _T(" <LAYER NAME=\"%s\"/>\r\n"), pszLayerName + 2);
|
|
ADD_AND_CHECK(cbXmlSize, cbCrtXmlSize, pszXML);
|
|
|
|
//
|
|
// Finally, close the open tags.
|
|
//
|
|
lstrcpy(szBuffer, _T(" </EXE>\r\n </APP>\r\n</DATABASE>"));
|
|
ADD_AND_CHECK(cbXmlSize, cbCrtXmlSize, pszXML);
|
|
|
|
LogMsg(_T("[CollectFix] XML:\n%s\n"), g_szXML);
|
|
|
|
//
|
|
// Display the XML if the user wants to see it.
|
|
//
|
|
if (dwFlags & CFF_SHOWXML) {
|
|
DialogBoxParam(g_hInstance,
|
|
MAKEINTRESOURCE(IDD_XML),
|
|
g_hDlg,
|
|
ShowXMLDlgProc,
|
|
(LPARAM)(g_szXML + 1));
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Create the SDB file for the user.
|
|
//
|
|
if (!(CreateSDBFile(pszShortName, pszFileCreated))) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Delete the XML file that we created.
|
|
//
|
|
lstrcpy(szXmlFile, pszFileCreated);
|
|
PathRenameExtension(szXmlFile, _T(".xml"));
|
|
DeleteFile(szXmlFile);
|
|
|
|
//
|
|
// Set the SHIM_FILE_LOG env var.
|
|
//
|
|
if (dwFlags & CFF_SHIMLOG) {
|
|
DeleteFile(SHIM_FILE_LOG_NAME);
|
|
SetEnvironmentVariable(_T("SHIM_FILE_LOG"), SHIM_FILE_LOG_NAME);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
CleanupSupportForApp(
|
|
TCHAR* pszShortName
|
|
)
|
|
/*++
|
|
CleanupSupportForApp
|
|
|
|
Description: Cleanup the mess after we're done with
|
|
the specified app.
|
|
--*/
|
|
{
|
|
TCHAR* pszShimDB = NULL;
|
|
int nLen = 0;
|
|
|
|
nLen = lstrlen(pszShortName);
|
|
|
|
pszShimDB = (TCHAR*)HeapAlloc(GetProcessHeap(),
|
|
HEAP_ZERO_MEMORY,
|
|
(nLen + MAX_PATH)*sizeof(TCHAR));
|
|
|
|
if (NULL == pszShimDB) {
|
|
LogMsg(_T("[CleanupSupportForApp] Failed to allocate memory\n"));
|
|
return;
|
|
}
|
|
|
|
GetSystemWindowsDirectory(pszShimDB, MAX_PATH);
|
|
lstrcat(pszShimDB, _T("\\AppPatch"));
|
|
|
|
SetCurrentDirectory(pszShimDB);
|
|
|
|
lstrcat(pszShimDB, _T("\\"));
|
|
lstrcat(pszShimDB, pszShortName);
|
|
|
|
//
|
|
// Attempt to delete the XML file.
|
|
//
|
|
PathRenameExtension(pszShimDB, _T(".xml"));
|
|
|
|
DeleteFile(pszShimDB);
|
|
|
|
//
|
|
// Remove the previous SDB file, if one exists.
|
|
//
|
|
if (g_szSDBToDelete[0]) {
|
|
InstallSDB(g_szSDBToDelete, FALSE);
|
|
DeleteFile(g_szSDBToDelete);
|
|
}
|
|
|
|
HeapFree(GetProcessHeap(), 0, pszShimDB);
|
|
}
|
|
|
|
void
|
|
ShowShimLog(
|
|
void
|
|
)
|
|
/*++
|
|
ShowShimLog
|
|
|
|
Description: Show the shim log file in notepad.
|
|
|
|
--*/
|
|
{
|
|
STARTUPINFO si;
|
|
PROCESS_INFORMATION pi;
|
|
TCHAR szCmd[MAX_PATH] = _T("");
|
|
TCHAR szError[MAX_PATH];
|
|
|
|
GetSystemWindowsDirectory(szCmd, MAX_PATH);
|
|
|
|
SetCurrentDirectory(szCmd);
|
|
|
|
if (GetFileAttributes(_T("AppPatch\\") SHIM_FILE_LOG_NAME) == -1) {
|
|
LoadString(g_hInstance, IDS_NO_LOGFILE, szError, MAX_PATH);
|
|
MessageBox(NULL, szError, g_szAppTitle, MB_ICONEXCLAMATION | MB_OK);
|
|
return;
|
|
}
|
|
|
|
lstrcat(szCmd, _T("\\notepad.exe AppPatch\\") SHIM_FILE_LOG_NAME);
|
|
|
|
ZeroMemory(&si, sizeof(si));
|
|
si.cb = sizeof(si);
|
|
|
|
if (!CreateProcess(NULL,
|
|
szCmd,
|
|
NULL,
|
|
NULL,
|
|
FALSE,
|
|
NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW,
|
|
NULL,
|
|
NULL,
|
|
&si,
|
|
&pi)) {
|
|
|
|
LogMsg(_T("[ShowShimLog] CreateProcess \"%s\" failed 0x%X\n"),
|
|
szCmd, GetLastError());
|
|
return;
|
|
}
|
|
|
|
CloseHandle(pi.hThread);
|
|
CloseHandle(pi.hProcess);
|
|
}
|
|
|
|
|