1694 lines
49 KiB
C++
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);
|
|
}
|