mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-15 23:20:36 +01:00
741 lines
23 KiB
C++
741 lines
23 KiB
C++
/*****************************************************************************/
|
|
/* FileTree.cpp Copyright (c) Ladislav Zezula 2018 */
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Common implementation of a file tree object for various ROOt file formats */
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Date Ver Who Comment */
|
|
/* -------- ---- --- ------- */
|
|
/* 29.05.18 1.00 Lad The first version of FileTree.cpp */
|
|
/*****************************************************************************/
|
|
|
|
#define __CASCLIB_SELF__
|
|
#include "../CascLib.h"
|
|
#include "../CascCommon.h"
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Local arrays
|
|
|
|
static BYTE PathSeparators[256] =
|
|
{
|
|
/* 0x00 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0x10 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0x20 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
|
/* 0x30 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0x40 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
/* 0x50 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00
|
|
|
|
// Filled by zeros up to 256 bytes
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Local functions
|
|
|
|
#define START_ITEM_COUNT 0x4000
|
|
|
|
inline DWORD GET_NODE_INT32(void * node, size_t offset)
|
|
{
|
|
PDWORD PtrValue = (PDWORD)((LPBYTE)node + offset);
|
|
|
|
return PtrValue[0];
|
|
}
|
|
|
|
inline void SET_NODE_INT32(void * node, size_t offset, DWORD value)
|
|
{
|
|
PDWORD PtrValue = (PDWORD)((LPBYTE)node + offset);
|
|
|
|
PtrValue[0] = value;
|
|
}
|
|
|
|
#ifdef CASCLIB_DEV
|
|
//static DWORD dwFileCount = 0;
|
|
//
|
|
//static void WatchFileNode(PCASC_FILE_NODE pFileNode, const char * szFileName, bool bNewNodeInserted)
|
|
//{
|
|
// const char * szSuffix = bNewNodeInserted ? "NEW" : "EXISTING";
|
|
// const char * szFormat = "FileNode %p: CKey: %s, NameHash: %I64x (\"%s\") - %s\n";
|
|
// char szBuffer[MD5_STRING_SIZE + 1];
|
|
//
|
|
// // Selected nodes only
|
|
// if(dwFileCount < 10 && !_strnicmp(szFileName, "base", 4))
|
|
// {
|
|
// printf(szFormat, pFileNode,
|
|
// StringFromBinary(pFileNode->pCKeyEntry->CKey, MD5_HASH_SIZE, szBuffer),
|
|
// pFileNode->FileNameHash,
|
|
// szFileName,
|
|
// szSuffix);
|
|
// dwFileCount++;
|
|
// }
|
|
//}
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Protected functions
|
|
|
|
// Inserts a new file node to the file tree.
|
|
// If the pointer to file node array changes, the function also rebuilds all maps
|
|
PCASC_FILE_NODE CASC_FILE_TREE::InsertNew(PCASC_CKEY_ENTRY pCKeyEntry)
|
|
{
|
|
PCASC_FILE_NODE pFileNode;
|
|
|
|
// Create a brand new node
|
|
pFileNode = InsertNew();
|
|
if(pFileNode != NULL)
|
|
{
|
|
// Initialize the file node's CKeyEntry
|
|
pFileNode->pCKeyEntry = pCKeyEntry;
|
|
|
|
// Don't insert the node into any of the arrays here.
|
|
// That is the caller's responsibility
|
|
}
|
|
return pFileNode;
|
|
}
|
|
|
|
PCASC_FILE_NODE CASC_FILE_TREE::InsertNew()
|
|
{
|
|
PCASC_FILE_NODE pFileNode;
|
|
void * SaveItemArray = NodeTable.ItemArray(); // We need to save the array pointers. If it changes, we must rebuild both maps
|
|
|
|
// Create a brand new node
|
|
pFileNode = (PCASC_FILE_NODE)NodeTable.Insert(1);
|
|
if(pFileNode != NULL)
|
|
{
|
|
// Initialize the file node
|
|
pFileNode->FileNameHash = 0;
|
|
pFileNode->pCKeyEntry = NULL;
|
|
pFileNode->Parent = 0;
|
|
pFileNode->NameIndex = 0;
|
|
pFileNode->NameLength = 0;
|
|
pFileNode->Flags = 0;
|
|
|
|
// We need to supply a file data id for the new entry, otherwise the rebuilding function
|
|
// will use the uninitialized one
|
|
SetExtras(pFileNode, CASC_INVALID_ID, CASC_INVALID_ID, CASC_INVALID_ID);
|
|
|
|
// If the array pointer changed or we are close to the size of the array, we need to rebuild the maps
|
|
if(NodeTable.ItemArray() != SaveItemArray || (NodeTable.ItemCount() * 3 / 2) > NameMap.HashTableSize())
|
|
{
|
|
// Rebuild both maps. Note that rebuilding also inserts all items to the maps, so no need to insert them here
|
|
if(!RebuildNameMaps())
|
|
{
|
|
pFileNode = NULL;
|
|
assert(false);
|
|
}
|
|
}
|
|
}
|
|
return pFileNode;
|
|
}
|
|
|
|
// Insert the node to the map of FileNameHash -> CASC_FILE_NODE
|
|
bool CASC_FILE_TREE::InsertToNameMap(PCASC_FILE_NODE pFileNode)
|
|
{
|
|
bool bResult = false;
|
|
|
|
// Insert the file node to the table
|
|
if(pFileNode->FileNameHash != 0)
|
|
bResult = NameMap.InsertObject(pFileNode, &pFileNode->FileNameHash);
|
|
return bResult;
|
|
}
|
|
|
|
// Inserts the file node to the array of file data ids
|
|
bool CASC_FILE_TREE::InsertToIdTable(PCASC_FILE_NODE pFileNode)
|
|
{
|
|
PCASC_FILE_NODE * RefElement;
|
|
DWORD FileDataId = CASC_INVALID_ID;
|
|
|
|
if(FileDataIds.IsInitialized())
|
|
{
|
|
// Retrieve the file data id
|
|
GetExtras(pFileNode, &FileDataId, NULL, NULL);
|
|
if(FileDataId != CASC_INVALID_ID)
|
|
{
|
|
// Sanity check
|
|
assert(FileDataId < CASC_INVALID_ID);
|
|
|
|
// Insert the element to the array
|
|
RefElement = (PCASC_FILE_NODE *)FileDataIds.InsertAt(FileDataId);
|
|
if(RefElement != NULL)
|
|
{
|
|
RefElement[0] = pFileNode;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CASC_FILE_TREE::SetNodePlainName(PCASC_FILE_NODE pFileNode, const char * szPlainName, const char * szPlainNameEnd)
|
|
{
|
|
char * szNodeName;
|
|
size_t nLength = (szPlainNameEnd - szPlainName);
|
|
|
|
// Insert all chars to the name array
|
|
szNodeName = (char *)NameTable.Insert(nLength);
|
|
if(szNodeName != NULL)
|
|
{
|
|
// Copy the plain name to the node. Do not include the string terminator
|
|
memcpy(szNodeName, szPlainName, nLength);
|
|
|
|
// Supply the file name to the file node
|
|
pFileNode->NameIndex = (DWORD)NameTable.IndexOf(szNodeName);
|
|
pFileNode->NameLength = (USHORT)nLength;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CASC_FILE_TREE::SetKeyLength(DWORD aKeyLength)
|
|
{
|
|
if(aKeyLength > MD5_HASH_SIZE)
|
|
return false;
|
|
KeyLength = aKeyLength;
|
|
return true;
|
|
}
|
|
|
|
DWORD CASC_FILE_TREE::GetNextFileDataId()
|
|
{
|
|
if(FileDataIds.IsInitialized())
|
|
return (DWORD)(FileDataIds.ItemCount() + 1);
|
|
return CASC_INVALID_ID;
|
|
}
|
|
|
|
bool CASC_FILE_TREE::RebuildNameMaps()
|
|
{
|
|
PCASC_FILE_NODE pFileNode;
|
|
size_t nMaxItems = NodeTable.ItemCountMax();
|
|
|
|
// Free the map of "FullName -> CASC_FILE_NODE"
|
|
NameMap.Free();
|
|
|
|
// Create new map map "FullName -> CASC_FILE_NODE"
|
|
if(NameMap.Create(nMaxItems, sizeof(ULONGLONG), FIELD_OFFSET(CASC_FILE_NODE, FileNameHash)) != ERROR_SUCCESS)
|
|
return false;
|
|
|
|
// Reset the entire array, but buffers allocated
|
|
FileDataIds.Reset();
|
|
|
|
// Parse all items and insert them to the map
|
|
for(size_t i = 0; i < NodeTable.ItemCount(); i++)
|
|
{
|
|
// Retrieve the n-th object
|
|
pFileNode = (PCASC_FILE_NODE)NodeTable.ItemAt(i);
|
|
if(pFileNode != NULL)
|
|
{
|
|
// Insert it to the map "FileNameHash -> CASC_FILE_NODE"
|
|
if(pFileNode->FileNameHash != 0)
|
|
InsertToNameMap(pFileNode);
|
|
|
|
// Insert it to the array "FileDataId -> CASC_FILE_NODE"
|
|
if(FileDataIds.IsInitialized())
|
|
InsertToIdTable(pFileNode);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Public functions
|
|
|
|
DWORD CASC_FILE_TREE::Create(DWORD Flags)
|
|
{
|
|
PCASC_FILE_NODE pRootNode;
|
|
size_t FileNodeSize = FIELD_OFFSET(CASC_FILE_NODE, ExtraValues);
|
|
DWORD dwErrCode;
|
|
|
|
// Initialize the file tree
|
|
memset(this, 0, sizeof(CASC_FILE_TREE));
|
|
KeyLength = MD5_HASH_SIZE;
|
|
|
|
// Shall we use the data ID in the tree node?
|
|
if(Flags & FTREE_FLAG_USE_DATA_ID)
|
|
{
|
|
// Set the offset of the file data id in the entry
|
|
FileDataIdOffset = FileNodeSize;
|
|
FileNodeSize += sizeof(DWORD);
|
|
|
|
// Create the array for FileDataId -> CASC_FILE_NODE
|
|
dwErrCode = FileDataIds.Create<PCASC_FILE_NODE>(START_ITEM_COUNT);
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
return dwErrCode;
|
|
}
|
|
|
|
// Shall we use the locale ID in the tree node?
|
|
if(Flags & FTREE_FLAG_USE_LOCALE_FLAGS)
|
|
{
|
|
LocaleFlagsOffset = FileNodeSize;
|
|
FileNodeSize += sizeof(DWORD);
|
|
}
|
|
|
|
if(Flags & FTREE_FLAG_USE_CONTENT_FLAGS)
|
|
{
|
|
ContentFlagsOffset = FileNodeSize;
|
|
FileNodeSize += sizeof(DWORD);
|
|
}
|
|
|
|
// Align the file node size to 8 bytes
|
|
FileNodeSize = ALIGN_TO_SIZE(FileNodeSize, 8);
|
|
|
|
// Initialize the dynamic array
|
|
dwErrCode = NodeTable.Create(FileNodeSize, START_ITEM_COUNT);
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
// Create the dynamic array that will hold the node names
|
|
dwErrCode = NameTable.Create<char>(START_ITEM_COUNT);
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
// Insert the first "root" node, without name
|
|
pRootNode = (PCASC_FILE_NODE)NodeTable.Insert(1);
|
|
if(pRootNode != NULL)
|
|
{
|
|
// Initialize the node
|
|
memset(pRootNode, 0, NodeTable.ItemSize());
|
|
pRootNode->Parent = CASC_INVALID_INDEX;
|
|
pRootNode->NameIndex = CASC_INVALID_INDEX;
|
|
pRootNode->Flags = CFN_FLAG_FOLDER;
|
|
SetExtras(pRootNode, CASC_INVALID_ID, CASC_INVALID_ID, CASC_INVALID_ID);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create both maps
|
|
if(!RebuildNameMaps())
|
|
dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
return dwErrCode;
|
|
}
|
|
|
|
void CASC_FILE_TREE::Free()
|
|
{
|
|
// Free both arrays
|
|
NodeTable.Free();
|
|
NameTable.Free();
|
|
FileDataIds.Free();
|
|
|
|
// Free the name map
|
|
NameMap.Free();
|
|
|
|
// Zero the object
|
|
memset(this, 0, sizeof(CASC_FILE_TREE));
|
|
}
|
|
|
|
PCASC_FILE_NODE CASC_FILE_TREE::InsertByName(PCASC_CKEY_ENTRY pCKeyEntry, const char * szFileName, DWORD FileDataId, DWORD LocaleFlags, DWORD ContentFlags)
|
|
{
|
|
PCASC_FILE_NODE pFileNode;
|
|
ULONGLONG FileNameHash;
|
|
//bool bNewNodeInserted = false;
|
|
|
|
// Sanity checks
|
|
assert(szFileName != NULL && szFileName[0] != 0);
|
|
assert(pCKeyEntry != NULL);
|
|
|
|
// Calculate the file name hash
|
|
FileNameHash = CalcFileNameHash(szFileName);
|
|
|
|
// Do nothing if the file name is there already.
|
|
pFileNode = (PCASC_FILE_NODE)NameMap.FindObject(&FileNameHash);
|
|
if(pFileNode == NULL)
|
|
{
|
|
// Insert new item
|
|
pFileNode = InsertNew(pCKeyEntry);
|
|
if(pFileNode != NULL)
|
|
{
|
|
// Supply the name hash
|
|
pFileNode->FileNameHash = FileNameHash;
|
|
//bNewNodeInserted = true;
|
|
|
|
// Set the file data id and the extra values
|
|
SetExtras(pFileNode, FileDataId, LocaleFlags, ContentFlags);
|
|
|
|
// Insert the file node to the hash map
|
|
InsertToNameMap(pFileNode);
|
|
|
|
// Also make sure that it's in the file data id table, if the table is initialized
|
|
InsertToIdTable(pFileNode);
|
|
|
|
// Set the file name of the new file node
|
|
SetNodeFileName(pFileNode, szFileName);
|
|
|
|
// If we created a new node, we need to increment the reference count
|
|
assert(pCKeyEntry->RefCount < 0xFFFFFFFF);
|
|
pCKeyEntry->RefCount++;
|
|
FileNodes++;
|
|
}
|
|
}
|
|
|
|
#ifdef CASCLIB_DEV
|
|
//WatchFileNode(pFileNode, szFileName, bNewNodeInserted);
|
|
#endif
|
|
|
|
return pFileNode;
|
|
}
|
|
|
|
PCASC_FILE_NODE CASC_FILE_TREE::InsertByHash(PCASC_CKEY_ENTRY pCKeyEntry, ULONGLONG FileNameHash, DWORD FileDataId, DWORD LocaleFlags, DWORD ContentFlags)
|
|
{
|
|
PCASC_FILE_NODE pFileNode;
|
|
|
|
// Sanity checks
|
|
assert(FileDataIds.IsInitialized());
|
|
assert(FileDataId != CASC_INVALID_ID);
|
|
assert(FileNameHash != 0);
|
|
assert(pCKeyEntry != NULL);
|
|
|
|
// Insert the node to the tree by file data id
|
|
pFileNode = InsertById(pCKeyEntry, FileDataId, LocaleFlags, ContentFlags);
|
|
if(pFileNode != NULL)
|
|
{
|
|
// Supply the name hash
|
|
pFileNode->FileNameHash = FileNameHash;
|
|
|
|
// Insert the file node to the hash map
|
|
InsertToNameMap(pFileNode);
|
|
}
|
|
return pFileNode;
|
|
}
|
|
|
|
PCASC_FILE_NODE CASC_FILE_TREE::InsertById(PCASC_CKEY_ENTRY pCKeyEntry, DWORD FileDataId, DWORD LocaleFlags, DWORD ContentFlags)
|
|
{
|
|
PCASC_FILE_NODE pFileNode;
|
|
|
|
// Sanity checks
|
|
assert(FileDataIds.IsInitialized());
|
|
assert(FileDataId != CASC_INVALID_ID);
|
|
assert(pCKeyEntry != NULL);
|
|
|
|
// Check whether the file data id exists in the array of file data ids
|
|
if((pFileNode = FindById(FileDataId)) == NULL)
|
|
{
|
|
// Insert the new file node
|
|
pFileNode = InsertNew(pCKeyEntry);
|
|
if(pFileNode != NULL)
|
|
{
|
|
// Set the file data id and the extra values
|
|
SetExtras(pFileNode, FileDataId, LocaleFlags, ContentFlags);
|
|
|
|
// Insert the file node to the FileDataId array
|
|
InsertToIdTable(pFileNode);
|
|
|
|
// Increment the number of references
|
|
pCKeyEntry->RefCount++;
|
|
}
|
|
}
|
|
return pFileNode;
|
|
}
|
|
|
|
PCASC_FILE_NODE CASC_FILE_TREE::ItemAt(size_t nItemIndex)
|
|
{
|
|
return (PCASC_FILE_NODE)NodeTable.ItemAt(nItemIndex);
|
|
}
|
|
|
|
PCASC_FILE_NODE CASC_FILE_TREE::PathAt(char * szBuffer, size_t cchBuffer, size_t nItemIndex)
|
|
{
|
|
PCASC_FILE_NODE * RefFileNode;
|
|
PCASC_FILE_NODE pFileNode = NULL;
|
|
|
|
// If we have FileDataId, then we need to enumerate the files by FileDataId
|
|
if(FileDataIds.IsInitialized())
|
|
{
|
|
RefFileNode = (PCASC_FILE_NODE *)FileDataIds.ItemAt(nItemIndex);
|
|
if(RefFileNode != NULL)
|
|
{
|
|
pFileNode = RefFileNode[0];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pFileNode = (PCASC_FILE_NODE)NodeTable.ItemAt(nItemIndex);
|
|
}
|
|
|
|
// Construct the full path
|
|
PathAt(szBuffer, cchBuffer, pFileNode);
|
|
return pFileNode;
|
|
}
|
|
|
|
size_t CASC_FILE_TREE::PathAt(char * szBuffer, size_t cchBuffer, PCASC_FILE_NODE pFileNode)
|
|
{
|
|
PCASC_FILE_NODE pParentNode;
|
|
const char * szNamePtr;
|
|
char * szSaveBuffer = szBuffer;
|
|
char * szBufferEnd = szBuffer + cchBuffer - 1;
|
|
|
|
if(pFileNode != NULL && pFileNode->Parent != CASC_INVALID_INDEX)
|
|
{
|
|
// Copy all parents
|
|
pParentNode = (PCASC_FILE_NODE)NodeTable.ItemAt(pFileNode->Parent);
|
|
if(pParentNode != NULL)
|
|
{
|
|
// Query the parent and move the buffer
|
|
szBuffer = szBuffer + PathAt(szBuffer, cchBuffer, pParentNode);
|
|
}
|
|
|
|
// Retrieve the node name
|
|
szNamePtr = (const char *)NameTable.ItemAt(pFileNode->NameIndex);
|
|
|
|
// Check whether we have enough space
|
|
if((szBuffer + pFileNode->NameLength) < szBufferEnd)
|
|
{
|
|
// Copy the path part
|
|
memcpy(szBuffer, szNamePtr, pFileNode->NameLength);
|
|
szBuffer += pFileNode->NameLength;
|
|
|
|
// Append backslash
|
|
if((pFileNode->Flags & CFN_FLAG_FOLDER) && ((szBuffer + 1) < szBufferEnd))
|
|
{
|
|
*szBuffer++ = (pFileNode->Flags & CFN_FLAG_MOUNT_POINT) ? ':' : '\\';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Terminate buffer with zero
|
|
szBuffer[0] = 0;
|
|
|
|
// Return length of the copied string
|
|
return (szBuffer - szSaveBuffer);
|
|
}
|
|
|
|
PCASC_FILE_NODE CASC_FILE_TREE::Find(const char * szFullPath, DWORD FileDataId, PCASC_FIND_DATA pFindData)
|
|
{
|
|
PCASC_FILE_NODE pFileNode = NULL;
|
|
ULONGLONG FileNameHash;
|
|
|
|
// Can we search by FileDataId?
|
|
if(FileDataIds.IsInitialized() && (FileDataId != CASC_INVALID_ID || IsFileDataIdName(szFullPath, FileDataId)))
|
|
{
|
|
pFileNode = FindById(FileDataId);
|
|
}
|
|
else
|
|
{
|
|
if(szFullPath != NULL && szFullPath[0] != 0)
|
|
{
|
|
FileNameHash = CalcFileNameHash(szFullPath);
|
|
pFileNode = (PCASC_FILE_NODE)NameMap.FindObject(&FileNameHash);
|
|
}
|
|
}
|
|
|
|
// Did we find anything?
|
|
if(pFileNode != NULL && pFindData != NULL)
|
|
{
|
|
GetExtras(pFileNode, &pFindData->dwFileDataId, &pFindData->dwLocaleFlags, &pFindData->dwContentFlags);
|
|
}
|
|
return pFileNode;
|
|
}
|
|
|
|
PCASC_FILE_NODE CASC_FILE_TREE::Find(PCASC_CKEY_ENTRY pCKeyEntry)
|
|
{
|
|
PCASC_FILE_NODE pFileNode;
|
|
|
|
for(size_t i = 0; i < NodeTable.ItemCount(); i++)
|
|
{
|
|
pFileNode = (PCASC_FILE_NODE)NodeTable.ItemAt(i);
|
|
if((pFileNode->Flags & (CFN_FLAG_FOLDER | CFN_FLAG_MOUNT_POINT)) == 0)
|
|
{
|
|
if(pFileNode->pCKeyEntry == pCKeyEntry)
|
|
return pFileNode;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
PCASC_FILE_NODE CASC_FILE_TREE::Find(ULONGLONG FileNameHash)
|
|
{
|
|
return (PCASC_FILE_NODE)NameMap.FindObject(&FileNameHash);
|
|
}
|
|
|
|
PCASC_FILE_NODE CASC_FILE_TREE::FindById(DWORD FileDataId)
|
|
{
|
|
PCASC_FILE_NODE * RefElement;
|
|
PCASC_FILE_NODE pFileNode = NULL;
|
|
|
|
if(FileDataId != CASC_INVALID_ID && FileDataIds.IsInitialized())
|
|
{
|
|
// Insert the element to the array
|
|
RefElement = (PCASC_FILE_NODE *)FileDataIds.ItemAt(FileDataId);
|
|
if(RefElement != NULL)
|
|
{
|
|
pFileNode = RefElement[0];
|
|
}
|
|
}
|
|
return pFileNode;
|
|
}
|
|
|
|
bool CASC_FILE_TREE::SetNodeFileName(PCASC_FILE_NODE pFileNode, const char * szFileName)
|
|
{
|
|
ULONGLONG FileNameHash = 0;
|
|
PCASC_FILE_NODE pFolderNode = NULL;
|
|
CASC_PATH<char> PathBuffer;
|
|
LPCSTR szNodeBegin = szFileName;
|
|
size_t nFileNode = NodeTable.IndexOf(pFileNode);
|
|
size_t i;
|
|
DWORD Parent = 0;
|
|
|
|
// Sanity checks
|
|
assert(szFileName != NULL && szFileName[0] != 0);
|
|
|
|
// Traverse the entire path. For each subfolder, we insert an appropriate fake entry
|
|
for(i = 0; szFileName[i] != 0; i++)
|
|
{
|
|
char chOneChar = szFileName[i];
|
|
|
|
// Is there a path separator, such as '\\' or '/'?
|
|
// Also support TVFS "mount points", like "DivideAndConquer.w3m:war3map.doo"
|
|
if(PathSeparators[chOneChar])
|
|
{
|
|
size_t nHashLength = i;
|
|
|
|
// If there is a reparse point mark (':'), we need to include it as part of the name
|
|
if(PathSeparators[chOneChar] == 0x02)
|
|
{
|
|
PathBuffer.AppendChar(chOneChar);
|
|
nHashLength++;
|
|
}
|
|
|
|
// Calculate hash of the file name up to the end of the node name
|
|
FileNameHash = CalcNormNameHash(PathBuffer, nHashLength);
|
|
|
|
// If the entry is not there yet, create new one
|
|
if((pFolderNode = Find(FileNameHash)) == NULL)
|
|
{
|
|
// Insert new entry to the tree
|
|
pFolderNode = InsertNew();
|
|
if(pFolderNode == NULL)
|
|
return false;
|
|
|
|
// Fill-in flags, name hash and parent
|
|
pFolderNode->Flags |= (chOneChar == ':') ? (CFN_FLAG_FOLDER | CFN_FLAG_MOUNT_POINT) : CFN_FLAG_FOLDER;
|
|
pFolderNode->FileNameHash = FileNameHash;
|
|
pFolderNode->Parent = Parent;
|
|
FolderNodes++;
|
|
|
|
// Set the node sub name to the node
|
|
SetNodePlainName(pFolderNode, szNodeBegin, szFileName + i);
|
|
|
|
// Insert the entry to the name map
|
|
InsertToNameMap(pFolderNode);
|
|
}
|
|
|
|
// In case we're in the middle a mount point construction (called by CASC_FILE_TREE::InsertByName()),
|
|
// then we can get into situation where the call to Find() found the newly constructed item.
|
|
// In that case, we just set the name and bail out
|
|
else if(pFolderNode == pFileNode)
|
|
{
|
|
// The item must be a mount point, with name hash already set.
|
|
assert(pFolderNode->FileNameHash == FileNameHash);
|
|
assert(szFileName[i + 1] == 0);
|
|
|
|
// Fill-in the flags and parent
|
|
pFolderNode->Flags |= (CFN_FLAG_FOLDER | CFN_FLAG_MOUNT_POINT);
|
|
pFolderNode->Parent = Parent;
|
|
FolderNodes++;
|
|
|
|
// Set the node sub name to the node
|
|
SetNodePlainName(pFolderNode, szNodeBegin, szFileName + i);
|
|
return true;
|
|
}
|
|
|
|
// Move the parent to the current node
|
|
Parent = (DWORD)NodeTable.IndexOf(pFolderNode);
|
|
|
|
// Move the begin of the node after the separator
|
|
szNodeBegin = szFileName + i + 1;
|
|
|
|
// If the separator character was already appended, skip the rest of the loop
|
|
if(PathSeparators[chOneChar] == 0x02)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Append the character, if not appended yet
|
|
PathBuffer.AppendChar(AsciiToUpperTable_BkSlash[chOneChar]);
|
|
}
|
|
|
|
// If anything left, this is gonna be our node name
|
|
if(szNodeBegin < szFileName + i)
|
|
{
|
|
// We need to reset the file node pointer, as the file node table might have changed
|
|
pFileNode = (PCASC_FILE_NODE)NodeTable.ItemAt(nFileNode);
|
|
|
|
// Write the plain file name to the node
|
|
SetNodePlainName(pFileNode, szNodeBegin, szFileName + i);
|
|
pFileNode->Parent = Parent;
|
|
|
|
// Also insert the node to the hash table so CascOpenFile can find it
|
|
if(pFileNode->FileNameHash == 0)
|
|
{
|
|
pFileNode->FileNameHash = CalcNormNameHash(PathBuffer, i);
|
|
InsertToNameMap(pFileNode);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
size_t CASC_FILE_TREE::GetMaxFileIndex()
|
|
{
|
|
if(FileDataIds.IsInitialized())
|
|
{
|
|
return FileDataIds.ItemCount();
|
|
}
|
|
else
|
|
{
|
|
return NodeTable.ItemCount();
|
|
}
|
|
}
|
|
|
|
size_t CASC_FILE_TREE::GetCount()
|
|
{
|
|
return NodeTable.ItemCount();
|
|
}
|
|
|
|
size_t CASC_FILE_TREE::IndexOf(PCASC_FILE_NODE pFileNode)
|
|
{
|
|
return NodeTable.IndexOf(pFileNode);
|
|
}
|
|
|
|
void CASC_FILE_TREE::GetExtras(PCASC_FILE_NODE pFileNode, PDWORD PtrFileDataId, PDWORD PtrLocaleFlags, PDWORD PtrContentFlags)
|
|
{
|
|
DWORD FileDataId = CASC_INVALID_ID;
|
|
DWORD LocaleFlags = CASC_INVALID_ID;
|
|
DWORD ContentFlags = CASC_INVALID_ID;
|
|
|
|
// Retrieve the data ID, if supported
|
|
if(PtrFileDataId != NULL)
|
|
{
|
|
if(FileDataIdOffset != 0)
|
|
FileDataId = GET_NODE_INT32(pFileNode, FileDataIdOffset);
|
|
PtrFileDataId[0] = FileDataId;
|
|
}
|
|
|
|
// Retrieve the locale ID, if supported
|
|
if(PtrLocaleFlags != NULL)
|
|
{
|
|
if(LocaleFlagsOffset != 0)
|
|
LocaleFlags = GET_NODE_INT32(pFileNode, LocaleFlagsOffset);
|
|
PtrLocaleFlags[0] = LocaleFlags;
|
|
}
|
|
|
|
if(PtrContentFlags != NULL)
|
|
{
|
|
if(ContentFlagsOffset != 0)
|
|
ContentFlags = GET_NODE_INT32(pFileNode, ContentFlagsOffset);
|
|
PtrContentFlags[0] = ContentFlags;
|
|
}
|
|
}
|
|
|
|
void CASC_FILE_TREE::SetExtras(PCASC_FILE_NODE pFileNode, DWORD FileDataId, DWORD LocaleFlags, DWORD ContentFlags)
|
|
{
|
|
// Set the file data ID, if supported
|
|
if(FileDataIdOffset != 0)
|
|
{
|
|
SET_NODE_INT32(pFileNode, FileDataIdOffset, FileDataId);
|
|
}
|
|
|
|
// Set the locale ID, if supported
|
|
if(LocaleFlagsOffset != 0)
|
|
{
|
|
SET_NODE_INT32(pFileNode, LocaleFlagsOffset, LocaleFlags);
|
|
}
|
|
|
|
// Set the locale ID, if supported
|
|
if(ContentFlagsOffset != 0)
|
|
{
|
|
SET_NODE_INT32(pFileNode, ContentFlagsOffset, ContentFlags);
|
|
}
|
|
}
|