aboutsummaryrefslogtreecommitdiff
path: root/dep/CascLib/src/CascRootFile_TVFS.cpp
diff options
context:
space:
mode:
authorShauren <shauren.trinity@gmail.com>2019-06-06 16:48:21 +0200
committerShauren <shauren.trinity@gmail.com>2019-06-08 17:09:24 +0200
commitfc330fd8ff0115804d9c4b53a1f810c00dd63de9 (patch)
treecfa10998fed66779834bf0b7a9b8b799d33d91d4 /dep/CascLib/src/CascRootFile_TVFS.cpp
parent82c7b6c5688495d90c4ee5995a4ff74039348296 (diff)
Dep/CascLib: Update to ladislav-zezula/CascLib@a1197edf0b3bd4d52c3f39be7fa7b44bb0b98012
Diffstat (limited to 'dep/CascLib/src/CascRootFile_TVFS.cpp')
-rw-r--r--dep/CascLib/src/CascRootFile_TVFS.cpp634
1 files changed, 634 insertions, 0 deletions
diff --git a/dep/CascLib/src/CascRootFile_TVFS.cpp b/dep/CascLib/src/CascRootFile_TVFS.cpp
new file mode 100644
index 00000000000..b115a215037
--- /dev/null
+++ b/dep/CascLib/src/CascRootFile_TVFS.cpp
@@ -0,0 +1,634 @@
+/*****************************************************************************/
+/* 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;
+
+//-----------------------------------------------------------------------------
+// Handler definition for TVFS root file
+
+//static FILE * fp = NULL;
+
+// 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;
+ }
+
+ int CaptureVfsSpanEntries(TVFS_DIRECTORY_HEADER & DirHeader, PENCODED_KEY pEKey, PDWORD PtrSpanSize, DWORD dwVfsOffset)
+ {
+ LPBYTE pbVfsFileTable = DirHeader.pbDirectoryData + DirHeader.VfsTableOffset;
+ LPBYTE pbVfsFileEntry = pbVfsFileTable + dwVfsOffset;
+ LPBYTE pbVfsFileEnd = pbVfsFileTable + DirHeader.VfsTableSize;
+ LPBYTE pbCftFileTable;
+ LPBYTE pbCftFileEntry;
+ LPBYTE pbCftFileEnd;
+ LPBYTE pbTemp = NULL;
+ size_t ItemSize = sizeof(DWORD) + sizeof(DWORD) + DirHeader.CftOffsSize;
+ DWORD dwCftOffset;
+ DWORD dwSpanCount;
+ DWORD dwSpanSize;
+
+ // Get the number of span entries
+ if(!(pbVfsFileTable <= pbVfsFileEntry && pbVfsFileEntry <= pbVfsFileEnd))
+ return ERROR_INVALID_PARAMETER;
+ dwSpanCount = *pbVfsFileEntry++;
+
+ // 1 - 224 = valid file, 225-254 = other file, 255 = deleted file
+ // We will ignore all files with unsupported span count
+ if(dwSpanCount == 0 || dwSpanCount > 224)
+ return ERROR_BAD_FORMAT;
+
+ // So far we've only saw entries with 1 span.
+ // Need to test files with multiple spans. Ignore such files for now.
+ assert(dwSpanCount == 1);
+
+ // Capture the array of span items
+ if(CaptureArray_(pbVfsFileEntry, pbVfsFileEnd, &pbTemp, ItemSize, dwSpanCount) == NULL)
+ return ERROR_BAD_FORMAT;
+
+ //
+ // 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
+ //
+
+ // Get the offset to the Container File Table
+ dwCftOffset = ConvertBytesToInteger_X(pbVfsFileEntry + sizeof(DWORD) + sizeof(DWORD), DirHeader.CftOffsSize);
+ dwSpanSize = ConvertBytesToInteger_4(pbVfsFileEntry + sizeof(DWORD));
+
+ // Go to the Container File Table and fetch the EKey from there
+ pbCftFileTable = DirHeader.pbDirectoryData + DirHeader.CftTableOffset;
+ pbCftFileEntry = pbCftFileTable + dwCftOffset;
+ pbCftFileEnd = pbCftFileTable + DirHeader.CftTableSize;
+ if((pbCftFileEntry + DirHeader.EKeySize) > pbCftFileEnd)
+ return ERROR_BAD_FORMAT;
+
+ // Give the pointer to the EKey
+ memcpy(pEKey->Value, pbCftFileEntry, DirHeader.EKeySize);
+ PtrSpanSize[0] = dwSpanSize;
+ return ERROR_SUCCESS;
+ }
+
+ //
+ // 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;
+ ENCODED_KEY EKey;
+ char * szSavePathPtr = PathBuffer.szPtr;
+ DWORD dwSpanSize = 0;
+ int nError;
+
+ // Prepare the EKey structure to be filled with zeros
+ memset(&EKey, 0, sizeof(ENCODED_KEY));
+
+ // 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 VFS and Container Table Entry in order to get the file EKey
+ nError = CaptureVfsSpanEntries(DirHeader, &EKey, &dwSpanSize, PathEntry.NodeValue);
+ if (nError != ERROR_SUCCESS)
+ return nError;
+
+ // We need to check whether this is another TVFS directory file
+ if (IsVfsSubDirectory(hs, DirHeader, SubHeader, EKey, 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 == dwSpanSize);
+ FileTree.InsertByName(pCKeyEntry, PathBuffer.szBegin);
+ }
+
+ ParseDirectoryData(hs, SubHeader, PathBuffer);
+ CASC_FREE(SubHeader.pbDirectoryData);
+ }
+ else
+ {
+// if (fp != NULL)
+// {
+// fwrite(PathBuffer.szBegin, 1, (PathBuffer.szPtr - PathBuffer.szBegin), fp);
+// fprintf(fp, "\n");
+// }
+
+ // 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 = 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;
+}