mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-18 00:18:43 +01:00
716 lines
31 KiB
C++
716 lines
31 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;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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;
|
|
}
|
|
|
|
// Returns size of "container file table offset" field 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_AppendNode(CASC_PATH<char> & PathBuffer, TVFS_PATH_TABLE_ENTRY & PathEntry)
|
|
{
|
|
// Append the prefix separator, if needed
|
|
if (PathEntry.NodeFlags & TVFS_PTE_PATH_SEPARATOR_PRE)
|
|
PathBuffer.AppendChar('/');
|
|
|
|
// Append the name fragment, if any
|
|
if (PathEntry.pbNameEnd > PathEntry.pbNamePtr)
|
|
PathBuffer.AppendStringN((const char *)PathEntry.pbNamePtr, (PathEntry.pbNameEnd - PathEntry.pbNamePtr), false);
|
|
|
|
// Append the postfix separator, if needed
|
|
if (PathEntry.NodeFlags & TVFS_PTE_PATH_SEPARATOR_POST)
|
|
PathBuffer.AppendChar('/');
|
|
|
|
return true;
|
|
}
|
|
|
|
static DWORD 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 CaptureVfsSpanEntries(TVFS_DIRECTORY_HEADER & DirHeader, LPBYTE pbVfsSpanEntry, PCASC_CKEY_ENTRY PtrSpanEntry, size_t SpanCount)
|
|
{
|
|
LPBYTE pbCftFileTable;
|
|
LPBYTE pbCftFileEntry;
|
|
LPBYTE pbCftFileEnd;
|
|
LPBYTE pbVfsFileTable = DirHeader.pbDirectoryData + DirHeader.VfsTableOffset;
|
|
LPBYTE pbVfsFileEnd = pbVfsFileTable + DirHeader.VfsTableSize;
|
|
size_t ItemSize = sizeof(DWORD) + sizeof(DWORD) + DirHeader.CftOffsSize;
|
|
|
|
// Check whether all spans are included in the valid range
|
|
if(pbVfsSpanEntry < pbVfsFileTable || (pbVfsSpanEntry + (ItemSize * SpanCount)) > pbVfsFileEnd)
|
|
return NULL;
|
|
|
|
// Convert all spans
|
|
for(size_t i = 0; i < SpanCount; i++)
|
|
{
|
|
DWORD dwCftOffset = ConvertBytesToInteger_X(pbVfsSpanEntry + sizeof(DWORD) + sizeof(DWORD), DirHeader.CftOffsSize);
|
|
|
|
//
|
|
// 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
|
|
//
|
|
|
|
// Resolve the Container File Table entry
|
|
pbCftFileTable = DirHeader.pbDirectoryData + DirHeader.CftTableOffset;
|
|
pbCftFileEntry = pbCftFileTable + dwCftOffset;
|
|
pbCftFileEnd = pbCftFileTable + DirHeader.CftTableSize;
|
|
|
|
// Capture the EKey and the file size
|
|
if((pbCftFileEntry + DirHeader.EKeySize + sizeof(DWORD)) > pbCftFileEnd)
|
|
return NULL;
|
|
|
|
// Copy the EKey and content size
|
|
CaptureEncodedKey(PtrSpanEntry->EKey, pbCftFileEntry, DirHeader.EKeySize);
|
|
PtrSpanEntry->ContentSize = ConvertBytesToInteger_4(pbVfsSpanEntry + sizeof(DWORD));
|
|
|
|
// Move to the next entry
|
|
pbVfsSpanEntry += ItemSize;
|
|
PtrSpanEntry++;
|
|
}
|
|
|
|
return pbVfsSpanEntry;
|
|
}
|
|
|
|
//
|
|
// 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, LPBYTE 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, 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.
|
|
DWORD IsVfsSubDirectory(TCascStorage * hs, TVFS_DIRECTORY_HEADER & DirHeader, TVFS_DIRECTORY_HEADER & SubHeader, LPBYTE EKey, DWORD dwFileSize)
|
|
{
|
|
PCASC_CKEY_ENTRY pCKeyEntry;
|
|
LPBYTE pbVfsData = NULL;
|
|
DWORD cbVfsData = dwFileSize;
|
|
DWORD dwErrCode = 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)) != NULL)
|
|
{
|
|
// Load the entire file into memory
|
|
pbVfsData = LoadInternalFileToMemory(hs, pCKeyEntry, &cbVfsData);
|
|
if (pbVfsData && cbVfsData)
|
|
{
|
|
// Capture the file folder. This also serves as test
|
|
dwErrCode = CaptureDirectoryHeader(SubHeader, pbVfsData, pbVfsData + cbVfsData);
|
|
if (dwErrCode == ERROR_SUCCESS)
|
|
return dwErrCode;
|
|
|
|
// Clear the captured header
|
|
memset(&SubHeader, 0, sizeof(TVFS_DIRECTORY_HEADER));
|
|
CASC_FREE(pbVfsData);
|
|
}
|
|
}
|
|
}
|
|
|
|
return dwErrCode;
|
|
}
|
|
|
|
PCASC_CKEY_ENTRY InsertUnknownCKeyEntry(TCascStorage * hs, LPBYTE pbEKey, size_t cbEKey, DWORD ContentSize)
|
|
{
|
|
PCASC_CKEY_ENTRY pCKeyEntry;
|
|
|
|
// Insert a new entry to the array. DO NOT ALLOW enlarge array here
|
|
pCKeyEntry = (PCASC_CKEY_ENTRY)hs->CKeyArray.Insert(1, false);
|
|
if(pCKeyEntry != NULL)
|
|
{
|
|
memset(pCKeyEntry, 0, sizeof(CASC_CKEY_ENTRY));
|
|
memcpy(pCKeyEntry->EKey, pbEKey, cbEKey);
|
|
pCKeyEntry->StorageOffset = CASC_INVALID_OFFS64;
|
|
pCKeyEntry->ContentSize = ContentSize;
|
|
pCKeyEntry->EncodedSize = CASC_INVALID_SIZE;
|
|
pCKeyEntry->Flags = CASC_CE_HAS_EKEY | CASC_CE_HAS_EKEY_PARTIAL;
|
|
pCKeyEntry->SpanCount = 1;
|
|
|
|
// Copy the information from index files to the CKey entry
|
|
CopyEKeyEntry(hs, pCKeyEntry);
|
|
|
|
// Insert the item into EKey map
|
|
hs->EKeyMap.InsertObject(pCKeyEntry, pCKeyEntry->EKey);
|
|
}
|
|
|
|
return pCKeyEntry;
|
|
}
|
|
|
|
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, CASC_PATH<char> & PathBuffer, LPBYTE pbPathTablePtr, LPBYTE pbPathTableEnd)
|
|
{
|
|
TVFS_DIRECTORY_HEADER SubHeader;
|
|
TVFS_PATH_TABLE_ENTRY PathEntry;
|
|
PCASC_CKEY_ENTRY pCKeyEntry;
|
|
LPBYTE pbVfsSpanEntry;
|
|
size_t nSavePos = PathBuffer.Save();
|
|
DWORD dwSpanCount;
|
|
DWORD dwErrCode;
|
|
|
|
// Sanity check
|
|
assert(SpanArray.IsInitialized());
|
|
|
|
// 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
|
|
dwErrCode = ParsePathFileTable(hs, DirHeader, PathBuffer, pbPathTablePtr, pbDirectoryEnd);
|
|
if (dwErrCode != ERROR_SUCCESS)
|
|
return dwErrCode;
|
|
|
|
// 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;
|
|
|
|
// If it's one span, it's either a subdirectory or an entire file
|
|
if(dwSpanCount == 1)
|
|
{
|
|
CASC_CKEY_ENTRY SpanEntry;
|
|
|
|
// Capture the single span entry
|
|
pbVfsSpanEntry = CaptureVfsSpanEntries(DirHeader, pbVfsSpanEntry, &SpanEntry, 1);
|
|
if(pbVfsSpanEntry == NULL)
|
|
return ERROR_FILE_CORRUPT;
|
|
|
|
// Find the CKey entry
|
|
pCKeyEntry = FindCKeyEntry_EKey(hs, SpanEntry.EKey);
|
|
if(pCKeyEntry == NULL)
|
|
{
|
|
// Some files are in the ROOT manifest even if they are not in ENCODING and DOWNLOAD.
|
|
// Example: "2018 - New CASC\00001", file "DivideAndConquer.w3m:war3mapMap.blp"
|
|
pCKeyEntry = InsertUnknownCKeyEntry(hs, SpanEntry.EKey, DirHeader.EKeySize, SpanEntry.ContentSize);
|
|
if(pCKeyEntry == NULL)
|
|
{
|
|
return ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
}
|
|
|
|
// We need to check whether this is another TVFS directory file
|
|
if (IsVfsSubDirectory(hs, DirHeader, SubHeader, SpanEntry.EKey, SpanEntry.ContentSize) == ERROR_SUCCESS)
|
|
{
|
|
// Add colon (':')
|
|
PathBuffer.AppendChar(':');
|
|
|
|
// The file content size should already be there
|
|
assert(pCKeyEntry->ContentSize == SpanEntry.ContentSize);
|
|
FileTree.InsertByName(pCKeyEntry, PathBuffer);
|
|
|
|
// Parse the subdir
|
|
ParseDirectoryData(hs, SubHeader, PathBuffer);
|
|
CASC_FREE(SubHeader.pbDirectoryData);
|
|
}
|
|
else
|
|
{
|
|
// If the content content size is not there, supply it now
|
|
if(pCKeyEntry->ContentSize == CASC_INVALID_SIZE)
|
|
pCKeyEntry->ContentSize = SpanEntry.ContentSize;
|
|
FileTree.InsertByName(pCKeyEntry, PathBuffer);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PCASC_CKEY_ENTRY pSpanEntries;
|
|
PCASC_FILE_NODE pFileNode;
|
|
USHORT RefCount;
|
|
bool bFilePresent = true;
|
|
|
|
//
|
|
// Need to support multi-span files, possibly lager than 4 GB
|
|
// Example: CoD: Black Ops 4, file "zone/base.xpak" 0x16 spans, over 15 GB size
|
|
//
|
|
|
|
// Allocate buffer for all span entries
|
|
pSpanEntries = (PCASC_CKEY_ENTRY)SpanArray.Insert(dwSpanCount);
|
|
if(pSpanEntries == NULL)
|
|
return ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
// Capture all span entries
|
|
pbVfsSpanEntry = CaptureVfsSpanEntries(DirHeader, pbVfsSpanEntry, pSpanEntries, dwSpanCount);
|
|
if(pbVfsSpanEntry == NULL)
|
|
return ERROR_FILE_CORRUPT;
|
|
|
|
// Parse all span entries
|
|
for(DWORD dwSpanIndex = 0; dwSpanIndex < dwSpanCount; dwSpanIndex++)
|
|
{
|
|
PCASC_CKEY_ENTRY pSpanEntry = pSpanEntries + dwSpanIndex;
|
|
|
|
// Find the CKey entry
|
|
pCKeyEntry = FindCKeyEntry_EKey(hs, pSpanEntries[dwSpanIndex].EKey);
|
|
if(pCKeyEntry == NULL)
|
|
{
|
|
bFilePresent = false;
|
|
break;
|
|
}
|
|
|
|
// Supply the content size
|
|
if(pCKeyEntry->ContentSize == CASC_INVALID_SIZE)
|
|
pCKeyEntry->ContentSize = pSpanEntry->ContentSize;
|
|
assert(pCKeyEntry->ContentSize == pSpanEntry->ContentSize);
|
|
|
|
// Fill-in the span entry
|
|
if(dwSpanIndex == 0)
|
|
{
|
|
pCKeyEntry->SpanCount = (BYTE)(dwSpanCount);
|
|
pCKeyEntry->RefCount++;
|
|
}
|
|
else
|
|
{
|
|
// Mark the CKey entry as a file span. Note that a CKey entry
|
|
// can actually be both a file span and a standalone file:
|
|
// * zone/zm_red.xpak - { zone/zm_red.xpak_1, zone/zm_red.xpak_2, ..., zone/zm_red.xpak_6 }
|
|
pCKeyEntry->Flags |= CASC_CE_FILE_SPAN;
|
|
}
|
|
|
|
// Copy all from the existing CKey entry
|
|
memcpy(pSpanEntry, pCKeyEntry, sizeof(CASC_CKEY_ENTRY));
|
|
}
|
|
|
|
// Do nothing if the file is not present locally
|
|
if(bFilePresent)
|
|
{
|
|
// Insert a new file node that will contain pointer to the span entries
|
|
RefCount = pSpanEntries->RefCount;
|
|
pFileNode = FileTree.InsertByName(pSpanEntries, PathBuffer);
|
|
pSpanEntries->RefCount = RefCount;
|
|
|
|
if(pFileNode == NULL)
|
|
return ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reset the position of the path buffer
|
|
PathBuffer.Restore(nSavePos);
|
|
}
|
|
}
|
|
|
|
// Return the total number of entries
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
DWORD ParseDirectoryData(TCascStorage * hs, TVFS_DIRECTORY_HEADER & DirHeader, CASC_PATH<char> & 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);
|
|
}
|
|
|
|
DWORD Load(TCascStorage * hs, TVFS_DIRECTORY_HEADER & RootHeader)
|
|
{
|
|
CASC_PATH<char> PathBuffer;
|
|
DWORD dwErrCode;
|
|
|
|
// Save the length of the key
|
|
FileTree.SetKeyLength(RootHeader.EKeySize);
|
|
|
|
// Initialize the array of span entries
|
|
dwErrCode = SpanArray.Create(sizeof(CASC_CKEY_ENTRY), 0x100);
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
return dwErrCode;
|
|
|
|
// 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);
|
|
}
|
|
|
|
CASC_ARRAY SpanArray; // Array of CASC_SPAN_ENTRY for all multi-span files
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Public functions - TVFS root
|
|
|
|
DWORD RootHandler_CreateTVFS(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
|
|
{
|
|
TRootHandler_TVFS * pRootHandler = NULL;
|
|
TVFS_DIRECTORY_HEADER RootHeader;
|
|
DWORD dwErrCode;
|
|
|
|
// Capture the entire root directory
|
|
dwErrCode = TRootHandler_TVFS::CaptureDirectoryHeader(RootHeader, pbRootFile, pbRootFile + cbRootFile);
|
|
if(dwErrCode == 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
|
|
dwErrCode = pRootHandler->Load(hs, RootHeader);
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
{
|
|
delete pRootHandler;
|
|
pRootHandler = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Assign the root directory (or NULL) and return error
|
|
hs->pRootHandler = pRootHandler;
|
|
return dwErrCode;
|
|
}
|