mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-16 15:40:45 +01:00
816 lines
32 KiB
C++
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;
|
|
}
|