|
|
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <direct.h>
#include <windows.h>
#include "patchapi.h"
#include "const.h"
#include "ansparse.h"
#include "dirwak2a.h"
#include "filetree.h"
#include "crc32.h"
#ifdef __BOUNDSCHECKER__
#include "nmevtrpt.h"
#endif
// noise about 'this'
#pragma warning(disable:4355)
// multi-thread support
static MULTI_THREAD_STRUCT* myThreadStruct = NULL; static HANDLE* myThreadHandle = NULL; static ULONG g_iThreads = 0;
// patch options
static DWORD g_iBestMethod; static BOOL g_blnCollectStat; static BOOL g_blnFullLog;
///////////////////////////////////////////////////////////////////////////////
//
// class FileTree
//
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// FileTree, the constructor for the object FileTree, works in conjunction with
// Create so that the constructor is guanranteed to return
//
// Parameters:
//
// pwszLocalRoot, the destination directory that the tree will work on
//
// Return:
//
// none, constructor must return
//
///////////////////////////////////////////////////////////////////////////////
FileTree::FileTree(IN CONST WCHAR* pwszLocalRoot) : m_Root(this, NULL, EMPTY) { m_cDirectories = 0; m_cFiles = 0; m_cFilesDetermined = 0; m_cbTotalFileSize = 0; memset(m_aHashTable, 0, sizeof(m_aHashTable)); memset(m_aNameTable, 0, sizeof(m_aNameTable)); m_cchLocalRoot = wcslen(pwszLocalRoot); wcscpy(m_wszLocalRoot, pwszLocalRoot); m_cFilesExisting = 0; m_cFilesZeroLength = 0; m_cFilesRenamed = 0; m_cFilesCopied = 0; m_cFilesChanged = 0; m_cbChangedFileSize = 0; m_cbChangedFilePatchedSize = 0; m_cbChangedFileNotPatchedSize = 0; m_cFilesNoMatch = 0; m_cbNoMatchFileSize = 0; m_pLanguage = NULL; m_pAnsParse = NULL; m_blnBase = FALSE; m_iSize = 0; }
///////////////////////////////////////////////////////////////////////////////
//
// ~FileTree, the destructor for the object FileTree
//
// Parameters:
//
// none
//
// Return:
//
// none
//
///////////////////////////////////////////////////////////////////////////////
FileTree::~FileTree(VOID) { // clear the filenodes for the base tree, all other tree should have no
// file node
if(m_blnBase) { for(UINT i = 0; i < HASH_SIZE; i++) { FileNode* pFile = m_aNameTable[i]; while(pFile) { m_aNameTable[i] = m_aNameTable[i]->m_pNextNameHash; delete pFile; pFile = m_aNameTable[i]; } } }
// tell the scriptfile to remove the directories after apply patch
ToScriptFile(this, m_pLanguage->s_hScriptFile, ACTION_DELETE_DIRECTORY, m_pAnsParse->m_wszBaseDirectory + DRIVE_LETTER_LENGTH, NULL, FALSE); ToScriptFile(this, m_pLanguage->s_hScriptFile, ACTION_DELETE_DIRECTORY, PATCH_SUB_PATCH, NULL, FALSE); // flush the script file
ToScriptFile(this, m_pLanguage->s_hScriptFile, NULL, NULL, NULL, TRUE);
// remove the critical section used by this FileTree object
DeleteCriticalSection(&CSScriptFile); }
///////////////////////////////////////////////////////////////////////////////
//
// CreateMultiThreadStruct, allocate and zero memory for the multi-thread
// supporting structures, this function is static
// because the structures are static, intended to be
// used by all instances of FileTree, so need to be
// called only once
//
// Parameters:
//
// iNumber, the number of threads
//
// Return:
//
// TRUE for successfully allocating memory
// FALSE if the memory allocation failed
//
///////////////////////////////////////////////////////////////////////////////
BOOL FileTree::CreateMultiThreadStruct(IN ULONG iNumber) { // allocate the memory for the structures
if(myThreadStruct == NULL && myThreadHandle == NULL) { myThreadStruct = new MULTI_THREAD_STRUCT[iNumber]; myThreadHandle = new HANDLE[iNumber]; g_iThreads = iNumber; }
// check for failed allocation
if(myThreadStruct == NULL && myThreadHandle != NULL) { delete [] myThreadHandle; myThreadHandle = NULL; g_iThreads = 0; }
if(myThreadHandle == NULL && myThreadStruct != NULL) { delete [] myThreadStruct; myThreadStruct = NULL; g_iThreads = 0; }
// zero out the memory, if the structures are really statically linked
// then we don't have to do this
if(myThreadStruct != NULL && myThreadHandle != NULL) { ZeroMemory(myThreadStruct, iNumber * sizeof(MULTI_THREAD_STRUCT)); ZeroMemory(myThreadHandle, iNumber * sizeof(HANDLE)); }
return(myThreadStruct != NULL && myThreadHandle != NULL); }
///////////////////////////////////////////////////////////////////////////////
//
// DeleteMultiThreadStruct, deallocate meory used by the multi-thread support
// structures, also a static function, need to be
// called only once
//
// Parameters:
//
// none
//
// Return:
//
// none, the memory are guanrateed to be de-allocated, and set to NULL
//
///////////////////////////////////////////////////////////////////////////////
VOID FileTree::DeleteMultiThreadStruct(VOID) { if(myThreadStruct != NULL) { delete [] myThreadStruct; myThreadStruct = NULL; } if(myThreadHandle != NULL) { delete [] myThreadHandle; myThreadHandle = NULL; } }
///////////////////////////////////////////////////////////////////////////////
//
// Create, the true function for initializing a FileTree object
//
// Parameters:
//
// pInLanguage, the language struct, contains information about directories
// and others specified in the answer file
// pInAnsParse, the parser for the answer file, contains information about
// the base directory and so on
// ppTree, pTree is the would be pointer to the FileTree
// blnBase, whether or not this FileTree is a base tree
//
// Return:
//
// PREP_BAD_PATH_ERROR, invalid directory encountered
// PREP_NO_MEMORY, memory allocation failed
// PREP_DIRECTORY_ERROR, cannot create some directory
// PREP_SCRIPT_FILE_ERROR, cannot create or write to the scriptfile
// PREP_INPUT_FILE_ERROR, pInLanguage is NULL, no input
// PREP_NO_ERROR, no error
//
///////////////////////////////////////////////////////////////////////////////
INT FileTree::Create(IN PPATCH_LANGUAGE pInLanguage, IN AnswerParser* pInAnsParse, OUT FileTree** ppTree, IN BOOL blnBase, IN DWORD iInBestMethod, IN BOOL blnInCollectStat, IN BOOL blnInFullLog) { DWORD cch; WCHAR wszBasePath[STRING_LENGTH]; WCHAR* pwszJunk; FileTree* pTree;
*ppTree = NULL;
g_iBestMethod = iInBestMethod; g_blnCollectStat = blnInCollectStat; g_blnFullLog = blnInFullLog;
if(pInLanguage) { cch = GetFullPathNameW(pInLanguage->s_wszDirectory, countof(wszBasePath), wszBasePath, &pwszJunk); if((cch == 0) || (cch >= countof(wszBasePath))) { return(PREP_BAD_PATH_ERROR); }
if(wszBasePath[cch - 1] != L'\\') { wszBasePath[cch++] = L'\\'; wszBasePath[cch] = L'\0'; }
DWORD dwAttributes = GetFileAttributesW(wszBasePath); if((dwAttributes == 0xFFFFFFFF) || ((dwAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)) { return(PREP_BAD_PATH_ERROR); }
pTree = new FileTree(wszBasePath); if(pTree == NULL) { return(PREP_NO_MEMORY); }
pTree->m_pLanguage = pInLanguage; pTree->m_pAnsParse = pInAnsParse; pTree->m_blnBase = blnBase;
// create some directories
if(!(pTree->CreateNewDirectory(pInLanguage->s_wszPatchDirectory, EMPTY) && pTree->CreateNewDirectory(pInLanguage->s_wszSubPatchDirectory, EMPTY) && pTree->CreateNewDirectory(pInLanguage->s_wszSubExceptDirectory, EMPTY))) { return(PREP_DIRECTORY_ERROR); } if(blnBase) { if(!pTree->CreateNewDirectory(pInAnsParse->m_wszBaseDirectory, EMPTY)) { return(PREP_DIRECTORY_ERROR); } } DisplayDebugMessage(FALSE, FALSE, FALSE, FALSE, L"Directory created for patching.");
// create the script file
pTree->m_pLanguage->s_hScriptFile = CreateFileW(pTree->m_pLanguage->s_wszScriptFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if(pTree->m_pLanguage->s_hScriptFile == INVALID_HANDLE_VALUE) { return(PREP_SCRIPT_FILE_ERROR); } // write the unicode header to file
WriteFile(pTree->m_pLanguage->s_hScriptFile, &UNICODE_HEAD, sizeof(WCHAR), &cch, NULL); ZeroMemory(pTree->m_strWriteBuffer, SUPER_LENGTH * sizeof(WCHAR)); DisplayDebugMessage(FALSE, FALSE, FALSE, FALSE, L"Script file created for patching.");
// initialized the critical section here
InitializeCriticalSection(&(pTree->CSScriptFile));
*ppTree = pTree; } else { return(PREP_INPUT_FILE_ERROR); }
return(PREP_NO_ERROR); }
///////////////////////////////////////////////////////////////////////////////
//
// Load, the entry point for processing files,
// for a base tree, pTree should be NULL,
// otherwise, use the pointer to base tree to process files
//
// Parameters:
//
// pTree, a FileTree object, used to as a match target
//
// Return:
//
// PREP_BAD_PATH_ERROR, invalid directory encountered
// PREP_NO_MEMORY, memory allocation failed
// PREP_DEPTH_ERROR, the directory tree is too deep
// PREP_UNKNOWN_ERROR, the directory tree is too deep
// PREP_NO_ERROR, no error
//
///////////////////////////////////////////////////////////////////////////////
INT FileTree::Load(IN FileTree* pTree) { INT rc = PREP_NO_ERROR; // do the directory walk, this funtion will use the callback functions
// to process files
rc = DirectoryWalk(this, NULL, m_wszLocalRoot, NotifyDirectory, NotifyFile, NotifyDirectoryEnd, pTree);
// at the end of matching, shutdown all threads and close the handles
for(UINT i = 0; i < g_iThreads; i++) { if(WaitForSingleObject(myThreadHandle[i], INFINITE) != WAIT_FAILED) { CloseHandle(myThreadHandle[i]); } myThreadHandle[i] = NULL; }
// determin error from the directory walk errors
switch(rc) { case DW_NO_ERROR: rc = PREP_NO_ERROR; break; case DW_MEMORY: rc = PREP_NO_MEMORY; break; case DW_ERROR: rc = PREP_BAD_PATH_ERROR; break; case DW_DEPTH: rc = PREP_DEPTH_ERROR; break; case DW_OTHER_ERROR: rc = PREP_UNKNOWN_ERROR; break; default: break; }
// print out the stats
if(!m_pLanguage->s_blnBase) { DisplayDebugMessage(FALSE, FALSE, FALSE, TRUE, L"Patch results:"); DisplayDebugMessage(FALSE, FALSE, FALSE, TRUE, L"%12I64u files total, %I64u bytes", m_cFiles, m_cbTotalFileSize); DisplayDebugMessage(FALSE, FALSE, FALSE, TRUE, L"%12I64u existing", m_cFilesExisting); DisplayDebugMessage(FALSE, FALSE, FALSE, TRUE, L"%12I64u zero-length", m_cFilesZeroLength); DisplayDebugMessage(FALSE, FALSE, FALSE, TRUE, L"%12I64u renamed", m_cFilesRenamed); DisplayDebugMessage(FALSE, FALSE, FALSE, TRUE, L"%12I64u copied (from another directory)", m_cFilesCopied); DisplayDebugMessage(FALSE, FALSE, FALSE, TRUE, L"%12I64u changed, %I64u bytes, %I64u patched + %I64u raw", m_cFilesChanged, m_cbChangedFileSize, m_cbChangedFilePatchedSize, m_cbChangedFileNotPatchedSize); DisplayDebugMessage(FALSE, FALSE, FALSE, TRUE, L"%12I64u unique unmatched, %I64u bytes", m_cFilesNoMatch, m_cbNoMatchFileSize); }
return(rc); }
///////////////////////////////////////////////////////////////////////////////
//
// NotifyDirectory, the callback function used when a new directory is opened
// and ready to be processed for files, the directory can be
// any directory that the user has access to, also, directory
// processing is not multi-threaded, only the files
//
// Parameters:
//
// context, a pointer to a FileTree object, this FileTree has called for a
// directory walk
// parentID, a pointer to the parent directory
// directory, the name of the directory
// path, the full path of the directory include then ending "\"
// childID, a would be pointer to the this directory, so this directory is a
// child of the parent directory
// pTree, the matching tree
//
// Return:
//
// PREP_NO_MEMORY, memory allocation failed
// PREP_DIRECTORY_ERROR, unable to recreate the directory for base directory
// PREP_NO_ERROR, no error
//
///////////////////////////////////////////////////////////////////////////////
INT FileTree::NotifyDirectory( IN VOID* context, IN VOID* parentID, IN CONST WCHAR* directory, IN CONST WCHAR* path, OUT VOID** childID, VOID* pTree ) { WCHAR wszDirectoryAttrib[LANGUAGE_LENGTH]; FileTree* pThis = (FileTree*)context; DirectoryNode* pParent = (DirectoryNode*)parentID;
if(parentID == NULL) { *childID = &pThis->m_Root; } else { // create the new directory node here
DirectoryNode *pNew = new DirectoryNode(pThis, (DirectoryNode*)parentID, directory); if(pNew == NULL) { return(PREP_NO_MEMORY); } wcscpy(pNew->m_wszLongDirectoryName, path); pNew->m_wszDirectoryName = pNew->m_wszLongDirectoryName + wcslen(path) - wcslen(directory); pThis->m_cDirectories++;
// go match this directory if matchable
if(pTree != NULL) pNew->Match((FileTree*)pTree);
*childID = pNew; }
if(pParent) { if(pThis->m_blnBase) { // create base directories
if(!pThis->CreateNewDirectory( pThis->m_pAnsParse->m_wszBaseDirectory, path + pThis->m_pLanguage->s_iDirectoryCount)) { return(PREP_DIRECTORY_ERROR); } } // save the directory creation to scriptfile
// most localized trees do contain localized directory names, we need
// to recreate them later with the same directory attributes
DWORD iAttrib = GetFileAttributesW(path); ZeroMemory(wszDirectoryAttrib, LANGUAGE_LENGTH * sizeof(WCHAR)); if((iAttrib & FILE_ATTRIBUTE_HIDDEN) == FILE_ATTRIBUTE_HIDDEN) { wcscat(wszDirectoryAttrib, DIR_HIDDEN); } if((iAttrib & FILE_ATTRIBUTE_READONLY) == FILE_ATTRIBUTE_READONLY) { wcscat(wszDirectoryAttrib, DIR_READONLY); } if((iAttrib & FILE_ATTRIBUTE_SYSTEM) == FILE_ATTRIBUTE_SYSTEM) { wcscat(wszDirectoryAttrib, DIR_SYSTEM); } if((iAttrib & FILE_ATTRIBUTE_COMPRESSED) == FILE_ATTRIBUTE_COMPRESSED) { wcscat(wszDirectoryAttrib, DIR_COMPRESSED); } if((iAttrib & FILE_ATTRIBUTE_ENCRYPTED) == FILE_ATTRIBUTE_ENCRYPTED) { wcscat(wszDirectoryAttrib, DIR_ENCRYPTED); } pThis->ToScriptFile(pThis, pThis->m_pLanguage->s_hScriptFile, ACTION_NEW_DIRECTORY, path + pThis->m_pLanguage->s_iDirectoryCount, wszDirectoryAttrib, FALSE); }
return(DW_NO_ERROR); }
///////////////////////////////////////////////////////////////////////////////
//
// NotifyFile, this is a callback function used when a new file is being
// processed, also a static function to allow same entry point
// for all files, supports multi-threaded file processing
//
// Parameters:
//
// context, a pointer to a FileTree object, this FileTree has called for a
// directory walk
// parentID, a pointer to the parent directory
// filename, the filename
// path, the full path of the directory include then ending "\"
// attributes, the file attribute
// filetime, the last modified time, not used
// creation, the creation time, not used
// filesize, the size of the file in bytes
// pTree, the matching tree
//
// Return:
//
// DW_OTHER_ERROR, thread creation failed
// DW_NO_ERROR, no error
//
///////////////////////////////////////////////////////////////////////////////
INT FileTree::NotifyFile( IN VOID* context, IN VOID* parentID, IN CONST WCHAR* filename, IN CONST WCHAR* path, IN DWORD attributes, IN FILETIME filetime, IN FILETIME creation, IN __int64 filesize, IN VOID* pTree ) { UINT i = 0; DWORD iThread = 0;
// find an empty thread slot for file processing
for(i = 0; i < g_iThreads; i++) { if(!myThreadStruct[i].blnInUse) break; } if(i < g_iThreads && !myThreadStruct[i].blnInUse) { // empty slot is found
// make sure the thread is done and close the handle
if(myThreadHandle[i] && WaitForSingleObject(myThreadHandle[i], INFINITE) != WAIT_FAILED) { CloseHandle(myThreadHandle[i]); } } else { // empty slot is not found
// wait for a thread to finish
DWORD dwEvent = WaitForMultipleObjects(g_iThreads, myThreadHandle, FALSE, INFINITE); // there is a struct not in use, so find it and close the handle
i = dwEvent - WAIT_OBJECT_0; CloseHandle(myThreadHandle[i]); } // reset the handle to null for insurance
myThreadHandle[i] = NULL;
// ready the multi-thread support struct to pass onto the actual processing
// function, should try not to pass pointers here unless absolutely
// necessary
myThreadStruct[i].blnInUse = TRUE; myThreadStruct[i].pThis = (FileTree*)context; myThreadStruct[i].pParent = (DirectoryNode*)parentID; // the reason for copy the filenames is that once the thread is running,
// the pointers are changed by the directory walk thread
wcscpy(myThreadStruct[i].filename, filename); wcscpy(myThreadStruct[i].path, path); myThreadStruct[i].attributes = attributes; myThreadStruct[i].filetime = filetime; myThreadStruct[i].creation = creation; myThreadStruct[i].filesize = filesize; myThreadStruct[i].pTree = (FileTree*)pTree; // create the thread and give the struct
myThreadHandle[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)StartFileThread, (LPVOID)&myThreadStruct[i], 0, &iThread); if(myThreadHandle[i] == NULL) { // thread creation failed
return(DW_OTHER_ERROR); }
return(DW_NO_ERROR); }
///////////////////////////////////////////////////////////////////////////////
//
// StartFileThread, this is the original thread entry point, I did some debug
// work here before, but now it is just a wrapper to jump to
// the next function
//
// Parameters:
//
// lpParam, the multi-thread struct, everything about this file ready to be
// processed
//
// Return:
//
// Whatever from ProcessFile
//
///////////////////////////////////////////////////////////////////////////////
DWORD WINAPI FileTree::StartFileThread(IN LPVOID lpParam) { MULTI_THREAD_STRUCT* pStruct = (MULTI_THREAD_STRUCT*)lpParam; // translate the parameters and call the processing function
return(ProcessFile(pStruct->pThis, pStruct->pParent, pStruct->filename, pStruct->path, pStruct->attributes, pStruct->filetime, pStruct->creation, pStruct->filesize, pStruct->pTree, pStruct)); }
///////////////////////////////////////////////////////////////////////////////
//
// ProcessFile, do actual file processing, determines what to do with the file,
// computes filehash, try to match it, every file node is created
// here, but deleted after usage to save memory
//
// Parameters:
//
// same as in NotifyFile
//
// Return:
//
// PREP_NO_MEMORY, allocation failed
// DW_NO_ERROR, no error
//
///////////////////////////////////////////////////////////////////////////////
INT FileTree::ProcessFile( IN FileTree* pThis, IN DirectoryNode* pParent, IN CONST WCHAR* filename, IN CONST WCHAR* path, IN DWORD attributes, IN FILETIME filetime, IN FILETIME creation, IN __int64 filesize, IN FileTree* pTree, IN VOID* pStruct ) { WCHAR wszFullPathFileName[STRING_LENGTH]; INT iLength = 0;
// is the file an exception?
if(!pThis->m_pAnsParse->IsFileExceptHash(filename)) { // create the file node here
FileNode* pNew = new FileNode(pParent, filename, filetime, filesize); if(pNew == NULL) { return(PREP_NO_MEMORY); } pThis->m_cFiles++; pThis->m_cbTotalFileSize += filesize;
// path includes the ending '\'
// process filenames to get fullpath name
iLength = wcslen(path); wcscpy(pNew->m_wszLongFileName, path); wcscat(pNew->m_wszLongFileName + iLength, filename); pNew->m_wszFileName = pNew->m_wszLongFileName + iLength;
// computes the file content hash
// file name hash is computed in (new FileNode)
pNew->ComputeHash();
if(!pThis->m_pLanguage->s_blnBase) { // match the file if the file is not in the base tree
// the directory node should already be matched
pNew->Match((FileTree*)pTree); // remove the filenode after use
delete pNew; pNew = NULL; } else { // this language is the base language, need to copy files to base directory
if(!pThis->CopyFileTo(pThis, ACTION_MOVE_FILE, pThis->m_pAnsParse->m_wszBaseDirectory, pNew->m_wszLongFileName + pThis->m_pLanguage->s_iDirectoryCount, pNew->m_wszLongFileName, attributes)) { // copy file failed, a warning
DisplayDebugMessage(FALSE, FALSE, FALSE, FALSE, L"warning, file copy %ls failed, e=%d", filename, GetLastError()); } } } else { // get full path, then move the file to patch\except directory
wcscpy(wszFullPathFileName, path); wcscat(wszFullPathFileName, filename); if(!pThis->CopyFileTo(pThis, ACTION_EXCEPT_FILE, pThis->m_pLanguage->s_wszSubExceptDirectory, filename, wszFullPathFileName, attributes)) { // copy file failed, a warning
DisplayDebugMessage(FALSE, FALSE, FALSE, FALSE, L"warning, except file copy %ls failed, e=%d", filename, GetLastError()); } }
// reset the thread status so other threads can close the thread and
// takes its place
((MULTI_THREAD_STRUCT*)pStruct)->blnInUse = FALSE;
return(DW_NO_ERROR); }
///////////////////////////////////////////////////////////////////////////////
//
// NotifyDirectoryEnd, this callback function is used when the end of directory
// is encountered
//
// Parameters:
//
// not used, just there for compiling
//
// Return:
//
// DW_NO_ERROR, no error
//
///////////////////////////////////////////////////////////////////////////////
INT FileTree::NotifyDirectoryEnd( IN VOID* pContext, IN VOID* pParentID, IN CONST WCHAR* pwszDirectory, IN CONST WCHAR* pwszPath, IN VOID* pChildID) { // when the end of the directory is reached, all file processig thread
// must be terminated, the reason is that each file rely on its parent,
// a directory node, to know where it is at and how to match itself, but
// the directory node pointer is changed when a new directory is
// encountered, to avoid errors, all threads must exit.
// the thread handles are not closed here, but they will be close either
// through a new file thread creation or the end of Load.
WaitForMultipleObjects(g_iThreads, myThreadHandle, TRUE, INFINITE); return(DW_NO_ERROR); }
///////////////////////////////////////////////////////////////////////////////
//
// CreateNewDirectory, creates a directory with no attributes set, if the
// directory already exist, no new directory is created and
// attributes are not changed
//
// Parameters:
//
// pwszLocal, the first part of the directory
// pwszBuffer, the second part of the directory,
// pwszLocal + pwszBuffer = full path of the new directory
//
// Return:
//
// DW_NO_ERROR, no error
//
///////////////////////////////////////////////////////////////////////////////
BOOL FileTree::CreateNewDirectory(IN WCHAR* pwszLocal, IN CONST WCHAR* pwszBuffer) { BOOL blnReturn = FALSE; ULONG iLength = 0; ULONG iEntireLength = 0;
iLength = wcslen(pwszLocal); wcscat(pwszLocal, pwszBuffer); // uses _wmdir instead of CreateDirectory so that error is easy to check,
// errno is defined by CRT stdlib.h, and 17 is the EEXIST error code
blnReturn = (_wmkdir(pwszLocal) == 0 || errno == 17 /*EEXIST*/); pwszLocal[iLength] = 0;
return(blnReturn); }
///////////////////////////////////////////////////////////////////////////////
//
// CopyFileTo, physically copy a file along with its attributes to another
// location, used to save files that are not patched
//
// Parameters:
//
// pThis, pointer to this filetree
// pwszWhat, the action
// pwszLocal, the destination directory
// pwszFileName, the full path filename minus the directory specified in the
// answerfile, pwszLocal + pwszFileName = new filename
// pwszOldFile, the full path oldfile
// attributes, this file's attributes
//
// Return:
//
// TRUE for file copied and logged into the scriptfile
// FALSE otherwise, should not stop processing files, but logs the entries
//
///////////////////////////////////////////////////////////////////////////////
BOOL FileTree::CopyFileTo(IN FileTree* pThis, IN CONST WCHAR* pwszWhat, IN WCHAR* pwszLocal, IN CONST WCHAR* pwszFileName, IN WCHAR* pwszOldFile, IN DWORD attributes) { BOOL blnReturn = FALSE; ULONG iLength = 0; ULONG iEntireLength = 0; WCHAR wszRandom[10]; WCHAR wszTempString[STRING_LENGTH];
iLength = wcslen(pwszLocal); iEntireLength = iLength + wcslen(pwszFileName); wcscpy(wszTempString, pwszLocal); wcscpy(wszTempString + iLength, pwszFileName); if(pwszWhat != ACTION_EXCEPT_FILE && pwszWhat != ACTION_NOT_PATCH_FILE && pwszWhat != ACTION_SAVED_FILE) { // error can be caused by file attributes
blnReturn = CopyFileW(pwszOldFile, wszTempString, FALSE); if(!blnReturn) { SetFileAttributesW(wszTempString, FILE_ATTRIBUTE_ARCHIVE); blnReturn = CopyFileW(pwszOldFile, wszTempString, FALSE); SetFileAttributesW(wszTempString, attributes); } } else { // choose a different name if possible
while((blnReturn = CopyFileW(pwszOldFile, wszTempString, TRUE)) == FALSE) { ZeroMemory(wszRandom, 10 * sizeof(WCHAR)); // randomly picks a number, total number of possible choice is 100,
// there shouldn't be 100 same name file in a file tree
_itow(rand() % FILE_LIMIT, wszRandom, 10); if(wcslen(wszRandom) + iEntireLength < STRING_LENGTH) { wcscpy(wszTempString + iEntireLength, wszRandom); } else { break; } } } if(blnReturn) { // remember what to do later
if(pwszWhat != ACTION_MOVE_FILE) { pThis->ToScriptFile(pThis, pThis->m_pLanguage->s_hScriptFile, pwszWhat, wszTempString + pThis->m_pLanguage->s_iPatchDirectoryCount, pwszOldFile + pThis->m_pLanguage->s_iDirectoryCount, FALSE); } else { pThis->ToScriptFile(pThis, pThis->m_pLanguage->s_hScriptFile, pwszWhat, wszTempString + DRIVE_LETTER_LENGTH, pwszOldFile + pThis->m_pLanguage->s_iDirectoryCount, FALSE); } }
return(blnReturn); }
///////////////////////////////////////////////////////////////////////////////
//
// ToScriptFile, save actions into the scriptfile so that apply patch can be
// done later on the client side
//
// Parameters:
//
// pThis, pointer to this filetree
// hFile, the handle of the script file
// pwszWhat, the action to be saved
// pwszFirst, first string to be saved
// pwszSecond, the second string to be saved
// blnFlush, TRUE to flush the saved buffer to write, FALSE = do not flush
//
// Note:
//
// where blnFlush is set to TRUE, all other parameters are ignored
//
// Return:
//
// none
//
///////////////////////////////////////////////////////////////////////////////
VOID FileTree::ToScriptFile(IN FileTree* pThis, IN HANDLE hFile, IN CONST WCHAR* pwszWhat, IN CONST WCHAR* pwszFirst, IN WCHAR* pwszSecond, IN BOOL blnFlush) { ULONG iFirstLength = 0; ULONG iLength = 0; ULONG iBegin = 0; ULONG iSecondLength = 0; ULONG iWriteBytes = 0;
__try { // critical section lock on the scriptfile
EnterCriticalSection(&(pThis->CSScriptFile)); if(!blnFlush && pwszWhat && pwszFirst && (iFirstLength = wcslen(pwszFirst)) > 0) { // 1: the action
// 1: the * separator
// 1: the end of line
// 1: the carriage return
iLength = 4 + iFirstLength; if(pwszSecond && (iSecondLength = wcslen(pwszSecond)) > 0) { // 1: the * separator
iLength += 1 + iSecondLength; } iBegin = pThis->m_iSize; pThis->m_iSize += iLength; if(pThis->m_iSize + 1 < SUPER_LENGTH) { // buffer the message up
wcscpy(pThis->m_strWriteBuffer + iBegin, pwszWhat); wcscpy(pThis->m_strWriteBuffer + iBegin + 1, SEPARATOR); wcscpy(pThis->m_strWriteBuffer + iBegin + 2, pwszFirst); if(pwszSecond && iSecondLength > 0) { wcscpy(pThis->m_strWriteBuffer + iBegin + 2 + iFirstLength, SEPARATOR); wcscpy(pThis->m_strWriteBuffer + iBegin + 3 + iFirstLength, pwszSecond); } wcscpy(pThis->m_strWriteBuffer + pThis->m_iSize - 2, ENDOFLINE); wcscpy(pThis->m_strWriteBuffer + pThis->m_iSize - 1, CRETURN); } else { // overflow, write the buffer out
WriteFile(hFile, pThis->m_strWriteBuffer, iBegin * sizeof(WCHAR), &iWriteBytes, NULL); ZeroMemory(pThis->m_strWriteBuffer, SUPER_LENGTH * sizeof(WCHAR)); wcscpy(pThis->m_strWriteBuffer, pwszWhat); wcscpy(pThis->m_strWriteBuffer + 1, SEPARATOR); wcscpy(pThis->m_strWriteBuffer + 2, pwszFirst); if(iSecondLength > 0) { wcscpy(pThis->m_strWriteBuffer + 2 + iFirstLength, SEPARATOR); wcscpy(pThis->m_strWriteBuffer + 3 + iFirstLength, pwszSecond); } wcscpy(pThis->m_strWriteBuffer + iLength - 2, ENDOFLINE); wcscpy(pThis->m_strWriteBuffer + iLength - 1, CRETURN); pThis->m_iSize = iLength; } } else if(blnFlush && pThis->m_iSize > 0) { // flush the buffer to file
WriteFile(hFile, pThis->m_strWriteBuffer, pThis->m_iSize * sizeof(WCHAR), &iWriteBytes, NULL); pThis->m_iSize = 0; } } __finally { LeaveCriticalSection(&(pThis->CSScriptFile)); } }
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// class DirectoryNode
//
////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// DirectoryNode, constructor for the DirectoryNode object, directory name hash
// is created here
//
// Parameters:
//
// pRoot, the filetree object that contains this directory
// pParent, the parent directory node
// pwszDirectoryName, the name of the directory
//
// Return:
//
// none
//
///////////////////////////////////////////////////////////////////////////////
DirectoryNode::DirectoryNode(IN FileTree* pRoot, IN DirectoryNode* pParent, IN CONST WCHAR* pwszDirectoryName) { m_pParent = pParent; m_pFirstChild = NULL; m_pNext = NULL; m_pRoot = pRoot; m_pMatchingDirectory = NULL; m_cchDirectoryName = wcslen(pwszDirectoryName); m_wszDirectoryName = NULL; if(pParent != NULL) { m_pNext = pParent->m_pFirstChild; pParent->m_pFirstChild = this; } m_DirectoryNameHash = CRC32Update(0, (UCHAR*)pwszDirectoryName, sizeof(WCHAR) * m_cchDirectoryName); }
///////////////////////////////////////////////////////////////////////////////
//
// ~DirectoryNode, destructor, responsible for clean up its own child
// directories, file nodes are not removed here, but in the
// destructor of the filetree
//
// Parameters:
//
// none
//
// Return:
//
// none
//
///////////////////////////////////////////////////////////////////////////////
DirectoryNode::~DirectoryNode(VOID) { // remove the directory nodes
DirectoryNode* pDirectory = m_pFirstChild; while(pDirectory) { DirectoryNode* pNext = pDirectory->m_pNext; delete pDirectory; pDirectory = NULL; pDirectory = pNext; } }
///////////////////////////////////////////////////////////////////////////////
//
// Match, this function is used to match a directory with another directory in
// the BaseTree, must be called before processing a file, otherwise,
// some matches do not make any sense
//
// Parameters:
//
// pBaseTree, the pointer to the BaseTree
//
// Return:
//
// PREP_NO_ERROR, no error
//
///////////////////////////////////////////////////////////////////////////////
INT DirectoryNode::Match(IN FileTree* pBaseTree) { DirectoryNode* pChoice = NULL;
// match this directory
if(m_pParent == NULL) { // Rule: localized root's mate is base root
m_pMatchingDirectory = &pBaseTree->m_Root; } else if(m_pParent->m_pMatchingDirectory != NULL) { // Rule: choose the cousin with an identical name
// from this directory's parent's mate's children
pChoice = m_pParent->m_pMatchingDirectory->m_pFirstChild; while(pChoice != NULL) { if((m_DirectoryNameHash == pChoice->m_DirectoryNameHash) && (wcscmp(m_wszDirectoryName, pChoice->m_wszDirectoryName) == 0)) { // update the matching directory member
m_pMatchingDirectory = pChoice; break; } pChoice = pChoice->m_pNext; } } else { // Rule: I have no cousins if my parent has no mate.
}
return(PREP_NO_ERROR); }
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// class FileNode
//
////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// FileNode, constructor for the FileNode object, compute for a name hash, file
// content hash is computed by ComputeHash
//
// Parameters:
//
// pParent, the parent directory
// pwszFileName, the name of the file
// ftLastModified, time the file was touched, not used
// iFileSize, the size of the file in bytes
//
// Return:
//
// none
//
///////////////////////////////////////////////////////////////////////////////
FileNode::FileNode(IN DirectoryNode* pParent, IN CONST WCHAR* pwszFileName, IN FILETIME ftLastModified, IN __int64 iFileSize) { m_pParent = pParent; m_ftLastModified = ftLastModified; m_iFileSize = iFileSize; m_cchFileName = wcslen(pwszFileName); m_wszFileName = NULL; m_pNextHashHash = NULL; m_pNextNameHash = NULL; m_FileNameHash = CRC32Update(0, (UCHAR*)pwszFileName, sizeof(WCHAR) * m_cchFileName); unsigned iNameHash = m_FileNameHash % HASH_SIZE; m_pNextNameHash = pParent->m_pRoot->m_aNameTable[iNameHash]; pParent->m_pRoot->m_aNameTable[iNameHash] = this; m_fPostCopySource = 0; }
///////////////////////////////////////////////////////////////////////////////
//
// ~FileNode, the destructor for a file node, does not
//
// Parameters:
//
// none
//
// Return:
//
// none
//
///////////////////////////////////////////////////////////////////////////////
FileNode::~FileNode(VOID) { }
///////////////////////////////////////////////////////////////////////////////
//
// Nibble, an inline function used to calculate a value for file content hash
//
// Parameters:
//
// wch, a single unicoded character
//
// Return:
//
// a BYTE that is intended for hashing
//
///////////////////////////////////////////////////////////////////////////////
__inline BYTE Nibble(WCHAR wch) { if((wch >= L'0') && (wch <= L'9')) { return((BYTE)(wch - L'0')); } else if((wch >= L'A') && (wch <= L'F')) { return((BYTE)(wch - L'A' + 10)); } else if((wch >= L'a') && (wch <= L'f')) { return((BYTE)(wch - L'a' + 10)); } else { return(99); } }
///////////////////////////////////////////////////////////////////////////////
//
// ComputeHash, produces a hash value for the file content, almost unique for
// all files, the hash value is of MD5, a 16 byte value
//
// Parameters:
//
// none
//
// Return:
//
// PREP_NO_ERROR, no error
// PREP_HASH_ERROR, hash error, wrong hash value
//
///////////////////////////////////////////////////////////////////////////////
INT FileNode::ComputeHash(VOID) { INT rc = PREP_NO_ERROR;
if(m_iFileSize == 0) { memset(m_Hash, 0, sizeof(m_Hash)); } else { WCHAR wszBuffer[STRING_LENGTH]; // filename, then hash
if(GetFilePatchSignatureW(m_wszLongFileName, (PATCH_OPTION_NO_REBASE | PATCH_OPTION_NO_BINDFIX | PATCH_OPTION_NO_LOCKFIX | PATCH_OPTION_NO_RESTIMEFIX | PATCH_OPTION_SIGNATURE_MD5), NULL, 0, NULL, // no ignore
0, NULL, // no retain
sizeof(wszBuffer), // buffer size in bytes
wszBuffer)) { WCHAR* pwch = wszBuffer;
for(INT i = 0; i < sizeof(m_Hash); i++) { BYTE hiNibble = Nibble(*pwch++); BYTE loNibble = Nibble(*pwch++);
m_Hash[i] = (BYTE)((hiNibble << 4) + loNibble); if((hiNibble > 0x0F) || (loNibble > 0x0F)) { rc = PREP_HASH_ERROR; break; } } if(*pwch != L'\0') { rc = PREP_HASH_ERROR; } } else { DisplayDebugMessage(FALSE, FALSE, FALSE, TRUE, L"warning, GetFilePatchSignatureW() failed, GLE=%08X\n, F=%ls", GetLastError(), m_wszLongFileName); rc = PREP_HASH_ERROR; } }
if(rc == PREP_NO_ERROR) { // add this file to root's hash table
unsigned iHashHash = (*(UINT*)(&m_Hash[0])) % HASH_SIZE; m_pNextHashHash = m_pParent->m_pRoot->m_aHashTable[iHashHash]; m_pParent->m_pRoot->m_aHashTable[iHashHash] = this; }
return(rc); }
///////////////////////////////////////////////////////////////////////////////
//
// Match, the file node is attempting to match with another file in the base
// trees interms of filename and filecontent through hashing, there are
// a total of 6 rules, in practice, rule 0 and 2 can be ignored
//
// Parameters:
//
// pBaseTree, the FileTree object that is intended to match with
//
// Return:
//
// PREP_NO_ERROR, no error
//
///////////////////////////////////////////////////////////////////////////////
INT FileNode::Match(IN FileTree* pBaseTree) { WCHAR wszTempName[STRING_LENGTH]; DETERMINATION eDetermination; unsigned iHashHash = (*(UINT*)(&m_Hash[0])) % HASH_SIZE; unsigned iNameHash = m_FileNameHash % HASH_SIZE; FileTree *pLocalizedTree = m_pParent->m_pRoot; FileNode *pChoice = NULL;
pLocalizedTree->m_cFilesDetermined++; fprintf(stderr, "%I64u of %I64u\r", pLocalizedTree->m_cFilesDetermined, pLocalizedTree->m_cFiles);
// Rule 0
// What: files that are of the same name, same content, same location in base and localized tree
// What to do here: record the location and name of the files with a move tag in the script file
// What to do later: the base file needs to be moved from the base to localized directory
if(m_pParent->m_pMatchingDirectory != NULL) { pChoice = pBaseTree->m_aHashTable[iHashHash]; while(pChoice != NULL) { if((m_FileNameHash == pChoice->m_FileNameHash) && (m_pParent->m_pMatchingDirectory == pChoice->m_pParent) && (memcmp(m_Hash, pChoice->m_Hash, sizeof(m_Hash)) == 0) && (wcscmp(m_wszFileName, pChoice->m_wszFileName) == 0)) { eDetermination = DETERMINATION_EXISTING; pLocalizedTree->m_cFilesExisting++; wcscpy(wszTempName, pLocalizedTree->m_pAnsParse->m_wszBaseDirectory + DRIVE_LETTER_LENGTH); wcscat(wszTempName, pChoice->m_wszLongFileName + pBaseTree->m_pLanguage->s_iDirectoryCount); pLocalizedTree->ToScriptFile(pLocalizedTree, pLocalizedTree->m_pLanguage->s_hScriptFile, ACTION_MOVE_FILE, wszTempName, m_wszLongFileName + pLocalizedTree->m_pLanguage->s_iDirectoryCount, FALSE); goto done; } pChoice = pChoice->m_pNextHashHash; } }
// Rule 1
// What: files that are of zero length in the localized tree
// What to do here: record the location and name of the files with a zero tag in the script file
// What to do later: re-create the zero length file
if(m_iFileSize == 0) { eDetermination = DETERMINATION_ZERO_LENGTH; pLocalizedTree->ToScriptFile(pLocalizedTree, pLocalizedTree->m_pLanguage->s_hScriptFile, ACTION_NEW_ZERO_FILE, m_wszLongFileName + pLocalizedTree->m_pLanguage->s_iDirectoryCount, NULL, FALSE); pLocalizedTree->m_cFilesZeroLength++; goto done; }
// Rule 2
// What: files that are of the different name, same content, same location in base and localized tree
// What to do here: record the location of the files with a rename tag in the script file
// What to do later: the base file needs to be renamed(actually moved) from the base to localized directory
if(m_pParent->m_pMatchingDirectory != NULL) { pChoice = pBaseTree->m_aHashTable[iHashHash]; while(pChoice != NULL) { if((m_pParent->m_pMatchingDirectory == pChoice->m_pParent) && (memcmp(m_Hash, pChoice->m_Hash, sizeof(m_Hash)) == 0)) { eDetermination = DETERMINATION_RENAMED; pLocalizedTree->m_cFilesRenamed++; wcscpy(wszTempName, pLocalizedTree->m_pAnsParse->m_wszBaseDirectory + DRIVE_LETTER_LENGTH); wcscat(wszTempName, pChoice->m_wszLongFileName + pBaseTree->m_pLanguage->s_iDirectoryCount); pLocalizedTree->ToScriptFile(pLocalizedTree, pLocalizedTree->m_pLanguage->s_hScriptFile, ACTION_RENAME_FILE, wszTempName, m_wszLongFileName + pLocalizedTree->m_pLanguage->s_iDirectoryCount, FALSE); goto done; } pChoice = pChoice->m_pNextHashHash; } }
// Rule 3
// What: files that are of the different name, same content, different location in base and localized tree,
// different location means that "same" directory but the directory name is localized
// What to do here: record the location of the files with a copy tag in the script file
// What to do later: the base file needs to be copied from the base to localized directory
pChoice = pBaseTree->m_aHashTable[iHashHash]; while(pChoice != NULL) { if(memcmp(m_Hash, pChoice->m_Hash, sizeof(m_Hash)) == 0) { eDetermination = DETERMINATION_COPIED; pLocalizedTree->m_cFilesCopied++; wcscpy(wszTempName, pLocalizedTree->m_pAnsParse->m_wszBaseDirectory + DRIVE_LETTER_LENGTH); wcscat(wszTempName, pChoice->m_wszLongFileName + pBaseTree->m_pLanguage->s_iDirectoryCount); pLocalizedTree->ToScriptFile(pLocalizedTree, pLocalizedTree->m_pLanguage->s_hScriptFile, ACTION_COPY_FILE, wszTempName, m_wszLongFileName + pLocalizedTree->m_pLanguage->s_iDirectoryCount, FALSE); goto done; } pChoice = pChoice->m_pNextHashHash; }
// Rule 4
// What: files with the same name, whatever location, different file content
// What to do here: record the location of the base file, patch file, and destination file with a patch tag in the script file
// What to do later: the localized file needs to be re-created from the base and patch file
pChoice = pBaseTree->m_aNameTable[iNameHash]; { while(pChoice != NULL) { if((m_FileNameHash == pChoice->m_FileNameHash) && (wcscmp(m_wszFileName, pChoice->m_wszFileName) == 0)) { eDetermination = DETERMINATION_PATCHED; wcscpy(wszTempName, pLocalizedTree->m_pLanguage->s_wszSubPatchDirectory); wcscat(wszTempName, m_wszFileName); wcscat(wszTempName, PATCH_EXT); pLocalizedTree->m_cFilesChanged++; pLocalizedTree->m_cbChangedFileSize += m_iFileSize; goto done; } pChoice = pChoice->m_pNextNameHash; } }
// Rule 5
// What: files that are not matched, considered as unique
// What to do here: copy the file into the except directory for storage,
// record the location of the localized file with a saved file tag
// What to do later: move the except file to the localized file location
eDetermination = DETERMINATION_UNMATCHED; pLocalizedTree->CopyFileTo(pLocalizedTree, ACTION_SAVED_FILE, pLocalizedTree->m_pLanguage->s_wszSubExceptDirectory, m_wszFileName, m_wszLongFileName, 0); pLocalizedTree->m_cFilesNoMatch++; pLocalizedTree->m_cbNoMatchFileSize += m_iFileSize; done:
// If it's a patching candidate, try it. Might demote to unmatched.
if(eDetermination == DETERMINATION_PATCHED) { // try to patch it
__int64 cbPatchedSize = 0; WCHAR wszOldFile[STRING_LENGTH]; if((m_iFileSize < MAX_PATCH_TARGET_SIZE) && (BuildPatch(pChoice->m_wszLongFileName, m_wszLongFileName, wszTempName, &cbPatchedSize) == PREP_NO_ERROR) && (cbPatchedSize < m_iFileSize)) { // the oldfile is now in the base directory
wcscpy(wszOldFile, pLocalizedTree->m_pAnsParse->m_wszBaseDirectory + DRIVE_LETTER_LENGTH); wcscat(wszOldFile, pChoice->m_wszLongFileName + pBaseTree->m_pLanguage->s_iDirectoryCount); // the patch file and the newfile
wcscat(wszTempName, SEPARATOR); wcscat(wszTempName, m_wszLongFileName + pLocalizedTree->m_pLanguage->s_iDirectoryCount); // record the information for apply the patch later
pLocalizedTree->ToScriptFile(pLocalizedTree, pLocalizedTree->m_pLanguage->s_hScriptFile, ACTION_PATCH_FILE, wszOldFile, wszTempName + pLocalizedTree->m_pLanguage->s_iPatchDirectoryCount, FALSE); pLocalizedTree->m_cbChangedFilePatchedSize += cbPatchedSize; } else { // if un-success, move the file into the except directory and record the information, so the file
// can be moved to its original position later
eDetermination = DETERMINATION_UNMATCHED; pLocalizedTree->CopyFileTo(pLocalizedTree, ACTION_NOT_PATCH_FILE, pLocalizedTree->m_pLanguage->s_wszSubExceptDirectory, m_wszFileName, m_wszLongFileName, 0); DeleteFileW(wszTempName); pLocalizedTree->m_cbChangedFileNotPatchedSize += m_iFileSize; } }
if(g_blnFullLog) { DisplayDebugMessage(TRUE, FALSE, FALSE, FALSE, L"N=%05I64u\tF=%ls\tS=%I64u", pLocalizedTree->m_cFilesDetermined, m_wszFileName, m_iFileSize); } return(PREP_NO_ERROR); }
///////////////////////////////////////////////////////////////////////////////
//
// BuildPatch, a wrapper function that handles the actual patching
//
// Parameters:
//
// pwszBaseFileName, the oldfile, the base file
// pwszLocFileName, the localized file
// pwszTempPatchFileName, the intended patch filename, can be changed
//
// Return:
//
// PREP_UNKNOWN_ERROR, something went wrong with the patch file created
// PREP_NOT_PATCHABLE, not patched
// PREP_NO_ERROR, no error
//
///////////////////////////////////////////////////////////////////////////////
INT FileNode::BuildPatch(IN WCHAR* pwszBaseFileName, IN WCHAR* pwszLocFileName, IN OUT WCHAR* pwszTempPatchFileName, OUT __int64* pcbPatchSize) { INT rc = PREP_NO_ERROR; HANDLE hFile = INVALID_HANDLE_VALUE;
// choose a different name if possible
hFile = CreateFileW(pwszTempPatchFileName, GENERIC_READ, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); if(hFile == INVALID_HANDLE_VALUE) { ULONG iLength = wcslen(pwszTempPatchFileName); WCHAR wszRandom[10]; do { ZeroMemory(wszRandom, 10 * sizeof(WCHAR)); _itow(rand() % FILE_LIMIT, wszRandom, 10); wcscpy(pwszTempPatchFileName + iLength, wszRandom); hFile = CreateFileW(pwszTempPatchFileName, GENERIC_READ, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); } while(hFile == INVALID_HANDLE_VALUE); } CloseHandle(hFile);
// do patch
if(CreatePatchFileW(pwszBaseFileName, pwszLocFileName, pwszTempPatchFileName, g_iBestMethod, NULL)) { // collect the stats, don't do this unless it is necessary, open a file
// and close a file can take a long time
if(g_blnCollectStat) { HANDLE handle = CreateFileW(pwszTempPatchFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
if(handle != INVALID_HANDLE_VALUE) { DWORD dwFileSizeLow = 0; DWORD dwFileSizeHigh = 0; DWORD dwError = 0;
dwFileSizeLow = GetFileSize(handle, &dwFileSizeHigh); dwError = GetLastError(); if((dwFileSizeLow == 0xFFFFFFFF) && (dwError != NO_ERROR)) { DisplayDebugMessage(FALSE, FALSE, FALSE, FALSE, L"warning, Created a patch, but can't get patch file size. GLE=%08X, F=%ls", dwError, pwszTempPatchFileName); rc = PREP_UNKNOWN_ERROR; } else { *pcbPatchSize = (((__int64) dwFileSizeHigh) << 32) + dwFileSizeLow; rc = PREP_NO_ERROR; } CloseHandle(handle); } else { DisplayDebugMessage(FALSE, FALSE, FALSE, FALSE, L"warning, Created a patch, but can't open the patch file. GLE=%08X, F=%ls", GetLastError(), pwszTempPatchFileName); rc = PREP_UNKNOWN_ERROR; } } } else { DWORD dwError = GetLastError(); DisplayDebugMessage(FALSE, FALSE, FALSE, FALSE, L"warning, CreatePatchFileW(\"%ls\") failed, GLE=%08X", pwszLocFileName, dwError); rc = PREP_NOT_PATCHABLE; }
return(rc); }
|