Files
TrinityCore/dep/CascLib/src/CascRootFile_TVFS.cpp

649 lines
28 KiB
C++

/*****************************************************************************/
/* CascRootFile_TVFS.cpp Copyright (c) Ladislav Zezula 2018 */
/*---------------------------------------------------------------------------*/
/* ROOT handler for TACT VFS manifest format (root) */
/* Note: TACT = Trusted Application Content Transfer */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 24.05.18 1.00 Lad The first version of CascRootFile_TVFS.cpp */
/*****************************************************************************/
#define __CASCLIB_SELF__
#include "CascLib.h"
#include "CascCommon.h"
//-----------------------------------------------------------------------------
// Local defines
#define TVFS_FLAG_INCLUDE_CKEY 0x0001 // Include C-key in content file record
#define TVFS_FLAG_WRITE_SUPPORT 0x0002 // Write support. Include a table of encoding specifiers. This is required for writing files to the underlying storage. This bit is implied by the patch-support bit
#define TVFS_FLAG_PATCH_SUPPORT 0x0004 // Patch support. Include patch records in the content file records.
#define TVFS_FLAG_LOWERCASE_MANIFEST 0x0008 // Lowercase manifest. All paths in the path table have been converted to ASCII lowercase (i.e. [A-Z] converted to [a-z])
#define TVFS_PTE_PATH_SEPARATOR_PRE 0x0001 // There is path separator before the name
#define TVFS_PTE_PATH_SEPARATOR_POST 0x0002 // There is path separator after the name
#define TVFS_PTE_NODE_VALUE 0x0004 // The NodeValue in path table entry is valid
#define TVFS_FOLDER_NODE 0x80000000 // Highest bit is set if a file node is a folder
#define TVFS_FOLDER_SIZE_MASK 0x7FFFFFFF // Mask to get length of the folder
//-----------------------------------------------------------------------------
// Local structures
// In-memory layout of the TVFS file header
typedef struct _TVFS_DIRECTORY_HEADER
{
DWORD Signature; // Must be CASC_TVFS_ROOT_SIGNATURE
BYTE FormatVersion; // Version of the format. Should be 1.
BYTE HeaderSize; // Size of the header, in bytes
BYTE EKeySize; // Size of an E-Key. TACT uses 9-byte E-keys
BYTE PatchKeySize; // Size of a patch key. TACT uses 9-byte P-keys
DWORD Flags; // Flags. See TVFS_FLAG_XXX
// Followed by the offset table (variable length)
DWORD PathTableOffset; // Offset of the path table
DWORD PathTableSize; // Size of the path table
DWORD VfsTableOffset; // Offset of the VFS table
DWORD VfsTableSize; // Size of the VFS table
DWORD CftTableOffset; // Offset of the container file table
DWORD CftTableSize; // Size of the container file table
USHORT MaxDepth; // The maximum depth of the path prefix tree stored in the path table
DWORD EstTableOffset; // The offset of the encoding specifier table. Only if the write-support bit is set in the header flag
DWORD EstTableSize; // The size of the encoding specifier table. Only if the write-support bit is set in the header flag
DWORD CftOffsSize; // Byte length of the offset in the Content File Table entry
DWORD EstOffsSize; // Byte length of the offset in the Encoding Specifier Table entry
LPBYTE pbDirectoryData; // Pointer to the begin of directory data
LPBYTE pbDirectoryEnd; // Pointer to the end of directory data
// LPBYTE pbPathFileTable; // Begin and end of the path table
// LPBYTE pbPathTableEnd;
// LPBYTE pbVfsFileTable; // Begin and end of the VFS file table
// LPBYTE pbVfsTableEnd;
// LPBYTE pbCftFileTable; // Begin and end of the content file table
// LPBYTE pbCftTableEnd;
} TVFS_DIRECTORY_HEADER, *PTVFS_DIRECTORY_HEADER;
/*
// Minimum size of a valid path table entry. 1 byte + 1-byte name + 1 byte + DWORD
#define TVFS_HEADER_LENGTH FIELD_OFFSET(TVFS_DIRECTORY_HEADER, CftOffsSize)
// Minimum size of a valid path table entry. 1 byte + 1-byte name + 1 byte + DWORD
#define TVFS_MIN_PATH_ENTRY (1 + 1 + 1 + sizeof(DWORD))
// Minimum size of the VFS entry (SpanCount + FileOffset + SpanLength + CftOffset)
#define TVFS_MIN_VFS_ENTRY (1 + sizeof(DWORD) + sizeof(DWORD) + 1)
// Minimum size of the Content File Table entry (CASC_EKEY_SIZE + EncodedSize + ContentSize)
#define TVFS_MIN_CFT_ENTRY (CASC_EKEY_SIZE + sizeof(DWORD) + sizeof(DWORD))
// Minimum size of the TVFS folder data
#define TVFS_MIN_FILE_SIZE (TVFS_HEADER_LENGTH + TVFS_MIN_PATH_ENTRY + TVFS_MIN_VFS_ENTRY + TVFS_MIN_CFT_ENTRY)
// Maximum estimated file table. Empirically set to 8 MB, increase if needed.
#define TVFS_MAX_FILE_SIZE 0x00800000
*/
// In-memory layout of the path table entry
typedef struct _TVFS_PATH_TABLE_ENTRY
{
LPBYTE pbNamePtr; // Pointer to the begin of the node name
LPBYTE pbNameEnd; // Pointer to the end of the file name
DWORD NodeFlags; // TVFS_PTE_XXX
DWORD NodeValue; // Node value
} TVFS_PATH_TABLE_ENTRY, *PTVFS_PATH_TABLE_ENTRY;
// In-memory layout of VFS span entry
typedef struct _TVFS_SPAN_ENTRY
{
LPBYTE pbCftFileTable;
LPBYTE pbCftFileEntry;
LPBYTE pbCftFileEnd;
DWORD dwFileOffset; // Offset into the referenced file
DWORD dwSpanSize; // Size of the span
DWORD dwCftOffset; // Offset relative to the beginning of the container file table
} TVFS_SPAN_ENTRY, *PTVFS_SPAN_ENTRY;
//-----------------------------------------------------------------------------
// Handler definition for TVFS root file
// Structure for the root handler
struct TRootHandler_TVFS : public TFileTreeRoot
{
public:
TRootHandler_TVFS() : TFileTreeRoot(0)
{
// TVFS supports file names, but DOESN'T support CKeys.
dwFeatures |= CASC_FEATURE_FILE_NAMES;
dwNestLevel = 0;
}
// Returns size of "container file table offset" fiels in the VFS.
// - If the container file table is larger than 0xffffff bytes, it's 4 bytes
// - If the container file table is larger than 0xffff bytes, it's 3 bytes
// - If the container file table is larger than 0xff bytes, it's 2 bytes
// - If the container file table is smaller than 0xff bytes, it's 1 byte
static DWORD GetOffsetFieldSize(DWORD dwTableSize)
{
if(dwTableSize > 0xffffff)
return 4;
if(dwTableSize > 0xffff)
return 3;
if(dwTableSize > 0xff)
return 2;
return 1;
}
bool PathBuffer_AddChar(PATH_BUFFER & PathBuffer, char chOneChar)
{
if(PathBuffer.szPtr >= PathBuffer.szEnd)
return false;
*PathBuffer.szPtr++ = chOneChar;
*PathBuffer.szPtr = 0;
return true;
}
bool PathBuffer_AddString(PATH_BUFFER & PathBuffer, LPBYTE pbNamePtr, LPBYTE pbNameEnd)
{
size_t nLength = (pbNameEnd - pbNamePtr);
// Check whether we have enough space
if ((PathBuffer.szPtr + nLength) > PathBuffer.szEnd)
return false;
// Copy the node name
memcpy(PathBuffer.szPtr, pbNamePtr, nLength);
PathBuffer.szPtr += nLength;
return true;
}
bool PathBuffer_AppendNode(PATH_BUFFER & PathBuffer, TVFS_PATH_TABLE_ENTRY & PathEntry)
{
// Append the prefix separator, if needed
if (PathEntry.NodeFlags & TVFS_PTE_PATH_SEPARATOR_PRE)
PathBuffer_AddChar(PathBuffer, '/');
// Append the name fragment, if any
if (PathEntry.pbNameEnd > PathEntry.pbNamePtr)
PathBuffer_AddString(PathBuffer, PathEntry.pbNamePtr, PathEntry.pbNameEnd);
// Append the postfix separator, if needed
if (PathEntry.NodeFlags & TVFS_PTE_PATH_SEPARATOR_POST)
PathBuffer_AddChar(PathBuffer, '/');
// Always end the buffer with zero
PathBuffer.szPtr[0] = 0;
return true;
}
static int CaptureDirectoryHeader(TVFS_DIRECTORY_HEADER & DirHeader, LPBYTE pbDataPtr, LPBYTE pbDataEnd)
{
// Fill the header structure with zeros
memset(&DirHeader, 0, sizeof(TVFS_DIRECTORY_HEADER));
DirHeader.pbDirectoryData = pbDataPtr;
DirHeader.pbDirectoryEnd = pbDataEnd;
// Capture the signature
pbDataPtr = CaptureInteger32(pbDataPtr, pbDataEnd, &DirHeader.Signature);
if(pbDataPtr == NULL || DirHeader.Signature != CASC_TVFS_ROOT_SIGNATURE)
return ERROR_BAD_FORMAT;
// Capture the other four integers
pbDataPtr = CaptureByteArray(pbDataPtr, pbDataEnd, 4, &DirHeader.FormatVersion);
if(pbDataPtr == NULL || DirHeader.FormatVersion != 1 || DirHeader.EKeySize != 9 || DirHeader.PatchKeySize != 9 || DirHeader.HeaderSize < 8)
return ERROR_BAD_FORMAT;
// Capture the rest
pbDataPtr = CaptureByteArray(pbDataPtr, pbDataEnd, DirHeader.HeaderSize - FIELD_OFFSET(TVFS_DIRECTORY_HEADER, Flags), (LPBYTE)(&DirHeader.Flags));
if(pbDataPtr == NULL)
return ERROR_BAD_FORMAT;
// Swap the header values
DirHeader.Flags = ConvertBytesToInteger_4_LE((LPBYTE)(&DirHeader.Flags));
// Swap the offset table values
DirHeader.PathTableOffset = ConvertBytesToInteger_4((LPBYTE)(&DirHeader.PathTableOffset));
DirHeader.PathTableSize = ConvertBytesToInteger_4((LPBYTE)(&DirHeader.PathTableSize));
DirHeader.VfsTableOffset = ConvertBytesToInteger_4((LPBYTE)(&DirHeader.VfsTableOffset));
DirHeader.VfsTableSize = ConvertBytesToInteger_4((LPBYTE)(&DirHeader.VfsTableSize));
DirHeader.CftTableOffset = ConvertBytesToInteger_4((LPBYTE)(&DirHeader.CftTableOffset));
DirHeader.CftTableSize = ConvertBytesToInteger_4((LPBYTE)(&DirHeader.CftTableSize));
DirHeader.MaxDepth = (USHORT)ConvertBytesToInteger_2((LPBYTE)(&DirHeader.MaxDepth));
DirHeader.EstTableOffset = ConvertBytesToInteger_4((LPBYTE)(&DirHeader.EstTableOffset));
DirHeader.EstTableSize = ConvertBytesToInteger_4((LPBYTE)(&DirHeader.EstTableSize));
// Determine size of file table offsets
DirHeader.CftOffsSize = GetOffsetFieldSize(DirHeader.CftTableSize);
DirHeader.EstOffsSize = GetOffsetFieldSize(DirHeader.EstTableSize);
// Capture the path table
// DirHeader.pbPathFileTable = pbDirectory + DirHeader.PathTableOffset;
// DirHeader.pbPathTableEnd = pbDirectory + DirHeader.PathTableOffset + DirHeader.PathTableSize;
// if(DirHeader.pbPathTableEnd > pbDataEnd)
// return ERROR_BAD_FORMAT;
// Capture the VFS file table
// DirHeader.pbVfsFileTable = pbDirectory + DirHeader.VfsTableOffset;
// DirHeader.pbVfsTableEnd = pbDirectory + DirHeader.VfsTableOffset + DirHeader.VfsTableSize;
// if(DirHeader.pbVfsTableEnd > pbDataEnd)
// return ERROR_BAD_FORMAT;
// Capture the container file table
// DirHeader.pbCftFileTable = pbDirectory + DirHeader.CftTableOffset;
// DirHeader.pbCftTableEnd = pbDirectory + DirHeader.CftTableOffset + DirHeader.CftTableSize;
// if(DirHeader.pbCftTableEnd > pbDataEnd)
// return ERROR_BAD_FORMAT;
return ERROR_SUCCESS;
}
LPBYTE CaptureVfsSpanCount(TVFS_DIRECTORY_HEADER & DirHeader, DWORD dwVfsOffset, DWORD & SpanCount)
{
LPBYTE pbVfsFileTable = DirHeader.pbDirectoryData + DirHeader.VfsTableOffset;
LPBYTE pbVfsFileEntry = pbVfsFileTable + dwVfsOffset;
LPBYTE pbVfsFileEnd = pbVfsFileTable + DirHeader.VfsTableSize;
// Get the number of span entries
if(!(pbVfsFileTable <= pbVfsFileEntry && pbVfsFileEntry < pbVfsFileEnd))
return NULL;
SpanCount = *pbVfsFileEntry++;
// 1 - 224 = valid file, 225-254 = other file, 255 = deleted file
// We will ignore all files with unsupported span count
return (1 <= SpanCount && SpanCount <= 224) ? pbVfsFileEntry : NULL;
}
LPBYTE CaptureVfsSpanEntry(TVFS_DIRECTORY_HEADER & DirHeader, LPBYTE pbVfsSpanEntry, TVFS_SPAN_ENTRY & SpanEntry)
{
LPBYTE pbVfsFileTable = DirHeader.pbDirectoryData + DirHeader.VfsTableOffset;
LPBYTE pbVfsFileEnd = pbVfsFileTable + DirHeader.VfsTableSize;
size_t ItemSize = sizeof(DWORD) + sizeof(DWORD) + DirHeader.CftOffsSize;
// Check the range being captured
if(pbVfsSpanEntry < pbVfsFileTable || (pbVfsSpanEntry + ItemSize) > pbVfsFileEnd)
return NULL;
//
// Structure of the span entry:
// (4bytes): Offset into the referenced file (big endian)
// (4bytes): Size of the span (big endian)
// (?bytes): Offset into Container File Table. Length depends on container file table size
//
SpanEntry.dwFileOffset = ConvertBytesToInteger_4(pbVfsSpanEntry);
SpanEntry.dwSpanSize = ConvertBytesToInteger_4(pbVfsSpanEntry + sizeof(DWORD));
SpanEntry.dwCftOffset = ConvertBytesToInteger_X(pbVfsSpanEntry + sizeof(DWORD) + sizeof(DWORD), DirHeader.CftOffsSize);
// Resolve the Container File Table entries
SpanEntry.pbCftFileTable = DirHeader.pbDirectoryData + DirHeader.CftTableOffset;
SpanEntry.pbCftFileEntry = SpanEntry.pbCftFileTable + SpanEntry.dwCftOffset;
SpanEntry.pbCftFileEnd = SpanEntry.pbCftFileTable + DirHeader.CftTableSize;
return pbVfsSpanEntry + ItemSize;
}
//
// Structure of the path table entry:
// (1byte) 0x00 (optional) - means that there will be prefix path separator
// (1byte) File name length
// (?byte) File name
// (1byte) 0x00 (optional) - means that there will be postfix path separator
// (1byte) 0xFF (optional) - node value identifier
// (4byte) - node value
//
// Note: The path "data\archive\maps\file.bmp" could be cut into nodes like:
// data\0 (or data with subdirectory)
// arc
// hive\0
// maps\0 (or folder data)
// file.bmp
//
LPBYTE CapturePathEntry(TVFS_PATH_TABLE_ENTRY & PathEntry, LPBYTE pbPathTablePtr, LPBYTE pbPathTableEnd)
{
// Reset the path entry structure
PathEntry.pbNamePtr = pbPathTablePtr;
PathEntry.pbNameEnd = pbPathTablePtr;
PathEntry.NodeFlags = 0;
PathEntry.NodeValue = 0;
// Zero before the name means prefix path separator
if (pbPathTablePtr < pbPathTableEnd && pbPathTablePtr[0] == 0)
{
PathEntry.NodeFlags |= TVFS_PTE_PATH_SEPARATOR_PRE;
pbPathTablePtr++;
}
// Capture the length of the name fragment
if (pbPathTablePtr < pbPathTableEnd && pbPathTablePtr[0] != 0xFF)
{
// Capture length of the name fragment
size_t nLength = *pbPathTablePtr++;
if ((pbPathTablePtr + nLength) > pbPathTableEnd)
return NULL;
PathEntry.pbNamePtr = pbPathTablePtr;
PathEntry.pbNameEnd = pbPathTablePtr + nLength;
pbPathTablePtr += nLength;
}
// Zero after the name means postfix path separator
if (pbPathTablePtr < pbPathTableEnd && pbPathTablePtr[0] == 0)
{
PathEntry.NodeFlags |= TVFS_PTE_PATH_SEPARATOR_POST;
pbPathTablePtr++;
}
if (pbPathTablePtr < pbPathTableEnd)
{
// Check for node value
if (pbPathTablePtr[0] == 0xFF)
{
if ((pbPathTablePtr + 1 + sizeof(DWORD)) > pbPathTableEnd)
return NULL;
PathEntry.NodeValue = ConvertBytesToInteger_4(pbPathTablePtr + 1);
PathEntry.NodeFlags |= TVFS_PTE_NODE_VALUE;
pbPathTablePtr = pbPathTablePtr + 1 + sizeof(DWORD);
}
// Non-0xFF after the name means path separator after
else
{
PathEntry.NodeFlags |= TVFS_PTE_PATH_SEPARATOR_POST;
assert(pbPathTablePtr[0] != 0);
}
}
return pbPathTablePtr;
}
bool IsVfsFileEKey(TCascStorage * hs, ENCODED_KEY & EKey, size_t EKeyLength)
{
PCASC_CKEY_ENTRY pCKeyEntry;
size_t ItemCount = hs->VfsRootList.ItemCount();
// Search the array
for (size_t i = 0; i < ItemCount; i++)
{
pCKeyEntry = (PCASC_CKEY_ENTRY)hs->VfsRootList.ItemAt(i);
if (pCKeyEntry != NULL)
{
if (!memcmp(pCKeyEntry->EKey, EKey.Value, EKeyLength))
return true;
}
}
// Not found in the VFS list
return false;
}
// This function verifies whether a file is actually a sub-directory.
// If yes, it contains just another "TVFS" virtual file system, just like the ROOT file.
int IsVfsSubDirectory(TCascStorage * hs, TVFS_DIRECTORY_HEADER & DirHeader, TVFS_DIRECTORY_HEADER & SubHeader, ENCODED_KEY & EKey, DWORD dwFileSize)
{
PCASC_CKEY_ENTRY pCKeyEntry;
LPBYTE pbVfsData = NULL;
DWORD cbVfsData = dwFileSize;
int nError = ERROR_BAD_FORMAT;
// Verify whether the EKey is in the list of VFS root files
if(IsVfsFileEKey(hs, EKey, DirHeader.EKeySize))
{
// Locate the CKey entry
if((pCKeyEntry = FindCKeyEntry_EKey(hs, EKey.Value)) != NULL)
{
// Load the entire file into memory
pbVfsData = LoadInternalFileToMemory(hs, pCKeyEntry, &cbVfsData);
if (pbVfsData && cbVfsData)
{
// Capture the file folder. This also serves as test
nError = CaptureDirectoryHeader(SubHeader, pbVfsData, pbVfsData + cbVfsData);
if (nError == ERROR_SUCCESS)
return nError;
// Clear the captured header
memset(&SubHeader, 0, sizeof(TVFS_DIRECTORY_HEADER));
CASC_FREE(pbVfsData);
}
}
}
return nError;
}
void InsertRootVfsEntry(TCascStorage * hs, LPBYTE pbCKey, const char * szFormat, size_t nIndex)
{
PCASC_CKEY_ENTRY pCKeyEntry;
char szFileName[0x20];
// The CKey entry must exist
if((pCKeyEntry = FindCKeyEntry_CKey(hs, pbCKey)) != NULL)
{
CascStrPrintf(szFileName, _countof(szFileName), szFormat, nIndex);
Insert(szFileName, pCKeyEntry);
}
}
DWORD ParsePathFileTable(TCascStorage * hs, TVFS_DIRECTORY_HEADER & DirHeader, PATH_BUFFER & PathBuffer, LPBYTE pbPathTablePtr, LPBYTE pbPathTableEnd)
{
TVFS_DIRECTORY_HEADER SubHeader;
TVFS_PATH_TABLE_ENTRY PathEntry;
PCASC_CKEY_ENTRY pCKeyEntry;
LPBYTE pbVfsSpanEntry;
char * szSavePathPtr = PathBuffer.szPtr;
DWORD dwSpanCount = 0;
int nError;
// Parse the file table
while(pbPathTablePtr < pbPathTableEnd)
{
// Capture the single path table entry
pbPathTablePtr = CapturePathEntry(PathEntry, pbPathTablePtr, pbPathTableEnd);
if(pbPathTablePtr == NULL)
return ERROR_BAD_FORMAT;
// Append the node name to the total path. Also add backslash, if it's a folder
PathBuffer_AppendNode(PathBuffer, PathEntry);
// Folder component
if (PathEntry.NodeFlags & TVFS_PTE_NODE_VALUE)
{
// If the TVFS_FOLDER_NODE is set, then the path node is a directory,
// with its data immediately following the path node. Lower 31 bits of NodeValue
// contain the length of the directory (including the NodeValue!)
if (PathEntry.NodeValue & TVFS_FOLDER_NODE)
{
LPBYTE pbDirectoryEnd = pbPathTablePtr + (PathEntry.NodeValue & TVFS_FOLDER_SIZE_MASK) - sizeof(DWORD);
// Check the available data
assert((PathEntry.NodeValue & TVFS_FOLDER_SIZE_MASK) >= sizeof(DWORD));
// Recursively call the folder parser on the same file
nError = ParsePathFileTable(hs, DirHeader, PathBuffer, pbPathTablePtr, pbDirectoryEnd);
if (nError != ERROR_SUCCESS)
return nError;
// Skip the directory data
pbPathTablePtr = pbDirectoryEnd;
}
else
{
// Capture the number of VFS spans
pbVfsSpanEntry = CaptureVfsSpanCount(DirHeader, PathEntry.NodeValue, dwSpanCount);
if(pbVfsSpanEntry == NULL)
return ERROR_BAD_FORMAT;
// TODO: Need to support multi-span files larger than 4 GB
// Example: CoD: Black Ops 4, file "zone/base.xpak" 0x16 spans, over 15 GB size
assert(dwSpanCount == 1);
dwSpanCount = 1;
// Parse all span entries
for(DWORD i = 0; i < dwSpanCount; i++)
{
TVFS_SPAN_ENTRY SpanEntry;
ENCODED_KEY EKey = {0};
// Capture the n-th span entry
pbVfsSpanEntry = CaptureVfsSpanEntry(DirHeader, pbVfsSpanEntry, SpanEntry);
if(pbVfsSpanEntry == NULL)
return ERROR_FILE_CORRUPT;
// Capture the encoded key
if((SpanEntry.pbCftFileEntry + DirHeader.EKeySize) > SpanEntry.pbCftFileEnd)
return ERROR_BAD_FORMAT;
// Copy the EKey
memcpy(EKey.Value, SpanEntry.pbCftFileEntry, DirHeader.EKeySize);
// We need to check whether this is another TVFS directory file
if (IsVfsSubDirectory(hs, DirHeader, SubHeader, EKey, SpanEntry.dwSpanSize) == ERROR_SUCCESS)
{
// Add colon (':')
PathBuffer_AddChar(PathBuffer, ':');
// Insert the file to the file tree
if((pCKeyEntry = FindCKeyEntry_EKey(hs, EKey.Value)) != NULL)
{
// The file content size should already be there
assert(pCKeyEntry->ContentSize == SpanEntry.dwSpanSize);
FileTree.InsertByName(pCKeyEntry, PathBuffer.szBegin);
}
ParseDirectoryData(hs, SubHeader, PathBuffer);
CASC_FREE(SubHeader.pbDirectoryData);
}
else
{
// Insert the file to the file tree
if((pCKeyEntry = FindCKeyEntry_EKey(hs, EKey.Value)) != NULL)
{
// If the file content is not there, supply it now
if(pCKeyEntry->ContentSize == CASC_INVALID_SIZE)
pCKeyEntry->ContentSize = SpanEntry.dwSpanSize;
FileTree.InsertByName(pCKeyEntry, PathBuffer.szBegin);
}
}
}
}
// Reset the position of the path buffer
PathBuffer.szPtr = szSavePathPtr;
PathBuffer.szPtr[0] = 0;
}
}
// Return the total number of entries
return ERROR_SUCCESS;
}
int ParseDirectoryData(TCascStorage * hs, TVFS_DIRECTORY_HEADER & DirHeader, PATH_BUFFER & PathBuffer)
{
LPBYTE pbRootDirectory = DirHeader.pbDirectoryData + DirHeader.PathTableOffset;
LPBYTE pbRootDirPtr = pbRootDirectory;
LPBYTE pbRootDirEnd = pbRootDirPtr + DirHeader.PathTableSize;
DWORD dwNodeValue = 0;
// Most usually, there is a root directory in the folder
if((pbRootDirPtr + 1 + sizeof(DWORD)) < pbRootDirEnd)
{
//
// The structure of the root directory
// -----------------------------------
// 1byte 0xFF
// 4bytes NodeValue (BigEndian). The most significant bit is set
// - Lower 31 bits contain length of the directory data, including NodeValue
//
if(pbRootDirPtr[0] == 0xFF)
{
// Get the NodeValue and check its highest bit
if(CaptureInteger32_BE(pbRootDirPtr + 1, pbRootDirEnd, &dwNodeValue) == NULL || (dwNodeValue & TVFS_FOLDER_NODE) == 0)
return ERROR_BAD_FORMAT;
// Get the range of the root directory
pbRootDirEnd = pbRootDirPtr + 1 + (dwNodeValue & TVFS_FOLDER_SIZE_MASK);
pbRootDirPtr = pbRootDirPtr + 1 + sizeof(DWORD);
// Check the directory
if(pbRootDirEnd > (pbRootDirectory + DirHeader.PathTableSize))
return ERROR_BAD_FORMAT;
}
}
// Now go parse the path file table
return ParsePathFileTable(hs, DirHeader, PathBuffer, pbRootDirPtr, pbRootDirEnd);
}
int Load(TCascStorage * hs, TVFS_DIRECTORY_HEADER & RootHeader)
{
// PCASC_CKEY_ENTRY pCKeyEntry;
PATH_BUFFER PathBuffer;
char szPathBuffer[MAX_PATH];
// Initialize the path buffer
memset(szPathBuffer, 0, sizeof(szPathBuffer));
PathBuffer.szBegin =
PathBuffer.szPtr = szPathBuffer;
PathBuffer.szEnd = szPathBuffer + MAX_PATH;
// Save the length of the key
FileTree.SetKeyLength(RootHeader.EKeySize);
// Insert the main VFS root file as named entry
InsertRootVfsEntry(hs, hs->VfsRoot.CKey, "vfs-root", 0);
// Insert all VFS roots folders as files
//for(size_t i = 0; i < hs->VfsRootList.ItemCount(); i++)
//{
// pCKeyEntry = (PCASC_CKEY_ENTRY)hs->VfsRootList.ItemAt(i);
// InsertRootVfsEntry(hs, pCKeyEntry->CKey, "vfs-%u", i+1);
//}
// Parse the entire directory data
return ParseDirectoryData(hs, RootHeader, PathBuffer);
}
DWORD dwNestLevel;
};
//-----------------------------------------------------------------------------
// Public functions - TVFS root
int RootHandler_CreateTVFS(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
{
TRootHandler_TVFS * pRootHandler = NULL;
TVFS_DIRECTORY_HEADER RootHeader;
int nError;
// Capture the entire root directory
nError = TRootHandler_TVFS::CaptureDirectoryHeader(RootHeader, pbRootFile, pbRootFile + cbRootFile);
if(nError == ERROR_SUCCESS)
{
// Allocate the root handler object
pRootHandler = new TRootHandler_TVFS();
if(pRootHandler != NULL)
{
// Load the root directory. If load failed, we free the object
nError = pRootHandler->Load(hs, RootHeader);
if(nError != ERROR_SUCCESS)
{
delete pRootHandler;
pRootHandler = NULL;
}
}
}
// Assign the root directory (or NULL) and return error
hs->pRootHandler = pRootHandler;
return nError;
}