Files
TrinityCore/dep/CascLib/src/common/FileTree.cpp

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