windows-nt/Source/XPSP1/NT/base/ntsetup/oemtools/oempatch/filetree.cpp
2020-09-26 16:20:57 +08:00

1694 lines
49 KiB
C++

#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);
}