Files
TrinityCore/dep/CascLib/src/CascRootFile_Diablo3.cpp

816 lines
32 KiB
C++

/*****************************************************************************/
/* 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_MAX_ASSETS 70 // Maximum possible number of assets
#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_ASSET_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_ASSETIDX_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;
// In-memory structure of the named entry
typedef struct _DIABLO3_NAMED_ENTRY
{
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
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;
// 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 parsed directory data
struct DIABLO3_DIRECTORY
{
DIABLO3_DIRECTORY()
{
pbAssetEntries = pbAssetIdxEntries = pbNamedEntries = NULL;
dwAssetEntries = dwAssetIdxEntries = dwNamedEntries = 0;
dwNodeIndex = 0;
}
CASC_BLOB Data; // The complete copy of the directory data
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
};
//-----------------------------------------------------------------------------
// 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
};
#define DIABLO3_ASSET_COUNT (sizeof(Assets) / sizeof(Assets[0]))
//-----------------------------------------------------------------------------
// Handler definitions for Diablo3 root file
struct TDiabloRoot : public TFileTreeRoot
{
public:
TDiabloRoot() : TFileTreeRoot(0)
{
memset(RootFolders, 0, sizeof(RootFolders));
pbCoreTocData = NULL;
pFileIndices = NULL;
nFileIndices = 0;
// Map for searching a real file extension
memset(&PackagesMap, 0, sizeof(CASC_MAP));
// We have file names and return CKey as result of search
dwFeatures |= (CASC_FEATURE_FILE_NAMES | CASC_FEATURE_ROOT_CKEY);
}
~TDiabloRoot()
{
FreeLoadingStuff();
}
PDIABLO3_ASSET_INFO GetAssetInfo(DWORD dwAssetIndex)
{
if(dwAssetIndex < DIABLO3_ASSET_COUNT && Assets[dwAssetIndex].szDirectoryName != NULL)
return &Assets[dwAssetIndex];
return NULL;
}
char * FindPackageName(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 = CascStrPrintf(szFileName, _countof(szFileName), "%s\\%s", szAssetName, szPlainName);
return (char *)PackagesMap.FindString(szFileName, szFileName + nLength);
}
DWORD LoadFileToMemory(TCascStorage * hs, const char * szFileName, CASC_BLOB & FileData)
{
PCASC_CKEY_ENTRY pCKeyEntry;
DWORD dwErrCode = ERROR_FILE_NOT_FOUND;
// Try to find CKey for the file
pCKeyEntry = GetFile(hs, szFileName);
if(pCKeyEntry != NULL)
dwErrCode = LoadInternalFileToMemory(hs, pCKeyEntry, FileData);
return dwErrCode;
}
static DWORD CaptureDirectoryData(
DIABLO3_DIRECTORY & DirHeader,
CASC_BLOB & Directory)
{
LPBYTE pbDirectory;
LPBYTE pbDataEnd;
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
//
// Clone the input data
DirHeader.Data.MoveFrom(Directory);
pbDirectory = DirHeader.Data.pbData;
pbDataEnd = DirHeader.Data.End();
// Get the header signature
pbDirectory = CaptureInteger32(pbDirectory, pbDataEnd, &Signature);
if((pbDirectory == NULL) || (Signature != CASC_DIABLO3_ROOT_SIGNATURE && Signature != DIABLO3_SUBDIR_SIGNATURE))
return ERROR_BAD_FORMAT;
// 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 ERROR_BAD_FORMAT;
// Capture the array of DIABLO3_ASSET_ENTRY
pbDirectory = CaptureArrayAsByte<DIABLO3_ASSET_ENTRY>(pbDirectory, pbDataEnd, &DirHeader.pbAssetEntries, DirHeader.dwAssetEntries);
if(pbDirectory == NULL)
return ERROR_BAD_FORMAT;
// Capture the number of DIABLO3_ASSETIDX_ENTRY items
pbDirectory = CaptureInteger32(pbDirectory, pbDataEnd, &DirHeader.dwAssetIdxEntries);
if(pbDirectory == NULL)
return ERROR_BAD_FORMAT;
// Capture the array of DIABLO3_ASSETIDX_ENTRY
pbDirectory = CaptureArrayAsByte<DIABLO3_ASSETIDX_ENTRY>(pbDirectory, pbDataEnd, &DirHeader.pbAssetIdxEntries, DirHeader.dwAssetIdxEntries);
if(pbDirectory == NULL)
return ERROR_BAD_FORMAT;
}
// Capture the number of DIABLO3_NAMED_ENTRY array
pbDirectory = CaptureInteger32(pbDirectory, pbDataEnd, &DirHeader.dwNamedEntries);
if(pbDirectory == NULL)
return ERROR_BAD_FORMAT;
// Note: Do not capture the array here. We will do that later,
// when we will be parsing the directory
DirHeader.pbNamedEntries = pbDirectory;
return ERROR_SUCCESS;
}
LPBYTE CaptureCoreTocHeader(
PDIABLO3_CORE_TOC_HEADER * PtrHeader,
PDWORD PtrMaxIndex,
LPBYTE pbDataPtr,
LPBYTE pbDataEnd)
{
PDIABLO3_CORE_TOC_HEADER pTocHeader = (PDIABLO3_CORE_TOC_HEADER)pbDataPtr;
DWORD dwMaxFileIndex = 0;
// Check the space for header
if((pbDataPtr + sizeof(DIABLO3_CORE_TOC_HEADER)) > pbDataEnd)
return NULL;
pbDataPtr += sizeof(DIABLO3_CORE_TOC_HEADER);
// Verify all asset arrays
for(size_t i = 0; i < DIABLO3_MAX_ASSETS; i++)
{
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++)
{
if(pTocEntry->FileIndex >= dwMaxFileIndex)
dwMaxFileIndex = pTocEntry->FileIndex;
pTocEntry++;
}
}
// Give data and return
PtrMaxIndex[0] = dwMaxFileIndex;
PtrHeader[0] = pTocHeader;
return pbDataPtr;
}
LPBYTE CaptureNamedEntry(
LPBYTE pbDataPtr,
LPBYTE pbDataEnd,
PDIABLO3_NAMED_ENTRY pEntry)
{
// 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;
}
return NULL;
}
DWORD LoadDirectoryFile(TCascStorage * hs, DIABLO3_DIRECTORY & DirHeader, PCASC_CKEY_ENTRY pCKeyEntry)
{
CASC_BLOB Data;
DWORD dwErrCode;
// Load the n-th folder, if exists
dwErrCode = LoadInternalFileToMemory(hs, pCKeyEntry, Data);
if(dwErrCode == ERROR_SUCCESS && Data.cbData)
return CaptureDirectoryData(DirHeader, Data);
// If the folder is not there, ignore the error
return ERROR_SUCCESS;
}
bool CreateAssetFileName(
CASC_PATH<char> & PathBuffer,
DWORD FileIndex,
DWORD SubIndex)
{
PDIABLO3_CORE_TOC_ENTRY pTocEntry;
PDIABLO3_ASSET_INFO pAssetInfo;
LPCSTR szPackageName = NULL;
LPCSTR szPlainName;
LPCSTR szFormat;
char szBuffer[MAX_PATH];
// Find and check the entry
pTocEntry = pFileIndices + FileIndex;
if(pTocEntry->FileIndex == FileIndex)
{
// Retrieve the asset information
szPlainName = (LPCSTR)(pbCoreTocData + pTocEntry->NameOffset);
pAssetInfo = GetAssetInfo(pTocEntry->AssetIndex);
// Construct the file name, up to the extension. Don't include the '.'
szFormat = (SubIndex != CASC_INVALID_INDEX) ? "%s\\%04u" : "%s";
CascStrPrintf(szBuffer, _countof(szBuffer), 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(pAssetInfo != NULL)
{
// Retrieve the asset name
szPackageName = FindPackageName(pAssetInfo->szDirectoryName, szBuffer);
if(szPackageName != NULL)
{
PathBuffer.AppendString(szPackageName, false);
return true;
}
// Append the directory name
PathBuffer.AppendString(pAssetInfo->szDirectoryName, false);
}
else
{
// Append generic name "Asset##" and continue
PathBuffer.AppendString("Asset", false);
PathBuffer.AppendChar((char)('0' + (pTocEntry->AssetIndex / 10)));
PathBuffer.AppendChar((char)('0' + (pTocEntry->AssetIndex % 10)));
}
// Append the content of the buffer
PathBuffer.AppendString(szBuffer, true);
// If we have an extension, use it. Otherwise, supply "a##"
if(pAssetInfo != NULL && pAssetInfo->szExtension != NULL)
{
PathBuffer.AppendChar('.');
PathBuffer.AppendString(pAssetInfo->szExtension, false);
}
else
{
CascStrPrintf(szBuffer, _countof(szBuffer), ".a%02u", pTocEntry->AssetIndex);
PathBuffer.AppendString(szBuffer, false);
}
return true;
}
return false;
}
// Parse the asset entries
DWORD ParseAssetEntries(
TCascStorage * hs,
DIABLO3_DIRECTORY & Directory,
CASC_PATH<char> & PathBuffer)
{
PDIABLO3_ASSET_ENTRY pEntry = (PDIABLO3_ASSET_ENTRY)Directory.pbAssetEntries;
PCASC_CKEY_ENTRY pCKeyEntry;
size_t nSavePos = PathBuffer.Save();
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);
}
// Restore the path buffer position
PathBuffer.Restore(nSavePos);
}
}
}
return ERROR_SUCCESS;
}
DWORD ParseAssetAndIdxEntries(
TCascStorage * hs,
DIABLO3_DIRECTORY & Directory,
CASC_PATH<char> & PathBuffer)
{
PDIABLO3_ASSETIDX_ENTRY pEntry = (PDIABLO3_ASSETIDX_ENTRY)Directory.pbAssetIdxEntries;
PCASC_CKEY_ENTRY pCKeyEntry;
size_t nSavePos = PathBuffer.Save();
DWORD dwEntries = Directory.dwAssetIdxEntries;
// 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, 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);
}
// Restore the path buffer position
PathBuffer.Restore(nSavePos);
}
}
}
return ERROR_SUCCESS;
}
// Parse the named entries of all folders
DWORD ParseDirectory_Phase1(
TCascStorage * hs,
DIABLO3_DIRECTORY & Directory,
CASC_PATH<char> & PathBuffer,
bool bIsRootDirectory)
{
DIABLO3_NAMED_ENTRY NamedEntry;
size_t nFolderIndex = 0;
size_t nSavePos = PathBuffer.Save();
DWORD dwErrCode = ERROR_SUCCESS;
// Do nothing if there is no named headers
if(Directory.pbNamedEntries && Directory.dwNamedEntries)
{
PCASC_CKEY_ENTRY pCKeyEntry;
PCASC_FILE_NODE pFileNode;
LPBYTE pbDataPtr = Directory.pbNamedEntries;
LPBYTE pbDataEnd = Directory.Data.End();
DWORD dwNodeIndex;
// Parse all entries
while(pbDataPtr < pbDataEnd)
{
// Capture the named entry
pbDataPtr = CaptureNamedEntry(pbDataPtr, pbDataEnd, &NamedEntry);
if(pbDataPtr == NULL)
return ERROR_BAD_FORMAT;
// Append the path fragment to the total path
PathBuffer.AppendStringN(NamedEntry.szFileName, (NamedEntry.szFileEnd - NamedEntry.szFileName), true);
// 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);
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
pCKeyEntry->Flags |= CASC_CE_FOLDER_ENTRY;
pFileNode->Flags |= CFN_FLAG_FOLDER;
// Load the sub-directory file
dwErrCode = LoadDirectoryFile(hs, RootFolders[nFolderIndex], pCKeyEntry);
if(dwErrCode != ERROR_SUCCESS)
return dwErrCode;
// Parse the sub-directory file
dwErrCode = ParseDirectory_Phase1(hs, RootFolders[nFolderIndex], PathBuffer, false);
if(dwErrCode != ERROR_SUCCESS)
return dwErrCode;
// Also save the item pointer and increment the folder index
RootFolders[nFolderIndex].dwNodeIndex = dwNodeIndex;
nFolderIndex++;
}
// Restore the path pointer
PathBuffer.Restore(nSavePos);
}
}
}
return dwErrCode;
}
// Parse the nameless entries of all folders
int ParseDirectory_Phase2(TCascStorage * hs)
{
CASC_PATH<char> PathBuffer;
char szBuffer[MAX_PATH];
// Parse each root subdirectory
for(size_t i = 0; i < DIABLO3_MAX_ROOT_FOLDERS; i++)
{
// Is this root folder loaded?
if(RootFolders[i].Data.pbData != NULL)
{
// Retrieve the parent name
if(RootFolders[i].dwNodeIndex != 0)
{
FileTree.PathAt(szBuffer, _countof(szBuffer), RootFolders[i].dwNodeIndex);
PathBuffer.SetPathRoot(szBuffer);
}
// 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);
}
}
return ERROR_SUCCESS;
}
// Creates an array of DIABLO3_CORE_TOC_ENTRY entries indexed by FileIndex
// Used as lookup table when we have FileIndex and need Asset+PlainName
DWORD CreateMapOfFileIndices(TCascStorage * hs, const char * szFileName)
{
PDIABLO3_CORE_TOC_HEADER pTocHeader = NULL;
DWORD dwMaxFileIndex = 0;
DWORD dwErrCode;
// Load the entire file to memory
dwErrCode = LoadFileToMemory(hs, szFileName, CoreTocFile);
if(dwErrCode == ERROR_SUCCESS && CoreTocFile.cbData)
{
LPBYTE pbCoreTocPtr = CoreTocFile.pbData;
LPBYTE pbCoreTocEnd = CoreTocFile.End();
// Capture the header
if((pbCoreTocPtr = CaptureCoreTocHeader(&pTocHeader, &dwMaxFileIndex, pbCoreTocPtr, pbCoreTocEnd)) == NULL)
return ERROR_BAD_FORMAT;
// If there are no indices, return NULL
if(dwMaxFileIndex == 0)
return ERROR_SUCCESS;
// 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;
dwErrCode = ERROR_SUCCESS;
}
}
return dwErrCode;
}
// 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?
DWORD CreateMapOfRealNames(TCascStorage * hs, const char * szFileName)
{
DWORD Signature = 0;
DWORD NumberOfNames = 0;
DWORD dwErrCode;
// Load the entire file to memory
dwErrCode = LoadFileToMemory(hs, szFileName, PackagesDat);
if(dwErrCode == ERROR_SUCCESS && PackagesDat.cbData)
{
LPBYTE pbPackagesPtr = PackagesDat.pbData;
LPBYTE pbPackagesEnd = PackagesDat.End();
// 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;
}
}
}
return ERROR_SUCCESS;
}
DWORD Load(TCascStorage * hs, DIABLO3_DIRECTORY & RootDirectory)
{
CASC_PATH<char> PathBuffer;
DWORD dwErrCode;
// 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
dwErrCode = ParseDirectory_Phase1(hs, RootDirectory, PathBuffer, true);
if(dwErrCode == 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.
dwErrCode = CreateMapOfFileIndices(hs, "Base\\CoreTOC.dat");
if(dwErrCode == 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);
}
// Free all stuff that was used during loading of the ROOT file
FreeLoadingStuff();
}
return dwErrCode;
}
void FreeLoadingStuff()
{
// Free the package map
PackagesMap.Free();
// Free the array of file indices
CASC_FREE(pFileIndices);
}
// 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;
CASC_BLOB CoreTocFile;
LPBYTE pbCoreTocData;
size_t nFileIndices;
// Map for searching a real file extension
CASC_BLOB PackagesDat;
CASC_MAP PackagesMap;
};
//-----------------------------------------------------------------------------
// Public functions
DWORD RootHandler_CreateDiablo3(TCascStorage * hs, CASC_BLOB & RootFile)
{
TDiabloRoot * pRootHandler = NULL;
DIABLO3_DIRECTORY RootDirectory;
DWORD dwErrCode = ERROR_BAD_FORMAT;
// Verify the header of the ROOT file
if((dwErrCode = TDiabloRoot::CaptureDirectoryData(RootDirectory, RootFile)) == ERROR_SUCCESS)
{
// Allocate the root handler object
if((pRootHandler = new TDiabloRoot()) != NULL)
{
// Load the root directory. If load failed, we free the object
dwErrCode = pRootHandler->Load(hs, RootDirectory);
if(dwErrCode != ERROR_SUCCESS)
{
delete pRootHandler;
pRootHandler = NULL;
}
}
}
// Assign the root directory (or NULL) and return error
hs->pRootHandler = pRootHandler;
return dwErrCode;
}