mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-16 07:30:42 +01:00
@@ -1,10 +1,6 @@
|
||||
StormLib history
|
||||
|
||||
================
|
||||
|
||||
CascLib history
|
||||
===============
|
||||
|
||||
Version 1.00
|
||||
|
||||
|
||||
|
||||
- Created
|
||||
|
||||
@@ -99,7 +99,7 @@ static TCHAR * CheckForIndexDirectory(TCascStorage * hs, const TCHAR * szSubDir)
|
||||
return hs->szIndexPath;
|
||||
}
|
||||
|
||||
delete [] szIndexPath;
|
||||
CASC_FREE(szIndexPath);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -270,7 +270,7 @@ static int LoadInfoVariable(PQUERY_KEY pVarBlob, const char * szLineBegin, const
|
||||
|
||||
// Initialize the blob
|
||||
pVarBlob->pbData = CASC_ALLOC(BYTE, (szLinePtr - szLineBegin) + 1);
|
||||
pVarBlob->cbData = (size_t)(szLinePtr - szLineBegin);
|
||||
pVarBlob->cbData = (DWORD)(szLinePtr - szLineBegin);
|
||||
|
||||
// Check for success
|
||||
if(pVarBlob->pbData == NULL)
|
||||
@@ -826,7 +826,7 @@ static int LoadCdnBuildFile(TCascStorage * hs, PQUERY_KEY pFileBlob)
|
||||
//-----------------------------------------------------------------------------
|
||||
// Public functions
|
||||
|
||||
int LoadBuildConfiguration(TCascStorage * hs)
|
||||
int LoadBuildInfo(TCascStorage * hs)
|
||||
{
|
||||
QUERY_KEY InfoFile = {NULL, 0};
|
||||
QUERY_KEY FileData = {NULL, 0};
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
|
||||
#include "CascPort.h"
|
||||
#include "common/Common.h"
|
||||
#include "common/Map.h"
|
||||
#include "common/FileStream.h"
|
||||
#include "common/ListFile.h"
|
||||
#include "common/Map.h"
|
||||
|
||||
// Headers from LibTomCrypt
|
||||
#include "libtomcrypt/src/headers/tomcrypt.h"
|
||||
@@ -48,6 +48,20 @@
|
||||
|
||||
#define CASC_SEARCH_HAVE_NAME 0x0001 // Indicated that previous search found a name
|
||||
|
||||
#define BLTE_HEADER_SIGNATURE 0x45544C42 // 'BLTE' header in the data files
|
||||
#define BLTE_HEADER_DELTA 0x1E // Distance of BLTE header from begin of the header area
|
||||
#define MAX_HEADER_AREA_SIZE 0x2A // Length of the file header area
|
||||
|
||||
// File header area in the data.xxx:
|
||||
// BYTE HeaderHash[MD5_HASH_SIZE]; // MD5 of the frame array
|
||||
// DWORD dwFileSize; // Size of the file (see comment before CascGetFileSize for details)
|
||||
// BYTE SomeSize[4]; // Some size (big endian)
|
||||
// BYTE Padding[6]; // Padding (?)
|
||||
// DWORD dwSignature; // Must be "BLTE"
|
||||
// BYTE HeaderSizeAsBytes[4]; // Header size in bytes (big endian)
|
||||
// BYTE MustBe0F; // Must be 0x0F. Optional, only if HeaderSizeAsBytes != 0
|
||||
// BYTE FrameCount[3]; // Frame count (big endian). Optional, only if HeaderSizeAsBytes != 0
|
||||
|
||||
// Prevent problems with CRT "min" and "max" functions,
|
||||
// as they are not defined on all platforms
|
||||
#define CASCLIB_MIN(a, b) ((a < b) ? a : b)
|
||||
@@ -57,16 +71,46 @@
|
||||
#define CASC_PACKAGE_BUFFER 0x1000
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Structures
|
||||
// On-disk structures
|
||||
|
||||
typedef struct _FILE_LOCALE_BLOCK
|
||||
{
|
||||
DWORD NumberOfFiles; // Number of entries
|
||||
DWORD Flags;
|
||||
DWORD Locales; // File locale mask (CASC_LOCALE_XXX)
|
||||
|
||||
// Followed by a block of 32-bit integers (count: NumberOfFiles)
|
||||
// Followed by the MD5 and file name hash (count: NumberOfFiles)
|
||||
|
||||
} FILE_LOCALE_BLOCK, *PFILE_LOCALE_BLOCK;
|
||||
|
||||
typedef struct _FILE_ROOT_ENTRY
|
||||
{
|
||||
DWORD EncodingKey[4]; // MD5 of the file
|
||||
ULONGLONG FileNameHash; // Jenkins hash of the file name
|
||||
|
||||
} FILE_ROOT_ENTRY, *PFILE_ROOT_ENTRY;
|
||||
|
||||
typedef struct _ROOT_BLOCK_INFO
|
||||
{
|
||||
PFILE_LOCALE_BLOCK pLocaleBlockHdr; // Pointer to the locale block
|
||||
PDWORD pInt32Array; // Pointer to the array of 32-bit integers
|
||||
PFILE_ROOT_ENTRY pRootEntries;
|
||||
|
||||
} ROOT_BLOCK_INFO, *PROOT_BLOCK_INFO;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// In-memory structures
|
||||
|
||||
class TMndxFindResult;
|
||||
struct TFileStream;
|
||||
struct _MAR_FILE;
|
||||
|
||||
typedef struct _CASC_INDEX_ENTRY
|
||||
{
|
||||
BYTE IndexKey[CASC_FILE_KEY_SIZE]; // The first 9 bytes of the encoding key
|
||||
BYTE FileOffset[5]; //
|
||||
BYTE FileSize[4];
|
||||
BYTE FileOffsetBE[5]; // Index of data file and offset within (big endian).
|
||||
BYTE FileSizeLE[4]; // Size occupied in the storage file (data.###). See comment before CascGetFileSize for details
|
||||
} CASC_INDEX_ENTRY, *PCASC_INDEX_ENTRY;
|
||||
|
||||
typedef struct _CASC_MAPPING_TABLE
|
||||
@@ -113,7 +157,7 @@ typedef struct _CASC_ENCODING_HEADER
|
||||
typedef struct _CASC_ENCODING_ENTRY
|
||||
{
|
||||
USHORT KeyCount; // Number of subitems
|
||||
BYTE FileSizeBytes[4]; // File size as bytes (big-endian)
|
||||
BYTE FileSizeBE[4]; // Compressed file size (header area + frame headers + compressed frames), in bytes
|
||||
BYTE EncodingKey[MD5_HASH_SIZE]; // File encoding key
|
||||
|
||||
// Followed by the index keys
|
||||
@@ -132,29 +176,35 @@ typedef struct _CASC_ROOT_LOCALE_BLOCK
|
||||
|
||||
} CASC_ROOT_LOCALE_BLOCK, *PCASC_ROOT_LOCALE_BLOCK;
|
||||
|
||||
typedef struct _CASC_ROOT_ENTRY
|
||||
{
|
||||
BYTE EncodingKey[MD5_HASH_SIZE]; // File encoding key (MD5)
|
||||
ULONGLONG FileNameHash; // Jenkins hash of the file name
|
||||
DWORD Locales; // File locales (see CASC_LOCALE_XXX)
|
||||
DWORD Flags; // File flags
|
||||
|
||||
} CASC_ROOT_ENTRY, *PCASC_ROOT_ENTRY;
|
||||
|
||||
typedef struct _CASC_ROOT_KEY_INFO
|
||||
{
|
||||
BYTE EncodingKey[MD5_HASH_SIZE]; // Encoding key for the file, obtained from root info
|
||||
ULONGLONG FileSize; // Size of the file, in bytes
|
||||
BYTE Flags; // File flags
|
||||
} CASC_ROOT_KEY_INFO, *PCASC_ROOT_KEY_INFO;
|
||||
|
||||
typedef struct _CASC_MNDX_ENTRY
|
||||
// Root file entry for CASC storages with MNDX root file (Heroes of the Storm)
|
||||
// Corresponds to the in-file structure
|
||||
typedef struct _CASC_ROOT_ENTRY_MNDX
|
||||
{
|
||||
DWORD Flags; // High 8 bits: Flags, low 24 bits: package index
|
||||
BYTE EncodingKey[MD5_HASH_SIZE]; // Encoding key for the file
|
||||
DWORD FileSize; // Size of the file, in bytes
|
||||
DWORD FileSize; // Uncompressed file size, in bytes
|
||||
|
||||
} CASC_MNDX_ENTRY, *PCASC_MNDX_ENTRY;
|
||||
} CASC_ROOT_ENTRY_MNDX, *PCASC_ROOT_ENTRY_MNDX;
|
||||
|
||||
// Root file entry for CASC storages without MNDX root file (World of Warcraft 6.0+)
|
||||
// Does not match to the in-file structure of the root entry
|
||||
typedef struct _CASC_ROOT_ENTRY
|
||||
{
|
||||
ULONGLONG FileNameHash; // Jenkins hash of the file name
|
||||
DWORD SumValue; // Sum value
|
||||
DWORD Locales; // Locale flags of the file
|
||||
DWORD EncodingKey[4]; // File encoding key (MD5)
|
||||
|
||||
} CASC_ROOT_ENTRY, *PCASC_ROOT_ENTRY;
|
||||
|
||||
// Definition of the hash table for CASC root items
|
||||
typedef struct _CASC_ROOT_HASH_TABLE
|
||||
{
|
||||
PCASC_ROOT_ENTRY TablePtr; // Pointer to the CASC root table
|
||||
DWORD TableSize; // Total size of the root table
|
||||
DWORD ItemCount; // Number of items currently in the table
|
||||
|
||||
} CASC_ROOT_HASH_TABLE, *PCASC_ROOT_HASH_TABLE;
|
||||
|
||||
typedef struct _CASC_MNDX_INFO
|
||||
{
|
||||
@@ -174,8 +224,8 @@ typedef struct _CASC_MNDX_INFO
|
||||
struct _MAR_FILE * pMarFile1; // File name list for the packages
|
||||
struct _MAR_FILE * pMarFile2; // File name list for names stripped of package names
|
||||
struct _MAR_FILE * pMarFile3; // File name list for complete names
|
||||
PCASC_MNDX_ENTRY pMndxEntries;
|
||||
PCASC_MNDX_ENTRY * ppValidEntries;
|
||||
PCASC_ROOT_ENTRY_MNDX pMndxEntries;
|
||||
PCASC_ROOT_ENTRY_MNDX * ppValidEntries;
|
||||
|
||||
} CASC_MNDX_INFO, *PCASC_MNDX_INFO;
|
||||
|
||||
@@ -208,6 +258,7 @@ typedef struct _TCascStorage
|
||||
DWORD dwRefCount; // Number of references
|
||||
DWORD dwGameInfo; // Game type
|
||||
DWORD dwBuildNumber; // Game build number
|
||||
DWORD dwFileBeginDelta; // This is number of bytes to shift back from archive offset (from index entry) to actual begin of file data
|
||||
|
||||
QUERY_KEY CdnConfigKey;
|
||||
QUERY_KEY CdnBuildKey;
|
||||
@@ -239,9 +290,7 @@ typedef struct _TCascStorage
|
||||
PCASC_ENCODING_ENTRY * ppEncodingEntries; // Map of encoding entries
|
||||
size_t nEncodingEntries;
|
||||
|
||||
PCASC_ROOT_ENTRY * ppRootEntries; // Sorted array of root entries
|
||||
PCASC_ROOT_ENTRY pRootEntries; // Linear array of root entries
|
||||
size_t nRootEntries; // Number of root entries
|
||||
CASC_ROOT_HASH_TABLE RootTable; // Hash table for the root entries
|
||||
|
||||
PCASC_MNDX_INFO pMndxInfo; // Used for storages which have MNDX/MAR file
|
||||
PCASC_PACKAGES pPackages; // Linear list of present packages
|
||||
@@ -258,9 +307,11 @@ typedef struct _TCascFile
|
||||
|
||||
DWORD ArchiveIndex; // Index of the archive (data.###)
|
||||
DWORD HeaderOffset; // Offset of the BLTE header, relative to the begin of the archive
|
||||
DWORD HeaderSize; // Length of the BLTE header
|
||||
DWORD FramesOffset; // Offset of the frame data, relative to the begin of the archive
|
||||
DWORD CompressedSize; // Compressed size of the file (in bytes)
|
||||
DWORD FileSize; // Size of file, in bytes
|
||||
BYTE FrameArrayHash[MD5_HASH_SIZE]; // MD5 hash of the frame array
|
||||
|
||||
PCASC_FILE_FRAME pFrames; // Array of file frames
|
||||
DWORD FrameCount; // Number of the file frames
|
||||
@@ -270,9 +321,15 @@ typedef struct _TCascFile
|
||||
DWORD CacheStart; // Starting offset in the cache
|
||||
DWORD CacheEnd; // Ending offset in the cache
|
||||
|
||||
} TCascFile;
|
||||
#ifdef CASCLIB_TEST // Extra fields for analyzing the file size problem
|
||||
DWORD FileSize_RootEntry; // File size, from the root entry
|
||||
DWORD FileSize_EncEntry; // File size, from the encoding entry
|
||||
DWORD FileSize_IdxEntry; // File size, from the index entry
|
||||
DWORD FileSize_HdrArea; // File size, as stated in the file header area
|
||||
DWORD FileSize_FrameSum; // File size as sum of frame sizes
|
||||
#endif
|
||||
|
||||
class TMndxFindResult;
|
||||
} TCascFile;
|
||||
|
||||
typedef struct _TCascSearch
|
||||
{
|
||||
@@ -284,8 +341,7 @@ typedef struct _TCascSearch
|
||||
char * szMask;
|
||||
char szFileName[MAX_PATH]; // Buffer for the file name
|
||||
char szNormName[MAX_PATH]; // Buffer for normalized file name
|
||||
ULONGLONG FileNameHash; // Name hash being searched right now
|
||||
size_t RootIndex; // Root index of the previously found item
|
||||
DWORD RootIndex; // Root index of the previously found item
|
||||
DWORD dwState; // Pointer to the state (0 = listfile, 1 = nameless, 2 = done)
|
||||
BYTE BitArray[1]; // Bit array of already-reported files
|
||||
|
||||
@@ -305,17 +361,10 @@ typedef struct _TCascSearch
|
||||
//
|
||||
|
||||
#if defined(_MSC_VER) && defined(_DEBUG)
|
||||
/*
|
||||
void * DbgRealloc(void * ptr, size_t nSize);
|
||||
|
||||
#define CASC_REALLOC(type, ptr, count) (type *)DbgRealloc(ptr, ((count) * sizeof(type)))
|
||||
#define CASC_REALLOC(type, ptr, count) (type *)HeapReAlloc(GetProcessHeap(), 0, ptr, ((count) * sizeof(type)))
|
||||
#define CASC_ALLOC(type, count) (type *)HeapAlloc(GetProcessHeap(), 0, ((count) * sizeof(type)))
|
||||
#define CASC_FREE(ptr) HeapFree(GetProcessHeap(), 0, ptr)
|
||||
*/
|
||||
|
||||
#define CASC_REALLOC(type, ptr, count) (type *)realloc(ptr, (count) * sizeof(type))
|
||||
#define CASC_ALLOC(type, count) (type *)malloc((count) * sizeof(type))
|
||||
#define CASC_FREE(ptr) free(ptr)
|
||||
|
||||
#else
|
||||
|
||||
@@ -336,7 +385,7 @@ ULONGLONG ConvertBytesToInteger_5(LPBYTE ValueAsBytes);
|
||||
//-----------------------------------------------------------------------------
|
||||
// Build configuration reading
|
||||
|
||||
int LoadBuildConfiguration(TCascStorage * hs);
|
||||
int LoadBuildInfo(TCascStorage * hs);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Internal file functions
|
||||
@@ -344,31 +393,23 @@ int LoadBuildConfiguration(TCascStorage * hs);
|
||||
TCascStorage * IsValidStorageHandle(HANDLE hStorage);
|
||||
TCascFile * IsValidFileHandle(HANDLE hFile);
|
||||
|
||||
PCASC_ROOT_ENTRY FindFirstRootEntry(TCascStorage * hs, const char * szFileName, size_t * PtrIndex);
|
||||
PCASC_ROOT_ENTRY FindRootEntry(TCascStorage * hs, const char * szFileName, DWORD * PtrTableIndex);
|
||||
PCASC_ENCODING_ENTRY FindEncodingEntry(TCascStorage * hs, PQUERY_KEY pEncodingKey, size_t * PtrIndex);
|
||||
PCASC_INDEX_ENTRY FindIndexEntry(TCascStorage * hs, PQUERY_KEY pIndexKey);
|
||||
|
||||
int CascDecompress(void * pvOutBuffer, PDWORD pcbOutBuffer, void * pvInBuffer, DWORD cbInBuffer);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Dump data
|
||||
// Dumping CASC data structures
|
||||
|
||||
#ifdef _DEBUG
|
||||
void CascDumpSparseArray(const char * szFileName, void * pvSparseArray);
|
||||
void CascDumpNameFragTable(const char * szFileName, void * pvMarFile);
|
||||
void CascDumpFileNames(const char * szFileName, void * pvMarFile);
|
||||
void CascDumpMndxRoot(const char * szFileName, PCASC_MNDX_INFO pMndxInfo);
|
||||
void CascDumpIndexEntries(const char * szFileName, TCascStorage * hs);
|
||||
void CascDumpStorage(const char * szFileName, TCascStorage * hs, const TCHAR * szListFile);
|
||||
void CascDumpMndxRoot(const char * szFileName, PCASC_MNDX_INFO pMndxInfo);
|
||||
void CascDumpRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile, const char * szFormat, const TCHAR * szListFile, int nDumpLevel);
|
||||
void CascDumpFile(const char * szFileName, HANDLE hFile);
|
||||
#else // _DEBUG
|
||||
#define CascDumpSparseArray(n,a) /* */
|
||||
#define CascDumpNameFragTable(n, m) /* */
|
||||
#define CascDumpFileNames(n, m) /* */
|
||||
#define CascDumpMndxRoot(n,i) /* */
|
||||
#define CascDumpIndexEntries(n,h) /* */
|
||||
#define CascDumpStorage(n,h) /* */
|
||||
#define CascDumpFile(n,h) /* */
|
||||
#endif // _DEBUG
|
||||
|
||||
#endif // __CASCCOMMON_H__
|
||||
|
||||
@@ -16,24 +16,19 @@
|
||||
#ifdef _DEBUG // The entire file is only valid for debug purposes
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Local functions
|
||||
// Forward definitions
|
||||
|
||||
static char * StringFromIndexKey(LPBYTE md5, char * szBuffer)
|
||||
{
|
||||
return StringFromBinary(md5, 9, szBuffer);
|
||||
}
|
||||
LPBYTE VerifyLocaleBlock(PROOT_BLOCK_INFO pBlockInfo, LPBYTE pbFilePointer, LPBYTE pbFileEnd);
|
||||
|
||||
static char * StringFromMD5(LPBYTE md5, char * szBuffer)
|
||||
{
|
||||
return StringFromBinary(md5, MD5_HASH_SIZE, szBuffer);
|
||||
}
|
||||
//-----------------------------------------------------------------------------
|
||||
// Sort compare functions
|
||||
|
||||
static int CompareIndexEntries_FilePos(const void *, const void * pvIndexEntry1, const void * pvIndexEntry2)
|
||||
{
|
||||
PCASC_INDEX_ENTRY pIndexEntry1 = (PCASC_INDEX_ENTRY)pvIndexEntry1;
|
||||
PCASC_INDEX_ENTRY pIndexEntry2 = (PCASC_INDEX_ENTRY)pvIndexEntry2;
|
||||
ULONGLONG FileOffset1 = ConvertBytesToInteger_5(pIndexEntry1->FileOffset);
|
||||
ULONGLONG FileOffset2 = ConvertBytesToInteger_5(pIndexEntry2->FileOffset);
|
||||
ULONGLONG FileOffset1 = ConvertBytesToInteger_5(pIndexEntry1->FileOffsetBE);
|
||||
ULONGLONG FileOffset2 = ConvertBytesToInteger_5(pIndexEntry2->FileOffsetBE);
|
||||
DWORD ArchIndex1 = (DWORD)(FileOffset1 >> 0x1E);
|
||||
DWORD ArchIndex2 = (DWORD)(FileOffset2 >> 0x1E);
|
||||
|
||||
@@ -54,98 +49,121 @@ static int CompareIndexEntries_FilePos(const void *, const void * pvIndexEntry1,
|
||||
return 0;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Local functions
|
||||
|
||||
static char ** CreateFileNameArray(TCascStorage * hs, const TCHAR * szListFile)
|
||||
static char * StringFromMD5(LPBYTE md5, char * szBuffer)
|
||||
{
|
||||
PCASC_ROOT_ENTRY pRootEntry;
|
||||
char ** FileNameArray = NULL;
|
||||
void * pvListFile;
|
||||
size_t nRootIndex;
|
||||
char szFileName1[MAX_PATH+1];
|
||||
char szFileName2[MAX_PATH+1];
|
||||
|
||||
// Open the listfile stream and initialize the listfile cache
|
||||
pvListFile = ListFile_OpenExternal(szListFile);
|
||||
if(pvListFile != NULL)
|
||||
{
|
||||
// Allocate the array of file names
|
||||
FileNameArray = CASC_ALLOC(char*, hs->nRootEntries);
|
||||
if(FileNameArray != NULL)
|
||||
{
|
||||
// Zero the name array
|
||||
memset(FileNameArray, 0, hs->nRootEntries * sizeof(char *));
|
||||
|
||||
// Perform search
|
||||
while(ListFile_GetNext(pvListFile, "*", szFileName1, MAX_PATH))
|
||||
{
|
||||
// Create normalized name
|
||||
strcpy(szFileName2, szFileName1);
|
||||
NormalizeFileName_UpperBkSlash(szFileName2);
|
||||
|
||||
// Try to find the root entry
|
||||
pRootEntry = FindFirstRootEntry(hs, szFileName2, &nRootIndex);
|
||||
if(pRootEntry != NULL)
|
||||
{
|
||||
assert(nRootIndex < hs->nRootEntries);
|
||||
if(FileNameArray[nRootIndex] == NULL)
|
||||
FileNameArray[nRootIndex] = NewStr(szFileName1, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close the listfile cache
|
||||
ListFile_Free(pvListFile);
|
||||
}
|
||||
|
||||
return FileNameArray;
|
||||
return StringFromBinary(md5, MD5_HASH_SIZE, szBuffer);
|
||||
}
|
||||
|
||||
static void FreeFileNameArray(TCascStorage * hs, char ** FileNameArray)
|
||||
static char * FormatFileName(const char * szFormat, TCascStorage * hs)
|
||||
{
|
||||
if(FileNameArray != NULL)
|
||||
char * szFileName;
|
||||
char * szSrc;
|
||||
char * szTrg;
|
||||
|
||||
// Create copy of the file name
|
||||
szFileName = szSrc = szTrg = NewStr(szFormat, 0);
|
||||
if(szFileName != NULL)
|
||||
{
|
||||
// Free all sub-entries
|
||||
for(size_t i = 0; i < hs->nRootEntries; i++)
|
||||
// Format the file name
|
||||
while(szSrc[0] != 0)
|
||||
{
|
||||
if(FileNameArray[i] != NULL)
|
||||
CASC_FREE(FileNameArray[i]);
|
||||
if(szSrc[0] == '%')
|
||||
{
|
||||
// Replace "%build%" with a build number
|
||||
if(!strncmp(szSrc, "%build%", 7))
|
||||
{
|
||||
szTrg += sprintf(szTrg, "%u", hs->dwBuildNumber);
|
||||
szSrc += 7;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Just copy the character
|
||||
*szTrg++ = *szSrc++;
|
||||
}
|
||||
|
||||
// Free the array itself
|
||||
CASC_FREE(FileNameArray);
|
||||
// Terminate the target file name
|
||||
szTrg[0] = 0;
|
||||
}
|
||||
|
||||
return szFileName;
|
||||
}
|
||||
|
||||
FILE * CreateDumpFile(const char * szFormat, TCascStorage * hs)
|
||||
{
|
||||
FILE * fp = NULL;
|
||||
char * szFileName;
|
||||
|
||||
// Validate the storage handle
|
||||
if(hs != NULL)
|
||||
{
|
||||
// Format the real file name
|
||||
szFileName = FormatFileName(szFormat, hs);
|
||||
if(szFileName != NULL)
|
||||
{
|
||||
// Create the dump file
|
||||
fp = fopen(szFileName, "wt");
|
||||
CASC_FREE(szFileName);
|
||||
}
|
||||
}
|
||||
|
||||
return fp;
|
||||
}
|
||||
|
||||
static void DumpIndexKey(
|
||||
FILE * fp,
|
||||
TCascStorage * hs,
|
||||
LPBYTE pbEncodingKey,
|
||||
PCASC_INDEX_ENTRY * ppIndexEntries)
|
||||
LPBYTE pbIndexKey,
|
||||
int nDumpLevel)
|
||||
{
|
||||
PCASC_INDEX_ENTRY pIndexEntry;
|
||||
TCascFile * hf;
|
||||
QUERY_KEY QueryKey;
|
||||
size_t EntryIndex = 0;
|
||||
char szMd5[MD5_STRING_SIZE];
|
||||
HANDLE hFile;
|
||||
BYTE HeaderArea[MAX_HEADER_AREA_SIZE];
|
||||
char szBuffer[0x20];
|
||||
|
||||
QueryKey.pbData = pbEncodingKey;
|
||||
QueryKey.pbData = pbIndexKey;
|
||||
QueryKey.cbData = MD5_HASH_SIZE;
|
||||
pIndexEntry = FindIndexEntry(hs, &QueryKey);
|
||||
if(pIndexEntry != NULL)
|
||||
{
|
||||
ULONGLONG FileOffset = ConvertBytesToInteger_5(pIndexEntry->FileOffset);
|
||||
ULONGLONG FileOffset = ConvertBytesToInteger_5(pIndexEntry->FileOffsetBE);
|
||||
DWORD ArchIndex = (DWORD)(FileOffset >> 0x1E);
|
||||
DWORD FileSize = *(PDWORD)pIndexEntry->FileSize;
|
||||
|
||||
// Mark the index entry as dumped
|
||||
ppIndexEntries[EntryIndex] = NULL;
|
||||
DWORD FileSize = ConvertBytesToInteger_4_LE(pIndexEntry->FileSizeLE);
|
||||
|
||||
// Mask the file offset
|
||||
FileOffset &= 0x3FFFFFFF;
|
||||
fprintf(fp, " Index: %s, ArchIdx: %02x FileOffset %08x FileSize: %lx\n",
|
||||
StringFromIndexKey(pIndexEntry->IndexKey, szMd5),
|
||||
fprintf(fp, " data.%03u at 0x%08x (0x%lx bytes)\n",
|
||||
ArchIndex,
|
||||
(DWORD)FileOffset,
|
||||
FileSize);
|
||||
|
||||
if(nDumpLevel > 2)
|
||||
{
|
||||
QueryKey.pbData = pIndexEntry->IndexKey;
|
||||
QueryKey.cbData = MD5_HASH_SIZE;
|
||||
if(CascOpenFileByIndexKey((HANDLE)hs, &QueryKey, 0, &hFile))
|
||||
{
|
||||
// Make sure that the data file is open and frame header loaded
|
||||
CascGetFileSize(hFile, NULL);
|
||||
hf = IsValidFileHandle(hFile);
|
||||
assert(hf->pStream != NULL);
|
||||
|
||||
// Read the header area
|
||||
FileOffset = hf->HeaderOffset - BLTE_HEADER_DELTA;
|
||||
FileStream_Read(hf->pStream, &FileOffset, HeaderArea, sizeof(HeaderArea));
|
||||
CascCloseFile(hFile);
|
||||
|
||||
// Dump the header area
|
||||
fprintf(fp, " FileSize: %X Rest: %s\n",
|
||||
ConvertBytesToInteger_4_LE(&HeaderArea[0x10]),
|
||||
StringFromBinary(&HeaderArea[0x14], 10, szBuffer));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -157,122 +175,31 @@ static void DumpEncodingEntry(
|
||||
FILE * fp,
|
||||
TCascStorage * hs,
|
||||
PCASC_ENCODING_ENTRY pEncodingEntry,
|
||||
PCASC_INDEX_ENTRY * ppIndexEntries)
|
||||
int nDumpLevel)
|
||||
{
|
||||
LPBYTE pbEncodingKey;
|
||||
LPBYTE pbIndexKey;
|
||||
char szMd5[MD5_STRING_SIZE];
|
||||
|
||||
fprintf(fp, " Encoding Key: %s Key Count: %u Size: %lx\n",
|
||||
StringFromMD5(pEncodingEntry->EncodingKey, szMd5),
|
||||
pEncodingEntry->KeyCount,
|
||||
ConvertBytesToInteger_4(pEncodingEntry->FileSizeBytes));
|
||||
// If the encoding key exists
|
||||
if(pEncodingEntry != NULL)
|
||||
{
|
||||
fprintf(fp, " Size %lx Key Count: %u\n",
|
||||
ConvertBytesToInteger_4(pEncodingEntry->FileSizeBE),
|
||||
pEncodingEntry->KeyCount);
|
||||
|
||||
// If there is a file key
|
||||
if(pEncodingEntry->KeyCount != 0)
|
||||
{
|
||||
// Get the first encoding key
|
||||
pbEncodingKey = pEncodingEntry->EncodingKey + MD5_HASH_SIZE;
|
||||
|
||||
// Dump all encoding keys
|
||||
// Dump all index keys
|
||||
pbIndexKey = pEncodingEntry->EncodingKey + MD5_HASH_SIZE;
|
||||
for(DWORD j = 0; j < pEncodingEntry->KeyCount; j++)
|
||||
{
|
||||
fprintf(fp, " Index Key: %s\n", StringFromMD5(pbEncodingKey, szMd5));
|
||||
DumpIndexKey(fp, hs, pbEncodingKey, ppIndexEntries);
|
||||
pbEncodingKey += MD5_HASH_SIZE;
|
||||
fprintf(fp, " %s\n", StringFromMD5(pbIndexKey, szMd5));
|
||||
DumpIndexKey(fp, hs, pbIndexKey, nDumpLevel);
|
||||
pbIndexKey += MD5_HASH_SIZE;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(fp, " ZERO FILE KEYS\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void DumpEncodingEntry(
|
||||
FILE * fp,
|
||||
TCascStorage * hs,
|
||||
PCASC_ROOT_ENTRY pRootEntry,
|
||||
PCASC_ENCODING_ENTRY * ppEncodingKeys,
|
||||
PCASC_INDEX_ENTRY * ppIndexEntries)
|
||||
{
|
||||
PCASC_ENCODING_ENTRY pEncodingKey;
|
||||
QUERY_KEY QueryKey;
|
||||
size_t EntryIndex = 0;
|
||||
|
||||
// Find the encoding key
|
||||
QueryKey.pbData = pRootEntry->EncodingKey;
|
||||
QueryKey.cbData = MD5_HASH_SIZE;
|
||||
pEncodingKey = FindEncodingEntry(hs, &QueryKey, &EntryIndex);
|
||||
if(pEncodingKey == NULL)
|
||||
{
|
||||
fprintf(fp, " NO ENCODING KEY\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the file key, clear the encoding key
|
||||
ppEncodingKeys[EntryIndex] = NULL;
|
||||
DumpEncodingEntry(fp, hs, pEncodingKey, ppIndexEntries);
|
||||
}
|
||||
|
||||
static void DumpRootEntries(FILE * fp, TCascStorage * hs, char ** FileNameArray)
|
||||
{
|
||||
PCASC_ENCODING_ENTRY * ppEncodingEntries; // Array of encoding entries
|
||||
PCASC_INDEX_ENTRY * ppIndexEntries; // Complete list of key entries for all files
|
||||
const char * szFileName = NULL;
|
||||
ULONGLONG PrevNameHash = (ULONGLONG)-1;
|
||||
char szMd5[MD5_STRING_SIZE];
|
||||
|
||||
// Create copy of the encoding keys and file keys
|
||||
ppEncodingEntries = CASC_ALLOC(PCASC_ENCODING_ENTRY, hs->nEncodingEntries);
|
||||
ppIndexEntries = CASC_ALLOC(PCASC_INDEX_ENTRY, hs->pIndexEntryMap->ItemCount);
|
||||
if(ppEncodingEntries && ppIndexEntries)
|
||||
{
|
||||
// Copy all pointers
|
||||
memcpy(ppEncodingEntries, hs->ppEncodingEntries, hs->nEncodingEntries * sizeof(PCASC_ENCODING_ENTRY));
|
||||
Map_EnumObjects(hs->pIndexEntryMap, (void **)ppIndexEntries);
|
||||
|
||||
// Parse all entries
|
||||
for(size_t i = 0; i < hs->nRootEntries; i++)
|
||||
{
|
||||
PCASC_ROOT_ENTRY pRootEntry = hs->ppRootEntries[i];
|
||||
const char * szDuplicate = "";
|
||||
|
||||
// Check duplicates
|
||||
if(pRootEntry->FileNameHash != PrevNameHash)
|
||||
szFileName = FileNameArray[i];
|
||||
else
|
||||
szDuplicate = "(DUPLICATE) ";
|
||||
|
||||
// Dump the root entry
|
||||
fprintf(fp, "NameHash: %016llx Locales: %08lx MD5: %s %sFileName: %s\n",
|
||||
pRootEntry->FileNameHash,
|
||||
pRootEntry->Locales,
|
||||
StringFromMD5(pRootEntry->EncodingKey, szMd5),
|
||||
szDuplicate,
|
||||
szFileName);
|
||||
|
||||
DumpEncodingEntry(fp, hs, pRootEntry, ppEncodingEntries, ppIndexEntries);
|
||||
PrevNameHash = pRootEntry->FileNameHash;
|
||||
fprintf(fp, "\n");
|
||||
}
|
||||
|
||||
// Dump all orphaned encoding keys
|
||||
for(size_t i = 0; i < hs->nEncodingEntries; i++)
|
||||
{
|
||||
if(ppEncodingEntries[i] != NULL)
|
||||
{
|
||||
fprintf(fp, "[NO ROOT KEY]\n");
|
||||
|
||||
DumpEncodingEntry(fp, hs, ppEncodingEntries[i], ppIndexEntries);
|
||||
ppEncodingEntries[i] = NULL;
|
||||
|
||||
fprintf(fp, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
CASC_FREE(ppIndexEntries);
|
||||
CASC_FREE(ppEncodingEntries);
|
||||
fprintf(fp, " NO ENCODING KEYS\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,7 +325,7 @@ void CascDumpFileNames(const char * szFileName, void * pvMarFile)
|
||||
|
||||
void CascDumpMndxRoot(const char * szFileName, PCASC_MNDX_INFO pMndxInfo)
|
||||
{
|
||||
PCASC_MNDX_ENTRY pMndxEntry;
|
||||
PCASC_ROOT_ENTRY_MNDX pRootEntry;
|
||||
FILE * fp;
|
||||
char szMd5[MD5_STRING_SIZE];
|
||||
|
||||
@@ -409,12 +336,12 @@ void CascDumpMndxRoot(const char * szFileName, PCASC_MNDX_INFO pMndxInfo)
|
||||
fprintf(fp, "Indx Fl+Asset EncodingKey FileSize\n==== ======== ================================ ========\n");
|
||||
for(DWORD i = 0; i < pMndxInfo->MndxEntriesValid; i++)
|
||||
{
|
||||
pMndxEntry = pMndxInfo->ppValidEntries[i];
|
||||
pRootEntry = pMndxInfo->ppValidEntries[i];
|
||||
|
||||
fprintf(fp, "%04X %08X %s %08X\n", i,
|
||||
pMndxEntry->Flags,
|
||||
StringFromMD5(pMndxEntry->EncodingKey, szMd5),
|
||||
pMndxEntry->FileSize);
|
||||
pRootEntry->Flags,
|
||||
StringFromMD5(pRootEntry->EncodingKey, szMd5),
|
||||
pRootEntry->FileSize);
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
@@ -446,11 +373,11 @@ void CascDumpIndexEntries(const char * szFileName, TCascStorage * hs)
|
||||
for(size_t i = 0; i < nIndexEntries; i++)
|
||||
{
|
||||
PCASC_INDEX_ENTRY pIndexEntry = ppIndexEntries[i];
|
||||
ULONGLONG ArchOffset = ConvertBytesToInteger_5(pIndexEntry->FileOffset);
|
||||
ULONGLONG ArchOffset = ConvertBytesToInteger_5(pIndexEntry->FileOffsetBE);
|
||||
DWORD ArchIndex = (DWORD)(ArchOffset >> 0x1E);
|
||||
DWORD FileSize;
|
||||
|
||||
FileSize = ConvertBytesToInteger_4_LE(pIndexEntry->FileSize);
|
||||
FileSize = ConvertBytesToInteger_4_LE(pIndexEntry->FileSizeLE);
|
||||
ArchOffset &= 0x3FFFFFFF;
|
||||
|
||||
fprintf(fp, " %02X %08X %08X %s\n", ArchIndex, ArchOffset, FileSize, StringFromBinary(pIndexEntry->IndexKey, CASC_FILE_KEY_SIZE, szIndexKey));
|
||||
@@ -463,33 +390,81 @@ void CascDumpIndexEntries(const char * szFileName, TCascStorage * hs)
|
||||
}
|
||||
}
|
||||
|
||||
void CascDumpStorage(const char * szFileName, TCascStorage * hs, const TCHAR * szListFile)
|
||||
void CascDumpRootFile(
|
||||
TCascStorage * hs,
|
||||
LPBYTE pbRootFile,
|
||||
DWORD cbRootFile,
|
||||
const char * szFormat,
|
||||
const TCHAR * szListFile,
|
||||
int nDumpLevel)
|
||||
{
|
||||
char ** FileNameArray = NULL;
|
||||
PCASC_ENCODING_ENTRY pEncodingEntry;
|
||||
ROOT_BLOCK_INFO BlockInfo;
|
||||
PLISTFILE_MAP pListMap;
|
||||
QUERY_KEY EncodingKey;
|
||||
LPBYTE pbRootFileEnd = pbRootFile + cbRootFile;
|
||||
LPBYTE pbFilePointer;
|
||||
FILE * fp;
|
||||
char szOneLine[0x100];
|
||||
DWORD i;
|
||||
|
||||
// Validate the storage handle
|
||||
if(hs != NULL)
|
||||
// This function only dumps WoW-style root file
|
||||
assert(*(PDWORD)pbRootFile != CASC_MNDX_SIGNATURE);
|
||||
|
||||
// Create the dump file
|
||||
fp = CreateDumpFile(szFormat, hs);
|
||||
if(fp != NULL)
|
||||
{
|
||||
// Create the dump file
|
||||
fp = fopen(szFileName, "wt");
|
||||
if(fp != NULL)
|
||||
// Create the listfile map
|
||||
// DWORD dwTickCount = GetTickCount();
|
||||
pListMap = ListFile_CreateMap(szListFile);
|
||||
// dwTickCount = GetTickCount() - dwTickCount;
|
||||
|
||||
// Dump the root entries as-is
|
||||
for(pbFilePointer = pbRootFile; pbFilePointer <= pbRootFileEnd; )
|
||||
{
|
||||
// If we also have listfile, open it
|
||||
if(szListFile != NULL)
|
||||
FileNameArray = CreateFileNameArray(hs, szListFile);
|
||||
// Validate the root block
|
||||
pbFilePointer = VerifyLocaleBlock(&BlockInfo, pbFilePointer, pbRootFileEnd);
|
||||
if(pbFilePointer == NULL)
|
||||
break;
|
||||
|
||||
// Dump all root keys
|
||||
fprintf(fp, "Root Entries\n=========\n\n");
|
||||
DumpRootEntries(fp, hs, FileNameArray);
|
||||
// Dump the locale block
|
||||
fprintf(fp, "Flags: %08X Locales: %08X NumberOfFiles: %u\n"
|
||||
"=========================================================\n",
|
||||
BlockInfo.pLocaleBlockHdr->Flags,
|
||||
BlockInfo.pLocaleBlockHdr->Locales,
|
||||
BlockInfo.pLocaleBlockHdr->NumberOfFiles);
|
||||
|
||||
FreeFileNameArray(hs, FileNameArray);
|
||||
fclose(fp);
|
||||
// Dump the hashes and encoding keys
|
||||
for(i = 0; i < BlockInfo.pLocaleBlockHdr->NumberOfFiles; i++)
|
||||
{
|
||||
// Dump the entry
|
||||
fprintf(fp, "%08X %08X-%08X %s %s\n",
|
||||
(DWORD)(BlockInfo.pInt32Array[i]),
|
||||
(DWORD)(BlockInfo.pRootEntries[i].FileNameHash >> 0x20),
|
||||
(DWORD)(BlockInfo.pRootEntries[i].FileNameHash),
|
||||
StringFromMD5((LPBYTE)BlockInfo.pRootEntries[i].EncodingKey, szOneLine),
|
||||
ListFile_FindName(pListMap, BlockInfo.pRootEntries[i].FileNameHash));
|
||||
|
||||
// Find the encoding entry in the encoding table
|
||||
if(nDumpLevel > 1)
|
||||
{
|
||||
EncodingKey.pbData = (LPBYTE)BlockInfo.pRootEntries[i].EncodingKey;
|
||||
EncodingKey.cbData = MD5_HASH_SIZE;
|
||||
pEncodingEntry = FindEncodingEntry(hs, &EncodingKey, NULL);
|
||||
DumpEncodingEntry(fp, hs, pEncodingEntry, nDumpLevel);
|
||||
}
|
||||
}
|
||||
|
||||
// Put extra newline
|
||||
fprintf(fp, "\n");
|
||||
}
|
||||
|
||||
ListFile_FreeMap(pListMap);
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CascDumpFile(const char * szFileName, HANDLE hFile)
|
||||
{
|
||||
FILE * fp;
|
||||
|
||||
@@ -72,7 +72,7 @@ static bool VerifyRootEntry(TCascSearch * pSearch, PCASC_ROOT_ENTRY pRootEntry,
|
||||
// dwRootEntries++;
|
||||
|
||||
// Now try to find that encoding key in the array of encoding keys
|
||||
QueryKey.pbData = pRootEntry->EncodingKey;
|
||||
QueryKey.pbData = (LPBYTE)pRootEntry->EncodingKey;
|
||||
QueryKey.cbData = MD5_HASH_SIZE;
|
||||
pEncodingEntry = FindEncodingEntry(hs, &QueryKey, NULL);
|
||||
if(pEncodingEntry == NULL)
|
||||
@@ -94,7 +94,9 @@ static bool VerifyRootEntry(TCascSearch * pSearch, PCASC_ROOT_ENTRY pRootEntry,
|
||||
pFindData->FileNameHash = pRootEntry->FileNameHash;
|
||||
pFindData->dwPackageIndex = 0;
|
||||
pFindData->dwLocaleFlags = pRootEntry->Locales;
|
||||
pFindData->dwFileSize = ConvertBytesToInteger_4(pEncodingEntry->FileSizeBytes);
|
||||
|
||||
// Fill-in the file size
|
||||
pFindData->dwFileSize = ConvertBytesToInteger_4(pEncodingEntry->FileSizeBE);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -104,7 +106,7 @@ static TCascSearch * AllocateSearchHandle(TCascStorage * hs, const TCHAR * szLis
|
||||
size_t cbToAllocate;
|
||||
|
||||
// When using the MNDX info, do not allocate the extra bit array
|
||||
cbToAllocate = sizeof(TCascSearch) + ((hs->pMndxInfo == NULL) ? (hs->nRootEntries / 8) : 0);
|
||||
cbToAllocate = sizeof(TCascSearch) + ((hs->pMndxInfo == NULL) ? (hs->RootTable.TableSize / 8) : 0);
|
||||
pSearch = (TCascSearch *)CASC_ALLOC(BYTE, cbToAllocate);
|
||||
if(pSearch != NULL)
|
||||
{
|
||||
@@ -147,53 +149,28 @@ static bool DoStorageSearch_ListFile(TCascSearch * pSearch, PCASC_FIND_DATA pFin
|
||||
{
|
||||
PCASC_ROOT_ENTRY pRootEntry;
|
||||
TCascStorage * hs = pSearch->hs;
|
||||
size_t RootIndex;
|
||||
DWORD TableIndex = 0;
|
||||
|
||||
for(;;)
|
||||
// Get next file from the listfile
|
||||
while(ListFile_GetNext(pSearch->pCache, pSearch->szMask, pSearch->szFileName, MAX_PATH))
|
||||
{
|
||||
// Shall we get a new file name from the listfile?
|
||||
if(pSearch->FileNameHash == 0)
|
||||
#ifdef _DEBUG
|
||||
//if(!_stricmp(pSearch->szFileName, "Character\\NightElf\\Female\\NightElf_FemaleFacialLowerHair01_02_HD.blp"))
|
||||
// DebugBreak();
|
||||
#endif
|
||||
|
||||
// Find the root entry
|
||||
pRootEntry = FindRootEntry(hs, pSearch->szFileName, &TableIndex);
|
||||
if(pRootEntry != NULL)
|
||||
{
|
||||
// Try to get next file from the listfile
|
||||
if(!ListFile_GetNext(pSearch->pCache, pSearch->szMask, pSearch->szFileName, MAX_PATH))
|
||||
break;
|
||||
|
||||
// if(!_stricmp(pSearch->szFileName, "Interface\\Glues\\MODELS\\UI_MainMenu_Warlords\\Caustic_MedFreq.blp"))
|
||||
// DebugBreak();
|
||||
|
||||
// Normalize the file name
|
||||
strcpy(pSearch->szNormName, pSearch->szFileName);
|
||||
NormalizeFileName_UpperBkSlash(pSearch->szNormName);
|
||||
|
||||
// Find the first root entry belonging to this file name
|
||||
pRootEntry = FindFirstRootEntry(pSearch->hs, pSearch->szNormName, &RootIndex);
|
||||
if(pRootEntry == NULL)
|
||||
continue;
|
||||
|
||||
// We have the name now, search all locales
|
||||
pSearch->FileNameHash = pRootEntry->FileNameHash;
|
||||
pSearch->RootIndex = RootIndex;
|
||||
}
|
||||
|
||||
// Is the root index in range?
|
||||
while(pSearch->RootIndex < hs->nRootEntries)
|
||||
{
|
||||
// Get the next root entry. If name mismatches, stop searching
|
||||
pRootEntry = hs->ppRootEntries[pSearch->RootIndex];
|
||||
if(pRootEntry->FileNameHash != pSearch->FileNameHash)
|
||||
break;
|
||||
|
||||
// Verify whether the file exists in the storage
|
||||
if(VerifyRootEntry(pSearch, pRootEntry, pFindData, pSearch->RootIndex++))
|
||||
if(VerifyRootEntry(pSearch, pRootEntry, pFindData, TableIndex))
|
||||
{
|
||||
strcpy(pFindData->szFileName, pSearch->szFileName);
|
||||
pFindData->szPlainName = (char *)GetPlainFileName(pFindData->szFileName);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the name hash and root index and retry search
|
||||
pSearch->FileNameHash = 0;
|
||||
}
|
||||
|
||||
// Listfile search ended
|
||||
@@ -206,13 +183,13 @@ static bool DoStorageSearch_Hash(TCascSearch * pSearch, PCASC_FIND_DATA pFindDat
|
||||
TCascStorage * hs = pSearch->hs;
|
||||
|
||||
// Check if there is more files with the same name hash
|
||||
while(pSearch->RootIndex < hs->nRootEntries)
|
||||
while(pSearch->RootIndex < hs->RootTable.TableSize)
|
||||
{
|
||||
// Get the pointer to the root entry
|
||||
pRootEntry = hs->ppRootEntries[pSearch->RootIndex];
|
||||
pRootEntry = hs->RootTable.TablePtr + pSearch->RootIndex;
|
||||
|
||||
// Verify if that root entry exists in the CASC storage
|
||||
// Note that the file name will be there from the previous search
|
||||
// and was not found before
|
||||
if(VerifyRootEntry(pSearch, pRootEntry, pFindData, pSearch->RootIndex))
|
||||
{
|
||||
pFindData->szFileName[0] = 0;
|
||||
@@ -242,7 +219,6 @@ static bool DoStorageSearch(TCascSearch * pSearch, PCASC_FIND_DATA pFindData)
|
||||
pSearch->pCache = ListFile_OpenExternal(pSearch->szListFile);
|
||||
|
||||
// Move the search phase to the listfile searching
|
||||
pSearch->FileNameHash = 0;
|
||||
pSearch->RootIndex = 0;
|
||||
pSearch->dwState++;
|
||||
|
||||
@@ -259,7 +235,7 @@ static bool DoStorageSearch(TCascSearch * pSearch, PCASC_FIND_DATA pFindData)
|
||||
return true;
|
||||
|
||||
// Move to the nameless search state
|
||||
pSearch->RootIndex = 0;
|
||||
assert(pSearch->RootIndex == 0);
|
||||
pSearch->dwState++;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ extern "C" {
|
||||
#define CASC_LOCALE_UNKNOWN1 0x00000001
|
||||
#define CASC_LOCALE_ENUS 0x00000002
|
||||
#define CASC_LOCALE_KOKR 0x00000004
|
||||
#define CASC_LOCALE_UNKNOWN8 0x00000008
|
||||
#define CASC_LOCALE_RESERVED 0x00000008
|
||||
#define CASC_LOCALE_FRFR 0x00000010
|
||||
#define CASC_LOCALE_DEDE 0x00000020
|
||||
#define CASC_LOCALE_ZHCN 0x00000040
|
||||
@@ -81,6 +81,24 @@ extern "C" {
|
||||
#define CASC_LOCALE_ITIT 0x00008000
|
||||
#define CASC_LOCALE_PTPT 0x00010000
|
||||
|
||||
#define CASC_LOCALE_BIT_ENUS 0x01
|
||||
#define CASC_LOCALE_BIT_KOKR 0x02
|
||||
#define CASC_LOCALE_DUAL_LANG 0x03
|
||||
#define CASC_LOCALE_BIT_FRFR 0x04
|
||||
#define CASC_LOCALE_BIT_DEDE 0x05
|
||||
#define CASC_LOCALE_BIT_ZHCN 0x06
|
||||
#define CASC_LOCALE_BIT_ESES 0x07
|
||||
#define CASC_LOCALE_BIT_ZHTW 0x08
|
||||
#define CASC_LOCALE_BIT_ENGB 0x09
|
||||
#define CASC_LOCALE_BIT_ENCN 0x0A
|
||||
#define CASC_LOCALE_BIT_ENTW 0x0B
|
||||
#define CASC_LOCALE_BIT_ESMX 0x0C
|
||||
#define CASC_LOCALE_BIT_RURU 0x0D
|
||||
#define CASC_LOCALE_BIT_PTBR 0x0E
|
||||
#define CASC_LOCALE_BIT_ITIT 0x0F
|
||||
#define CASC_LOCALE_BIT_PTPT 0x10
|
||||
|
||||
|
||||
#define MAX_CASC_KEY_LENGTH 0x10 // Maximum length of the key (equal to MD5 hash)
|
||||
|
||||
#ifndef MD5_HASH_SIZE
|
||||
@@ -110,6 +128,8 @@ typedef enum _CASC_STORAGE_INFO_CLASS
|
||||
{
|
||||
CascStorageFileCount,
|
||||
CascStorageFeatures,
|
||||
CascStorageGameInfo,
|
||||
CascStorageGameBuild,
|
||||
CascStorageInfoClassMax
|
||||
|
||||
} CASC_STORAGE_INFO_CLASS, *PCASC_STORAGE_INFO_CLASS;
|
||||
@@ -148,7 +168,7 @@ void qsort_pointer_array(void ** base, size_t num, int (*compare)(const void *,
|
||||
//-----------------------------------------------------------------------------
|
||||
// Functions for storage manipulation
|
||||
|
||||
bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwFlags, HANDLE * phStorage);
|
||||
bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwLocaleMask, HANDLE * phStorage);
|
||||
bool WINAPI CascGetStorageInfo(HANDLE hStorage, CASC_STORAGE_INFO_CLASS InfoClass, void * pvStorageInfo, size_t cbStorageInfo, size_t * pcbLengthNeeded);
|
||||
bool WINAPI CascCloseStorage(HANDLE hStorage);
|
||||
|
||||
|
||||
@@ -202,6 +202,11 @@ DWORD GetNumberOfSetBits(DWORD Value32)
|
||||
|
||||
static bool RootFileRead(LPBYTE pbFilePointer, LPBYTE pbFileEnd, void * pvBuffer, size_t dwBytesToRead)
|
||||
{
|
||||
// False if the file pointer is beyond the end
|
||||
if(pbFilePointer > pbFileEnd)
|
||||
return false;
|
||||
|
||||
// False if there is not enough bytes available
|
||||
if((size_t)(pbFileEnd - pbFilePointer) < dwBytesToRead)
|
||||
return false;
|
||||
|
||||
@@ -2887,7 +2892,7 @@ PCASC_PACKAGE FindMndxPackage(TCascStorage * hs, const char * szFileName)
|
||||
|
||||
static bool FillFindData(TCascSearch * pSearch, PCASC_FIND_DATA pFindData, TMndxFindResult * pStruct1C)
|
||||
{
|
||||
CASC_ROOT_KEY_INFO RootKeyInfo;
|
||||
PCASC_ROOT_ENTRY_MNDX pRootEntry = NULL;
|
||||
TCascStorage * hs = pSearch->hs;
|
||||
PCASC_PACKAGE pPackage;
|
||||
char * szStrippedName;
|
||||
@@ -2899,6 +2904,7 @@ static bool FillFindData(TCascSearch * pSearch, PCASC_FIND_DATA pFindData, TMndx
|
||||
// Fill the file name
|
||||
memcpy(pFindData->szFileName, pStruct1C->szFoundPath, pStruct1C->cchFoundPath);
|
||||
pFindData->szFileName[pStruct1C->cchFoundPath] = 0;
|
||||
pFindData->szPlainName = (char *)GetPlainFileName(pFindData->szFileName);
|
||||
pFindData->dwFileSize = CASC_INVALID_SIZE;
|
||||
|
||||
// Fill the file size
|
||||
@@ -2910,10 +2916,10 @@ static bool FillFindData(TCascSearch * pSearch, PCASC_FIND_DATA pFindData, TMndx
|
||||
while(szStrippedName[0] == '/')
|
||||
szStrippedName++;
|
||||
|
||||
nError = SearchMndxInfo(hs->pMndxInfo, szStrippedName, (DWORD)(pPackage - hs->pPackages->Packages), &RootKeyInfo);
|
||||
nError = SearchMndxInfo(hs->pMndxInfo, szStrippedName, (DWORD)(pPackage - hs->pPackages->Packages), &pRootEntry);
|
||||
if(nError == ERROR_SUCCESS)
|
||||
{
|
||||
pFindData->dwFileSize = (DWORD)RootKeyInfo.FileSize;
|
||||
pFindData->dwFileSize = pRootEntry->FileSize;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -3014,7 +3020,7 @@ int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
|
||||
{
|
||||
if(pMndxInfo->pMarFile1 == NULL || pMndxInfo->pMarFile2 == NULL || pMndxInfo->pMarFile3 == NULL)
|
||||
nError = ERROR_BAD_FORMAT;
|
||||
if(pMndxInfo->MndxEntrySize != sizeof(CASC_MNDX_ENTRY))
|
||||
if(pMndxInfo->MndxEntrySize != sizeof(CASC_ROOT_ENTRY_MNDX))
|
||||
nError = ERROR_BAD_FORMAT;
|
||||
}
|
||||
|
||||
@@ -3028,7 +3034,7 @@ int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
|
||||
if(nError == ERROR_SUCCESS && FileNameCount == pMndxInfo->MndxEntriesValid)
|
||||
{
|
||||
cbToAllocate = pMndxInfo->MndxEntriesTotal * pMndxInfo->MndxEntrySize;
|
||||
pMndxInfo->pMndxEntries = (PCASC_MNDX_ENTRY)CASC_ALLOC(BYTE, cbToAllocate);
|
||||
pMndxInfo->pMndxEntries = (PCASC_ROOT_ENTRY_MNDX)CASC_ALLOC(BYTE, cbToAllocate);
|
||||
if(pMndxInfo->pMndxEntries != NULL)
|
||||
{
|
||||
if(!RootFileRead(pbRootFile + pMndxInfo->MndxEntriesOffset, pbRootFileEnd, pMndxInfo->pMndxEntries, cbToAllocate))
|
||||
@@ -3045,10 +3051,10 @@ int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
|
||||
if(nError == ERROR_SUCCESS)
|
||||
{
|
||||
assert(pMndxInfo->MndxEntriesValid <= pMndxInfo->MndxEntriesTotal);
|
||||
pMndxInfo->ppValidEntries = CASC_ALLOC(PCASC_MNDX_ENTRY, pMndxInfo->MndxEntriesValid + 1);
|
||||
pMndxInfo->ppValidEntries = CASC_ALLOC(PCASC_ROOT_ENTRY_MNDX, pMndxInfo->MndxEntriesValid + 1);
|
||||
if(pMndxInfo->ppValidEntries != NULL)
|
||||
{
|
||||
PCASC_MNDX_ENTRY pMndxEntry = pMndxInfo->pMndxEntries;
|
||||
PCASC_ROOT_ENTRY_MNDX pRootEntry = pMndxInfo->pMndxEntries;
|
||||
DWORD ValidEntryCount = 1; // edx
|
||||
DWORD nIndex1 = 0;
|
||||
|
||||
@@ -3056,14 +3062,14 @@ int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
|
||||
pMndxInfo->ppValidEntries[nIndex1++] = pMndxInfo->pMndxEntries;
|
||||
|
||||
// Put the remaining entries
|
||||
for(i = 0; i < pMndxInfo->MndxEntriesTotal; i++, pMndxEntry++)
|
||||
for(i = 0; i < pMndxInfo->MndxEntriesTotal; i++, pRootEntry++)
|
||||
{
|
||||
if(ValidEntryCount > pMndxInfo->MndxEntriesValid)
|
||||
break;
|
||||
|
||||
if(pMndxEntry->Flags & 0x80000000)
|
||||
if(pRootEntry->Flags & 0x80000000)
|
||||
{
|
||||
pMndxInfo->ppValidEntries[nIndex1++] = pMndxEntry + 1;
|
||||
pMndxInfo->ppValidEntries[nIndex1++] = pRootEntry + 1;
|
||||
ValidEntryCount++;
|
||||
}
|
||||
}
|
||||
@@ -3106,9 +3112,9 @@ int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
|
||||
return nError;
|
||||
}
|
||||
|
||||
int SearchMndxInfo(PCASC_MNDX_INFO pMndxInfo, const char * szFileName, DWORD dwPackage, PCASC_ROOT_KEY_INFO pFoundInfo)
|
||||
int SearchMndxInfo(PCASC_MNDX_INFO pMndxInfo, const char * szFileName, DWORD dwPackage, PCASC_ROOT_ENTRY_MNDX * ppRootEntry)
|
||||
{
|
||||
PCASC_MNDX_ENTRY pMndxEntry;
|
||||
PCASC_ROOT_ENTRY_MNDX pRootEntry;
|
||||
TMndxFindResult Struct1C;
|
||||
|
||||
// Search the database for the file name
|
||||
@@ -3124,20 +3130,19 @@ int SearchMndxInfo(PCASC_MNDX_INFO pMndxInfo, const char * szFileName, DWORD dwP
|
||||
if(Struct1C.FileNameIndex < pMndxInfo->MndxEntriesValid)
|
||||
{
|
||||
// HOTS: E945F4
|
||||
pMndxEntry = pMndxInfo->ppValidEntries[Struct1C.FileNameIndex];
|
||||
while((pMndxEntry->Flags & 0x00FFFFFF) != dwPackage)
|
||||
pRootEntry = pMndxInfo->ppValidEntries[Struct1C.FileNameIndex];
|
||||
while((pRootEntry->Flags & 0x00FFFFFF) != dwPackage)
|
||||
{
|
||||
// The highest bit serves as a terminator if set
|
||||
if(pMndxEntry->Flags & 0x80000000)
|
||||
if(pRootEntry->Flags & 0x80000000)
|
||||
return ERROR_FILE_NOT_FOUND;
|
||||
|
||||
pMndxEntry++;
|
||||
pRootEntry++;
|
||||
}
|
||||
|
||||
// Fill the root info
|
||||
memcpy(pFoundInfo->EncodingKey, pMndxEntry->EncodingKey, MD5_HASH_SIZE);
|
||||
pFoundInfo->FileSize = pMndxEntry->FileSize;
|
||||
pFoundInfo->Flags = (BYTE)((pMndxEntry->Flags >> 0x18) & 0x3F);
|
||||
// Give the root entry pointer to the caller
|
||||
if(ppRootEntry != NULL)
|
||||
ppRootEntry[0] = pRootEntry;
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -3213,14 +3218,18 @@ extern "C" {
|
||||
|
||||
extern "C" void * allocate_zeroed_memory_x86(size_t bytes)
|
||||
{
|
||||
return calloc(bytes, 1);
|
||||
void * ptr = CASC_ALLOC(BYTE, bytes);
|
||||
|
||||
if(ptr != NULL)
|
||||
memset(ptr, 0, bytes);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
extern "C" void free_memory_x86(void * ptr)
|
||||
{
|
||||
if(ptr != NULL)
|
||||
{
|
||||
free(ptr);
|
||||
CASC_FREE(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -358,7 +358,7 @@ inline bool IS_SINGLE_CHAR_MATCH(TGenericArray & Table, DWORD ItemIndex)
|
||||
|
||||
int LoadMndxRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile);
|
||||
PCASC_PACKAGE FindMndxPackage(TCascStorage * hs, const char * szFileName);
|
||||
int SearchMndxInfo(PCASC_MNDX_INFO pMndxInfo, const char * szFileName, DWORD dwPackage, PCASC_ROOT_KEY_INFO pFoundInfo);
|
||||
int SearchMndxInfo(PCASC_MNDX_INFO pMndxInfo, const char * szFileName, DWORD dwPackage, PCASC_ROOT_ENTRY_MNDX * ppFoundInfo);
|
||||
bool DoStorageSearch_MNDX(TCascSearch * pSearch, PCASC_FIND_DATA pFindData);
|
||||
void FreeMndxInfo(PCASC_MNDX_INFO pMndxInfo);
|
||||
|
||||
|
||||
@@ -69,115 +69,47 @@ PCASC_ENCODING_ENTRY FindEncodingEntry(TCascStorage * hs, PQUERY_KEY pEncodingKe
|
||||
}
|
||||
|
||||
// Also used in CascSearchFile
|
||||
PCASC_ROOT_ENTRY FindFirstRootEntry(TCascStorage * hs, const char * szFileName, size_t * PtrIndex)
|
||||
PCASC_ROOT_ENTRY FindRootEntry(TCascStorage * hs, const char * szFileName, DWORD * PtrTableIndex)
|
||||
{
|
||||
PCASC_ROOT_ENTRY pFoundEntry = NULL;
|
||||
PCASC_ROOT_ENTRY pRootEntry;
|
||||
ULONGLONG FileNameHash;
|
||||
DWORD TableIndex;
|
||||
uint32_t dwHashHigh = 0;
|
||||
uint32_t dwHashLow = 0;
|
||||
size_t StartEntry = 0;
|
||||
size_t MidlEntry = 0;
|
||||
size_t EndEntry = hs->nRootEntries;
|
||||
|
||||
// Calculate the HASH value of the normalized file name
|
||||
hashlittle2(szFileName, strlen(szFileName), &dwHashHigh, &dwHashLow);
|
||||
FileNameHash = ((ULONGLONG)dwHashHigh << 0x20) | dwHashLow;
|
||||
|
||||
// Perform binary search
|
||||
while(StartEntry < EndEntry)
|
||||
{
|
||||
// Calculate the middle of the interval
|
||||
MidlEntry = StartEntry + ((EndEntry - StartEntry) / 2);
|
||||
pRootEntry = hs->ppRootEntries[MidlEntry];
|
||||
// Get the first table index
|
||||
TableIndex = (DWORD)(FileNameHash & (hs->RootTable.TableSize - 1));
|
||||
assert(hs->RootTable.ItemCount < hs->RootTable.TableSize);
|
||||
|
||||
// Did we find it?
|
||||
// Search the proper entry
|
||||
for(;;)
|
||||
{
|
||||
// Does the has match?
|
||||
pRootEntry = hs->RootTable.TablePtr + TableIndex;
|
||||
if(pRootEntry->FileNameHash == FileNameHash)
|
||||
{
|
||||
pFoundEntry = pRootEntry;
|
||||
break;
|
||||
if(PtrTableIndex != NULL)
|
||||
PtrTableIndex[0] = TableIndex;
|
||||
return pRootEntry;
|
||||
}
|
||||
|
||||
// Move the interval to the left or right
|
||||
(FileNameHash < pRootEntry->FileNameHash) ? EndEntry = MidlEntry : StartEntry = MidlEntry + 1;
|
||||
// If the entry is free, the file is not there
|
||||
if(pRootEntry->FileNameHash == 0 && pRootEntry->SumValue == 0)
|
||||
return NULL;
|
||||
|
||||
// Move to the next entry
|
||||
TableIndex = (DWORD)((TableIndex + 1) & (hs->RootTable.TableSize - 1));
|
||||
}
|
||||
|
||||
// Move the pointer back to the first entry with that hash
|
||||
if(pFoundEntry != NULL)
|
||||
{
|
||||
while(MidlEntry > 0 && hs->ppRootEntries[MidlEntry - 1]->FileNameHash == FileNameHash)
|
||||
{
|
||||
pFoundEntry = hs->ppRootEntries[MidlEntry - 1];
|
||||
MidlEntry--;
|
||||
}
|
||||
}
|
||||
|
||||
// Return what we found
|
||||
if(PtrIndex != NULL)
|
||||
*PtrIndex = MidlEntry;
|
||||
return pFoundEntry;
|
||||
}
|
||||
|
||||
// Check the root directory for that hash
|
||||
PCASC_ROOT_ENTRY FindRootEntryLocale(TCascStorage * hs, char * szFileName, DWORD Locale)
|
||||
{
|
||||
PCASC_ROOT_ENTRY pThatEntry = NULL;
|
||||
PCASC_ROOT_ENTRY pENUSEntry = NULL;
|
||||
PCASC_ROOT_ENTRY pENGBEntry = NULL;
|
||||
PCASC_ROOT_ENTRY pAnyEntry = NULL;
|
||||
PCASC_ROOT_ENTRY pEndEntry = NULL;
|
||||
PCASC_ROOT_ENTRY pRootEntry = NULL;
|
||||
ULONGLONG FileNameHash;
|
||||
size_t EntryIndex = 0;
|
||||
size_t EndEntry = hs->nRootEntries;
|
||||
|
||||
// Find a root entry with the given name hash
|
||||
pRootEntry = FindFirstRootEntry(hs, szFileName, &EntryIndex);
|
||||
if(pRootEntry != NULL)
|
||||
{
|
||||
// Rememeber the file name hash
|
||||
pEndEntry = hs->pRootEntries + hs->nRootEntries;
|
||||
FileNameHash = pRootEntry->FileNameHash;
|
||||
|
||||
// Find all suitable root entries
|
||||
while(EntryIndex < EndEntry)
|
||||
{
|
||||
// Get the root entry
|
||||
pRootEntry = hs->ppRootEntries[EntryIndex++];
|
||||
if(pRootEntry->FileNameHash != FileNameHash)
|
||||
break;
|
||||
|
||||
// If a locale has been given, check it
|
||||
if(pThatEntry == NULL && Locale != 0 && (Locale & pRootEntry->Locales))
|
||||
pThatEntry = pRootEntry;
|
||||
if(pENUSEntry == NULL && (pRootEntry->Locales & CASC_LOCALE_ENUS))
|
||||
pENUSEntry = pRootEntry;
|
||||
if(pENGBEntry == NULL && (pRootEntry->Locales & CASC_LOCALE_ENGB))
|
||||
pENGBEntry = pRootEntry;
|
||||
if(pAnyEntry == NULL)
|
||||
pAnyEntry = pRootEntry;
|
||||
|
||||
// Move to the next one
|
||||
pRootEntry++;
|
||||
}
|
||||
|
||||
// Return the key by priority
|
||||
if(pThatEntry != NULL)
|
||||
return pThatEntry;
|
||||
if(pENGBEntry != NULL)
|
||||
return pENGBEntry;
|
||||
if(pENUSEntry != NULL)
|
||||
return pENUSEntry;
|
||||
}
|
||||
|
||||
// Return whatever we got
|
||||
return pAnyEntry;
|
||||
}
|
||||
|
||||
static TCascFile * CreateFileHandle(TCascStorage * hs, PCASC_INDEX_ENTRY pIndexEntry)
|
||||
{
|
||||
ULONGLONG FileOffsMask = ((ULONGLONG)1 << hs->KeyMapping[0].SegmentBits) - 1;
|
||||
ULONGLONG FileOffset = ConvertBytesToInteger_5(pIndexEntry->FileOffset);
|
||||
ULONGLONG FileOffset = ConvertBytesToInteger_5(pIndexEntry->FileOffsetBE);
|
||||
TCascFile * hf;
|
||||
|
||||
// Allocate the CASC file structure
|
||||
@@ -190,12 +122,13 @@ static TCascFile * CreateFileHandle(TCascStorage * hs, PCASC_INDEX_ENTRY pIndexE
|
||||
hf->HeaderOffset = (DWORD)(FileOffset & FileOffsMask);
|
||||
hf->szClassName = "TCascFile";
|
||||
|
||||
// Copy the compressed file size
|
||||
hf->CompressedSize = ConvertBytesToInteger_4_LE(pIndexEntry->FileSize) - 0x1E;
|
||||
// Copy the file size. Note that for all files except ENCODING,
|
||||
// this is the compressed file size
|
||||
hf->CompressedSize = ConvertBytesToInteger_4_LE(pIndexEntry->FileSizeLE);
|
||||
|
||||
// For now, we set the file size to be equal to compressed size
|
||||
// This is used when loading the "encoding" file, which does not
|
||||
// have entry in the encoding itself
|
||||
// This is used when loading the ENCODING file, which does not
|
||||
// have entry in the encoding table
|
||||
hf->FileSize = hf->CompressedSize;
|
||||
|
||||
// Increment the number of references to the archive
|
||||
@@ -206,10 +139,9 @@ static TCascFile * CreateFileHandle(TCascStorage * hs, PCASC_INDEX_ENTRY pIndexE
|
||||
return hf;
|
||||
}
|
||||
|
||||
static bool OpenFileByIndexKey(TCascStorage * hs, PQUERY_KEY pIndexKey, DWORD dwFlags, HANDLE * phFile)
|
||||
static bool OpenFileByIndexKey(TCascStorage * hs, PQUERY_KEY pIndexKey, DWORD dwFlags, TCascFile ** ppCascFile)
|
||||
{
|
||||
PCASC_INDEX_ENTRY pIndexEntry;
|
||||
TCascFile * hf = NULL;
|
||||
int nError = ERROR_SUCCESS;
|
||||
|
||||
CASCLIB_UNUSED(dwFlags);
|
||||
@@ -222,43 +154,53 @@ static bool OpenFileByIndexKey(TCascStorage * hs, PQUERY_KEY pIndexKey, DWORD dw
|
||||
// Create the file handle structure
|
||||
if(nError == ERROR_SUCCESS)
|
||||
{
|
||||
hf = CreateFileHandle(hs, pIndexEntry);
|
||||
*phFile = (HANDLE)hf;
|
||||
if(hf == NULL)
|
||||
ppCascFile[0] = CreateFileHandle(hs, pIndexEntry);
|
||||
if(ppCascFile[0] == NULL)
|
||||
nError = ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
#ifdef CASCLIB_TEST
|
||||
if(nError == ERROR_SUCCESS && ppCascFile[0] != NULL)
|
||||
{
|
||||
ppCascFile[0]->FileSize_IdxEntry = ConvertBytesToInteger_4_LE(pIndexEntry->FileSizeLE);
|
||||
}
|
||||
#endif
|
||||
|
||||
if(nError != ERROR_SUCCESS)
|
||||
SetLastError(nError);
|
||||
return (nError == ERROR_SUCCESS);
|
||||
}
|
||||
|
||||
static bool OpenFileByEncodingKey(TCascStorage * hs, PQUERY_KEY pEncodingKey, DWORD dwFlags, HANDLE * phFile)
|
||||
static bool OpenFileByEncodingKey(TCascStorage * hs, PQUERY_KEY pEncodingKey, DWORD dwFlags, TCascFile ** ppCascFile)
|
||||
{
|
||||
PCASC_ENCODING_ENTRY pEncodingEntry;
|
||||
QUERY_KEY IndexKey;
|
||||
TCascFile * hf = NULL;
|
||||
int nError = ERROR_SUCCESS;
|
||||
|
||||
// Find the encoding entry
|
||||
pEncodingEntry = FindEncodingEntry(hs, pEncodingKey, NULL);
|
||||
if(pEncodingEntry == NULL)
|
||||
nError = ERROR_FILE_NOT_FOUND;
|
||||
{
|
||||
SetLastError(ERROR_FILE_NOT_FOUND);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prepare the file index and open the file by index
|
||||
// Note: We don't know what to do if there is more than just one index key
|
||||
// We always take the first file present. Is that correct?
|
||||
// IndexKey.pbData = pEncodingEntry->EncodingKey + (MD5_HASH_SIZE * pEncodingEntry->KeyCount);
|
||||
// assert(pEncodingEntry->KeyCount == 1);
|
||||
IndexKey.pbData = pEncodingEntry->EncodingKey + MD5_HASH_SIZE;
|
||||
IndexKey.cbData = MD5_HASH_SIZE;
|
||||
if(OpenFileByIndexKey(hs, &IndexKey, dwFlags, phFile))
|
||||
if(OpenFileByIndexKey(hs, &IndexKey, dwFlags, ppCascFile))
|
||||
{
|
||||
// Fix the file size from the encoding key
|
||||
hf = IsValidFileHandle(*phFile);
|
||||
if(hf != NULL)
|
||||
// Check if the file handle was created
|
||||
if(ppCascFile[0] != NULL)
|
||||
{
|
||||
hf->FileSize = ConvertBytesToInteger_4(pEncodingEntry->FileSizeBytes);
|
||||
// Fill-in the file size. For all files except ENCODING,
|
||||
// this overrides the value stored in the index entry.
|
||||
ppCascFile[0]->FileSize = ConvertBytesToInteger_4(pEncodingEntry->FileSizeBE);
|
||||
|
||||
#ifdef CASCLIB_TEST
|
||||
ppCascFile[0]->FileSize_EncEntry = ConvertBytesToInteger_4(pEncodingEntry->FileSizeBE);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -289,7 +231,7 @@ bool WINAPI CascOpenFileByIndexKey(HANDLE hStorage, PQUERY_KEY pIndexKey, DWORD
|
||||
}
|
||||
|
||||
// Use the internal function to open the file
|
||||
return OpenFileByIndexKey(hs, pIndexKey, dwFlags, phFile);
|
||||
return OpenFileByIndexKey(hs, pIndexKey, dwFlags, (TCascFile **)phFile);
|
||||
}
|
||||
|
||||
bool WINAPI CascOpenFileByEncodingKey(HANDLE hStorage, PQUERY_KEY pEncodingKey, DWORD dwFlags, HANDLE * phFile)
|
||||
@@ -312,12 +254,12 @@ bool WINAPI CascOpenFileByEncodingKey(HANDLE hStorage, PQUERY_KEY pEncodingKey,
|
||||
}
|
||||
|
||||
// Use the internal function fo open the file
|
||||
return OpenFileByEncodingKey(hs, pEncodingKey, dwFlags, phFile);
|
||||
return OpenFileByEncodingKey(hs, pEncodingKey, dwFlags, (TCascFile **)phFile);
|
||||
}
|
||||
|
||||
bool WINAPI CascOpenFile(HANDLE hStorage, const char * szFileName, DWORD dwLocale, DWORD dwFlags, HANDLE * phFile)
|
||||
{
|
||||
CASC_ROOT_KEY_INFO EncodingKeyInfo;
|
||||
PCASC_ROOT_ENTRY_MNDX pRootEntryMndx = NULL;
|
||||
PCASC_ROOT_ENTRY pRootEntry;
|
||||
PCASC_PACKAGE pPackage;
|
||||
TCascStorage * hs;
|
||||
@@ -326,6 +268,8 @@ bool WINAPI CascOpenFile(HANDLE hStorage, const char * szFileName, DWORD dwLocal
|
||||
char * szFileName2;
|
||||
int nError = ERROR_SUCCESS;
|
||||
|
||||
CASCLIB_UNUSED(dwLocale);
|
||||
|
||||
// Validate the storage handle
|
||||
hs = IsValidStorageHandle(hStorage);
|
||||
if(hs == NULL)
|
||||
@@ -360,11 +304,11 @@ bool WINAPI CascOpenFile(HANDLE hStorage, const char * szFileName, DWORD dwLocal
|
||||
while(szStrippedName[0] == '/')
|
||||
szStrippedName++;
|
||||
|
||||
nError = SearchMndxInfo(hs->pMndxInfo, szStrippedName, (DWORD)(pPackage - hs->pPackages->Packages), &EncodingKeyInfo);
|
||||
nError = SearchMndxInfo(hs->pMndxInfo, szStrippedName, (DWORD)(pPackage - hs->pPackages->Packages), &pRootEntryMndx);
|
||||
if(nError == ERROR_SUCCESS)
|
||||
{
|
||||
// Prepare the encoding key
|
||||
EncodingKey.pbData = EncodingKeyInfo.EncodingKey;
|
||||
EncodingKey.pbData = pRootEntryMndx->EncodingKey;
|
||||
EncodingKey.cbData = MD5_HASH_SIZE;
|
||||
}
|
||||
}
|
||||
@@ -376,23 +320,27 @@ bool WINAPI CascOpenFile(HANDLE hStorage, const char * szFileName, DWORD dwLocal
|
||||
else
|
||||
{
|
||||
// Convert the file name to lowercase + slashes
|
||||
NormalizeFileName_UpperBkSlash(szFileName2);
|
||||
NormalizeFileName_UpperBkSlash(szFileName2, szFileName2);
|
||||
|
||||
// Check the root directory for that hash
|
||||
pRootEntry = FindRootEntryLocale(hs, szFileName2, dwLocale);
|
||||
nError = (pRootEntry != NULL) ? ERROR_SUCCESS : ERROR_FILE_NOT_FOUND;
|
||||
pRootEntry = FindRootEntry(hs, szFileName2, NULL);
|
||||
if(pRootEntry != NULL)
|
||||
{
|
||||
// Prepare the root key
|
||||
EncodingKey.pbData = pRootEntry->EncodingKey;
|
||||
EncodingKey.pbData = (LPBYTE)pRootEntry->EncodingKey;
|
||||
EncodingKey.cbData = MD5_HASH_SIZE;
|
||||
nError = ERROR_SUCCESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
nError = ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
// Use the root key to find the file in the encoding table entry
|
||||
if(nError == ERROR_SUCCESS)
|
||||
{
|
||||
if(!OpenFileByEncodingKey(hs, &EncodingKey, dwFlags, phFile))
|
||||
if(!OpenFileByEncodingKey(hs, &EncodingKey, dwFlags, (TCascFile **)phFile))
|
||||
{
|
||||
assert(GetLastError() != ERROR_SUCCESS);
|
||||
nError = GetLastError();
|
||||
@@ -400,11 +348,18 @@ bool WINAPI CascOpenFile(HANDLE hStorage, const char * szFileName, DWORD dwLocal
|
||||
}
|
||||
|
||||
// Delete the file name copy
|
||||
delete [] szFileName2;
|
||||
CASC_FREE(szFileName2);
|
||||
}
|
||||
else
|
||||
nError = ERROR_NOT_ENOUGH_MEMORY;
|
||||
|
||||
#ifdef CASCLIB_TEST
|
||||
if(phFile[0] != NULL && pRootEntryMndx != NULL)
|
||||
{
|
||||
((TCascFile *)(phFile[0]))->FileSize_RootEntry = pRootEntryMndx->FileSize;
|
||||
}
|
||||
#endif
|
||||
|
||||
if(nError != ERROR_SUCCESS)
|
||||
SetLastError(nError);
|
||||
return (nError == ERROR_SUCCESS);
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
/* CascOpenStorage.cpp Copyright (c) Ladislav Zezula 2014 */
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* Storage functions for CASC */
|
||||
/* Note: WoW6 offsets refer to WoW.exe 6.0.3.19116 (32-bit) */
|
||||
/* SHA1: c10e9ffb7d040a37a356b96042657e1a0c95c0dd */
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* Date Ver Who Comment */
|
||||
/* -------- ---- --- ------- */
|
||||
@@ -13,10 +15,18 @@
|
||||
#include "CascCommon.h"
|
||||
#include "CascMndxRoot.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Dumping options
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define CASC_DUMP_ROOT_FILE 2 // The root file will be dumped (level 2)
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Local structures
|
||||
|
||||
#define CASC_ENCODING_SEGMENT_SIZE 0x1000
|
||||
#define CASC_INITIAL_ROOT_TABLE_SIZE 0x00100000
|
||||
#define CASC_ENCODING_SEGMENT_SIZE 0x1000
|
||||
|
||||
typedef struct _BLOCK_SIZE_AND_HASH
|
||||
{
|
||||
@@ -79,32 +89,6 @@ typedef struct _FILE_ENCODING_SEGMENT
|
||||
|
||||
} FILE_ENCODING_SEGMENT, *PFILE_ENCODING_SEGMENT;
|
||||
|
||||
typedef struct _FILE_LOCALE_BLOCK
|
||||
{
|
||||
DWORD NumberOfFiles; // Number of entries
|
||||
DWORD Flags;
|
||||
DWORD Locales; // File locale mask (CASC_LOCALE_XXX)
|
||||
|
||||
// Followed by a block of 32-bit integers (count: NumberOfFiles)
|
||||
// Followed by the MD5 and file name hash (count: NumberOfFiles)
|
||||
|
||||
} FILE_LOCALE_BLOCK, *PFILE_LOCALE_BLOCK;
|
||||
|
||||
typedef struct _FILE_ROOT_ENTRY
|
||||
{
|
||||
BYTE EncodingKey[MD5_HASH_SIZE]; // MD5 of the file
|
||||
ULONGLONG FileNameHash; // Jenkins hash of the file name
|
||||
|
||||
} FILE_ROOT_ENTRY, *PFILE_ROOT_ENTRY;
|
||||
|
||||
typedef struct _ROOT_BLOCK_INFO
|
||||
{
|
||||
PFILE_LOCALE_BLOCK pLocaleBlockHdr; // Pointer to the locale block
|
||||
PDWORD pInt32Array; // Pointer to the array of 32-bit integers
|
||||
PFILE_ROOT_ENTRY pRootEntries;
|
||||
|
||||
} ROOT_BLOCK_INFO, *PROOT_BLOCK_INFO;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Local variables
|
||||
|
||||
@@ -181,19 +165,6 @@ static void QUERY_KEY_FreeArray(PQUERY_KEY pBlobArray)
|
||||
CASC_FREE(pBlobArray);
|
||||
}
|
||||
|
||||
static int CompareRootEntries(const void *, const void * pvKeyEntry1, const void * pvKeyEntry2)
|
||||
{
|
||||
PCASC_ROOT_ENTRY pRootEntry1 = (PCASC_ROOT_ENTRY)pvKeyEntry1;
|
||||
PCASC_ROOT_ENTRY pRootEntry2 = (PCASC_ROOT_ENTRY)pvKeyEntry2;
|
||||
|
||||
// Compare name hash first
|
||||
if(pRootEntry1->FileNameHash < pRootEntry2->FileNameHash)
|
||||
return -1;
|
||||
if(pRootEntry1->FileNameHash > pRootEntry2->FileNameHash)
|
||||
return +1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool IsCascIndexHeader_V1(LPBYTE pbFileData, DWORD cbFileData)
|
||||
{
|
||||
PFILE_INDEX_HEADER_V1 pIndexHeader = (PFILE_INDEX_HEADER_V1)pbFileData;
|
||||
@@ -235,18 +206,18 @@ static bool IsCascIndexHeader_V2(LPBYTE pbFileData, DWORD cbFileData)
|
||||
return (HashHigh == pSizeAndHash->dwBlockHash);
|
||||
}
|
||||
|
||||
static LPBYTE VerifyLocaleBlock(PROOT_BLOCK_INFO pBlockInfo, LPBYTE pbFilePointer, LPBYTE pbFileEnd)
|
||||
LPBYTE VerifyLocaleBlock(PROOT_BLOCK_INFO pBlockInfo, LPBYTE pbFilePointer, LPBYTE pbFileEnd)
|
||||
{
|
||||
// Validate the locale header
|
||||
// Validate the file locale block
|
||||
pBlockInfo->pLocaleBlockHdr = (PFILE_LOCALE_BLOCK)pbFilePointer;
|
||||
pbFilePointer += sizeof(FILE_LOCALE_BLOCK);
|
||||
if(pbFilePointer >= pbFileEnd)
|
||||
pbFilePointer = (LPBYTE)(pBlockInfo->pLocaleBlockHdr + 1);
|
||||
if(pbFilePointer > pbFileEnd)
|
||||
return NULL;
|
||||
|
||||
// Validate the array of 32-bit integers
|
||||
pBlockInfo->pInt32Array = (PDWORD)pbFilePointer;
|
||||
pbFilePointer = (LPBYTE)(pBlockInfo->pInt32Array + pBlockInfo->pLocaleBlockHdr->NumberOfFiles);
|
||||
if(pbFilePointer >= pbFileEnd)
|
||||
if(pbFilePointer > pbFileEnd)
|
||||
return NULL;
|
||||
|
||||
// Validate the array of root entries
|
||||
@@ -488,7 +459,7 @@ static int VerifyAndParseKeyMapping_V2(PCASC_MAPPING_TABLE pKeyMapping, DWORD Ke
|
||||
if(PtrLastPart[0] == 0)
|
||||
return ERROR_SUCCESS;
|
||||
|
||||
HashLow = hashlittle(PtrLastPart + 1, 0x13, 0) | 0x80000000;
|
||||
HashLow = hashlittle(PtrLastPart + i, 0x13, 0) | 0x80000000;
|
||||
if(HashLow != PtrLastPart[0])
|
||||
return ERROR_BAD_FORMAT;
|
||||
}
|
||||
@@ -533,7 +504,7 @@ static int LoadKeyMapping(PCASC_MAPPING_TABLE pKeyMapping, DWORD KeyIndex)
|
||||
{
|
||||
// Retrieve the file size
|
||||
FileStream_GetSize(pStream, &FileSize);
|
||||
if((0 < FileSize && FileSize <= 0x90000) || 1)
|
||||
if(0 < FileSize && FileSize <= 0x100000)
|
||||
{
|
||||
// WoW6 actually reads THE ENTIRE file to memory
|
||||
// Verified on Mac build (x64)
|
||||
@@ -664,79 +635,6 @@ static int CreateMapOfEncodingKeys(TCascStorage * hs, PFILE_ENCODING_SEGMENT pEn
|
||||
return nError;
|
||||
}
|
||||
|
||||
static DWORD GetSizeOfEncodingFile(HANDLE hFile)
|
||||
{
|
||||
CASC_ENCODING_HEADER EncodingHeader;
|
||||
DWORD cbEncodingFile = 0;
|
||||
DWORD dwSegmentPos;
|
||||
DWORD dwNumSegments;
|
||||
DWORD dwBytesRead;
|
||||
|
||||
// Read the endoding header
|
||||
CascReadFile(hFile, &EncodingHeader, sizeof(CASC_ENCODING_HEADER), &dwBytesRead);
|
||||
if(dwBytesRead == sizeof(CASC_ENCODING_HEADER))
|
||||
{
|
||||
dwNumSegments = ConvertBytesToInteger_4(EncodingHeader.NumSegments);
|
||||
dwSegmentPos = ConvertBytesToInteger_4(EncodingHeader.SegmentsPos);
|
||||
|
||||
cbEncodingFile = sizeof(CASC_ENCODING_HEADER) +
|
||||
dwSegmentPos +
|
||||
dwNumSegments * (sizeof(FILE_ENCODING_SEGMENT) + CASC_ENCODING_SEGMENT_SIZE);
|
||||
}
|
||||
|
||||
// Reset the position back
|
||||
CascSetFilePointer(hFile, 0, NULL, FILE_BEGIN);
|
||||
return cbEncodingFile;
|
||||
}
|
||||
|
||||
static LPBYTE LoadCascFile(HANDLE hFile, DWORD cbMaxSize, PDWORD pcbFileData)
|
||||
{
|
||||
LPBYTE pbFileData = NULL;
|
||||
DWORD cbFileData;
|
||||
DWORD dwBytesRead = 0;
|
||||
int nError = ERROR_SUCCESS;
|
||||
|
||||
// Retrieve the size of the file
|
||||
cbFileData = CascGetFileSize(hFile, NULL);
|
||||
if(cbFileData != 0 && cbFileData != CASC_INVALID_SIZE)
|
||||
{
|
||||
// Trim the size to the maximum
|
||||
cbFileData = CASCLIB_MIN(cbMaxSize, cbFileData);
|
||||
|
||||
// Allocate the buffer that will hold the entire file
|
||||
pbFileData = CASC_ALLOC(BYTE, cbFileData);
|
||||
if(pbFileData != NULL)
|
||||
{
|
||||
// Read the entire file to memory
|
||||
CascReadFile(hFile, pbFileData, cbFileData, &dwBytesRead);
|
||||
if(dwBytesRead != cbFileData)
|
||||
nError = ERROR_FILE_CORRUPT;
|
||||
}
|
||||
else
|
||||
nError = ERROR_NOT_ENOUGH_MEMORY;
|
||||
}
|
||||
else
|
||||
nError = ERROR_FILE_CORRUPT;
|
||||
|
||||
// If something failed, clean-up the buffers
|
||||
if(nError != ERROR_SUCCESS)
|
||||
{
|
||||
// Clear the file data
|
||||
if(pbFileData != NULL)
|
||||
CASC_FREE(pbFileData);
|
||||
pbFileData = NULL;
|
||||
cbFileData = 0;
|
||||
|
||||
// Set the last error value
|
||||
SetLastError(nError);
|
||||
}
|
||||
|
||||
// Return what we got
|
||||
if(pcbFileData != NULL)
|
||||
*pcbFileData = cbFileData;
|
||||
return pbFileData;
|
||||
}
|
||||
|
||||
static int LoadIndexFiles(TCascStorage * hs)
|
||||
{
|
||||
DWORD IndexArray[CASC_INDEX_COUNT];
|
||||
@@ -772,6 +670,113 @@ static int LoadIndexFiles(TCascStorage * hs)
|
||||
return nError;
|
||||
}
|
||||
|
||||
static LPBYTE LoadEncodingFileToMemory(HANDLE hFile, DWORD * pcbEncodingFile)
|
||||
{
|
||||
CASC_ENCODING_HEADER EncodingHeader;
|
||||
LPBYTE pbEncodingFile = NULL;
|
||||
DWORD cbEncodingFile = 0;
|
||||
DWORD dwSegmentPos = 0;
|
||||
DWORD dwNumSegments = 0;
|
||||
DWORD dwBytesRead;
|
||||
int nError = ERROR_BAD_FORMAT;
|
||||
|
||||
// Read the encoding header
|
||||
CascReadFile(hFile, &EncodingHeader, sizeof(CASC_ENCODING_HEADER), &dwBytesRead);
|
||||
if(dwBytesRead == sizeof(CASC_ENCODING_HEADER))
|
||||
{
|
||||
dwNumSegments = ConvertBytesToInteger_4(EncodingHeader.NumSegments);
|
||||
dwSegmentPos = ConvertBytesToInteger_4(EncodingHeader.SegmentsPos);
|
||||
if(EncodingHeader.Magic[0] == 'E' && EncodingHeader.Magic[1] == 'N' && dwSegmentPos != 0 && dwNumSegments != 0)
|
||||
nError = ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
// Calculate and allocate space for the entire file
|
||||
if(nError == ERROR_SUCCESS)
|
||||
{
|
||||
cbEncodingFile = sizeof(CASC_ENCODING_HEADER) +
|
||||
dwSegmentPos +
|
||||
dwNumSegments * (sizeof(FILE_ENCODING_SEGMENT) + CASC_ENCODING_SEGMENT_SIZE);
|
||||
pbEncodingFile = CASC_ALLOC(BYTE, cbEncodingFile);
|
||||
if(pbEncodingFile == NULL)
|
||||
nError = ERROR_NOT_ENOUGH_MEMORY;
|
||||
}
|
||||
|
||||
// If all went OK, we load the entire file to memory
|
||||
if(nError == ERROR_SUCCESS)
|
||||
{
|
||||
// Copy the header itself
|
||||
memcpy(pbEncodingFile, &EncodingHeader, sizeof(CASC_ENCODING_HEADER));
|
||||
|
||||
// Read the rest of the data
|
||||
CascReadFile(hFile, pbEncodingFile + sizeof(CASC_ENCODING_HEADER), cbEncodingFile - sizeof(CASC_ENCODING_HEADER), &dwBytesRead);
|
||||
if(dwBytesRead != (cbEncodingFile - sizeof(CASC_ENCODING_HEADER)))
|
||||
nError = ERROR_FILE_CORRUPT;
|
||||
}
|
||||
|
||||
// Give the loaded file length
|
||||
if(pcbEncodingFile != NULL)
|
||||
*pcbEncodingFile = cbEncodingFile;
|
||||
return pbEncodingFile;
|
||||
}
|
||||
|
||||
static LPBYTE LoadRootFileToMemory(HANDLE hFile, DWORD * pcbRootFile)
|
||||
{
|
||||
TCascFile * hf;
|
||||
LPBYTE pbRootFile = NULL;
|
||||
DWORD cbRootFile = 0;
|
||||
DWORD dwBytesRead;
|
||||
BYTE StartOfFile[0x10];
|
||||
int nError = ERROR_SUCCESS;
|
||||
|
||||
// Dummy read the first 16 bytes
|
||||
CascReadFile(hFile, &StartOfFile, sizeof(StartOfFile), &dwBytesRead);
|
||||
if(dwBytesRead != sizeof(StartOfFile))
|
||||
nError = ERROR_BAD_FORMAT;
|
||||
|
||||
// Calculate and allocate space for the entire file
|
||||
if(nError == ERROR_SUCCESS)
|
||||
{
|
||||
// Convert the file handle to pointer to TCascFile
|
||||
hf = IsValidFileHandle(hFile);
|
||||
if(hf != NULL)
|
||||
{
|
||||
// Parse the frames to get the file size
|
||||
for(DWORD i = 0; i < hf->FrameCount; i++)
|
||||
{
|
||||
cbRootFile += hf->pFrames[i].FrameSize;
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate the error
|
||||
nError = (cbRootFile != 0) ? ERROR_SUCCESS : ERROR_FILE_CORRUPT;
|
||||
}
|
||||
|
||||
// Allocate space for the entire file
|
||||
if(nError == ERROR_SUCCESS)
|
||||
{
|
||||
pbRootFile = CASC_ALLOC(BYTE, cbRootFile);
|
||||
if(pbRootFile == NULL)
|
||||
nError = ERROR_NOT_ENOUGH_MEMORY;
|
||||
}
|
||||
|
||||
// If all went OK, we load the entire file to memory
|
||||
if(nError == ERROR_SUCCESS)
|
||||
{
|
||||
// Copy the header itself
|
||||
memcpy(pbRootFile, StartOfFile, sizeof(StartOfFile));
|
||||
|
||||
// Read the rest of the data
|
||||
CascReadFile(hFile, pbRootFile + sizeof(StartOfFile), cbRootFile - sizeof(StartOfFile), &dwBytesRead);
|
||||
if(dwBytesRead != (cbRootFile - sizeof(StartOfFile)))
|
||||
nError = ERROR_FILE_CORRUPT;
|
||||
}
|
||||
|
||||
// Give the loaded file length
|
||||
if(pcbRootFile != NULL)
|
||||
*pcbRootFile = cbRootFile;
|
||||
return pbRootFile;
|
||||
}
|
||||
|
||||
static int LoadEncodingFile(TCascStorage * hs)
|
||||
{
|
||||
PFILE_ENCODING_SEGMENT pEncodingSegment;
|
||||
@@ -788,18 +793,15 @@ static int LoadEncodingFile(TCascStorage * hs)
|
||||
if(!CascOpenFileByIndexKey((HANDLE)hs, &hs->EncodingEKey, 0, &hFile))
|
||||
nError = GetLastError();
|
||||
|
||||
// Load the encoding file to memory
|
||||
// Load the entire ENCODING file to memory
|
||||
if(nError == ERROR_SUCCESS)
|
||||
{
|
||||
// Retrieve the CASC header. We do not usually need to load
|
||||
// the entire file, but we need to know how big part of it we need
|
||||
cbEncodingFile = GetSizeOfEncodingFile(hFile);
|
||||
|
||||
// Load the entire file to memory
|
||||
pbEncodingFile = LoadCascFile(hFile, cbEncodingFile, &cbEncodingFile);
|
||||
// Load the necessary part of the ENCODING file to memory
|
||||
pbEncodingFile = LoadEncodingFileToMemory(hFile, &cbEncodingFile);
|
||||
if(pbEncodingFile == NULL || cbEncodingFile <= sizeof(CASC_ENCODING_HEADER))
|
||||
nError = ERROR_FILE_CORRUPT;
|
||||
|
||||
// Close the encoding file
|
||||
CascCloseFile(hFile);
|
||||
}
|
||||
|
||||
@@ -859,91 +861,237 @@ static int LoadEncodingFile(TCascStorage * hs)
|
||||
return nError;
|
||||
}
|
||||
|
||||
static int LoadRootFile(TCascStorage * hs, LPBYTE pbRootFile, DWORD cbRootFile)
|
||||
typedef struct _CHECK_ROOT_ENTRY_INPUT
|
||||
{
|
||||
PFILE_ROOT_ENTRY pSrcEntry;
|
||||
PCASC_ROOT_ENTRY pTrgEntry;
|
||||
ULONGLONG FileNameHash;
|
||||
DWORD SumValue;
|
||||
DWORD EncodingKey[4];
|
||||
|
||||
} CHECK_ROOT_ENTRY_INPUT, *PCHECK_ROOT_ENTRY_INPUT;
|
||||
|
||||
typedef struct _CHECK_ROOT_ENTRY_OUTPUT
|
||||
{
|
||||
DWORD field_0;
|
||||
DWORD field_4;
|
||||
DWORD field_8;
|
||||
bool field_C;
|
||||
|
||||
} CHECK_ROOT_ENTRY_OUTPUT, *PCHECK_ROOT_ENTRY_OUTPUT;
|
||||
|
||||
|
||||
// WoW6: 00413F61
|
||||
static bool EnlargeHashTableIfMoreThan75PercentUsed(PCASC_ROOT_HASH_TABLE pRootTable, DWORD NewItemCount)
|
||||
{
|
||||
// Don't relocate anything, just check
|
||||
assert((double)NewItemCount / (double)pRootTable->TableSize < .75);
|
||||
return true;
|
||||
}
|
||||
|
||||
// WOW6: 00414402
|
||||
// Finds an existing root table entry or a free one
|
||||
PCASC_ROOT_ENTRY CascRootTable_FindFreeEntryWithEnlarge(
|
||||
PCASC_ROOT_HASH_TABLE pRootTable,
|
||||
PCASC_ROOT_ENTRY pNewEntry)
|
||||
{
|
||||
PCASC_ROOT_ENTRY pEntry;
|
||||
DWORD TableIndex;
|
||||
|
||||
// The table size must be a power of two
|
||||
assert((pRootTable->TableSize & (pRootTable->TableSize - 1)) == 0);
|
||||
|
||||
// Make sure that number of occupied items is never bigger
|
||||
// than 75% of the table size
|
||||
if(!EnlargeHashTableIfMoreThan75PercentUsed(pRootTable, pRootTable->ItemCount + 1))
|
||||
return NULL;
|
||||
|
||||
// Get the start index of the table
|
||||
TableIndex = (DWORD)(pNewEntry->FileNameHash) & (pRootTable->TableSize - 1);
|
||||
|
||||
// If that entry is already occupied, move to a next entry
|
||||
for(;;)
|
||||
{
|
||||
// Check that entry if it's free or not
|
||||
pEntry = pRootTable->TablePtr + TableIndex;
|
||||
if(pEntry->SumValue == 0)
|
||||
break;
|
||||
|
||||
// Is the found entry equal to the existing one?
|
||||
if(pEntry->FileNameHash == pNewEntry->FileNameHash)
|
||||
break;
|
||||
|
||||
// Move to the next entry
|
||||
TableIndex = (TableIndex + 1) & (pRootTable->TableSize - 1);
|
||||
}
|
||||
|
||||
// Either return a free entry or an existing one
|
||||
return pEntry;
|
||||
}
|
||||
|
||||
// WOW6: 004145D1
|
||||
static void CascRootTable_InsertTableEntry(
|
||||
PCASC_ROOT_HASH_TABLE pRootTable,
|
||||
PCASC_ROOT_ENTRY pNewEntry)
|
||||
{
|
||||
PCASC_ROOT_ENTRY pEntry;
|
||||
|
||||
// Find an existing entry or an empty one
|
||||
pEntry = CascRootTable_FindFreeEntryWithEnlarge(pRootTable, pNewEntry);
|
||||
assert(pEntry != NULL);
|
||||
|
||||
// If that entry is not used yet, fill it in
|
||||
if(pEntry->FileNameHash == 0)
|
||||
{
|
||||
*pEntry = *pNewEntry;
|
||||
pRootTable->ItemCount++;
|
||||
}
|
||||
}
|
||||
|
||||
static int LoadWowRootFileLocales(
|
||||
TCascStorage * hs,
|
||||
LPBYTE pbRootFile,
|
||||
DWORD cbRootFile,
|
||||
DWORD dwLocaleMask,
|
||||
bool bLoadBlocksWithFlags80,
|
||||
BYTE HighestBitValue)
|
||||
{
|
||||
CASC_ROOT_ENTRY NewRootEntry;
|
||||
ROOT_BLOCK_INFO BlockInfo;
|
||||
LPBYTE pbRootFileEnd = pbRootFile + cbRootFile;
|
||||
LPBYTE pbFilePointer;
|
||||
size_t nRootEntries = 0;
|
||||
size_t nRootIndex = 0;
|
||||
int nError = ERROR_NOT_ENOUGH_MEMORY;
|
||||
|
||||
// Calculate the root entries
|
||||
// Now parse the root file
|
||||
for(pbFilePointer = pbRootFile; pbFilePointer <= pbRootFileEnd; )
|
||||
{
|
||||
// Validate the root block
|
||||
// Validate the file locale block
|
||||
pbFilePointer = VerifyLocaleBlock(&BlockInfo, pbFilePointer, pbRootFileEnd);
|
||||
if(pbFilePointer == NULL)
|
||||
break;
|
||||
|
||||
// Add the number of entries
|
||||
nRootEntries = nRootEntries + BlockInfo.pLocaleBlockHdr->NumberOfFiles;
|
||||
}
|
||||
// WoW.exe (build 19116): Entries with flag 0x100 set are skipped
|
||||
if(BlockInfo.pLocaleBlockHdr->Flags & 0x100)
|
||||
continue;
|
||||
|
||||
// Create a linear array of the root entries and sort it
|
||||
hs->pRootEntries = pTrgEntry = CASC_ALLOC(CASC_ROOT_ENTRY, nRootEntries);
|
||||
hs->ppRootEntries = CASC_ALLOC(PCASC_ROOT_ENTRY, nRootEntries);
|
||||
if(hs->ppRootEntries && hs->pRootEntries)
|
||||
{
|
||||
// Convert each entry from FILE_ROOT_ENTRY to CASC_ROOT_ENTRY
|
||||
for(pbFilePointer = pbRootFile; pbFilePointer <= pbRootFileEnd; )
|
||||
// WoW.exe (build 19116): Entries with flag 0x80 set are skipped if arg_4 is set to FALSE (which is by default)
|
||||
if(bLoadBlocksWithFlags80 == 0 && (BlockInfo.pLocaleBlockHdr->Flags & 0x80))
|
||||
continue;
|
||||
|
||||
// WoW.exe (build 19116): Entries with (flags >> 0x1F) not equal to arg_8 are skipped
|
||||
if((BYTE)(BlockInfo.pLocaleBlockHdr->Flags >> 0x1F) != HighestBitValue)
|
||||
continue;
|
||||
|
||||
// WoW.exe (build 19116): Locales other than defined mask are skipped too
|
||||
if((BlockInfo.pLocaleBlockHdr->Locales & dwLocaleMask) == 0)
|
||||
continue;
|
||||
|
||||
// Reset the sum value
|
||||
NewRootEntry.SumValue = 0;
|
||||
|
||||
// WoW.exe (build 19116): Blocks with zero files are skipped
|
||||
for(DWORD i = 0; i < BlockInfo.pLocaleBlockHdr->NumberOfFiles; i++)
|
||||
{
|
||||
// Validate the root block
|
||||
pbFilePointer = VerifyLocaleBlock(&BlockInfo, pbFilePointer, pbRootFileEnd);
|
||||
if(pbFilePointer == NULL)
|
||||
break;
|
||||
// (004147A3) Prepare the CASC_ROOT_ENTRY structure
|
||||
NewRootEntry.FileNameHash = BlockInfo.pRootEntries[i].FileNameHash;
|
||||
NewRootEntry.SumValue = NewRootEntry.SumValue + BlockInfo.pInt32Array[i];
|
||||
NewRootEntry.Locales = BlockInfo.pLocaleBlockHdr->Locales;
|
||||
NewRootEntry.EncodingKey[0] = BlockInfo.pRootEntries[i].EncodingKey[0];
|
||||
NewRootEntry.EncodingKey[1] = BlockInfo.pRootEntries[i].EncodingKey[1];
|
||||
NewRootEntry.EncodingKey[2] = BlockInfo.pRootEntries[i].EncodingKey[2];
|
||||
NewRootEntry.EncodingKey[3] = BlockInfo.pRootEntries[i].EncodingKey[3];
|
||||
|
||||
// Get the pointer to the first root entry
|
||||
pSrcEntry = (PFILE_ROOT_ENTRY)BlockInfo.pRootEntries;
|
||||
|
||||
// Convert all entries
|
||||
for(DWORD i = 0; i < BlockInfo.pLocaleBlockHdr->NumberOfFiles; i++)
|
||||
{
|
||||
// Copy the root entry
|
||||
CopyFileKey(pTrgEntry->EncodingKey, pSrcEntry->EncodingKey);
|
||||
pTrgEntry->FileNameHash = pSrcEntry->FileNameHash;
|
||||
pTrgEntry->Locales = BlockInfo.pLocaleBlockHdr->Locales;
|
||||
pTrgEntry->Flags = BlockInfo.pLocaleBlockHdr->Flags;
|
||||
|
||||
// if(pTrgEntry->FileNameHash == 0x5ddb88608673f698ULL)
|
||||
// DebugBreak();
|
||||
|
||||
// Insert the CASC root entry to the linear array of pointers
|
||||
hs->ppRootEntries[nRootIndex++] = pTrgEntry;
|
||||
|
||||
// Move to the next root entry
|
||||
pSrcEntry++;
|
||||
pTrgEntry++;
|
||||
}
|
||||
// Insert the root table item to the hash table
|
||||
CascRootTable_InsertTableEntry(&hs->RootTable, &NewRootEntry);
|
||||
NewRootEntry.SumValue++;
|
||||
}
|
||||
|
||||
// Save the number of entries
|
||||
assert(nRootIndex == nRootEntries);
|
||||
hs->nRootEntries = nRootIndex;
|
||||
|
||||
// Now sort the array
|
||||
qsort_pointer_array((void **)hs->ppRootEntries, hs->nRootEntries, CompareRootEntries, NULL);
|
||||
nError = ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
return nError;
|
||||
|
||||
/*
|
||||
FILE * fp = fopen("E:\\root_entries.txt", "wt");
|
||||
if(fp != NULL)
|
||||
{
|
||||
for(size_t i = 0; i < nRootEntries; i++)
|
||||
{
|
||||
fprintf(fp, "%08X: %016I64lX\n", i, hs->ppRootEntries[i]->FileNameHash);
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
*/
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int LoadRootFile(TCascStorage * hs)
|
||||
// WoW.exe: 004146C7 (BuildManifest::Load)
|
||||
static int LoadWowRootFileWithParams(
|
||||
TCascStorage * hs,
|
||||
LPBYTE pbRootFile,
|
||||
DWORD cbRootFile,
|
||||
DWORD dwLocaleBits,
|
||||
BYTE HighestBitValue)
|
||||
{
|
||||
// Load the locale as-is
|
||||
LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, dwLocaleBits, false, HighestBitValue);
|
||||
|
||||
// If we wanted enGB, we also load enUS for the missing files
|
||||
if(dwLocaleBits == CASC_LOCALE_ENGB)
|
||||
LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_ENUS, false, HighestBitValue);
|
||||
|
||||
if(dwLocaleBits == CASC_LOCALE_PTPT)
|
||||
LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_PTBR, false, HighestBitValue);
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
// Code from WoW.exe
|
||||
if(dwLocaleBits == CASC_LOCALE_DUAL_LANG)
|
||||
{
|
||||
// Is this english version of WoW?
|
||||
if(arg_4 == CASC_LOCALE_BIT_ENUS)
|
||||
{
|
||||
LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_ENGB, false, HighestBitValue);
|
||||
LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_ENUS, false, HighestBitValue);
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
// Is this portuguese version of WoW?
|
||||
if(arg_4 == CASC_LOCALE_BIT_PTBR)
|
||||
{
|
||||
LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_PTPT, false, HighestBitValue);
|
||||
LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, CASC_LOCALE_PTBR, false, HighestBitValue);
|
||||
}
|
||||
}
|
||||
|
||||
LoadWowRootFileLocales(hs, pbRootFile, cbRootFile, (1 << arg_4), false, HighestBitValue);
|
||||
*/
|
||||
|
||||
static int LoadWowRootFile(
|
||||
TCascStorage * hs,
|
||||
LPBYTE pbRootFile,
|
||||
DWORD cbRootFile,
|
||||
DWORD dwLocaleMask)
|
||||
{
|
||||
int nError;
|
||||
|
||||
// Dump the root file, if needed
|
||||
#ifdef CASC_DUMP_ROOT_FILE
|
||||
//CascDumpRootFile(hs,
|
||||
// pbRootFile,
|
||||
// cbRootFile,
|
||||
// "\\casc_root_%build%.txt",
|
||||
// _T("\\Ladik\\Appdir\\CascLib\\listfile\\listfile-wow6.txt"),
|
||||
// CASC_DUMP_ROOT_FILE);
|
||||
#endif
|
||||
|
||||
// Allocate root table entries. Note that the initial size
|
||||
// of the root table is set to 0x00200000 by World of Warcraft 6.x
|
||||
hs->RootTable.TablePtr = CASC_ALLOC(CASC_ROOT_ENTRY, CASC_INITIAL_ROOT_TABLE_SIZE);
|
||||
hs->RootTable.TableSize = CASC_INITIAL_ROOT_TABLE_SIZE;
|
||||
if(hs->RootTable.TablePtr == NULL)
|
||||
return ERROR_NOT_ENOUGH_MEMORY;
|
||||
|
||||
// Clear the entire table
|
||||
memset(hs->RootTable.TablePtr, 0, CASC_INITIAL_ROOT_TABLE_SIZE * sizeof(CASC_ROOT_ENTRY));
|
||||
|
||||
// Load the root file
|
||||
nError = LoadWowRootFileWithParams(hs, pbRootFile, cbRootFile, dwLocaleMask, 0);
|
||||
if(nError != ERROR_SUCCESS)
|
||||
return nError;
|
||||
|
||||
nError = LoadWowRootFileWithParams(hs, pbRootFile, cbRootFile, dwLocaleMask, 1);
|
||||
if(nError != ERROR_SUCCESS)
|
||||
return nError;
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
static int LoadRootFile(TCascStorage * hs, DWORD dwLocaleMask)
|
||||
{
|
||||
PDWORD FileSignature;
|
||||
HANDLE hFile = NULL;
|
||||
@@ -952,9 +1100,14 @@ static int LoadRootFile(TCascStorage * hs)
|
||||
int nError = ERROR_SUCCESS;
|
||||
|
||||
// Sanity checks
|
||||
assert(hs->RootTable.TablePtr == NULL);
|
||||
assert(hs->RootTable.ItemCount == 0);
|
||||
assert(hs->ppEncodingEntries != NULL);
|
||||
assert(hs->pRootEntries == NULL);
|
||||
assert(hs->nRootEntries == 0);
|
||||
|
||||
// Locale: The default parameter is 0 - in that case,
|
||||
// we load enUS+enGB
|
||||
if(dwLocaleMask == 0)
|
||||
dwLocaleMask = CASC_LOCALE_ENUS | CASC_LOCALE_ENGB;
|
||||
|
||||
// The root file is either MNDX file (Heroes of the Storm)
|
||||
// or a file containing an array of root entries (World of Warcraft 6.0+)
|
||||
@@ -963,15 +1116,15 @@ static int LoadRootFile(TCascStorage * hs)
|
||||
if(!CascOpenFileByEncodingKey((HANDLE)hs, &hs->RootKey, 0, &hFile))
|
||||
nError = GetLastError();
|
||||
|
||||
// Load ther entire root file to memory
|
||||
// Load the entire ROOT file to memory
|
||||
if(nError == ERROR_SUCCESS)
|
||||
{
|
||||
// Load the entire root file to memory
|
||||
pbRootFile = LoadCascFile(hFile, 0xFFFFFFFF, &cbRootFile);
|
||||
if(pbRootFile == NULL || cbRootFile == 0)
|
||||
// Load the necessary part of the ENCODING file to memory
|
||||
pbRootFile = LoadRootFileToMemory(hFile, &cbRootFile);
|
||||
if(pbRootFile == NULL || cbRootFile <= sizeof(PFILE_LOCALE_BLOCK))
|
||||
nError = ERROR_FILE_CORRUPT;
|
||||
|
||||
// Close the root file
|
||||
// Close the encoding file
|
||||
CascCloseFile(hFile);
|
||||
}
|
||||
|
||||
@@ -985,7 +1138,8 @@ static int LoadRootFile(TCascStorage * hs)
|
||||
}
|
||||
else
|
||||
{
|
||||
nError = LoadRootFile(hs, pbRootFile, cbRootFile);
|
||||
// WOW6: 00415000
|
||||
nError = LoadWowRootFile(hs, pbRootFile, cbRootFile, dwLocaleMask);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1007,10 +1161,8 @@ static TCascStorage * FreeCascStorage(TCascStorage * hs)
|
||||
FreeMndxInfo(hs->pMndxInfo);
|
||||
|
||||
// Free the pointers to file entries
|
||||
if(hs->ppRootEntries != NULL)
|
||||
CASC_FREE(hs->ppRootEntries);
|
||||
if(hs->pRootEntries != NULL)
|
||||
CASC_FREE(hs->pRootEntries);
|
||||
if(hs->RootTable.TablePtr != NULL)
|
||||
CASC_FREE(hs->RootTable.TablePtr);
|
||||
if(hs->ppEncodingEntries != NULL)
|
||||
CASC_FREE(hs->ppEncodingEntries);
|
||||
if(hs->pEncodingHeader != NULL)
|
||||
@@ -1074,13 +1226,11 @@ static TCascStorage * FreeCascStorage(TCascStorage * hs)
|
||||
//-----------------------------------------------------------------------------
|
||||
// Public functions
|
||||
|
||||
bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwFlags, HANDLE * phStorage)
|
||||
bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwLocaleMask, HANDLE * phStorage)
|
||||
{
|
||||
TCascStorage * hs;
|
||||
int nError = ERROR_SUCCESS;
|
||||
|
||||
CASCLIB_UNUSED(dwFlags);
|
||||
|
||||
// Allocate the storage structure
|
||||
hs = (TCascStorage *)CASC_ALLOC(TCascStorage, 1);
|
||||
if(hs == NULL)
|
||||
@@ -1092,6 +1242,7 @@ bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwFlags, HANDLE * ph
|
||||
// Prepare the base storage parameters
|
||||
memset(hs, 0, sizeof(TCascStorage));
|
||||
hs->szClassName = "TCascStorage";
|
||||
hs->dwFileBeginDelta = 0xFFFFFFFF;
|
||||
hs->dwRefCount = 1;
|
||||
nError = InitializeCascDirectories(hs, szDataPath);
|
||||
}
|
||||
@@ -1099,7 +1250,7 @@ bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwFlags, HANDLE * ph
|
||||
// Now we need to load the root file so we know the config files
|
||||
if(nError == ERROR_SUCCESS)
|
||||
{
|
||||
nError = LoadBuildConfiguration(hs);
|
||||
nError = LoadBuildInfo(hs);
|
||||
}
|
||||
|
||||
// Load the index files
|
||||
@@ -1117,17 +1268,9 @@ bool WINAPI CascOpenStorage(const TCHAR * szDataPath, DWORD dwFlags, HANDLE * ph
|
||||
// Load the index files
|
||||
if(nError == ERROR_SUCCESS)
|
||||
{
|
||||
nError = LoadRootFile(hs);
|
||||
nError = LoadRootFile(hs, dwLocaleMask);
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
// if(nError == ERROR_SUCCESS)
|
||||
// {
|
||||
// CascDumpStorage("E:\\casc_dump.txt", hs, _T("e:\\Ladik\\Appdir\\CascLib\\listfile\\listfile-wow6.txt"));
|
||||
// CascDumpIndexEntries("E:\\casc_index.txt", hs);
|
||||
// }
|
||||
#endif
|
||||
|
||||
// If something failed, free the storage and return
|
||||
if(nError != ERROR_SUCCESS)
|
||||
{
|
||||
@@ -1147,7 +1290,7 @@ bool WINAPI CascGetStorageInfo(
|
||||
size_t * pcbLengthNeeded)
|
||||
{
|
||||
TCascStorage * hs;
|
||||
DWORD dwCascFeatures = 0;
|
||||
DWORD dwInfoValue = 0;
|
||||
|
||||
// Verify the storage handle
|
||||
hs = IsValidStorageHandle(hStorage);
|
||||
@@ -1161,41 +1304,41 @@ bool WINAPI CascGetStorageInfo(
|
||||
switch(InfoClass)
|
||||
{
|
||||
case CascStorageFileCount:
|
||||
|
||||
// Check the buffer size
|
||||
if(cbStorageInfo < sizeof(DWORD))
|
||||
{
|
||||
*pcbLengthNeeded = sizeof(DWORD);
|
||||
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Give the number of files
|
||||
*(PDWORD)pvStorageInfo = (DWORD)hs->pIndexEntryMap->ItemCount;
|
||||
return true;
|
||||
dwInfoValue = (DWORD)hs->pIndexEntryMap->ItemCount;
|
||||
break;
|
||||
|
||||
case CascStorageFeatures:
|
||||
|
||||
// Check the buffer size
|
||||
if(cbStorageInfo < sizeof(DWORD))
|
||||
{
|
||||
*pcbLengthNeeded = sizeof(DWORD);
|
||||
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Construct the features
|
||||
if(hs->pMndxInfo != NULL)
|
||||
dwCascFeatures |= CASC_FEATURE_LISTFILE;
|
||||
dwInfoValue |= CASC_FEATURE_LISTFILE;
|
||||
break;
|
||||
|
||||
// Give the number of files
|
||||
*(PDWORD)pvStorageInfo = dwCascFeatures;
|
||||
return true;
|
||||
case CascStorageGameInfo:
|
||||
dwInfoValue = hs->dwGameInfo;
|
||||
break;
|
||||
|
||||
case CascStorageGameBuild:
|
||||
dwInfoValue = hs->dwBuildNumber;
|
||||
break;
|
||||
|
||||
default:
|
||||
SetLastError(ERROR_INVALID_PARAMETER);
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Return the required DWORD value
|
||||
//
|
||||
|
||||
if(cbStorageInfo < sizeof(DWORD))
|
||||
{
|
||||
*pcbLengthNeeded = sizeof(DWORD);
|
||||
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Give the number of files
|
||||
*(PDWORD)pvStorageInfo = dwInfoValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -15,28 +15,11 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Local structures
|
||||
|
||||
#define BLTE_HEADER_SIGNATURE 0x45544C42
|
||||
|
||||
// Data file begin:
|
||||
// BYTE HeaderHash[MD5_HASH_SIZE]; // MD5 of the frame array
|
||||
// DWORD dwFileSize; // Size of the file
|
||||
// BYTE SomeSize[4]; // Some size (big endian)
|
||||
// BYTE Padding[6]; // Padding (?)
|
||||
|
||||
typedef struct _BLTE_HEADER
|
||||
{
|
||||
DWORD dwSignature; // Must be "BLTE"
|
||||
BYTE HeaderSizeAsBytes[4]; // Header size in bytes (big endian)
|
||||
BYTE MustBe0F; // Must be 0x0F
|
||||
BYTE FrameCount[3]; // Number of frames (big endian)
|
||||
|
||||
} BLTE_HEADER, *PBLTE_HEADER;
|
||||
|
||||
typedef struct _BLTE_FRAME
|
||||
{
|
||||
BYTE CompressedSize[4]; // Compressed file size as big endian
|
||||
BYTE FrameSize[4]; // File size as big endian
|
||||
BYTE md5[MD5_HASH_SIZE]; // Hash of the frame
|
||||
BYTE md5[MD5_HASH_SIZE]; // Hash of the compressed frame
|
||||
|
||||
} BLTE_FRAME, *PBLTE_FRAME;
|
||||
|
||||
@@ -65,11 +48,6 @@ static int EnsureDataStreamIsOpen(TCascFile * hf)
|
||||
// Open the stream
|
||||
pStream = FileStream_OpenFile(szDataFile, STREAM_FLAG_READ_ONLY | STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE);
|
||||
hs->DataFileArray[hf->ArchiveIndex] = pStream;
|
||||
|
||||
// TODO: There is 0x1E bytes at the beginning of the file stream
|
||||
// Ignore them for now, but we will want to know what they mean
|
||||
// Offs0000: MD5 of something
|
||||
// Offs0010: 2 bytes
|
||||
CASC_FREE(szDataFile);
|
||||
}
|
||||
}
|
||||
@@ -79,7 +57,7 @@ static int EnsureDataStreamIsOpen(TCascFile * hf)
|
||||
return (hf->pStream != NULL) ? ERROR_SUCCESS : ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
static int LoadFileFrames(TCascFile * hf, DWORD FrameCount)
|
||||
static int LoadFileFrames(TCascFile * hf)
|
||||
{
|
||||
PBLTE_FRAME pFileFrames;
|
||||
PBLTE_FRAME pFileFrame;
|
||||
@@ -93,40 +71,41 @@ static int LoadFileFrames(TCascFile * hf, DWORD FrameCount)
|
||||
assert(hf->pFrames != NULL);
|
||||
|
||||
// Allocate frame array
|
||||
pFileFrames = pFileFrame = CASC_ALLOC(BLTE_FRAME, FrameCount);
|
||||
pFileFrames = pFileFrame = CASC_ALLOC(BLTE_FRAME, hf->FrameCount);
|
||||
if(pFileFrames != NULL)
|
||||
{
|
||||
// Load the frame array
|
||||
ArchiveFileOffset = hf->FramesOffset;
|
||||
if(FileStream_Read(hf->pStream, &ArchiveFileOffset, pFileFrames, FrameCount * sizeof(BLTE_FRAME)))
|
||||
if(FileStream_Read(hf->pStream, &ArchiveFileOffset, pFileFrames, hf->FrameCount * sizeof(BLTE_FRAME)))
|
||||
{
|
||||
// Move the raw archive offset
|
||||
ArchiveFileOffset += (hf->FrameCount * sizeof(BLTE_FRAME));
|
||||
|
||||
// Copy the frames to the file structure
|
||||
for(DWORD i = 0; i < FrameCount; i++, pFileFrame++)
|
||||
for(DWORD i = 0; i < hf->FrameCount; i++, pFileFrame++)
|
||||
{
|
||||
hf->pFrames[i].FrameArchiveOffset = (DWORD)ArchiveFileOffset;
|
||||
hf->pFrames[i].FrameFileOffset = FrameOffset;
|
||||
hf->pFrames[i].CompressedSize = ConvertBytesToInteger_4(pFileFrame->CompressedSize);
|
||||
hf->pFrames[i].FrameSize = ConvertBytesToInteger_4(pFileFrame->FrameSize);
|
||||
hf->pFrames[i].FrameSize = ConvertBytesToInteger_4(pFileFrame->FrameSize);
|
||||
memcpy(hf->pFrames[i].md5, pFileFrame->md5, MD5_HASH_SIZE);
|
||||
|
||||
ArchiveFileOffset += hf->pFrames[i].CompressedSize;
|
||||
FrameOffset += hf->pFrames[i].FrameSize;
|
||||
FileSize += hf->pFrames[i].FrameSize;
|
||||
}
|
||||
|
||||
// Fill-in the frame count
|
||||
hf->FrameCount = FrameCount;
|
||||
}
|
||||
else
|
||||
nError = GetLastError();
|
||||
|
||||
// Verify the file size
|
||||
// assert(FileSize == hf->FileSize);
|
||||
// Note: Do not take the FileSize from the sum of frames.
|
||||
// This value is invalid when loading the ENCODING file.
|
||||
// hf->FileSize = FileSize;
|
||||
|
||||
#ifdef CASCLIB_TEST
|
||||
hf->FileSize_FrameSum = FileSize;
|
||||
#endif
|
||||
|
||||
// Free the array
|
||||
CASC_FREE(pFileFrames);
|
||||
}
|
||||
@@ -136,66 +115,115 @@ static int LoadFileFrames(TCascFile * hf, DWORD FrameCount)
|
||||
return nError;
|
||||
}
|
||||
|
||||
static int EnsureHeaderAreaIsLoaded(TCascFile * hf)
|
||||
{
|
||||
TCascStorage * hs = hf->hs;
|
||||
ULONGLONG FileOffset = hf->HeaderOffset;
|
||||
LPBYTE pbHeaderArea;
|
||||
DWORD FileSignature;
|
||||
DWORD FileSize;
|
||||
BYTE HeaderArea[MAX_HEADER_AREA_SIZE];
|
||||
int nError;
|
||||
|
||||
// We need the data file to be open
|
||||
nError = EnsureDataStreamIsOpen(hf);
|
||||
if(nError != ERROR_SUCCESS)
|
||||
return nError;
|
||||
|
||||
// Make sure that we already know the shift
|
||||
// to the begin of file data.
|
||||
// Note that older builds of Heroes of the Storm have entries pointing
|
||||
// to the beginning of the header area.
|
||||
// Newer versions of HOTS have encoding entries pointing directly to
|
||||
// the BLTE header
|
||||
if(hs->dwFileBeginDelta == 0xFFFFFFFF)
|
||||
{
|
||||
FileSignature = 0;
|
||||
FileOffset = hf->HeaderOffset;
|
||||
if(!FileStream_Read(hf->pStream, &FileOffset, &FileSignature, sizeof(DWORD)))
|
||||
return ERROR_FILE_CORRUPT;
|
||||
|
||||
hs->dwFileBeginDelta = (FileSignature == BLTE_HEADER_SIGNATURE) ? BLTE_HEADER_DELTA : 0;
|
||||
}
|
||||
|
||||
// If the file size is not loaded yet, do it
|
||||
if(hf->FrameCount == 0)
|
||||
{
|
||||
// Load the part before BLTE header + header itself
|
||||
FileOffset = hf->HeaderOffset - hs->dwFileBeginDelta;
|
||||
if(!FileStream_Read(hf->pStream, &FileOffset, HeaderArea, sizeof(HeaderArea)))
|
||||
return ERROR_FILE_CORRUPT;
|
||||
|
||||
// Copy the MD5 hash of the frame array
|
||||
memcpy(hf->FrameArrayHash, HeaderArea, MD5_HASH_SIZE);
|
||||
pbHeaderArea = HeaderArea + MD5_HASH_SIZE;
|
||||
|
||||
// Copy the file size
|
||||
FileSize = ConvertBytesToInteger_4_LE(pbHeaderArea);
|
||||
pbHeaderArea += 0x0E;
|
||||
|
||||
// Verify the BLTE signature
|
||||
if(ConvertBytesToInteger_4_LE(pbHeaderArea) != BLTE_HEADER_SIGNATURE)
|
||||
return ERROR_BAD_FORMAT;
|
||||
pbHeaderArea += sizeof(DWORD);
|
||||
|
||||
// Load the size of the frame headers
|
||||
hf->HeaderSize = ConvertBytesToInteger_4(pbHeaderArea);
|
||||
if(hf->HeaderSize & 0x80000000)
|
||||
return ERROR_BAD_FORMAT;
|
||||
pbHeaderArea += sizeof(DWORD);
|
||||
|
||||
// Read the header size
|
||||
assert(hs->dwFileBeginDelta <= BLTE_HEADER_DELTA);
|
||||
hf->HeaderOffset += (BLTE_HEADER_DELTA - hs->dwFileBeginDelta);
|
||||
hf->FrameCount = 1;
|
||||
|
||||
// Retrieve the frame count, if different from 1
|
||||
if(hf->HeaderSize != 0)
|
||||
{
|
||||
// The next byte must be 0x0F
|
||||
if(pbHeaderArea[0] != 0x0F)
|
||||
return ERROR_BAD_FORMAT;
|
||||
pbHeaderArea++;
|
||||
|
||||
// Next three bytes form number of frames
|
||||
hf->FrameCount = ConvertBytesToInteger_3(pbHeaderArea);
|
||||
}
|
||||
|
||||
#ifdef CASCLIB_TEST
|
||||
hf->FileSize_HdrArea = FileSize;
|
||||
#endif
|
||||
}
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
static int EnsureFrameHeadersLoaded(TCascFile * hf)
|
||||
{
|
||||
PBLTE_HEADER pBlteHeader;
|
||||
ULONGLONG FileOffset = hf->HeaderOffset;
|
||||
DWORD dwHeaderOffsetFixup = 0;
|
||||
DWORD dwFrameHeaderSize;
|
||||
DWORD dwFrameCount;
|
||||
BYTE HeaderBuffer[sizeof(BLTE_HEADER) + 0x20];
|
||||
int nError = ERROR_SUCCESS;
|
||||
int nError;
|
||||
|
||||
// Sanity check
|
||||
assert(hf->pStream != NULL);
|
||||
// Make sure we have header area loaded
|
||||
nError = EnsureHeaderAreaIsLoaded(hf);
|
||||
if(nError != ERROR_SUCCESS)
|
||||
return nError;
|
||||
|
||||
// If the frame headers are not loaded yet, do it
|
||||
if(hf->pFrames == NULL)
|
||||
{
|
||||
// Note that older builds of Heroes of the Storm have entries pointing
|
||||
// to the begin of the BLTE header, which is MD5 + some junk.
|
||||
// Newer versions of HOTS have encoding entries pointing directly to
|
||||
// the BLTE header
|
||||
FileStream_Read(hf->pStream, &FileOffset, HeaderBuffer, sizeof(HeaderBuffer));
|
||||
pBlteHeader = (PBLTE_HEADER)HeaderBuffer;
|
||||
|
||||
// If we don't have the BLTE header right there,
|
||||
// just get the block that is 0x1E bytes later
|
||||
if(pBlteHeader->dwSignature != BLTE_HEADER_SIGNATURE)
|
||||
{
|
||||
memcpy(&HeaderBuffer[0x00], &HeaderBuffer[0x1E], sizeof(BLTE_HEADER));
|
||||
dwHeaderOffsetFixup = 0x1E;
|
||||
}
|
||||
|
||||
// Check for the BLTE header signature
|
||||
if(pBlteHeader->dwSignature != BLTE_HEADER_SIGNATURE)
|
||||
return ERROR_BAD_FORMAT;
|
||||
hf->HeaderOffset += dwHeaderOffsetFixup;
|
||||
|
||||
// Check for a single unit file
|
||||
dwFrameHeaderSize = ConvertBytesToInteger_4(pBlteHeader->HeaderSizeAsBytes);
|
||||
dwFrameCount = (dwFrameHeaderSize != 0) ? ConvertBytesToInteger_3(pBlteHeader->FrameCount) : 1;
|
||||
|
||||
// Allocate the frame array
|
||||
hf->pFrames = CASC_ALLOC(CASC_FILE_FRAME, dwFrameCount);
|
||||
hf->pFrames = CASC_ALLOC(CASC_FILE_FRAME, hf->FrameCount);
|
||||
if(hf->pFrames != NULL)
|
||||
{
|
||||
// Save the number of frames
|
||||
hf->FrameCount = dwFrameCount;
|
||||
|
||||
// Either load the frames from the file or supply them on our own
|
||||
if(dwFrameHeaderSize != 0)
|
||||
if(hf->HeaderSize != 0)
|
||||
{
|
||||
if(pBlteHeader->MustBe0F != 0x0F)
|
||||
return ERROR_FILE_CORRUPT;
|
||||
|
||||
hf->FramesOffset = hf->HeaderOffset + sizeof(BLTE_HEADER);
|
||||
nError = LoadFileFrames(hf, dwFrameCount);
|
||||
hf->FramesOffset = hf->HeaderOffset + sizeof(DWORD) + sizeof(DWORD) + sizeof(DWORD);
|
||||
nError = LoadFileFrames(hf);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Offset of the first frame is right after the file frames
|
||||
hf->FramesOffset = hf->HeaderOffset + sizeof(pBlteHeader->dwSignature) + sizeof(pBlteHeader->HeaderSizeAsBytes);
|
||||
hf->FramesOffset = hf->HeaderOffset + sizeof(DWORD) + sizeof(DWORD);
|
||||
|
||||
hf->pFrames[0].FrameArchiveOffset = hf->FramesOffset;
|
||||
hf->pFrames[0].FrameFileOffset = 0;
|
||||
@@ -239,9 +267,27 @@ static PCASC_FILE_FRAME FindFileFrame(TCascFile * hf, DWORD FilePointer)
|
||||
//-----------------------------------------------------------------------------
|
||||
// Public functions
|
||||
|
||||
//
|
||||
// THE FILE SIZE PROBLEM
|
||||
//
|
||||
// There are members called "FileSize" in many CASC-related structure
|
||||
// For various files, these variables have different meaning.
|
||||
//
|
||||
// Storage FileName FileSize FrameSum HdrArea IdxEntry EncEntry RootEntry
|
||||
// ----------- -------- ---------- -------- -------- -------- -------- ---------
|
||||
// HotS(29049) ENCODING 0x0024BA45 - 0x0024b98a 0x0024BA45 0x0024BA45 n/a n/a
|
||||
// HotS(29049) ROOT 0x00193340 - 0x00193340 0x0010db65 0x0010db65 0x00193340 n/a
|
||||
// HotS(29049) (other) 0x00001080 - 0x00001080 0x000008eb 0x000008eb 0x00001080 0x00001080
|
||||
//
|
||||
// WoW(18888) ENCODING 0x030d487b - 0x030dee79 0x030d487b 0x030d487b n/a n/a
|
||||
// WoW(18888) ROOT 0x016a9800 - n/a 0x0131313d 0x0131313d 0x016a9800 n/a
|
||||
// WoW(18888) (other) 0x000007d0 - 0x000007d0 0x00000397 0x00000397 0x000007d0 n/a
|
||||
//
|
||||
|
||||
DWORD WINAPI CascGetFileSize(HANDLE hFile, PDWORD pdwFileSizeHigh)
|
||||
{
|
||||
TCascFile * hf;
|
||||
int nError;
|
||||
|
||||
CASCLIB_UNUSED(pdwFileSizeHigh);
|
||||
|
||||
@@ -252,6 +298,14 @@ DWORD WINAPI CascGetFileSize(HANDLE hFile, PDWORD pdwFileSizeHigh)
|
||||
return CASC_INVALID_SIZE;
|
||||
}
|
||||
|
||||
// Make sure that the file header area is loaded
|
||||
nError = EnsureHeaderAreaIsLoaded(hf);
|
||||
if(nError != ERROR_SUCCESS)
|
||||
{
|
||||
SetLastError(nError);
|
||||
return CASC_INVALID_SIZE;
|
||||
}
|
||||
|
||||
// Give the file size to the caller
|
||||
if(pdwFileSizeHigh != NULL)
|
||||
*pdwFileSizeHigh = 0;
|
||||
@@ -348,25 +402,19 @@ bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDW
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the file position is at or beyond end of file, do nothing
|
||||
if(hf->FilePointer >= hf->FileSize)
|
||||
{
|
||||
*pdwBytesRead = 0;
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
// Make sure we have that data file open
|
||||
if(nError == ERROR_SUCCESS)
|
||||
{
|
||||
nError = EnsureDataStreamIsOpen(hf);
|
||||
}
|
||||
|
||||
// If the file frames are not loaded yet, do it now
|
||||
if(nError == ERROR_SUCCESS)
|
||||
{
|
||||
nError = EnsureFrameHeadersLoaded(hf);
|
||||
}
|
||||
|
||||
// If the file position is at or beyond end of file, do nothing
|
||||
if(nError == ERROR_SUCCESS && hf->FilePointer >= hf->FileSize)
|
||||
{
|
||||
*pdwBytesRead = 0;
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
// Find the file frame where to read from
|
||||
if(nError == ERROR_SUCCESS)
|
||||
{
|
||||
@@ -423,7 +471,7 @@ bool WINAPI CascReadFile(HANDLE hFile, void * pvBuffer, DWORD dwBytesToRead, PDW
|
||||
}
|
||||
|
||||
// Verify the block MD5
|
||||
if(IsValidMD5(pFrame->md5) && !VerifyDataBlockHash(pbRawData, pFrame->CompressedSize, pFrame->md5))
|
||||
if(!VerifyDataBlockHash(pbRawData, pFrame->CompressedSize, pFrame->md5))
|
||||
{
|
||||
CASC_FREE(pbRawData);
|
||||
nError = ERROR_FILE_CORRUPT;
|
||||
|
||||
@@ -61,20 +61,6 @@ unsigned char AsciiToUpperTable[256] =
|
||||
|
||||
unsigned char IntToHexChar[] = "0123456789abcdef";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Support for memory reallocation
|
||||
|
||||
#if defined(_MSC_VER) && defined(_DEBUG)
|
||||
void * DbgRealloc(void * ptr, size_t nSize)
|
||||
{
|
||||
// HeapReAlloc does not support NULL as previous block
|
||||
if(ptr == NULL)
|
||||
return HeapAlloc(GetProcessHeap, 0, nSize);
|
||||
|
||||
return HeapReAlloc(GetProcessHeap(), 0, ptr, nSize);
|
||||
}
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// GetLastError/SetLastError support for non-Windows platform
|
||||
|
||||
@@ -232,11 +218,14 @@ TCHAR * CombinePath(const TCHAR * szDirectory, const TCHAR * szSubDir)
|
||||
return szFullPath;
|
||||
}
|
||||
|
||||
void NormalizeFileName_UpperBkSlash(char * szFileName)
|
||||
void NormalizeFileName_UpperBkSlash(const char * szSrcFileName, char * szTrgFileName)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
// Normalize the file name: ToLower + BackSlashToSlash
|
||||
for(size_t i = 0; szFileName[i] != 0; i++)
|
||||
szFileName[i] = AsciiToUpperTable[szFileName[i]];
|
||||
for(i = 0; szSrcFileName[i] != 0; i++)
|
||||
szTrgFileName[i] = AsciiToUpperTable[szSrcFileName[i]];
|
||||
szTrgFileName[i] = 0;
|
||||
}
|
||||
|
||||
void NormalizeFileName_LowerSlash(char * szFileName)
|
||||
|
||||
@@ -28,13 +28,6 @@ extern unsigned char AsciiToLowerTable[256];
|
||||
extern unsigned char AsciiToUpperTable[256];
|
||||
extern unsigned char IntToHexChar[];
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Memory management helper
|
||||
|
||||
#if defined(_MSC_VER) && defined(_DEBUG)
|
||||
void * DbgRealloc(void * ptr, size_t nSize);
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// GetLastError/SetLastError support for non-Windows platform
|
||||
|
||||
@@ -57,7 +50,7 @@ TCHAR * NewStrFromAnsi(LPBYTE pbStringBegin, LPBYTE pbStringEnd);
|
||||
|
||||
TCHAR * CombinePath(const TCHAR * szPath, const TCHAR * szSubDir);
|
||||
|
||||
void NormalizeFileName_UpperBkSlash(char * szFileName);
|
||||
void NormalizeFileName_UpperBkSlash(const char * szSrcFileName, char * szTrgFileName);
|
||||
void NormalizeFileName_LowerSlash(char * szFileName);
|
||||
|
||||
int ConvertDigitToInt32(const TCHAR * szString, PDWORD PtrValue);
|
||||
|
||||
@@ -2551,6 +2551,14 @@ bool FileStream_SetCallback(TFileStream * pStream, STREAM_DOWNLOAD_CALLBACK pfnC
|
||||
*/
|
||||
bool FileStream_Read(TFileStream * pStream, ULONGLONG * pByteOffset, void * pvBuffer, DWORD dwBytesToRead)
|
||||
{
|
||||
//FILE * fp = fopen("E:\\Loading.txt", "at");
|
||||
//if(fp != NULL)
|
||||
//{
|
||||
// ULONGLONG ByteOffset = (pByteOffset != NULL) ? pByteOffset[0] : 0;
|
||||
// fprintf(fp, "%-32ws\t%08X\t%08X\n", GetPlainFileName(pStream->szFileName), (ULONG)ByteOffset, dwBytesToRead);
|
||||
// fclose(fp);
|
||||
//}
|
||||
|
||||
assert(pStream->StreamRead != NULL);
|
||||
return pStream->StreamRead(pStream, pByteOffset, pvBuffer, dwBytesToRead);
|
||||
}
|
||||
|
||||
@@ -125,12 +125,12 @@ static size_t ReadListFileLine(TListFileCache * pCache, char * szLine, size_t nM
|
||||
}
|
||||
|
||||
// If we have found a newline, stop loading
|
||||
if(*pCache->pPos == 0x0D || *pCache->pPos == 0x0A)
|
||||
if(pCache->pPos[0] == 0x0D || pCache->pPos[0] == 0x0A)
|
||||
break;
|
||||
|
||||
// Blizzard listfiles can also contain information about patch:
|
||||
// Pass1\Files\MacOS\unconditional\user\Background Downloader.app\Contents\Info.plist~Patch(Data#frFR#base-frFR,1326)
|
||||
if(*pCache->pPos == '~')
|
||||
if(pCache->pPos[0] == '~')
|
||||
szExtraString = szLine;
|
||||
|
||||
// Copy the character
|
||||
@@ -190,7 +190,7 @@ static TListFileCache * CreateListFileCache(RELOAD_CACHE pfnReloadCache, CLOSE_S
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Listfile functions
|
||||
// Functions for parsing an external listfile
|
||||
|
||||
void * ListFile_OpenExternal(const TCHAR * szListFile)
|
||||
{
|
||||
@@ -264,3 +264,160 @@ void ListFile_Free(void * pvListFile)
|
||||
CASC_FREE(pCache);
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Functions for creating a listfile map
|
||||
|
||||
#define LISTMAP_INITIAL 0x100000
|
||||
|
||||
static PLISTFILE_MAP ListMap_Create()
|
||||
{
|
||||
PLISTFILE_MAP pListMap;
|
||||
size_t cbToAllocate;
|
||||
|
||||
// Create buffer for the listfile
|
||||
// Note that because the listfile is quite big and CASC_REALLOC
|
||||
// is a costly operation, we want to have as few reallocs as possible.
|
||||
cbToAllocate = sizeof(LISTFILE_MAP) + LISTMAP_INITIAL;
|
||||
pListMap = (PLISTFILE_MAP)CASC_ALLOC(BYTE, cbToAllocate);
|
||||
if(pListMap != NULL)
|
||||
{
|
||||
// Fill the listfile buffer
|
||||
memset(pListMap, 0, sizeof(LISTFILE_MAP));
|
||||
pListMap->cbBufferMax = LISTMAP_INITIAL;
|
||||
}
|
||||
|
||||
return pListMap;
|
||||
}
|
||||
|
||||
static PLISTFILE_MAP ListMap_InsertName(PLISTFILE_MAP pListMap, const char * szFileName, size_t nLength)
|
||||
{
|
||||
PLISTFILE_ENTRY pListEntry;
|
||||
char szFileName2[MAX_PATH+1];
|
||||
size_t cbToAllocate;
|
||||
size_t cbEntrySize;
|
||||
uint32_t dwHashHigh = 0;
|
||||
uint32_t dwHashLow = 0;
|
||||
|
||||
// Make sure there is enough space in the list map
|
||||
cbEntrySize = sizeof(LISTFILE_ENTRY) + nLength;
|
||||
cbEntrySize = ALIGN_TO_SIZE(cbEntrySize, 8);
|
||||
if((pListMap->cbBuffer + cbEntrySize) > pListMap->cbBufferMax)
|
||||
{
|
||||
cbToAllocate = sizeof(LISTFILE_MAP) + (pListMap->cbBufferMax * 3) / 2;
|
||||
pListMap = (PLISTFILE_MAP)CASC_REALLOC(BYTE, pListMap, cbToAllocate);
|
||||
if(pListMap == NULL)
|
||||
return NULL;
|
||||
|
||||
pListMap->cbBufferMax = (pListMap->cbBufferMax * 3) / 2;
|
||||
}
|
||||
|
||||
// Get the pointer to the first entry
|
||||
pListEntry = (PLISTFILE_ENTRY)((LPBYTE)(pListMap + 1) + pListMap->cbBuffer);
|
||||
|
||||
// Get the name hash
|
||||
NormalizeFileName_UpperBkSlash(szFileName, szFileName2);
|
||||
hashlittle2(szFileName2, nLength, &dwHashHigh, &dwHashLow);
|
||||
|
||||
// Calculate the HASH value of the normalized file name
|
||||
pListEntry->FileNameHash = ((ULONGLONG)dwHashHigh << 0x20) | dwHashLow;
|
||||
pListEntry->cbEntrySize = (DWORD)cbEntrySize;
|
||||
memcpy(pListEntry->szFileName, szFileName, nLength);
|
||||
pListEntry->szFileName[nLength] = 0;
|
||||
|
||||
// Move the next entry
|
||||
pListMap->cbBuffer += cbEntrySize;
|
||||
pListMap->nEntries++;
|
||||
return pListMap;
|
||||
}
|
||||
|
||||
static PLISTFILE_MAP ListMap_Finish(PLISTFILE_MAP pListMap)
|
||||
{
|
||||
PLISTFILE_ENTRY pListEntry;
|
||||
PCASC_MAP pMap;
|
||||
LPBYTE pbEntry;
|
||||
|
||||
// Sanity check
|
||||
assert(pListMap->pNameMap == NULL);
|
||||
|
||||
// Create the map
|
||||
pListMap->pNameMap = pMap = Map_Create((DWORD)pListMap->nEntries, sizeof(ULONGLONG), 0);
|
||||
if(pListMap->pNameMap == NULL)
|
||||
{
|
||||
ListFile_FreeMap(pListMap);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Fill the map
|
||||
pbEntry = (LPBYTE)(pListMap + 1);
|
||||
for(size_t i = 0; i < pListMap->nEntries; i++)
|
||||
{
|
||||
// Get the listfile entry
|
||||
pListEntry = (PLISTFILE_ENTRY)pbEntry;
|
||||
pbEntry += pListEntry->cbEntrySize;
|
||||
|
||||
// Insert the entry to the map
|
||||
Map_InsertObject(pMap, pListEntry);
|
||||
}
|
||||
|
||||
return pListMap;
|
||||
}
|
||||
|
||||
PLISTFILE_MAP ListFile_CreateMap(const TCHAR * szListFile)
|
||||
{
|
||||
PLISTFILE_MAP pListMap = NULL;
|
||||
void * pvListFile;
|
||||
char szFileName[MAX_PATH+1];
|
||||
size_t nLength;
|
||||
|
||||
// Only if the listfile name has been given
|
||||
if(szListFile != NULL)
|
||||
{
|
||||
// Create map for the listfile
|
||||
pListMap = ListMap_Create();
|
||||
if(pListMap != NULL)
|
||||
{
|
||||
// Open the external listfile
|
||||
pvListFile = ListFile_OpenExternal(szListFile);
|
||||
if(pvListFile != NULL)
|
||||
{
|
||||
// Go through the entire listfile and insert each name to the map
|
||||
while((nLength = ListFile_GetNext(pvListFile, "*", szFileName, MAX_PATH)) != 0)
|
||||
{
|
||||
// Insert the file name to the map
|
||||
pListMap = ListMap_InsertName(pListMap, szFileName, nLength);
|
||||
if(pListMap == NULL)
|
||||
break;
|
||||
}
|
||||
|
||||
// Finish the listfile map
|
||||
pListMap = ListMap_Finish(pListMap);
|
||||
|
||||
// Free the listfile
|
||||
ListFile_Free(pvListFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the created map
|
||||
return pListMap;
|
||||
}
|
||||
|
||||
const char * ListFile_FindName(PLISTFILE_MAP pListMap, ULONGLONG FileNameHash)
|
||||
{
|
||||
PLISTFILE_ENTRY pListEntry = NULL;
|
||||
|
||||
if(pListMap != NULL)
|
||||
pListEntry = (PLISTFILE_ENTRY)Map_FindObject(pListMap->pNameMap, &FileNameHash);
|
||||
return (pListEntry != NULL) ? pListEntry->szFileName : "";
|
||||
}
|
||||
|
||||
void ListFile_FreeMap(PLISTFILE_MAP pListMap)
|
||||
{
|
||||
if(pListMap != NULL)
|
||||
{
|
||||
if(pListMap->pNameMap != NULL)
|
||||
Map_Free(pListMap->pNameMap);
|
||||
CASC_FREE(pListMap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,40 @@
|
||||
#ifndef __LISTFILE_H__
|
||||
#define __LISTFILE_H__
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Structures
|
||||
|
||||
typedef struct _LISTFILE_ENTRY
|
||||
{
|
||||
ULONGLONG FileNameHash; // Hash of the file name
|
||||
DWORD cbEntrySize; // Length of this entry, in bytes
|
||||
char szFileName[1]; // File name, aligned to 8-byte boundary
|
||||
|
||||
} LISTFILE_ENTRY, *PLISTFILE_ENTRY;
|
||||
|
||||
typedef struct _LISTFILE_MAP
|
||||
{
|
||||
PCASC_MAP pNameMap; // Map of hash-to-name
|
||||
size_t cbBufferMax; // Total size of the buffer, in bytes
|
||||
size_t cbBuffer; // Current size of the buffer, in bytes
|
||||
size_t nEntries; // Number of entries
|
||||
|
||||
// First LISTFILE_ENTRY starts here
|
||||
|
||||
} LISTFILE_MAP, *PLISTFILE_MAP;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Functions for parsing an external listfile
|
||||
|
||||
void * ListFile_OpenExternal(const TCHAR * szListFile);
|
||||
size_t ListFile_GetNext(void * pvListFile, const char * szMask, char * szBuffer, size_t nMaxChars);
|
||||
void ListFile_Free(void * pvListFile);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Functions for creating a listfile map
|
||||
|
||||
PLISTFILE_MAP ListFile_CreateMap(const TCHAR * szListFile);
|
||||
const char * ListFile_FindName(PLISTFILE_MAP pListMap, ULONGLONG FileNameHash);
|
||||
void ListFile_FreeMap(PLISTFILE_MAP pListMap);
|
||||
|
||||
#endif // __LISTFILE_H__
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Structures
|
||||
|
||||
#define KEY_LENGTH_STRING 0xFFFFFFFF // Pass this to Map_Create as dwKeyLength when you want map of string->object
|
||||
#define KEY_LENGTH_STRING 0xFFFFFFFF // Pass this to Map_Create as dwKeyLength when you want map of string->object
|
||||
|
||||
typedef struct _CASC_MAP
|
||||
{
|
||||
|
||||
@@ -38,7 +38,7 @@ recastnavigation (Recast is state of the art navigation mesh construction toolse
|
||||
|
||||
CascLib (An open-source implementation of library for reading CASC storage from Blizzard games since 2014)
|
||||
https://github.com/ladislav-zezula/CascLib
|
||||
Version: 3e3f4f443cb7b2893cbbe60b0a6efafebf48bc40
|
||||
Version: 5d3789af3435534c288c2145e158d422651c7fe1
|
||||
|
||||
zmqpp (C++ binding for 0mq/zmq is a 'high-level' library that hides most of the c-style interface core 0mq provides.)
|
||||
https://github.com/zeromq/zmqpp
|
||||
|
||||
Reference in New Issue
Block a user