diff options
author | Shauren <shauren.trinity@gmail.com> | 2019-06-06 16:48:21 +0200 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2019-06-08 17:09:24 +0200 |
commit | fc330fd8ff0115804d9c4b53a1f810c00dd63de9 (patch) | |
tree | cfa10998fed66779834bf0b7a9b8b799d33d91d4 /dep/CascLib/src/CascRootFile_Diablo3.cpp | |
parent | 82c7b6c5688495d90c4ee5995a4ff74039348296 (diff) |
Dep/CascLib: Update to ladislav-zezula/CascLib@a1197edf0b3bd4d52c3f39be7fa7b44bb0b98012
Diffstat (limited to 'dep/CascLib/src/CascRootFile_Diablo3.cpp')
-rw-r--r-- | dep/CascLib/src/CascRootFile_Diablo3.cpp | 1552 |
1 files changed, 621 insertions, 931 deletions
diff --git a/dep/CascLib/src/CascRootFile_Diablo3.cpp b/dep/CascLib/src/CascRootFile_Diablo3.cpp index f695ad978b1..bbe369c7646 100644 --- a/dep/CascLib/src/CascRootFile_Diablo3.cpp +++ b/dep/CascLib/src/CascRootFile_Diablo3.cpp @@ -20,61 +20,36 @@ #define DIABLO3_SUBDIR_SIGNATURE 0xEAF1FE87 #define DIABLO3_PACKAGES_SIGNATURE 0xAABB0002 #define DIABLO3_MAX_SUBDIRS 0x20 - -#define DIABLO3_INVALID_INDEX 0xFFFFFFFF -#define DIABLO3_INVALID_FILE 0xFFFFFFFF #define DIABLO3_MAX_ASSETS 70 // Maximum possible number of assets -#define DIABLO3_MAX_LEVEL0_LENGTH 0x10 // Maximum length of the level-0 directory name - -#define INVALID_FILE_INDEX 0xFFFFFFFF -#define INVALID_ASSET_INDEX 0xFF - -#define ENTRY_FLAG_DIRECTORY_ENTRY 0x80 // The file is actually a directory entry -#define ENTRY_FLAG_PLAIN_NAME 0x01 // If set, the file entry contains offset of the plain file name -#define ENTRY_FLAG_FULL_NAME 0x02 // If set, the file entry contains offset of the full name -#define ENTRY_FLAG_FLAGS_MASK 0xF0 // Mask for the entry flags -#define ENTRY_FLAG_NAME_MASK 0x0F // Mask for the entry file name type - -// Values for CASC_FILE_ENTRY::dwFlags -#define CASC_ENTRY_SHORT_NAME 0x000000001 // If set, the name is in format XXYYplain-name[\sub-index].ext -#define CASC_ENTRY_HAS_SUBINDEX 0x000000002 // If set, the subitem is present in the file name (i.e. XXYYplain-name\sub-index.ext) - -#define SEARCH_PHASE_NAMES 0 // Searching named entry -#define SEARCH_PHASE_FILE_IDS 1 // Searching filed by ID - -// Macro for constructing 64-bit integer from root-index, file-index and sub-index -// The result value is RRAAAAAAAASSSSSS -#define MAKE_INDEX64(ri, fi, si) (((ULONGLONG)ri << 0x38) | ((ULONGLONG)fi << 0x18) | ((ULONGLONG)si)) -#define INDEX64_ROOT_INDEX(hash) (DWORD)((hash >> 0x38) & 0x000000FF) -#define INDEX64_FILE_INDEX(hash) (DWORD)((hash >> 0x18) & 0xFFFFFFFF) -#define INDEX64_SUB_INDEX(hash) (DWORD)((hash >> 0x00) & 0x00FFFFFF) +#define DIABLO3_MAX_ROOT_FOLDERS 0x20 // Maximum count of root directory named entries // On-disk structure for a file given by file number -typedef struct _DIABLO3_FILEID1_ENTRY +typedef struct _DIABLO3_ASSET_ENTRY { - ENCODING_KEY EncodingKey; // Encoding key for the file - DWORD FileIndex; // File index -} DIABLO3_FILEID1_ENTRY, *PDIABLO3_FILEID1_ENTRY; + CONTENT_KEY CKey; // Content key for the file + DWORD FileIndex; // File index +} DIABLO3_ASSET_ENTRY, *PDIABLO3_ASSET_ENTRY; // On-disk structure for a file given by file number and suffix -typedef struct _DIABLO3_FILEID2_ENTRY +typedef struct _DIABLO3_ASSETIDX_ENTRY { - ENCODING_KEY EncodingKey; // Encoding key for the file - DWORD FileIndex; // File index - DWORD SubIndex; // File subindex, like "SoundBank\3D Ambience\0000.smp" -} DIABLO3_FILEID2_ENTRY, *PDIABLO3_FILEID2_ENTRY; + CONTENT_KEY CKey; // Content key for the file + DWORD FileIndex; // File index + DWORD SubIndex; // File subindex, like "SoundBank\3D Ambience\0000.smp" +} DIABLO3_ASSETIDX_ENTRY, *PDIABLO3_ASSETIDX_ENTRY; -// On-disk structure of the named entry +// In-memory structure of the named entry typedef struct _DIABLO3_NAMED_ENTRY { - ENCODING_KEY EncodingKey; // Encoding key for the file - BYTE szFileName[1]; // ASCIIZ file name (variable length) + PCONTENT_KEY pCKey; // Pointer to the content key + const char * szFileName; // Pointer to the zero-terminated file name + const char * szFileEnd; // Position of the zero terminator (aka end of the file name) } DIABLO3_NAMED_ENTRY, *PDIABLO3_NAMED_ENTRY; // On-disk structure of CoreToc.dat header typedef struct _DIABLO3_CORE_TOC_HEADER { - DWORD EntryCounts[DIABLO3_MAX_ASSETS]; // Array of number of entries (files) for each asset (level-1 directory) + DWORD EntryCounts[DIABLO3_MAX_ASSETS]; // Array of number of entries (files) for each asset DWORD EntryOffsets[DIABLO3_MAX_ASSETS]; // Array of offsets of each DIABLO3_CORE_TOC_ENTRY, relative to data after header DWORD Unknowns[DIABLO3_MAX_ASSETS]; // Unknown DWORD Alignment; @@ -89,33 +64,19 @@ typedef struct _DIABLO3_CORE_TOC_ENTRY } DIABLO3_CORE_TOC_ENTRY, *PDIABLO3_CORE_TOC_ENTRY; -// In-memory structure of parsed directory header -typedef struct _DIABLO3_DIR_HEADER -{ - LPBYTE pbEntries1; - LPBYTE pbEntries2; - LPBYTE pbEntries3; - DWORD dwEntries1; - DWORD dwEntries2; - DWORD dwEntries3; -} DIABLO3_DIR_HEADER, *PDIABLO3_DIR_HEADER; - -// In-memory structure of loaded CoreTOC.dat -typedef struct _DIABLO3_CORE_TOC +// In-memory structure of parsed directory data +typedef struct _DIABLO3_DIRECTORY { - DIABLO3_CORE_TOC_HEADER Hdr; // Header of CoreTOC.dat - - LPBYTE pbCoreToc; // Content of the CoreTOC.dat file - DIABLO3_CORE_TOC_ENTRY Entries[1]; // Buffer for storing the entries (variable length) - -} DIABLO3_CORE_TOC, *PDIABLO3_CORE_TOC; - -// On-disk structure of Packages.dat header -typedef struct _DIABLO3_PACKAGES_DAT_HEADER -{ - DWORD Signature; - DWORD NumberOfNames; -} DIABLO3_PACKAGES_DAT_HEADER, *PDIABLO3_PACKAGES_DAT_HEADER; + LPBYTE pbDirectoryData; // The begin of the directory data block + LPBYTE pbDirectoryEnd; // The end of the directory data block + LPBYTE pbAssetEntries; // Pointer to asset entries without subitem number. Example: "SoundBank\SoundFile.smp" + LPBYTE pbAssetIdxEntries; // Pointer to asset entries with subitem number + LPBYTE pbNamedEntries; // Pointer to named entries. These are for files with arbitrary names, and they do not belong to an asset + DWORD dwAssetEntries; // Number of asset entries without subitem number + DWORD dwAssetIdxEntries; + DWORD dwNamedEntries; + DWORD dwNodeIndex; // Index of file node for this folder +} DIABLO3_DIRECTORY, *PDIABLO3_DIRECTORY; // Structure for conversion DirectoryID -> Directory name typedef struct _DIABLO3_ASSET_INFO @@ -126,35 +87,6 @@ typedef struct _DIABLO3_ASSET_INFO } DIABLO3_ASSET_INFO; typedef const DIABLO3_ASSET_INFO * PDIABLO3_ASSET_INFO; -// In-memory structure of a file entry in the linear file list -typedef struct _CASC_FILE_ENTRY -{ - ENCODING_KEY EncodingKey; // Encoding key - ULONGLONG FileNameHash; // Hash of the full file name - DWORD dwFileName; // Offset of the name (in name's dynamic array) - DWORD dwFlags; // Entry flags (see CASC_ENTRY_XXXX) - - DWORD NameOffset; // Offset of the name (in name's dynamic array) - USHORT SubIndex; // File\SubFile index - BYTE AssetIndex; // Asset index (aka directory index) - BYTE EntryFlags; // Entry flags -} CASC_FILE_ENTRY, *PCASC_FILE_ENTRY; - -//----------------------------------------------------------------------------- -// Structure definitions for Diablo3 root file - -struct TRootHandler_Diablo3 : public TRootHandler -{ - // Linear global list of all files - DYNAMIC_ARRAY FileTable; - - // Linear global list of names - DYNAMIC_ARRAY FileNames; - - // Global map of FileName -> FileEntry - PCASC_MAP pRootMap; -}; - //----------------------------------------------------------------------------- // Local variables @@ -231,969 +163,727 @@ static const DIABLO3_ASSET_INFO Assets[] = {"Accolade", "aco"}, // 0x42 }; -static const DIABLO3_ASSET_INFO UnknownAsset = {"Unknown", "unk"}; - #define DIABLO3_ASSET_COUNT (sizeof(Assets) / sizeof(Assets[0])) //----------------------------------------------------------------------------- -// Local functions - -static PDIABLO3_ASSET_INFO GetAssetInfo(DWORD dwAssetIndex) -{ - if(dwAssetIndex < DIABLO3_ASSET_COUNT && Assets[dwAssetIndex].szDirectoryName != NULL) - return &Assets[dwAssetIndex]; - return &UnknownAsset; -} - -static DWORD VerifyNamedFileEntry(LPBYTE pbNamedEntry, LPBYTE pbFileEnd) -{ - LPBYTE pbFileName = ((PDIABLO3_NAMED_ENTRY)pbNamedEntry)->szFileName; - - // Find the end of the name - while(pbFileName < pbFileEnd && pbFileName[0] != 0) - pbFileName++; +// Handler definitions for Diablo3 root file - // Did we get past the end of the root file? - if(pbFileName >= pbFileEnd) - return 0; - pbFileName++; - - // Return the length of the structure - return (DWORD)(pbFileName - pbNamedEntry); -} - -static char * FindPackageName( - PCASC_MAP pPackageMap, - const char * szAssetName, - const char * szPlainName) +struct TDiabloRoot : public TFileTreeRoot { - char szFileName[MAX_PATH+1]; - size_t nLength; + public: - // Construct the name without extension and find it in the map - nLength = sprintf(szFileName, "%s\\%s", szAssetName, szPlainName); - return (char *)Map_FindString(pPackageMap, szFileName, szFileName + nLength); -} + TDiabloRoot() : TFileTreeRoot(0) + { + memset(RootFolders, 0, sizeof(RootFolders)); + pFileIndices = NULL; + pbCoreTocFile = NULL; + pbCoreTocData = NULL; + nFileIndices = 0; + cbCoreTocFile = 0; + + // Map for searching a real file extension + memset(&PackagesMap, 0, sizeof(CASC_MAP)); + pbPackagesDat = NULL; + cbPackagesDat = 0; + + // We have file names and return CKey as result of search + dwFeatures |= (CASC_FEATURE_FILE_NAMES | CASC_FEATURE_ROOT_CKEY); + } -static size_t CreateShortName( - PCASC_MAP pPackageMap, - DWORD dwRootIndex, // Level-0-dir: Index of the root subdirectory - DWORD dwAssetIndex, // Level-1-dir: Index of the asset name - const char * szPlainName, // Plain name of the file, without extension - DWORD dwSubIndex, - char * szBuffer) -{ - PDIABLO3_ASSET_INFO pAssetInfo = GetAssetInfo(dwAssetIndex); - const char * szPackageName = NULL; - const char * szFormat; - size_t nLength; - - // Write the level-0 directory index as 2-digit hexa number - assert(dwRootIndex < 0x100); - *szBuffer++ = IntToHexChar[dwRootIndex >> 0x04]; - *szBuffer++ = IntToHexChar[dwRootIndex & 0x0F]; - - // Write the level-1 directory index as 2-digit hexa number - assert(dwAssetIndex < 0x100); - *szBuffer++ = IntToHexChar[dwAssetIndex >> 0x04]; - *szBuffer++ = IntToHexChar[dwAssetIndex & 0x0F]; - - // Construct the file name with ending "." for extension - szFormat = (dwSubIndex != DIABLO3_INVALID_INDEX) ? "%s\\%04u." : "%s."; - nLength = sprintf(szBuffer, szFormat, szPlainName, dwSubIndex); - - // Try to fixup the file extension from the package name. - // File extensions are not predictable because for subitems, - // they are not always equal to the main items: - // - // SoundBank\3D Ambience.sbk - // SoundBank\3D Ambience\0000.smp - // SoundBank\3D Ambience\0002.smp - // ... - // SoundBank\Angel.sbk - // SoundBank\Angel\0000.fsb - // SoundBank\Angel\0002.fsb - // - // We use the Base\Data_D3\PC\Misc\Packages.dat for real file extensions, where possible - // - if(pPackageMap != NULL) + ~TDiabloRoot() { - // Retrieve the asset name - szPackageName = FindPackageName(pPackageMap, pAssetInfo->szDirectoryName, szBuffer); - if(szPackageName != NULL) - { - strcpy(szBuffer, szPackageName + strlen(pAssetInfo->szDirectoryName) + 1); - nLength = strlen(szBuffer); - } + FreeLoadingStuff(); } - // If we havent't found the package, we either use the default asset extension or "unk" - if(szPackageName == NULL) + void AppendBackslashToTotalPath(PATH_BUFFER & PathBuffer) { - if(dwSubIndex == DIABLO3_INVALID_INDEX) + if(PathBuffer.szPtr < PathBuffer.szEnd) { - strcpy(szBuffer + nLength, pAssetInfo->szExtension); - nLength += strlen(pAssetInfo->szExtension); - } - else - { - strcpy(szBuffer + nLength, "unk"); - nLength += 3; + PathBuffer.szPtr[0] = '\\'; + PathBuffer.szPtr++; } } - // Return the length of the short file name - return nLength + 4; -} - -static size_t CreateFileName( - TRootHandler_Diablo3 * pRootHandler, - const char * szShortName, // Short file name of the file - char * szBuffer) -{ - PCASC_FILE_ENTRY pRootEntry; - const char * szNameLevel0; - const char * szNameLevel1 = NULL; - DWORD dwRootIndex0 = 0; - DWORD dwAssetIndex = 0; - - // Retrieve the level-0 and level-1 directory indexes - ConvertStringToInt08(szShortName+0, &dwRootIndex0); - ConvertStringToInt08(szShortName+2, &dwAssetIndex); - - // Retrieve the name of the level-0 directory (aka root subdirectory) - pRootEntry = (PCASC_FILE_ENTRY)Array_ItemAt(&pRootHandler->FileTable, dwRootIndex0); - szNameLevel0 = (char *)Array_ItemAt(&pRootHandler->FileNames, pRootEntry->dwFileName); - - // Retrieve the name of the level-1 directory (aka asset name) - if(dwAssetIndex < DIABLO3_ASSET_COUNT) - szNameLevel1 = Assets[dwAssetIndex].szDirectoryName; - if(szNameLevel1 == NULL) - szNameLevel1 = UnknownAsset.szDirectoryName; - - // Copy the rest of the name as-is - return sprintf(szBuffer, "%s\\%s\\%s", szNameLevel0, szNameLevel1, szShortName + 4); -} - - -// Creates a map of String -> Pointer -static PCASC_MAP CreatePackageMap( - LPBYTE pbPackagesDat, - LPBYTE pbPackagesEnd) -{ - PDIABLO3_PACKAGES_DAT_HEADER pDatHeader = (PDIABLO3_PACKAGES_DAT_HEADER)pbPackagesDat; - PCASC_MAP pPackageMap; - - // Get the header - if((pbPackagesDat + sizeof(DIABLO3_PACKAGES_DAT_HEADER)) >= pbPackagesEnd) - return NULL; - pbPackagesDat += sizeof(DIABLO3_PACKAGES_DAT_HEADER); - - // Check the signature and name count - if(pDatHeader->Signature != DIABLO3_PACKAGES_SIGNATURE) - return NULL; - - // Create the map for fast search of the file name - pPackageMap = Map_Create(pDatHeader->NumberOfNames, KEY_LENGTH_STRING, 0); - if(pPackageMap != NULL) + void AppendPathToTotalPath(PATH_BUFFER & PathBuffer, const char * szFileName, const char * szFileEnd) { - char * szFileName = (char *)pbPackagesDat; + char * szPathPtr = PathBuffer.szPtr; + size_t nLength = (szFileEnd - szFileName); - // Go as long as there is something - for(DWORD i = 0; i < pDatHeader->NumberOfNames; i++) + // Append the name + if((szPathPtr + nLength) < PathBuffer.szEnd) { - // Get the file extension - if((LPBYTE)szFileName >= pbPackagesEnd) - break; - - // Insert the file name to the map. The file extension is not included - Map_InsertString(pPackageMap, szFileName, true); - szFileName = szFileName + strlen(szFileName) + 1; + memcpy(szPathPtr, szFileName, nLength); + szPathPtr += nLength; } - } - - return pPackageMap; -} - -// Insert an entry with file name as-is -static int InsertFileEntry( - TRootHandler_Diablo3 * pRootHandler, - ENCODING_KEY & EncodingKey, - const char * szFileName, - size_t cchFileName) -{ - PCASC_FILE_ENTRY pFileEntry; - // We must not allow the file name array to be reallocated. - // Reallocating the array would cause pointers in TRootHandler_Diablo3::pRootMap - // become invalid - if(pRootHandler->FileTable.ItemCount >= pRootHandler->FileTable.ItemCountMax) - { - assert(false); - return ERROR_NOT_ENOUGH_MEMORY; + // Append zero terminator + if(szPathPtr < PathBuffer.szEnd) + szPathPtr[0] = 0; + PathBuffer.szPtr = szPathPtr; } - // Insert the plain name to the root handler's global name list - szFileName = (const char *)Array_Insert(&pRootHandler->FileNames, szFileName, cchFileName); - if(szFileName == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Make sure that we don't exceed the file limit at this phase - pFileEntry = (PCASC_FILE_ENTRY)Array_Insert(&pRootHandler->FileTable, NULL, 1); - assert(pFileEntry != NULL); - - // Store the info into the file entry - pFileEntry->EncodingKey = EncodingKey; - pFileEntry->FileNameHash = CalcFileNameHash(szFileName); - pFileEntry->dwFileName = (DWORD)Array_IndexOf(&pRootHandler->FileNames, szFileName); - pFileEntry->dwFlags = 0; - - // Verify collisions (debug version only) - assert(Map_FindObject(pRootHandler->pRootMap, &pFileEntry->FileNameHash, NULL) == NULL); - - // Calculate the file name hash - Map_InsertObject(pRootHandler->pRootMap, pFileEntry, &pFileEntry->FileNameHash); - - // Success - return ERROR_SUCCESS; -} - -static int ParseDirEntries_FileId1( - TRootHandler_Diablo3 * pRootHandler, - LPBYTE pbFileEntries, - DWORD dwFileEntries, - DWORD dwRootDirIndex) -{ - PDIABLO3_FILEID1_ENTRY pEntry = (PDIABLO3_FILEID1_ENTRY)pbFileEntries; - PCASC_FILE_ENTRY pFileEntry; - - // Overflow test - if((pRootHandler->FileTable.ItemCount + dwFileEntries) >= pRootHandler->FileTable.ItemCountMax) + PDIABLO3_ASSET_INFO GetAssetInfo(DWORD dwAssetIndex) { - assert(false); - return ERROR_NOT_ENOUGH_MEMORY; + if(dwAssetIndex < DIABLO3_ASSET_COUNT && Assets[dwAssetIndex].szDirectoryName != NULL) + return &Assets[dwAssetIndex]; + return NULL; } - // Parse the all ID1 entries in the file - for(DWORD i = 0; i < dwFileEntries; i++, pEntry++) + char * FindPackageName(const char * szAssetName, const char * szPlainName) { - // Insert the file entry to the global list - pFileEntry = (PCASC_FILE_ENTRY)Array_Insert(&pRootHandler->FileTable, NULL, 1); - assert(pFileEntry != NULL); - - // Fill the index entry - pFileEntry->EncodingKey = pEntry->EncodingKey; - pFileEntry->FileNameHash = MAKE_INDEX64(dwRootDirIndex, pEntry->FileIndex, 0); - pFileEntry->dwFlags = CASC_ENTRY_SHORT_NAME; - } - - return ERROR_SUCCESS; -} + char szFileName[MAX_PATH+1]; + size_t nLength; -static int ParseDirEntries_FileId2( - TRootHandler_Diablo3 * pRootHandler, - LPBYTE pbFileEntries, - DWORD dwFileEntries, - DWORD dwRootDirIndex) -{ - PDIABLO3_FILEID2_ENTRY pEntry = (PDIABLO3_FILEID2_ENTRY)pbFileEntries; - PCASC_FILE_ENTRY pFileEntry; - - // Overflow test - if((pRootHandler->FileTable.ItemCount + dwFileEntries) >= pRootHandler->FileTable.ItemCountMax) - { - assert(false); - return ERROR_NOT_ENOUGH_MEMORY; + // Construct the name without extension and find it in the map + nLength = CascStrPrintf(szFileName, _countof(szFileName), "%s\\%s", szAssetName, szPlainName); + return (char *)PackagesMap.FindString(szFileName, szFileName + nLength); } - // Parse the all ID1 entries in the file - for(DWORD i = 0; i < dwFileEntries; i++, pEntry++) + LPBYTE LoadFileToMemory(TCascStorage * hs, const char * szFileName, DWORD * pcbFileData) { - // Insert the file entry to the global list - pFileEntry = (PCASC_FILE_ENTRY)Array_Insert(&pRootHandler->FileTable, NULL, 1); - assert(pFileEntry != NULL); - - // Fill the index entry - pFileEntry->EncodingKey = pEntry->EncodingKey; - pFileEntry->FileNameHash = MAKE_INDEX64(dwRootDirIndex, pEntry->FileIndex, pEntry->SubIndex); - pFileEntry->dwFlags = CASC_ENTRY_SHORT_NAME | CASC_ENTRY_HAS_SUBINDEX; - } + PCASC_CKEY_ENTRY pCKeyEntry; + LPBYTE pbFileData = NULL; - return ERROR_SUCCESS; -} - -static int ParseDirEntries_Named( - TRootHandler_Diablo3 * pRootHandler, - LPBYTE pbFileEntries, - LPBYTE pbFileEnd, - DWORD dwFileEntries, - DWORD dwRootDirIndex) -{ - char szFileName[MAX_PATH+1]; - char * szNamePtr = szFileName; - DWORD cbFileEntry; - int nError = ERROR_SUCCESS; + // Try to find CKey for the file + pCKeyEntry = GetFile(hs, szFileName); + if(pCKeyEntry != NULL) + pbFileData = LoadInternalFileToMemory(hs, pCKeyEntry, pcbFileData); - // Overflow test - if((pRootHandler->FileTable.ItemCount + dwFileEntries) >= pRootHandler->FileTable.ItemCountMax) - { - assert(false); - return ERROR_NOT_ENOUGH_MEMORY; + return pbFileData; } - // If we the file is not in the root directory itself, - // prepare the prefix for the root directory. - if(dwRootDirIndex != DIABLO3_INVALID_INDEX) + static LPBYTE CaptureDirectoryData( + DIABLO3_DIRECTORY & DirHeader, + LPBYTE pbDirectory, + DWORD cbDirectory) { - PCASC_FILE_ENTRY pRootEntry = (PCASC_FILE_ENTRY)Array_ItemAt(&pRootHandler->FileTable, dwRootDirIndex); - const char * szRootName = (const char *)Array_ItemAt(&pRootHandler->FileNames, pRootEntry->dwFileName); + LPBYTE pbDirectoryData = pbDirectory; + LPBYTE pbDataEnd = pbDirectory + cbDirectory; + DWORD Signature = 0; + + // + // Structure of a Diablo3 directory header + // 1) Signature (4 bytes) + // 2) Number of DIABLO3_ASSET_ENTRY entries (4 bytes) + // 3) Array of DIABLO3_ASSET_ENTRY entries + // 4) Number of DIABLO3_ASSETIDX_ENTRY entries (4 bytes) + // 5) Array of DIABLO3_ASSETIDX_ENTRY entries + // 6) Number of DIABLO3_NAMED_ENTRY entries (4 bytes) + // 7) Array of DIABLO3_NAMED_ENTRY entries + // + + // Prepare the header signature + memset(&DirHeader, 0, sizeof(DIABLO3_DIRECTORY)); + + // Get the header signature + pbDirectory = CaptureInteger32(pbDirectory, pbDataEnd, &Signature); + if((pbDirectory == NULL) || (Signature != CASC_DIABLO3_ROOT_SIGNATURE && Signature != DIABLO3_SUBDIR_SIGNATURE)) + return NULL; + + // Subdirectories have extra two arrays + if(Signature == DIABLO3_SUBDIR_SIGNATURE) + { + // Capture the number of DIABLO3_ASSET_ENTRY items + pbDirectory = CaptureInteger32(pbDirectory, pbDataEnd, &DirHeader.dwAssetEntries); + if(pbDirectory == NULL) + return NULL; + + // Capture the array of DIABLO3_ASSET_ENTRY + pbDirectory = CaptureArray(pbDirectory, pbDataEnd, &DirHeader.pbAssetEntries, DIABLO3_ASSET_ENTRY, DirHeader.dwAssetEntries); + if(pbDirectory == NULL) + return NULL; + + // Capture the number of DIABLO3_ASSETIDX_ENTRY items + pbDirectory = CaptureInteger32(pbDirectory, pbDataEnd, &DirHeader.dwAssetIdxEntries); + if(pbDirectory == NULL) + return NULL; + + // Capture the array of DIABLO3_ASSETIDX_ENTRY + pbDirectory = CaptureArray(pbDirectory, pbDataEnd, &DirHeader.pbAssetIdxEntries, DIABLO3_ASSETIDX_ENTRY, DirHeader.dwAssetIdxEntries); + if(pbDirectory == NULL) + return NULL; + } - // Copy the root directory name - while(szRootName[0] != 0) - *szNamePtr++ = *szRootName++; + // Capture the number of DIABLO3_NAMED_ENTRY array + pbDirectory = CaptureInteger32(pbDirectory, pbDataEnd, &DirHeader.dwNamedEntries); + if(pbDirectory == NULL) + return NULL; - // Append the backslash - *szNamePtr++ = '\\'; - } + // Note: Do not capture the array here. We will do that later, + // when we will be parsing the directory + DirHeader.pbNamedEntries = pbDirectory; - // Parse the file entry - while(pbFileEntries < pbFileEnd) - { - PDIABLO3_NAMED_ENTRY pNamedEntry = (PDIABLO3_NAMED_ENTRY)pbFileEntries; - DWORD cchFileName; - - // Verify the named entry whether it does not go beyond the EOF - cbFileEntry = VerifyNamedFileEntry(pbFileEntries, pbFileEnd); - if(cbFileEntry == 0) - return ERROR_FILE_CORRUPT; - - // Append the file name to the prepared file name - // This way we obtain the full name and the name lookup - // will be fully operational - memcpy(szNamePtr, pNamedEntry->szFileName, (cbFileEntry - sizeof(ENCODING_KEY))); - cchFileName = (DWORD)((szNamePtr - szFileName) + (cbFileEntry - sizeof(ENCODING_KEY))); - - // Insert the named entry to the global file table - nError = InsertFileEntry(pRootHandler, - pNamedEntry->EncodingKey, - szFileName, - cchFileName); - if(nError != ERROR_SUCCESS) - return nError; - - // Move the pointer to the next entry - pbFileEntries += cbFileEntry; + // Put the directory range + DirHeader.pbDirectoryData = pbDirectoryData; + DirHeader.pbDirectoryEnd = pbDirectoryData + cbDirectory; + return pbDirectory; } - return ERROR_SUCCESS; -} - -static void ResolveFullFileNames( - TRootHandler_Diablo3 * pRootHandler, - PDIABLO3_CORE_TOC_ENTRY pCoreTocEntries, - PCASC_MAP pPackageMap, - LPBYTE pbCoreTocFile, - DWORD dwFileIndexes) -{ - PCASC_FILE_ENTRY pFileEntry; - char * szPlainName; - char * szNamePtr; - size_t nLength; - DWORD dwRootIndex; - DWORD dwFileIndex; - DWORD dwSubIndex; - char szShortName[MAX_PATH+1]; - char szFullName[MAX_PATH+1]; - - // Keep compiler happy - CASCLIB_UNUSED(dwFileIndexes); - - // Parse the entire file table - for(size_t i = 0; i < pRootHandler->FileTable.ItemCount; i++) + LPBYTE CaptureCoreTocHeader( + PDIABLO3_CORE_TOC_HEADER * PtrHeader, + PDWORD PtrMaxIndex, + LPBYTE pbDataPtr, + LPBYTE pbDataEnd) { - // Retrieve the file entry at n-th position - pFileEntry = (PCASC_FILE_ENTRY)Array_ItemAt(&pRootHandler->FileTable, i); + PDIABLO3_CORE_TOC_HEADER pTocHeader = (PDIABLO3_CORE_TOC_HEADER)pbDataPtr; + DWORD dwMaxFileIndex = 0; - // Skip the items that already have full name - if(pFileEntry->dwFlags & CASC_ENTRY_SHORT_NAME) - { - // Retrieve the file index of that file - dwRootIndex = INDEX64_ROOT_INDEX(pFileEntry->FileNameHash); - dwFileIndex = INDEX64_FILE_INDEX(pFileEntry->FileNameHash); - dwSubIndex = (pFileEntry->dwFlags & CASC_ENTRY_HAS_SUBINDEX) ? INDEX64_SUB_INDEX(pFileEntry->FileNameHash) : DIABLO3_INVALID_INDEX; - assert(dwFileIndex < dwFileIndexes); - - // Get the plain name of the file - szPlainName = (char *)(pbCoreTocFile + pCoreTocEntries[dwFileIndex].NameOffset); - - // Create the short file name - nLength = CreateShortName(pPackageMap, - dwRootIndex, - pCoreTocEntries[dwFileIndex].AssetIndex, - szPlainName, - dwSubIndex, - szShortName); - - // Insert the short name to the list of the names - szNamePtr = (char *)Array_Insert(&pRootHandler->FileNames, szShortName, nLength + 1); - pFileEntry->dwFileName = (DWORD)Array_IndexOf(&pRootHandler->FileNames, szNamePtr); - - // Create the full file name - nLength = CreateFileName(pRootHandler, szShortName, szFullName); - pFileEntry->FileNameHash = CalcFileNameHash(szFullName); - - // Insert the entry to the name map. Use the mapping of FullName -> FileHash - Map_InsertObject(pRootHandler->pRootMap, pFileEntry, &pFileEntry->FileNameHash); - } - } -} + // Check the space for header + if((pbDataPtr + sizeof(DIABLO3_CORE_TOC_HEADER)) > pbDataEnd) + return NULL; + pbDataPtr += sizeof(DIABLO3_CORE_TOC_HEADER); -static LPBYTE LoadFileToMemory(TCascStorage * hs, LPBYTE pbEncodingKey, DWORD * pcbFileData) -{ - QUERY_KEY EncodingKey; - LPBYTE pbFileData = NULL; - HANDLE hFile; - DWORD cbBytesRead = 0; - DWORD cbFileData = 0; - - // Open the file by encoding key - EncodingKey.pbData = pbEncodingKey; - EncodingKey.cbData = MD5_HASH_SIZE; - if(CascOpenFileByEncodingKey((HANDLE)hs, &EncodingKey, 0, &hFile)) - { - // Retrieve the file size - cbFileData = CascGetFileSize(hFile, NULL); - if(cbFileData > 0) + // Verify all asset arrays + for(size_t i = 0; i < DIABLO3_MAX_ASSETS; i++) { - pbFileData = CASC_ALLOC(BYTE, cbFileData); - if(pbFileData != NULL) + PDIABLO3_CORE_TOC_ENTRY pTocEntry = (PDIABLO3_CORE_TOC_ENTRY)(pbDataPtr + pTocHeader->EntryOffsets[i]); + DWORD EntryOffset = pTocHeader->EntryOffsets[i]; + DWORD EntryCount = pTocHeader->EntryCounts[i]; + + // Verify file range + if((pbDataPtr + EntryOffset + EntryCount * sizeof(DIABLO3_CORE_TOC_ENTRY)) > pbDataEnd) + return NULL; + + // Find out the entry with the maximum index + for(DWORD n = 0; n < EntryCount; n++) { - CascReadFile(hFile, pbFileData, cbFileData, &cbBytesRead); + if(pTocEntry->FileIndex >= dwMaxFileIndex) + dwMaxFileIndex = pTocEntry->FileIndex; + pTocEntry++; } } - // Close the file - CascCloseFile(hFile); + // Give data and return + PtrMaxIndex[0] = dwMaxFileIndex; + PtrHeader[0] = pTocHeader; + return pbDataPtr; } - // Give the file to the caller - if(pcbFileData != NULL) - pcbFileData[0] = cbBytesRead; - return pbFileData; -} - -static LPBYTE LoadFileToMemory(TCascStorage * hs, const char * szFileName, DWORD * pcbFileData) -{ - LPBYTE pbEncodingKey = NULL; - LPBYTE pbFileData = NULL; - - // Try to find encoding key for the file - pbEncodingKey = RootHandler_GetKey(hs->pRootHandler, szFileName); - if(pbEncodingKey != NULL) - pbFileData = LoadFileToMemory(hs, pbEncodingKey, pcbFileData); - - return pbFileData; -} - -static int ParseDirectoryHeader( - PDIABLO3_DIR_HEADER pDirHeader, - LPBYTE pbDirFile, - LPBYTE pbFileEnd) -{ - DWORD dwSignature = 0; - - // - // Structure of a Diablo3 directory file - // 1) Signature (4 bytes) - // 2) Number of DIABLO3_FILEID1_ENTRY entries (4 bytes) - // 3) Array of DIABLO3_FILEID1_ENTRY entries - // 4) Number of DIABLO3_FILEID2_ENTRY entries (4 bytes) - // 5) Array of DIABLO3_FILEID2_ENTRY entries - // 6) Number of DIABLO3_NAMED_ENTRY entries (4 bytes) - // 7) Array of DIABLO3_NAMED_ENTRY entries - // - - // Prepare the header signature - memset(pDirHeader, 0, sizeof(DIABLO3_DIR_HEADER)); - - // Get the signature - if((pbDirFile + sizeof(DWORD)) >= pbFileEnd) - return ERROR_BAD_FORMAT; - dwSignature = *(PDWORD)pbDirFile; - - // Check the signature - if(dwSignature != CASC_DIABLO3_ROOT_SIGNATURE && dwSignature != DIABLO3_SUBDIR_SIGNATURE) - return ERROR_BAD_FORMAT; - pbDirFile += sizeof(DWORD); - - // Subdirectories have extra two arrays - if(dwSignature == DIABLO3_SUBDIR_SIGNATURE) + LPBYTE CaptureNamedEntry( + LPBYTE pbDataPtr, + LPBYTE pbDataEnd, + PDIABLO3_NAMED_ENTRY pEntry) { - // Get the number of DIABLO3_FILEID1_ENTRY items - if((pbDirFile + sizeof(DWORD)) >= pbFileEnd) - return ERROR_BAD_FORMAT; - pDirHeader->dwEntries1 = *(PDWORD)pbDirFile; - - // Get the array of DIABLO3_FILEID1_ENTRY - pDirHeader->pbEntries1 = (pbDirFile + sizeof(DWORD)); - pbDirFile = pbDirFile + sizeof(DWORD) + pDirHeader->dwEntries1 * sizeof(DIABLO3_FILEID1_ENTRY); - - // Get the number of DIABLO3_FILEID2_ENTRY items - if((pbDirFile + sizeof(DWORD)) >= pbFileEnd) - return ERROR_BAD_FORMAT; - pDirHeader->dwEntries2 = *(PDWORD)pbDirFile; - - // Get the array of DIABLO3_FILEID2_ENTRY - pDirHeader->pbEntries2 = (pbDirFile + sizeof(DWORD)); - pbDirFile = pbDirFile + sizeof(DWORD) + pDirHeader->dwEntries2 * sizeof(DIABLO3_FILEID2_ENTRY); - } + // Capture the content key + pbDataPtr = CaptureContentKey(pbDataPtr, pbDataEnd, &pEntry->pCKey); + if(pbDataPtr == NULL) + return NULL; + + // Capture file name. Must be ASCIIZ file name + pEntry->szFileName = (const char *)pbDataPtr; + while(pbDataPtr < pbDataEnd && pbDataPtr[0] != 0) + pbDataPtr++; + + // Did we find a zero char? + if(pbDataPtr < pbDataEnd && pbDataPtr[0] == 0) + { + pEntry->szFileEnd = (const char *)pbDataPtr; + return pbDataPtr + 1; + } - // Get the pointer and length DIABLO3_NAMED_ENTRY array - if((pbDirFile + sizeof(DWORD)) >= pbFileEnd) - return ERROR_BAD_FORMAT; - pDirHeader->dwEntries3 = *(PDWORD)pbDirFile; - pDirHeader->pbEntries3 = (pbDirFile + sizeof(DWORD)); - return ERROR_SUCCESS; -} + return NULL; + } -static DWORD ScanDirectoryFile( - TCascStorage * hs, - LPBYTE pbRootFile, - LPBYTE pbFileEnd) -{ - PDIABLO3_NAMED_ENTRY pNamedEntry; - DIABLO3_DIR_HEADER RootHeader; - DIABLO3_DIR_HEADER DirHeader; - LPBYTE pbSubDir; - DWORD dwTotalFileCount; - DWORD cbNamedEntry; - DWORD cbSubDir; - int nError; - - // Parse the directory header in order to retrieve the items - nError = ParseDirectoryHeader(&RootHeader, pbRootFile, pbFileEnd); - if(nError != ERROR_SUCCESS) - return 0; - - // Add the root directory's entries - dwTotalFileCount = RootHeader.dwEntries1 + RootHeader.dwEntries2 + RootHeader.dwEntries3; - - // Parse the named entries - for(DWORD i = 0; i < RootHeader.dwEntries3; i++) + int LoadDirectoryFile(TCascStorage * hs, DIABLO3_DIRECTORY & DirHeader, PCASC_CKEY_ENTRY pCKeyEntry) { - // Get the this named entry - if((cbNamedEntry = VerifyNamedFileEntry(RootHeader.pbEntries3, pbFileEnd)) == 0) - return 0; - pNamedEntry = (PDIABLO3_NAMED_ENTRY)RootHeader.pbEntries3; - RootHeader.pbEntries3 += cbNamedEntry; - - // Load the subdirectory to memory - pbSubDir = LoadFileToMemory(hs, pNamedEntry->EncodingKey.Value, &cbSubDir); - if(pbSubDir != NULL) + LPBYTE pbData; + DWORD cbData = 0; + + // Load the n-th folder + pbData = LoadInternalFileToMemory(hs, pCKeyEntry, &cbData); + if(pbData && cbData) { - // Count the files in the subdirectory - if(ParseDirectoryHeader(&DirHeader, pbSubDir, pbSubDir + cbSubDir) == ERROR_SUCCESS) + if(CaptureDirectoryData(DirHeader, pbData, cbData) == NULL) { - dwTotalFileCount += DirHeader.dwEntries1 + DirHeader.dwEntries2 + DirHeader.dwEntries3; + // Clear the directory + CASC_FREE(pbData); + return ERROR_BAD_FORMAT; } - - // Free the subdirectory - CASC_FREE(pbSubDir); } + return ERROR_SUCCESS; } - // Return the total number of entries - return dwTotalFileCount; -} + bool CreateAssetFileName( + PATH_BUFFER & PathBuffer, + DWORD FileIndex, + DWORD SubIndex) + { + PDIABLO3_CORE_TOC_ENTRY pTocEntry; + PDIABLO3_ASSET_INFO pAssetInfo; + const char * szPackageName = NULL; + const char * szPlainName; + const char * szFormat; + char * szPathEnd = PathBuffer.szEnd; + char * szPathPtr = PathBuffer.szPtr; + size_t nLength = 0; + + // Find and check the entry + pTocEntry = pFileIndices + FileIndex; + if(pTocEntry->FileIndex == FileIndex) + { + // Retrieve the asset information + pAssetInfo = GetAssetInfo(pTocEntry->AssetIndex); + + // Either use the asset info for getting the folder name or supply "Asset##" + if(pAssetInfo != NULL) + { + CascStrCopy(szPathPtr, (szPathEnd - szPathPtr), pAssetInfo->szDirectoryName); + szPathPtr += strlen(szPathPtr); + } + else + { + szPathPtr[0] = 'A'; + szPathPtr[1] = 's'; + szPathPtr[2] = 's'; + szPathPtr[3] = 'e'; + szPathPtr[4] = 't'; + szPathPtr[5] = (char)('0' + (pTocEntry->AssetIndex / 10)); + szPathPtr[6] = (char)('0' + (pTocEntry->AssetIndex % 10)); + szPathPtr += 7; + } -static int ParseDirectoryFile( - TRootHandler_Diablo3 * pRootHandler, - LPBYTE pbDirFile, - LPBYTE pbFileEnd, - DWORD dwRootDirIndex) -{ - DIABLO3_DIR_HEADER DirHeader; - int nError; + // Put the backslash + if(szPathPtr < PathBuffer.szEnd) + *szPathPtr++ = '\\'; + + // Construct the file name, up to the extension. Don't include the '.' + szPlainName = (const char *)(pbCoreTocData + pTocEntry->NameOffset); + szFormat = (SubIndex != CASC_INVALID_INDEX) ? "%s\\%04u" : "%s"; + nLength = CascStrPrintf(szPathPtr, (szPathEnd - szPathPtr), szFormat, szPlainName, SubIndex); + + // Try to fixup the file extension from the package name. + // File extensions are not predictable because for subitems, + // they are not always equal to the main items: + // + // SoundBank\3D Ambience.sbk + // SoundBank\3D Ambience\0000.smp + // SoundBank\3D Ambience\0002.smp + // ... + // SoundBank\Angel.sbk + // SoundBank\Angel\0000.fsb + // SoundBank\Angel\0002.fsb + // + // We use the Base\Data_D3\PC\Misc\Packages.dat for real file extensions, where possible + // + + if(PackagesMap.IsInitialized() && pAssetInfo != NULL) + { + // Retrieve the asset name + szPackageName = FindPackageName(pAssetInfo->szDirectoryName, szPathPtr); + if(szPackageName != NULL) + { + CascStrCopy(PathBuffer.szPtr, (PathBuffer.szEnd - PathBuffer.szPtr), szPackageName); + return true; + } + } - // Sanity checks - assert(pRootHandler->FileTable.ItemArray != NULL); - assert(pRootHandler->FileTable.ItemCount < pRootHandler->FileTable.ItemCountMax); + // Use the extension from the AssetInfo, if we have any + if(pAssetInfo != NULL && pAssetInfo->szExtension != NULL) + { + szPathPtr[nLength++] = '.'; + CascStrCopy(szPathPtr + nLength, (szPathEnd - (szPathPtr + nLength)), pAssetInfo->szExtension); + return true; + } - // Parse the directory header in order to retrieve the items - nError = ParseDirectoryHeader(&DirHeader, pbDirFile, pbFileEnd); - if(nError != ERROR_SUCCESS) - return nError; + // Otherwise, supply "a##" + CascStrPrintf(szPathPtr + nLength, (szPathEnd - (szPathPtr + nLength)), ".a%02u", pTocEntry->AssetIndex); + return true; + } - // Process all DIABLO3_FILEID1_ENTRY entries. These are for files - // belonging to an asset group, without subitem number. - // Example: "SoundBank\SoundFile.smp" - // We skip inserting them to the name map, because the names are not known yet - if(DirHeader.pbEntries1 && DirHeader.dwEntries1) - { - assert(dwRootDirIndex != DIABLO3_INVALID_INDEX); - nError = ParseDirEntries_FileId1(pRootHandler, DirHeader.pbEntries1, DirHeader.dwEntries1, dwRootDirIndex); - if(nError != ERROR_SUCCESS) - return nError; + return false; } - // Parse all DIABLO3_FILEID2_ENTRY entries. These are for files - // belonging to an asset group, with a subitem number. - // Example: "SoundBank\SoundFile\0001.smp" - // We skip inserting them to the name map, because the names are not known yet - if(DirHeader.pbEntries2 && DirHeader.dwEntries2) + // Parse the asset entries + int ParseAssetEntries( + TCascStorage * hs, + DIABLO3_DIRECTORY & Directory, + PATH_BUFFER & PathBuffer) { - assert(dwRootDirIndex != DIABLO3_INVALID_INDEX); - nError = ParseDirEntries_FileId2(pRootHandler, DirHeader.pbEntries2, DirHeader.dwEntries2, dwRootDirIndex); - if(nError != ERROR_SUCCESS) - return nError; - } + PDIABLO3_ASSET_ENTRY pEntry = (PDIABLO3_ASSET_ENTRY)Directory.pbAssetEntries; + PCASC_CKEY_ENTRY pCKeyEntry; + DWORD dwEntries = Directory.dwAssetEntries; + // Do nothing if there is no entries + if(pEntry != NULL && dwEntries != 0) + { + // Insert all asset entries to the file tree + for(DWORD i = 0; i < dwEntries; i++, pEntry++) + { + pCKeyEntry = FindCKeyEntry_CKey(hs, pEntry->CKey.Value); + if(pCKeyEntry != NULL) + { + // Construct the full path name of the entry + if(CreateAssetFileName(PathBuffer, pEntry->FileIndex, CASC_INVALID_INDEX)) + { + // Insert the entry to the file tree + FileTree.InsertByName(pCKeyEntry, PathBuffer.szBegin); + } + } + } + } - // Parse all named entries. These are for files with arbitrary names, - // and they do not belong to an asset. - if(DirHeader.pbEntries3 && DirHeader.dwEntries3) - { - nError = ParseDirEntries_Named(pRootHandler, DirHeader.pbEntries3, pbFileEnd, DirHeader.dwEntries3, dwRootDirIndex); - if(nError != ERROR_SUCCESS) - return nError; + return ERROR_SUCCESS; } - // Give the directory to the caller - return nError; -} - -static int ParseCoreTOC( - TRootHandler_Diablo3 * pRootHandler, - PCASC_MAP pPackageMap, - LPBYTE pbCoreTocFile, - LPBYTE pbCoreTocEnd) -{ - PDIABLO3_CORE_TOC_HEADER pTocHeader; - PDIABLO3_CORE_TOC_ENTRY pSortedEntries; - PDIABLO3_CORE_TOC_ENTRY pTocEntry; - LPBYTE pbCoreTocNames; - DWORD dwFileIndexes = 0; - DWORD i; - - // Check the space for header - if((pbCoreTocFile + sizeof(DIABLO3_CORE_TOC_HEADER)) > pbCoreTocEnd) - return ERROR_FILE_CORRUPT; - pTocHeader = (PDIABLO3_CORE_TOC_HEADER)pbCoreTocFile; - pbCoreTocFile += sizeof(DIABLO3_CORE_TOC_HEADER); - - // Calculate space needed for allocation - for(i = 0; i < DIABLO3_MAX_ASSETS; i++) + int ParseAssetAndIdxEntries( + TCascStorage * hs, + DIABLO3_DIRECTORY & Directory, + PATH_BUFFER & PathBuffer) { - // Get the first entry - pTocEntry = (PDIABLO3_CORE_TOC_ENTRY)(pbCoreTocFile + pTocHeader->EntryOffsets[i]); + PDIABLO3_ASSETIDX_ENTRY pEntry = (PDIABLO3_ASSETIDX_ENTRY)Directory.pbAssetIdxEntries; + PCASC_CKEY_ENTRY pCKeyEntry; + DWORD dwEntries = Directory.dwAssetIdxEntries; - // Find out the entry with the maximum index - for(DWORD n = 0; n < pTocHeader->EntryCounts[i]; n++) + // Do nothing if there is no entries + if(pEntry != NULL && dwEntries != 0) { - if(pTocEntry->FileIndex >= dwFileIndexes) - dwFileIndexes = pTocEntry->FileIndex + 1; - pTocEntry++; + // Insert all asset entries to the file tree + for(DWORD i = 0; i < dwEntries; i++, pEntry++) + { + pCKeyEntry = FindCKeyEntry_CKey(hs, pEntry->CKey.Value); + if(pCKeyEntry != NULL) + { + // Construct the full path name of the entry + if(CreateAssetFileName(PathBuffer, pEntry->FileIndex, pEntry->SubIndex)) + { + // Insert the entry to the file tree +// fprintf(fp, "%08u %04u %s\n", pEntry->FileIndex, pEntry->SubIndex, PathBuffer.szBegin); + FileTree.InsertByName(pCKeyEntry, PathBuffer.szBegin); + } + } + } } + + return ERROR_SUCCESS; } - // Allocate and populate the array of DIABLO3_CORE_TOC_ENTRYs - pSortedEntries = CASC_ALLOC(DIABLO3_CORE_TOC_ENTRY, dwFileIndexes); - if(pSortedEntries != NULL) + // Parse the named entries of all folders + int ParseDirectory_Phase1( + TCascStorage * hs, + DIABLO3_DIRECTORY & Directory, + PATH_BUFFER & PathBuffer, + bool bIsRootDirectory) { - // Initialize all entries to invalid - memset(pSortedEntries, 0xFF, dwFileIndexes * sizeof(DIABLO3_CORE_TOC_ENTRY)); + DIABLO3_NAMED_ENTRY NamedEntry; + char * szSavePtr = PathBuffer.szPtr; + size_t nFolderIndex = 0; + int nError = ERROR_SUCCESS; - // Populate the linear array with the entries - for(i = 0; i < DIABLO3_MAX_ASSETS; i++) + // Do nothing if there is no named headers + if(Directory.pbNamedEntries && Directory.dwNamedEntries) { - // Set the pointers - pTocEntry = (PDIABLO3_CORE_TOC_ENTRY)(pbCoreTocFile + pTocHeader->EntryOffsets[i]); - pbCoreTocNames = (LPBYTE)(pTocEntry + pTocHeader->EntryCounts[i]); - - // Setup the entries - for(DWORD n = 0; n < pTocHeader->EntryCounts[i]; n++) + PCASC_CKEY_ENTRY pCKeyEntry; + PCASC_FILE_NODE pFileNode; + LPBYTE pbDataPtr = Directory.pbNamedEntries; + LPBYTE pbDataEnd = Directory.pbDirectoryEnd; + DWORD dwNodeIndex; + + // Parse all entries + while(pbDataPtr < pbDataEnd) { - pSortedEntries[pTocEntry->FileIndex].AssetIndex = pTocEntry->AssetIndex; - pSortedEntries[pTocEntry->FileIndex].FileIndex = pTocEntry->FileIndex; - pSortedEntries[pTocEntry->FileIndex].NameOffset = (DWORD)(pbCoreTocNames - pbCoreTocFile) + pTocEntry->NameOffset; - pTocEntry++; + // Capture the named entry + pbDataPtr = CaptureNamedEntry(pbDataPtr, pbDataEnd, &NamedEntry); + if(pbDataPtr == NULL) + return ERROR_BAD_FORMAT; + + // Append the path fragment to the total path + AppendPathToTotalPath(PathBuffer, NamedEntry.szFileName, NamedEntry.szFileEnd); + + // Check whether the file exists in the storage + pCKeyEntry = FindCKeyEntry_CKey(hs, NamedEntry.pCKey->Value); + if(pCKeyEntry != NULL) + { + // Create file node belonging to this folder + pFileNode = FileTree.InsertByName(pCKeyEntry, PathBuffer.szBegin); + dwNodeIndex = (DWORD)FileTree.IndexOf(pFileNode); + + // If we are parsing root folder, we also need to load the data of the sub-folder file + if(bIsRootDirectory) + { + // Mark the node as directory + AppendBackslashToTotalPath(PathBuffer); + pCKeyEntry->Flags |= CASC_CE_FOLDER_ENTRY; + pFileNode->Flags |= CFN_FLAG_FOLDER; + + // Load the sub-directory file + nError = LoadDirectoryFile(hs, RootFolders[nFolderIndex], pCKeyEntry); + if(nError != ERROR_SUCCESS) + return nError; + + // Parse the sub-directory file + nError = ParseDirectory_Phase1(hs, RootFolders[nFolderIndex], PathBuffer, false); + if(nError != ERROR_SUCCESS) + return nError; + + // Also save the item pointer and increment the folder index + RootFolders[nFolderIndex].dwNodeIndex = dwNodeIndex; + nFolderIndex++; + } + + // Restore the path pointer + PathBuffer.szPtr = szSavePtr; + szSavePtr[0] = 0; + } } } - // Now use the linear array to resolve the asset indexes and plain names - ResolveFullFileNames(pRootHandler, pSortedEntries, pPackageMap, pbCoreTocFile, dwFileIndexes); - CASC_FREE(pSortedEntries); + return nError; } - return ERROR_SUCCESS; -} - -//----------------------------------------------------------------------------- -// Implementation of Diablo III root file - -static int D3Handler_Insert(TRootHandler_Diablo3 * pRootHandler, const char * szFileName, LPBYTE pbEncodingKey) -{ - ENCODING_KEY EncodingKey; - DWORD dwFileIndex; - - // Don't let the number of items to overflow - if(pRootHandler->FileTable.ItemCount >= pRootHandler->FileTable.ItemCountMax) - return ERROR_NOT_ENOUGH_MEMORY; - - // Insert the item - EncodingKey = *(PENCODING_KEY)pbEncodingKey; - dwFileIndex = InsertFileEntry(pRootHandler, - EncodingKey, - szFileName, - strlen(szFileName) + 1); - return (dwFileIndex != INVALID_FILE_INDEX) ? ERROR_SUCCESS : ERROR_NOT_ENOUGH_MEMORY; -} - -static LPBYTE D3Handler_Search(TRootHandler_Diablo3 * pRootHandler, TCascSearch * pSearch, PDWORD /* PtrFileSize */, PDWORD /* PtrLocaleFlags */, PDWORD /* PtrFileDataId */) -{ - PCASC_FILE_ENTRY pFileEntry; - const char * szSrcName = NULL; - - // Are we still inside the root directory range? - while(pSearch->IndexLevel1 < pRootHandler->FileTable.ItemCount) + // Parse the nameless entries of all folders + int ParseDirectory_Phase2(TCascStorage * hs) { - // Get the n-th directory and the file name - pFileEntry = (PCASC_FILE_ENTRY)Array_ItemAt(&pRootHandler->FileTable, pSearch->IndexLevel1); - szSrcName = (char *)Array_ItemAt(&pRootHandler->FileNames, pFileEntry->dwFileName); + PATH_BUFFER PathBuffer; + char szPathBuffer[MAX_PATH]; - // This is either a full file name or an abbreviated name - if(pFileEntry->dwFlags & CASC_ENTRY_SHORT_NAME) + // Parse each root subdirectory + for(size_t i = 0; i < DIABLO3_MAX_ROOT_FOLDERS; i++) { - CreateFileName(pRootHandler, szSrcName, pSearch->szFileName); - } - else - { - strcpy(pSearch->szFileName, szSrcName); + // Is this root folder loaded? + if(RootFolders[i].pbDirectoryData != NULL) + { + PathBuffer.szBegin = szPathBuffer; + PathBuffer.szPtr = szPathBuffer; + PathBuffer.szEnd = szPathBuffer + MAX_PATH - 1; + szPathBuffer[0] = 0; + + // Retrieve the parent name + if(RootFolders[i].dwNodeIndex != 0) + { + FileTree.PathAt(szPathBuffer, MAX_PATH, RootFolders[i].dwNodeIndex); + PathBuffer.szPtr = PathBuffer.szBegin + strlen(szPathBuffer); + } + + // Array of DIABLO3_ASSET_ENTRY entries. + // These are for files belonging to an asset, without subitem number. + // Example: "SoundBank\SoundFile.smp" + ParseAssetEntries(hs, RootFolders[i], PathBuffer); + + // Array of DIABLO3_ASSETIDX_ENTRY entries. + // These are for files belonging to an asset, with a subitem number. + // Example: "SoundBank\SoundFile\0001.smp" + ParseAssetAndIdxEntries(hs, RootFolders[i], PathBuffer); + } } - // Prepare for the next search - pSearch->IndexLevel1++; - return pFileEntry->EncodingKey.Value; + return ERROR_SUCCESS; } - // No more entries - return NULL; -} + // Creates an array of DIABLO3_CORE_TOC_ENTRY entries indexed by FileIndex + // Used as lookup table when we have FileIndex and need Asset+PlainName + int CreateMapOfFileIndices(TCascStorage * hs, const char * szFileName) + { + PDIABLO3_CORE_TOC_HEADER pTocHeader = NULL; + LPBYTE pbCoreTocPtr = pbCoreTocFile; + DWORD dwMaxFileIndex = 0; + int nError = ERROR_CAN_NOT_COMPLETE; -static void D3Handler_EndSearch(TRootHandler_Diablo3 * /* pRootHandler */, TCascSearch * /* pSearch */) -{ - // Do nothing -} + // Load the entire file to memory + pbCoreTocFile = pbCoreTocPtr = LoadFileToMemory(hs, szFileName, &cbCoreTocFile); + if(pbCoreTocFile && cbCoreTocFile) + { + LPBYTE pbCoreTocEnd = pbCoreTocFile + cbCoreTocFile; -static LPBYTE D3Handler_GetKey(TRootHandler_Diablo3 * pRootHandler, const char * szFileName) -{ - PCASC_FILE_ENTRY pFileEntry; - ULONGLONG FileNameHash = CalcFileNameHash(szFileName); + // Capture the header + if((pbCoreTocPtr = CaptureCoreTocHeader(&pTocHeader, &dwMaxFileIndex, pbCoreTocPtr, pbCoreTocEnd)) == NULL) + return ERROR_BAD_FORMAT; - // Find the file in the name table - pFileEntry = (PCASC_FILE_ENTRY)Map_FindObject(pRootHandler->pRootMap, &FileNameHash, NULL); - return (pFileEntry != NULL) ? pFileEntry->EncodingKey.Value : NULL; -} + // If there are no indices, return NULL + if(dwMaxFileIndex == 0) + return ERROR_SUCCESS; -static DWORD D3Handler_GetFileId(TRootHandler_Diablo3 * /* pRootHandler */, const char * /* szFileName */) -{ - // Not implemented for D3 - return 0; -} + // Allocate and populate the array of DIABLO3_CORE_TOC_ENTRYs + pFileIndices = CASC_ALLOC(DIABLO3_CORE_TOC_ENTRY, dwMaxFileIndex + 1); + if(pFileIndices != NULL) + { + // Initialize all entries to invalid + memset(pFileIndices, 0xFF, (dwMaxFileIndex + 1) * sizeof(DIABLO3_CORE_TOC_ENTRY)); + + // Populate the linear array with the file indices + for(size_t i = 0; i < DIABLO3_MAX_ASSETS; i++) + { + PDIABLO3_CORE_TOC_ENTRY pTocEntry = (PDIABLO3_CORE_TOC_ENTRY)(pbCoreTocPtr + pTocHeader->EntryOffsets[i]); + LPBYTE pbCoreTocNames = (LPBYTE)(pTocEntry + pTocHeader->EntryCounts[i]); + + // Setup the entries + for(DWORD n = 0; n < pTocHeader->EntryCounts[i]; n++) + { + DWORD dwFileIndex = pTocEntry->FileIndex; + + pFileIndices[dwFileIndex].AssetIndex = pTocEntry->AssetIndex; + pFileIndices[dwFileIndex].FileIndex = pTocEntry->FileIndex; + pFileIndices[dwFileIndex].NameOffset = (DWORD)(pbCoreTocNames - pbCoreTocPtr) + pTocEntry->NameOffset; + pTocEntry++; + } + } + + // Save the file to the root handler + pbCoreTocData = pbCoreTocPtr; + nFileIndices = dwMaxFileIndex; + nError = ERROR_SUCCESS; + } + } + return nError; + } -static void D3Handler_Close(TRootHandler_Diablo3 * pRootHandler) -{ - if(pRootHandler != NULL) + // Packages.dat contains a list of full file names (without locale prefix). + // They are not sorted, nor they correspond to file IDs. + // Does the sort order mean something? Perhaps we could use them as listfile? + int CreateMapOfRealNames(TCascStorage * hs, const char * szFileName) { - // Free the file map - Map_Free(pRootHandler->pRootMap); + DWORD Signature = 0; + DWORD NumberOfNames = 0; - // Free the array of the file entries and file names - Array_Free(&pRootHandler->FileTable); - Array_Free(&pRootHandler->FileNames); + // Load the entire file to memory + pbPackagesDat = LoadFileToMemory(hs, szFileName, &cbPackagesDat); + if(pbPackagesDat && cbPackagesDat) + { + LPBYTE pbPackagesPtr = pbPackagesDat; + LPBYTE pbPackagesEnd = pbPackagesDat + cbPackagesDat; +/* + LPBYTE pbPackagesPtr = pbPackagesDat + 8; + FILE * fp = fopen("E:\\Packages.dat", "wt"); + if(fp != NULL) + { + while(pbPackagesPtr < pbPackagesEnd) + { + fprintf(fp, "%s\n", pbPackagesPtr); + pbPackagesPtr = pbPackagesPtr + strlen((char *)pbPackagesPtr) + 1; + } + fclose(fp); + } +*/ + // Get the header. There is just Signature + NumberOfNames + if((pbPackagesPtr = CaptureInteger32(pbPackagesPtr, pbPackagesEnd, &Signature)) == NULL) + return ERROR_BAD_FORMAT; + if((pbPackagesPtr = CaptureInteger32(pbPackagesPtr, pbPackagesEnd, &NumberOfNames)) == NULL) + return ERROR_BAD_FORMAT; + if(Signature != DIABLO3_PACKAGES_SIGNATURE || NumberOfNames == 0) + return ERROR_BAD_FORMAT; + + // Create the map for fast search of the file name + if(PackagesMap.Create(NumberOfNames, 0, 0, KeyIsString) == ERROR_SUCCESS) + { + const char * szPackageName = (const char *)pbPackagesPtr; + + // Go as long as there is something + for(DWORD i = 0; i < NumberOfNames; i++) + { + // Get the file extension + if((LPBYTE)szPackageName >= pbPackagesEnd) + break; + + // Insert the file name to the map. The file extension is not included + PackagesMap.InsertString(szPackageName, true); + szPackageName = szPackageName + strlen(szPackageName) + 1; + } + } + } - // Free the root file itself - CASC_FREE(pRootHandler); + return ERROR_SUCCESS; } -} -/* -static void DumpRootFile(TDumpContext * dc, LPBYTE pbFileData, LPBYTE pbFileDataEnd) -{ - char szMD5Buffer[MD5_STRING_SIZE+1]; - DWORD dwSignature; - DWORD dwItemCount; - DWORD i; - - dwSignature = *(PDWORD)pbFileData; - if(dwSignature != CASC_DIABLO3_SUBDIR_SIGNATURE) - return; - pbFileData += sizeof(DWORD); - - // Dump items that contain EncodingKey + AssetId - dwItemCount = *(PDWORD)pbFileData; - pbFileData += sizeof(DWORD); - for(i = 0; i < dwItemCount; i++) + int Load(TCascStorage * hs, DIABLO3_DIRECTORY & RootDirectory) { - PCASC_DIABLO3_ASSET_ENTRY pEntry = (PCASC_DIABLO3_ASSET_ENTRY)pbFileData; + PATH_BUFFER PathBuffer; + char szPathBuffer[MAX_PATH]; + int nError; + + // Initialize path buffer and go parse the directory + PathBuffer.szBegin = szPathBuffer; + PathBuffer.szPtr = szPathBuffer; + PathBuffer.szEnd = szPathBuffer + MAX_PATH; + szPathBuffer[0] = 0; + + // Always parse the named entries first. They always point to a file. + // These are entries with arbitrary names, and they do not belong to an asset + nError = ParseDirectory_Phase1(hs, RootDirectory, PathBuffer, true); + if(nError == ERROR_SUCCESS) + { + // The asset entries in the ROOT file don't contain file names, but indices. + // To convert a file index to a file name, we need to load and parse the "Base\\CoreTOC.dat" file. + nError = CreateMapOfFileIndices(hs, "Base\\CoreTOC.dat"); + if(nError == ERROR_SUCCESS) + { + // The file "Base\Data_D3\PC\Misc\Packages.dat" contains the file names + // (without level-0 and level-1 directory). + // We can use these names for supplying the missing extensions + CreateMapOfRealNames(hs, "Base\\Data_D3\\PC\\Misc\\Packages.dat"); + + // Now parse all folders and resolve the full names + ParseDirectory_Phase2(hs); + } - if((pbFileData + sizeof(*pEntry)) > pbFileDataEnd) - return; - pbFileData += sizeof(*pEntry); + // Free all stuff that was used during loading of the ROOT file + FreeLoadingStuff(); + } - dump_print(dc, "%s %08X\n", StringFromMD5(pEntry->EncodingKey, szMD5Buffer), pEntry->AssetId); + return nError; } - // Terminate with two newlines - dump_print(dc, "\n"); - - // Dump items that contain EncodingKey + AssetId + FileNumber - dwItemCount = *(PDWORD)pbFileData; - pbFileData += sizeof(DWORD); - for(i = 0; i < dwItemCount; i++) + void FreeLoadingStuff() { - PCASC_DIABLO3_ASSET_ENTRY2 pEntry = (PCASC_DIABLO3_ASSET_ENTRY2)pbFileData; + // Free the captured root sub-directories + for(size_t i = 0; i < DIABLO3_MAX_SUBDIRS; i++) + CASC_FREE(RootFolders[i].pbDirectoryData); - if((pbFileData + sizeof(*pEntry)) > pbFileDataEnd) - return; - pbFileData += sizeof(*pEntry); + // Free the package map + PackagesMap.Free(); - dump_print(dc, "%s %08X %08X\n", StringFromMD5((LPBYTE)pEntry->EncodingKey, szMD5Buffer), pEntry->AssetId, pEntry->FileNumber); - } - - // Terminate with two newlines - dump_print(dc, "\n"); + // Free the array of file indices + CASC_FREE(pFileIndices); - // Dump items that contain EncodingKey + FileName - dwItemCount = *(PDWORD)pbFileData; - pbFileData += sizeof(DWORD); - for(i = 0; i < dwItemCount; i++) - { - PDIABLO3_NAMED_ENTRY pEntry = (PDIABLO3_NAMED_ENTRY)pbFileData; - DWORD dwEntrySize = VerifyNamedFileEntry(pbFileData, pbFileDataEnd); + // Free the loaded CoreTOC.dat file + CASC_FREE(pbCoreTocFile); - if((pbFileData + dwEntrySize) > pbFileDataEnd) - return; - pbFileData += dwEntrySize; - - dump_print(dc, "%s %s\n", StringFromMD5((LPBYTE)pEntry->EncodingKey, szMD5Buffer), pEntry->szFileName); + // Free the loaded Packages.dat file + CASC_FREE(pbPackagesDat); } - dump_print(dc, "\n\n"); -} -*/ + // Array of root directory subdirectories + DIABLO3_DIRECTORY RootFolders[DIABLO3_MAX_ROOT_FOLDERS]; + + // Array of DIABLO3_TOC_ENTRY structures, sorted by the file index + // Used for converting FileIndex -> Asset+PlainName during loading + PDIABLO3_CORE_TOC_ENTRY pFileIndices; + LPBYTE pbCoreTocFile; + LPBYTE pbCoreTocData; + size_t nFileIndices; + DWORD cbCoreTocFile; + + // Map for searching a real file extension + CASC_MAP PackagesMap; + LPBYTE pbPackagesDat; + DWORD cbPackagesDat; +}; + //----------------------------------------------------------------------------- // Public functions int RootHandler_CreateDiablo3(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile) { - TRootHandler_Diablo3 * pRootHandler; - PCASC_MAP pPackageMap = NULL; - LPBYTE pbRootFileEnd = pbRootFile + cbRootFile; - LPBYTE pbPackagesDat = NULL; - DWORD dwTotalFileCount; - DWORD cbPackagesDat = 0; - int nError; - - // Allocate the root handler object - hs->pRootHandler = pRootHandler = CASC_ALLOC(TRootHandler_Diablo3, 1); - if(pRootHandler == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Fill-in the handler functions - memset(pRootHandler, 0, sizeof(TRootHandler_Diablo3)); - pRootHandler->Insert = (ROOT_INSERT)D3Handler_Insert; - pRootHandler->Search = (ROOT_SEARCH)D3Handler_Search; - pRootHandler->EndSearch = (ROOT_ENDSEARCH)D3Handler_EndSearch; - pRootHandler->GetKey = (ROOT_GETKEY)D3Handler_GetKey; - pRootHandler->Close = (ROOT_CLOSE)D3Handler_Close; - pRootHandler->GetFileId = (ROOT_GETFILEID)D3Handler_GetFileId; - - // Fill-in the flags - pRootHandler->dwRootFlags |= ROOT_FLAG_HAS_NAMES; - - // Scan the total number of files in the root directories - // Reserve space for extra files - dwTotalFileCount = ScanDirectoryFile(hs, pbRootFile, pbRootFileEnd); - if(dwTotalFileCount == 0) - return ERROR_FILE_CORRUPT; - dwTotalFileCount += CASC_EXTRA_FILES; - - // Allocate the global linear file table - // Note: This is about 18 MB of memory for Diablo III PTR build 30013 - nError = Array_Create(&pRootHandler->FileTable, CASC_FILE_ENTRY, dwTotalFileCount); - if(nError != ERROR_SUCCESS) - return nError; + TDiabloRoot * pRootHandler = NULL; + DIABLO3_DIRECTORY RootDirectory; + int nError = ERROR_BAD_FORMAT; - // Allocate global buffer for file names. - // The size of the buffer was taken from Diablo III build 30013 - nError = Array_Create(&pRootHandler->FileNames, char, 0x01000000); - if(nError != ERROR_SUCCESS) - return nError; - - // Create map of ROOT_ENTRY -> FileEntry - pRootHandler->pRootMap = Map_Create(dwTotalFileCount, sizeof(ULONGLONG), FIELD_OFFSET(CASC_FILE_ENTRY, FileNameHash)); - if(pRootHandler->pRootMap == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - // Parse the ROOT file and insert all entries in the file table - nError = ParseDirectoryFile(pRootHandler, pbRootFile, pbRootFileEnd, DIABLO3_INVALID_INDEX); - if(nError == ERROR_SUCCESS) + // Verify the header of the ROOT file + if(TDiabloRoot::CaptureDirectoryData(RootDirectory, pbRootFile, cbRootFile) != NULL) { - size_t dwRootEntries = pRootHandler->FileTable.ItemCount; - - // We expect the number of level-0 to be less than maximum - assert(dwRootEntries < DIABLO3_MAX_SUBDIRS); - - // Now parse the all root items and load them - for(DWORD i = 0; i < dwRootEntries; i++) + // Allocate the root handler object + pRootHandler = new TDiabloRoot(); + if(pRootHandler != NULL) { - PCASC_FILE_ENTRY pRootEntry = (PCASC_FILE_ENTRY)Array_ItemAt(&pRootHandler->FileTable, i); - - // Load the entire file to memory - pbRootFile = LoadFileToMemory(hs, pRootEntry->EncodingKey.Value, &cbRootFile); - if(pbRootFile != NULL) + // Load the root directory. If load failed, we free the object + nError = pRootHandler->Load(hs, RootDirectory); + if(nError != ERROR_SUCCESS) { - nError = ParseDirectoryFile(pRootHandler, pbRootFile, pbRootFile + cbRootFile, i); - CASC_FREE(pbRootFile); + delete pRootHandler; + pRootHandler = NULL; } } } - // Note: The file "Base\Data_D3\PC\Misc\Packages.dat" contains the names - // of the files (without level-0 and level-1 directory). We can use these - // names for supplying the missing extensions - if(nError == ERROR_SUCCESS) - { - // Load the entire file to memory - pbPackagesDat = LoadFileToMemory(hs, "Base\\Data_D3\\PC\\Misc\\Packages.dat", &cbPackagesDat); - if(pbPackagesDat != NULL) - { - pPackageMap = CreatePackageMap(pbPackagesDat, pbPackagesDat + cbPackagesDat); - } - } - - // Vast majorify of files at this moment don't have names. - // We can load the Base\CoreTOC.dat file in order - // to get directory asset indexes, file names and extensions - if(nError == ERROR_SUCCESS) - { - LPBYTE pbCoreTOC; - DWORD cbCoreTOC = 0; - - // Load the entire file to memory - pbCoreTOC = LoadFileToMemory(hs, "Base\\CoreTOC.dat", &cbCoreTOC); - if(pbCoreTOC != NULL) - { - ParseCoreTOC(pRootHandler, pPackageMap, pbCoreTOC, pbCoreTOC + cbCoreTOC); - CASC_FREE(pbCoreTOC); - } - } - - // Free the packages map - if(pPackageMap != NULL) - Map_Free(pPackageMap); - if(pbPackagesDat != NULL) - CASC_FREE(pbPackagesDat); + // Assign the root directory (or NULL) and return error + hs->pRootHandler = pRootHandler; return nError; } |