diff options
Diffstat (limited to 'dep/CascLib/src/CascRootFile_Diablo3.cpp')
-rw-r--r-- | dep/CascLib/src/CascRootFile_Diablo3.cpp | 1189 |
1 files changed, 1189 insertions, 0 deletions
diff --git a/dep/CascLib/src/CascRootFile_Diablo3.cpp b/dep/CascLib/src/CascRootFile_Diablo3.cpp new file mode 100644 index 00000000000..98a42cc3226 --- /dev/null +++ b/dep/CascLib/src/CascRootFile_Diablo3.cpp @@ -0,0 +1,1189 @@ +/*****************************************************************************/ +/* CascRootFile_Diablo3.cpp Copyright (c) Ladislav Zezula 2015 */ +/*---------------------------------------------------------------------------*/ +/* Support for loading Diablo 3 ROOT file */ +/* Note: D3 offsets refer to Diablo III.exe 2.2.0.30013 (32-bit) */ +/* SHA1: e4f17eca8aad8dde70870bf932ac3f5b85f17a1f */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 04.03.15 1.00 Lad The first version of CascRootFile_Diablo3.cpp */ +/*****************************************************************************/ + +#define __CASCLIB_SELF__ +#include "CascLib.h" +#include "CascCommon.h" + +//----------------------------------------------------------------------------- +// Local structures + +#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) + +// On-disk structure for a file given by file number +typedef struct _DIABLO3_FILEID1_ENTRY +{ + ENCODING_KEY EncodingKey; // Encoding key for the file + DWORD FileIndex; // File index +} DIABLO3_FILEID1_ENTRY, *PDIABLO3_FILEID1_ENTRY; + +// On-disk structure for a file given by file number and suffix +typedef struct _DIABLO3_FILEID2_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; + +// On-disk 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) +} 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 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; +} DIABLO3_CORE_TOC_HEADER, *PDIABLO3_CORE_TOC_HEADER; + +// On-disk structure of the entry in CoreToc.dat +typedef struct _DIABLO3_CORE_TOC_ENTRY +{ + DWORD AssetIndex; // Index of the Diablo3 asset (aka directory) + DWORD FileIndex; // File index + DWORD NameOffset; // Offset of the plain file name + +} 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 +{ + 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; + +// Structure for conversion DirectoryID -> Directory name +typedef struct _DIABLO3_ASSET_INFO +{ + const char * szDirectoryName; // Directory name + const char * szExtension; + +} 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 + +static const DIABLO3_ASSET_INFO Assets[] = +{ +// DIR-NAME EXTENSION +// ========== ========= + {NULL, NULL}, // 0x00 + {"Actor", "acr"}, // 0x01 + {"Adventure", "adv"}, // 0x02 + {NULL, NULL}, // 0x03 + {NULL, NULL}, // 0x04 + {"AmbientSound", "ams"}, // 0x05 + {"Anim", "ani"}, // 0x06 + {"Anim2D", "an2"}, // 0x07 + {"AnimSet", "ans"}, // 0x08 + {"Appearance", "app"}, // 0x09 + {NULL, NULL}, // 0x0A + {"Cloth", "clt"}, // 0x0B + {"Conversation", "cnv"}, // 0x0C + {NULL, NULL}, // 0x0D + {"EffectGroup", "efg"}, // 0x0E + {"Encounter", "enc"}, // 0x0F + {NULL, NULL}, // 0x10 + {"Explosion", "xpl"}, // 0x11 + {NULL, NULL}, // 0x12 + {"Font", "fnt"}, // 0x13 + {"GameBalance", "gam"}, // 0x14 + {"Globals", "glo"}, // 0x15 + {"LevelArea", "lvl"}, // 0x16 + {"Light", "lit"}, // 0x17 + {"MarkerSet", "mrk"}, // 0x18 + {"Monster", "mon"}, // 0x19 + {"Observer", "obs"}, // 0x1A + {"Particle", "prt"}, // 0x1B + {"Physics", "phy"}, // 0x1C + {"Power", "pow"}, // 0x1D + {NULL, NULL}, // 0x1E + {"Quest", "qst"}, // 0x1F + {"Rope", "rop"}, // 0x20 + {"Scene", "scn"}, // 0x21 + {"SceneGroup", "scg"}, // 0x22 + {NULL, NULL}, // 0x23 + {"ShaderMap", "shm"}, // 0x24 + {"Shaders", "shd"}, // 0x25 + {"Shakes", "shk"}, // 0x26 + {"SkillKit", "skl"}, // 0x27 + {"Sound", "snd"}, // 0x28 + {"SoundBank", "sbk"}, // 0x29 + {"StringList", "stl"}, // 0x2A + {"Surface", "srf"}, // 0x2B + {"Textures", "tex"}, // 0x2C + {"Trail", "trl"}, // 0x2D + {"UI", "ui"}, // 0x2E + {"Weather", "wth"}, // 0x2F + {"Worlds", "wrl"}, // 0x30 + {"Recipe", "rcp"}, // 0x31 + {NULL, NULL}, // 0x32 + {"Condition", "cnd"}, // 0x33 + {NULL, NULL}, // 0x34 + {NULL, NULL}, // 0x35 + {NULL, NULL}, // 0x36 + {NULL, NULL}, // 0x37 + {"Act", "act"}, // 0x38 + {"Material", "mat"}, // 0x39 + {"QuestRange", "qsr"}, // 0x3A + {"Lore", "lor"}, // 0x3B + {"Reverb", "rev"}, // 0x3C + {"PhysMesh", "phm"}, // 0x3D + {"Music", "mus"}, // 0x3E + {"Tutorial", "tut"}, // 0x3F + {"BossEncounter", "bos"}, // 0x40 + {NULL, NULL}, // 0x41 + {"Accolade", "aco"}, // 0x42 +}; + +static const DIABLO3_ASSET_INFO UnknownAsset = {"Unknown", "xxx"}; + +#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++; + + // 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) +{ + char szFileName[MAX_PATH+1]; + size_t nLength; + + // 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); +} + +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) + { + // Retrieve the asset name + szPackageName = FindPackageName(pPackageMap, pAssetInfo->szDirectoryName, szBuffer); + if(szPackageName != NULL) + { + strcpy(szBuffer, szPackageName + strlen(pAssetInfo->szDirectoryName) + 1); + nLength = strlen(szBuffer); + } + } + + // If we havent't found the package, we either use the default asset extension or "xxx" + if(szPackageName == NULL) + { + if(dwSubIndex == DIABLO3_INVALID_INDEX) + { + strcpy(szBuffer + nLength, pAssetInfo->szExtension); + nLength += strlen(pAssetInfo->szExtension); + } + else + { + strcpy(szBuffer + nLength, "xxx"); + nLength += 3; + } + } + + // 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) + { + char * szFileName = (char *)pbPackagesDat; + + // Go as long as there is something + for(DWORD i = 0; i < pDatHeader->NumberOfNames; i++) + { + // 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; + } + } + + 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; + } + + // 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) + { + assert(false); + return ERROR_NOT_ENOUGH_MEMORY; + } + + // Parse the all ID1 entries in the file + for(DWORD i = 0; i < dwFileEntries; i++, pEntry++) + { + // 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; +} + +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; + } + + // Parse the all ID1 entries in the file + for(DWORD i = 0; i < dwFileEntries; i++, pEntry++) + { + // 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; + } + + 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; + + // Overflow test + if((pRootHandler->FileTable.ItemCount + dwFileEntries) >= pRootHandler->FileTable.ItemCountMax) + { + assert(false); + return ERROR_NOT_ENOUGH_MEMORY; + } + + // If we the file is not in the root directory itself, + // prepare the prefix for the root directory. + if(dwRootDirIndex != DIABLO3_INVALID_INDEX) + { + PCASC_FILE_ENTRY pRootEntry = (PCASC_FILE_ENTRY)Array_ItemAt(&pRootHandler->FileTable, dwRootDirIndex); + const char * szRootName = (const char *)Array_ItemAt(&pRootHandler->FileNames, pRootEntry->dwFileName); + + // Copy the root directory name + while(szRootName[0] != 0) + *szNamePtr++ = *szRootName++; + + // Append the backslash + *szNamePtr++ = '\\'; + } + + // 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; + } + + 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]; + + // Parse the entire file table + for(size_t i = 0; i < pRootHandler->FileTable.ItemCount; i++) + { + // Retrieve the file entry at n-th position + pFileEntry = (PCASC_FILE_ENTRY)Array_ItemAt(&pRootHandler->FileTable, i); + + // 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); + } + } +} + +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) + { + pbFileData = CASC_ALLOC(BYTE, cbFileData); + if(pbFileData != NULL) + { + CascReadFile(hFile, pbFileData, cbFileData, &cbBytesRead); + } + } + + // Close the file + CascCloseFile(hFile); + } + + // 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) + { + // 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); + } + + // 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; +} + +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++) + { + // 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) + { + // Count the files in the subdirectory + if(ParseDirectoryHeader(&DirHeader, pbSubDir, pbSubDir + cbSubDir) == ERROR_SUCCESS) + { + dwTotalFileCount += DirHeader.dwEntries1 + DirHeader.dwEntries2 + DirHeader.dwEntries3; + } + + // Free the subdirectory + CASC_FREE(pbSubDir); + } + } + + // Return the total number of entries + return dwTotalFileCount; +} + +static int ParseDirectoryFile( + TRootHandler_Diablo3 * pRootHandler, + LPBYTE pbDirFile, + LPBYTE pbFileEnd, + DWORD dwRootDirIndex) +{ + DIABLO3_DIR_HEADER DirHeader; + int nError; + + // Sanity checks + assert(pRootHandler->FileTable.ItemArray != NULL); + assert(pRootHandler->FileTable.ItemCount < pRootHandler->FileTable.ItemCountMax); + + // Parse the directory header in order to retrieve the items + nError = ParseDirectoryHeader(&DirHeader, pbDirFile, pbFileEnd); + if(nError != ERROR_SUCCESS) + return nError; + + // 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; + } + + // 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) + { + assert(dwRootDirIndex != DIABLO3_INVALID_INDEX); + nError = ParseDirEntries_FileId2(pRootHandler, DirHeader.pbEntries2, DirHeader.dwEntries2, dwRootDirIndex); + if(nError != ERROR_SUCCESS) + return nError; + } + + + // 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; + } + + // 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++) + { + // Get the first entry + pTocEntry = (PDIABLO3_CORE_TOC_ENTRY)(pbCoreTocFile + pTocHeader->EntryOffsets[i]); + + // Find out the entry with the maximum index + for(DWORD n = 0; n < pTocHeader->EntryCounts[i]; n++) + { + if(pTocEntry->FileIndex > dwFileIndexes) + dwFileIndexes = pTocEntry->FileIndex + 1; + pTocEntry++; + } + } + + // Allocate and populate the array of DIABLO3_CORE_TOC_ENTRYs + pSortedEntries = CASC_ALLOC(DIABLO3_CORE_TOC_ENTRY, dwFileIndexes); + if(pSortedEntries != NULL) + { + // Initialize all entries to invalid + memset(pSortedEntries, 0xFF, dwFileIndexes * sizeof(DIABLO3_CORE_TOC_ENTRY)); + + // Populate the linear array with the entries + for(i = 0; i < DIABLO3_MAX_ASSETS; i++) + { + // 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++) + { + pSortedEntries[pTocEntry->FileIndex].AssetIndex = pTocEntry->AssetIndex; + pSortedEntries[pTocEntry->FileIndex].FileIndex = pTocEntry->FileIndex; + pSortedEntries[pTocEntry->FileIndex].NameOffset = (DWORD)(pbCoreTocNames - pbCoreTocFile) + pTocEntry->NameOffset; + pTocEntry++; + } + } + + // Now use the linear array to resolve the asset indexes and plain names + ResolveFullFileNames(pRootHandler, pSortedEntries, pPackageMap, pbCoreTocFile, dwFileIndexes); + CASC_FREE(pSortedEntries); + } + + 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 */) +{ + PCASC_FILE_ENTRY pFileEntry; + const char * szSrcName = NULL; + + // Are we still inside the root directory range? + while(pSearch->IndexLevel1 < pRootHandler->FileTable.ItemCount) + { + // 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); + + // This is either a full file name or an abbreviated name + if(pFileEntry->dwFlags & CASC_ENTRY_SHORT_NAME) + { + CreateFileName(pRootHandler, szSrcName, pSearch->szFileName); + } + else + { + strcpy(pSearch->szFileName, szSrcName); + } + + // Prepare for the next search + pSearch->IndexLevel1++; + return pFileEntry->EncodingKey.Value; + } + + // No more entries + return NULL; +} + +static void D3Handler_EndSearch(TRootHandler_Diablo3 * /* pRootHandler */, TCascSearch * /* pSearch */) +{ + // Do nothing +} + +static LPBYTE D3Handler_GetKey(TRootHandler_Diablo3 * pRootHandler, const char * szFileName) +{ + PCASC_FILE_ENTRY pFileEntry; + ULONGLONG FileNameHash = CalcFileNameHash(szFileName); + + // Find the file in the name table + pFileEntry = (PCASC_FILE_ENTRY)Map_FindObject(pRootHandler->pRootMap, &FileNameHash, NULL); + return (pFileEntry != NULL) ? pFileEntry->EncodingKey.Value : NULL; +} + +static void D3Handler_Close(TRootHandler_Diablo3 * pRootHandler) +{ + if(pRootHandler != NULL) + { + // Free the file map + Map_Free(pRootHandler->pRootMap); + + // Free the array of the file entries and file names + Array_Free(&pRootHandler->FileTable); + Array_Free(&pRootHandler->FileNames); + + // Free the root file itself + CASC_FREE(pRootHandler); + } +} + +/* +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++) + { + PCASC_DIABLO3_ASSET_ENTRY pEntry = (PCASC_DIABLO3_ASSET_ENTRY)pbFileData; + + if((pbFileData + sizeof(*pEntry)) > pbFileDataEnd) + return; + pbFileData += sizeof(*pEntry); + + dump_print(dc, "%s %08X\n", StringFromMD5(pEntry->EncodingKey, szMD5Buffer), pEntry->AssetId); + } + + // 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++) + { + PCASC_DIABLO3_ASSET_ENTRY2 pEntry = (PCASC_DIABLO3_ASSET_ENTRY2)pbFileData; + + if((pbFileData + sizeof(*pEntry)) > pbFileDataEnd) + return; + pbFileData += sizeof(*pEntry); + + dump_print(dc, "%s %08X %08X\n", StringFromMD5((LPBYTE)pEntry->EncodingKey, szMD5Buffer), pEntry->AssetId, pEntry->FileNumber); + } + + // Terminate with two newlines + dump_print(dc, "\n"); + + // 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); + + if((pbFileData + dwEntrySize) > pbFileDataEnd) + return; + pbFileData += dwEntrySize; + + dump_print(dc, "%s %s\n", StringFromMD5((LPBYTE)pEntry->EncodingKey, szMD5Buffer), pEntry->szFileName); + } + + dump_print(dc, "\n\n"); +} +*/ +//----------------------------------------------------------------------------- +// 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; + + // 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; + + // 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) + { + 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(size_t i = 0; i < dwRootEntries; i++) + { + 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) + { + nError = ParseDirectoryFile(pRootHandler, pbRootFile, pbRootFile + cbRootFile, i); + CASC_FREE(pbRootFile); + } + } + } + + // 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); + return nError; +} |