diff options
Diffstat (limited to 'dep/CascLib/src/CascRootFile_TVFS.cpp')
-rw-r--r-- | dep/CascLib/src/CascRootFile_TVFS.cpp | 634 |
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; +} |