#include #include #include #include #include "list.h" #include "patchapi.h" #include "patchprv.h" #include "patchlzx.h" #include "xmlutil.h" #include "pg.h" #define DIRECTORY_PATH 0 #define FILE_PATH 1 #define HASHTABLE_SIZE 257 ///////////////////////////////////////////////////////////////////////// // PathNormalize ///////////////////////////////////////////////////////////////////////// HRESULT PathNormalize(LPWSTR pwzPath, LPWSTR *ppwzAbsolutePath, DWORD dwFlag, BOOL bCreate) { HRESULT hr = S_OK; WCHAR pwzTempDir[MAX_PATH], pwzAbsolutePath[MAX_PATH]; DWORD ccDir = MAX_PATH; *ppwzAbsolutePath = NULL; //If path is relative, prepend the current directory if (PathIsRelative(pwzPath)) { GetCurrentDirectory(ccDir, pwzTempDir); StrCat(pwzTempDir, L"\\"); StrCat(pwzTempDir, pwzPath); } else StrCpy(pwzTempDir, pwzPath); if (!PathCanonicalize(pwzAbsolutePath, pwzTempDir)) { printf("Dir \"%ws\" canonicalize error\n", pwzTempDir); hr = E_FAIL; goto exit; } //If path is supposed to be a diesctory, append a trailing slash if not already there ccDir = lstrlen(pwzAbsolutePath); if (dwFlag == DIRECTORY_PATH && pwzAbsolutePath[ccDir -1] != L'\\') { pwzAbsolutePath[ccDir] = L'\\'; pwzAbsolutePath[ccDir +1] = L'\0'; } //Make sure the direcotry exists if (dwFlag == DIRECTORY_PATH && !bCreate) { if(!PathIsDirectory(pwzAbsolutePath)) { printf("Dir \"%ws\" is not a valid directory\n", pwzPath); hr = E_FAIL; goto exit; } } //Make sure the file exists else if (dwFlag == FILE_PATH) { if(!bCreate) { if(!PathFileExists(pwzAbsolutePath)) { printf("File \"%ws\" does not exist\n", pwzPath); hr = E_FAIL; goto exit; } } if(PathIsDirectory(pwzAbsolutePath)) { printf("File \"%ws\" is a directory\n", pwzPath); hr = E_FAIL; goto exit; } } (*ppwzAbsolutePath) = WSTRDupDynamic(pwzAbsolutePath); exit: return hr; } ///////////////////////////////////////////////////////////////////////// // IsValidManifestImport ///////////////////////////////////////////////////////////////////////// HRESULT IsValidManifestImport (LPWSTR pwzManifestPath) { HRESULT hr = S_OK; IAssemblyManifestImport *pManImport = NULL; IAssemblyIdentity *pAssemblyId = NULL; if((hr = CreateAssemblyManifestImport(&pManImport, pwzManifestPath, NULL, 0)) != S_OK) goto exit; if((hr = pManImport->GetAssemblyIdentity(&pAssemblyId)) != S_OK) goto exit; exit: SAFERELEASE(pAssemblyId); SAFERELEASE(pManImport); return hr; } ///////////////////////////////////////////////////////////////////////// // FindAllFiles ///////////////////////////////////////////////////////////////////////// HRESULT FindAllFiles (LPWSTR pwzDir, List *pFileList) { HRESULT hr = S_OK; HANDLE hFind = INVALID_HANDLE_VALUE; WIN32_FIND_DATA fdFile; CString sSearchString, sFileName; DWORD dwHash = 0; DWORD dwLastError = 0; // set up search string to find all files in the passed in directory sSearchString.Assign(pwzDir); sSearchString.Append(L"*"); if (sSearchString._cc > MAX_PATH) { hr = CO_E_PATHTOOLONG; printf("Error: Search path too long\n"); goto exit; } hFind = FindFirstFile(sSearchString._pwz, &fdFile); if (hFind == INVALID_HANDLE_VALUE) { hr = E_FAIL; printf("Find file error\n"); goto exit; } //enumerate through all the files in the directory, // and recursivly call FindAllAssemblies on any directories encountered while(TRUE) { LPWSTR pwzFileName = NULL; if (StrCmp(fdFile.cFileName, L".") != 0 && StrCmp(fdFile.cFileName, L"..") != 0) { CString sFilePath; //create absolute file name by appending the filename to the dir name sFilePath.Assign(pwzDir); sFilePath.Append(fdFile.cFileName); if (sSearchString._cc > MAX_PATH) { hr = CO_E_PATHTOOLONG; printf("Error: File path too long\n"); goto exit; } //If the file is a directory, recursivly call FindAllFiles if ((fdFile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { sFilePath.Append(L"\\"); if (FAILED(hr = FindAllFiles(sFilePath._pwz, pFileList))) goto exit; } // If a file add it to our master list else { if (StrCmp(sFilePath._pwz, g_sSourceManifest._pwz)) { sFileName.Assign(sFilePath._pwz+ g_sSourceBase._cc -1); dwHash = HashString(sFileName._pwz, HASHTABLE_SIZE, false); sFileName.ReleaseOwnership(&pwzFileName); pFileList[dwHash].AddTail(pwzFileName); } } } if (!FindNextFile(hFind, &fdFile)) { dwLastError = GetLastError(); break; } if(dwLastError == ERROR_NO_MORE_FILES) hr = S_OK; else hr = HRESULT_FROM_WIN32(dwLastError); } hr = S_OK; exit: return hr; } ///////////////////////////////////////////////////////////////////////// // CrossReferenceFiles ///////////////////////////////////////////////////////////////////////// HRESULT CrossReferenceFiles (LPWSTR pwzDir, List *pFileList, List *pPatchableFiles) { HRESULT hr = S_OK; HANDLE hFind = INVALID_HANDLE_VALUE; WIN32_FIND_DATA fdFile; CString sSearchString, sFileName; DWORD dwHash=0; LISTNODE pos; LPWSTR pwzBuf = NULL; DWORD dwLastError = 0; // set up search string to find all files in the passed in directory sSearchString.Assign(pwzDir); sSearchString.Append(L"*"); if (sSearchString._cc > MAX_PATH) { hr = CO_E_PATHTOOLONG; printf("Error: Search path too long\n"); goto exit; } hFind = FindFirstFile(sSearchString._pwz, &fdFile); if (hFind == INVALID_HANDLE_VALUE) { hr = E_FAIL; printf("Find file error\n"); goto exit; } //enumerate through all the files in the directory, // and recursivly call FindAllAssemblies on any directories encountered while(dwLastError != ERROR_NO_MORE_FILES) { LPWSTR pwzFileName = NULL; if (StrCmp(fdFile.cFileName, L".") != 0 && StrCmp(fdFile.cFileName, L"..") != 0) { CString sFilePath; //create absolute file name by appending the filename to the dir name sFilePath.Assign(pwzDir); sFilePath.Append(fdFile.cFileName); if (sSearchString._cc > MAX_PATH) { hr = CO_E_PATHTOOLONG; printf("Error: File path too long\n"); goto exit; } //If the file is a directory, recursivly call CrossReferenceFiles if ((fdFile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { sFilePath.Append(L"\\"); if (FAILED(hr = CrossReferenceFiles(sFilePath._pwz, pFileList, pPatchableFiles))) goto exit; } // If a file, check to see if it can be patched else { sFileName.Assign(sFilePath._pwz+g_sDestBase._cc -1); dwHash = HashString(sFileName._pwz, HASHTABLE_SIZE, false); pos = pFileList[dwHash].GetHeadPosition(); while (pos) { pwzBuf = pFileList[dwHash].GetNext(pos); //if equal, attempt to path file if (!StrCmpI(pwzBuf, sFileName._pwz)) { sFileName.ReleaseOwnership(&pwzFileName); pPatchableFiles->AddTail(pwzFileName); break; } } } } if (!FindNextFile(hFind, &fdFile)) { dwLastError = GetLastError(); break; } } if(dwLastError == ERROR_NO_MORE_FILES) hr = S_OK; else hr = HRESULT_FROM_WIN32(dwLastError); hr = S_OK; exit: return hr; } ///////////////////////////////////////////////////////////////////////// // MyProgressCallback ///////////////////////////////////////////////////////////////////////// BOOL CALLBACK MyProgressCallback(PVOID CallbackContext, ULONG CurrentPosition, ULONG MaximumPosition) { UNREFERENCED_PARAMETER( CallbackContext ); if ( MaximumPosition != 0 ) fprintf(stderr, "\r%6.2f%% complete", ( CurrentPosition * 100.0 ) / MaximumPosition ); return TRUE; } ///////////////////////////////////////////////////////////////////////// // ApplyPatchToFiles ///////////////////////////////////////////////////////////////////////// HRESULT ApplyPatchToFiles (List *pPatchableFiles, /* out */ List *pPatchedFiles, LPWSTR pwzSourceDir, LPWSTR pwzDestDir, LPWSTR pwzPatchDir) { HRESULT hr = S_OK; LISTNODE pos; LPWSTR pwzBuf = NULL, pwzDestFile = NULL; CString sSourceFile, sDestFile, sPatchFile, sRelativeDest, sRelativePatch; PATCH_OPTION_DATA OptionData = { sizeof( PATCH_OPTION_DATA ) }; PATCH_OPTION_DATA *OptionDataPointer = &OptionData; BOOL bSuccess = FALSE; ULONG OldFileCount; //CreatePatchFileEx allows you to create patchs file which //can patch from multiple source files. You can have up to // 256 different source files, hence the arrays with 256 elements. //FileNameArray has 257 elemnts since the destination file (which // there can only be one) is stored as the first element and all the // source files, are stored in the remaining 256 elements. // BUGBUG - t-peterf to document why 256, 257. PATCH_OLD_FILE_INFO_W OldFileInfo[ 256 ]; LPWSTR FileNameArray[ 257 ]; LPSTR OldFileSymPathArray[256]; LPSTR NewFileSymPath = new CHAR[MAX_PATH]; ULONG OptionFlags = PATCH_OPTION_USE_LZX_BEST | PATCH_OPTION_USE_LZX_LARGE | PATCH_OPTION_FAIL_IF_SAME_FILE | PATCH_OPTION_FAIL_IF_BIGGER | PATCH_OPTION_INTERLEAVE_FILES; //Step through list of patchable files pos = pPatchableFiles->GetHeadPosition(); while (pos) { pwzBuf = pPatchableFiles->GetNext(pos); //Set up source file path sSourceFile.Assign(pwzSourceDir); sSourceFile.Append(pwzBuf); //Set up dest file path sDestFile.Assign(pwzDestDir); sDestFile.Append(pwzBuf); //set up patchfile path sPatchFile.Assign(pwzPatchDir); sPatchFile.Append(pwzBuf); sPatchFile.Append(L"._p"); CreateDirectoryHierarchy(sPatchFile._pwz, NULL); //Set up Patching Information OldFileCount = 1; OldFileInfo[0].SizeOfThisStruct = sizeof( PATCH_OLD_FILE_INFO); OldFileInfo[0].OldFileName = sSourceFile._pwz; OldFileInfo[0].IgnoreRangeArray = NULL; OldFileInfo[0].IgnoreRangeCount = NULL; OldFileInfo[0].RetainRangeArray = NULL; OldFileInfo[0].RetainRangeCount = NULL; OldFileSymPathArray[0] = new CHAR[MAX_PATH]; WideCharToMultiByte(CP_ACP, 0, pwzSourceDir, lstrlen(pwzSourceDir), OldFileSymPathArray[0], MAX_PATH, NULL, NULL); WideCharToMultiByte(CP_ACP, 0, pwzDestDir, lstrlen(pwzDestDir), NewFileSymPath, MAX_PATH, NULL, NULL); OptionData.SizeOfThisStruct = NULL; OptionData.SymLoadCallback = NULL; OptionData.SymLoadContext = FileNameArray; OptionData.ExtendedOptionFlags = NULL; OptionData.InterleaveMapArray = NULL; OptionData.MaxLzxWindowSize = NULL; OptionData.SymbolOptionFlags = NULL; OptionData.NewFileSymbolPath = (LPCSTR)NewFileSymPath; OptionData.OldFileSymbolPathArray = (LPCSTR *)OldFileSymPathArray; FileNameArray[0] = WSTRDupDynamic(sDestFile._pwz); FileNameArray[1] = WSTRDupDynamic(sSourceFile._pwz); //Applypatch bSuccess = CreatePatchFileEx( OldFileCount, OldFileInfo, sDestFile._pwz, sPatchFile._pwz, OptionFlags, OptionDataPointer, MyProgressCallback, NULL ); sRelativeDest.Assign(sDestFile._pwz + g_sDestBase._cc-1); sRelativePatch.Assign(sPatchFile._pwz + g_sDestBase._cc-1); if(bSuccess) { fprintf(stderr, "\r%ws has been patched successfully to %ws\n", sRelativeDest._pwz, sRelativePatch._pwz); sDestFile.ReleaseOwnership(&pwzDestFile); pPatchedFiles->AddTail(pwzDestFile); } else fprintf(stderr, "\rCould/would not patch %ws. Not adding file to patch manifest\n",sRelativeDest._pwz); } SAFEDELETEARRAY(NewFileSymPath); return hr; } ///////////////////////////////////////////////////////////////////////// // CheckForDuplicate ///////////////////////////////////////////////////////////////////////// HRESULT CheckForDuplicate(LPWSTR pwzSourceManifestPath, LPWSTR pwzDestManifestPath) { HRESULT hr = S_OK; IXMLDOMDocument2 *pXMLDoc = NULL; IXMLDOMNode *pNode = NULL; IAssemblyManifestImport *pSourceManImport = NULL; IAssemblyIdentity *pSourceAssemblyId = NULL; CString sSearchString, sBuffer; BSTR bstrSearchString = NULL; LPWSTR pwzBuf=NULL; DWORD ccBuf=0; LPWSTR rpwzAttrNames[6] = { SXS_ASSEMBLY_IDENTITY_STD_ATTRIBUTE_NAME_PROCESSOR_ARCHITECTURE, SXS_ASSEMBLY_IDENTITY_STD_ATTRIBUTE_NAME_NAME, SXS_ASSEMBLY_IDENTITY_STD_ATTRIBUTE_NAME_PUBLIC_KEY_TOKEN, SXS_ASSEMBLY_IDENTITY_STD_ATTRIBUTE_NAME_VERSION, SXS_ASSEMBLY_IDENTITY_STD_ATTRIBUTE_NAME_LANGUAGE, SXS_ASSEMBLY_IDENTITY_STD_ATTRIBUTE_NAME_TYPE, }; if(FAILED(hr = LoadXMLDocument(pwzDestManifestPath, &pXMLDoc))) goto exit; if(FAILED(hr = CreateAssemblyManifestImport(&pSourceManImport, pwzSourceManifestPath, NULL, 0))) goto exit; if(FAILED(hr = pSourceManImport->GetAssemblyIdentity(&pSourceAssemblyId))) goto exit; //set up search string sSearchString.Assign(L"/assembly/Patch/SourceAssembly/assemblyIdentity["); for (int i = 0; i < 6; i++) { if (i) sSearchString.Append(L" and "); sSearchString.Append(L"@"); sSearchString.Append(rpwzAttrNames[i]); sSearchString.Append(L"=\""); if (FAILED(hr = pSourceAssemblyId->GetAttribute(rpwzAttrNames[i], &pwzBuf, &ccBuf))) goto exit; sBuffer.TakeOwnership(pwzBuf, ccBuf); sSearchString.Append(sBuffer); sSearchString.Append(L"\""); } sSearchString.Append(L"]"); bstrSearchString = ::SysAllocString(sSearchString._pwz); if (!bstrSearchString) { hr = E_OUTOFMEMORY; goto exit; } //if source assembly already exists, exit and do nothing if (FAILED(hr = pXMLDoc->selectSingleNode(bstrSearchString, &pNode))) goto exit; else if (hr == S_OK) { fprintf(stderr, "Duplicate Source Assembly! Not adding to manfiest\n"); hr = S_FALSE; goto exit; } else if (hr == S_FALSE) { hr = S_OK; goto exit; } exit: if(bstrSearchString) ::SysFreeString(bstrSearchString); SAFERELEASE(pXMLDoc); SAFERELEASE(pNode); SAFERELEASE(pSourceAssemblyId); SAFERELEASE(pSourceManImport); return hr; } ///////////////////////////////////////////////////////////////////////// // Usage ///////////////////////////////////////////////////////////////////////// HRESULT GetPatchDirectory(LPWSTR pwzSourceManifestPath, LPWSTR *ppwzPatchDir) { HRESULT hr = S_OK; IAssemblyManifestImport *pSourceManImport = NULL; IAssemblyIdentity *pSourceAssemblyId = NULL; LPWSTR pwzBuf=NULL; DWORD ccBuf =0; CString sBuffer, sPatchDir; if(FAILED(hr = CreateAssemblyManifestImport(&pSourceManImport, pwzSourceManifestPath, NULL, 0))) goto exit; if(FAILED(hr = pSourceManImport->GetAssemblyIdentity(&pSourceAssemblyId))) goto exit; if (FAILED(hr = pSourceAssemblyId->GetDisplayName(ASMID_DISPLAYNAME_NOMANGLING, &pwzBuf, &ccBuf))) goto exit; sBuffer.TakeOwnership(pwzBuf, ccBuf); sPatchDir.Assign(g_sDestBase); sPatchDir.Append(L"__patch__\\"); sPatchDir.Append(pwzBuf); sPatchDir.Append(L"\\"); sPatchDir.ReleaseOwnership(ppwzPatchDir); exit: return hr; } ///////////////////////////////////////////////////////////////////////// // Usage ///////////////////////////////////////////////////////////////////////// HRESULT Usage() { printf("Usage: \n" "pg [source manifest path] [dest manifest path]:\n" "\t[source manifest path] = path of the Application for which you want to generate a manifest for\n" "\t[dest manifest path] = path of the Application for which you want to generate a manifest for\n"); return S_OK; } ///////////////////////////////////////////////////////////////////////// // wmain ///////////////////////////////////////////////////////////////////////// int __cdecl wmain(int argc, WCHAR **argv) { HRESULT hr = S_OK; LPWSTR pwzSourceManifest=NULL, pwzDestManifest=NULL; List pFileList[HASHTABLE_SIZE], PatchableFiles, PatchedFiles; BOOL bCoInitialized = FALSE; CString sPatchDir; LPWSTR pwzBuf=NULL; hr = CoInitialize(NULL); if (FAILED(hr)) { goto exit; } bCoInitialized = TRUE; if (!StrCmp(argv[1], L"-?") || !(argc == 3)) { hr = Usage(); goto exit; } if (FAILED(hr = PathNormalize(argv[1], &pwzSourceManifest, FILE_PATH, FALSE))) { hr = Usage(); goto exit; } if (FAILED(hr = PathNormalize(argv[2], &pwzDestManifest, FILE_PATH, FALSE))) { hr = Usage(); goto exit; } g_sSourceManifest.Assign(pwzSourceManifest); g_sSourceBase.Assign(g_sSourceManifest); g_sSourceBase.RemoveLastElement(); g_sSourceBase.Append(L"\\"); g_sDestBase.Assign(pwzDestManifest); g_sDestBase.RemoveLastElement(); g_sDestBase.Append(L"\\"); //Bugbug, could run into naming conflicts with the patch dir sPatchDir.Assign(g_sDestBase); sPatchDir.Append(L"__patch__\\"); if(FAILED(hr = IsValidManifestImport(pwzSourceManifest))) { fprintf(stderr, "%ws is not a valid application manifest\n", pwzSourceManifest); goto exit; } if(FAILED(hr = IsValidManifestImport(pwzDestManifest))) { fprintf(stderr, "%ws is not a valid application manifest\n", pwzDestManifest); goto exit; } if((hr = CheckForDuplicate(pwzSourceManifest, pwzDestManifest)) != S_OK) goto exit; if(FAILED(hr = GetPatchDirectory(g_sSourceManifest._pwz, &pwzBuf))) goto exit; sPatchDir.TakeOwnership(pwzBuf); if(FAILED(hr = FindAllFiles(g_sSourceBase._pwz, pFileList))) goto exit; if(FAILED(hr = CrossReferenceFiles(g_sDestBase._pwz, pFileList, &PatchableFiles))) goto exit; if(FAILED(hr = ApplyPatchToFiles(&PatchableFiles, &PatchedFiles, g_sSourceBase._pwz, g_sDestBase._pwz, sPatchDir._pwz))) goto exit; if(FAILED(hr = CreatePatchManifest(&PatchedFiles, sPatchDir._pwz, pwzSourceManifest, pwzDestManifest))) goto exit; exit: SAFEDELETEARRAY(pwzSourceManifest); SAFEDELETEARRAY(pwzDestManifest); if (bCoInitialized) CoUninitialize(); if (FAILED(hr)) fprintf(stderr, "\nFailed with code 0x%x", hr); return 0; }